Skip to content

Commit e7e9bee

Browse files
authored
Interactive, arrow-key guided setup for whygraph init (#32)
* Add interactive, arrow-key guided setup to `whygraph init` Prompts for the agent, analyze/rationale LLMs (+ per-provider API keys), and the source-control provider (+ token), shows a secret-masked summary panel to confirm, then writes both whygraph.example.toml (no secrets) and a ready-to-run whygraph.toml. --yes and any non-TTY invocation skip all prompts and preserve today's behavior. * Fix ruff format check failure; document pre-push checks in CLAUDE.md CI checks ruff format --check in addition to ruff check, which wasn't run before the last push. Reformat the affected files and add a "Before pushing" section to CLAUDE.md spelling out all three commands CI gates on, so this doesn't happen again.
1 parent fa94df0 commit e7e9bee

14 files changed

Lines changed: 1392 additions & 33 deletions

File tree

CLAUDE.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,18 @@ uv run whygraph-mcp # launch MCP server on stdio (Ctrl-C to exit)
2727

2828
A root `Makefile` wraps these plus dev-only tooling — `make` lists targets; `make db` / `make db-down` run a DBGate viewer for both databases (via `docker-compose.example.yml`), `make inspect` launches the MCP Inspector.
2929

30+
## Before pushing
31+
32+
CI (`ci-code-checks`) gates every PR on two parallel jobs: **lint** (`uv run ruff check src/ tests/` *and* `uv run ruff format --check src/ tests/` — both, not just the first) and **tests** (`uv run pytest`). Run all three locally before pushing or opening a PR:
33+
34+
```bash
35+
uv run ruff check src/ tests/
36+
uv run ruff format --check src/ tests/ # `ruff check` passing does NOT imply this passes
37+
uv run pytest
38+
```
39+
40+
If `ruff format --check` fails, run `uv run ruff format src/ tests/` to fix it in place, then re-run the check before pushing.
41+
3042
## Architecture
3143

3244
Top-level packages under `src/whygraph/`:

docs/getting-started/quickstart.md

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,16 @@ From the repo you want to analyze:
1111
whygraph init
1212
```
1313

14-
This creates `.whygraph/whygraph.db`, writes a commented `whygraph.example.toml`, and adds the right
15-
`.gitignore` entries. It's idempotent - run it again any time. It does *not* index CodeGraph yet;
16-
that's the next step.
14+
On a terminal this runs a short guided setup - pick your agent, the analyze/rationale LLMs (with
15+
optional API keys), and the source-control provider (with an optional GitHub token), then review a
16+
summary that masks every secret and confirm. It creates `.whygraph/whygraph.db`, writes a commented
17+
`whygraph.example.toml` (never any secrets) and a ready-to-run `whygraph.toml` (with the secrets you
18+
entered), and adds the right `.gitignore` entries. Every prompt is defaulted, so a bare Enter accepts
19+
it. It's idempotent - run it again any time; an existing `whygraph.toml` is only touched if you ask.
20+
It does *not* index CodeGraph yet; that's the next step.
21+
22+
Prefer no prompts? `whygraph init --yes` (and any non-interactive shell - pipes, CI, the git hooks)
23+
accepts every default without asking, writing a default `whygraph.toml` only if none exists.
1724

1825
## 2. Scan
1926

docs/reference/cli.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,22 @@ Bootstrap the WhyGraph database under `.whygraph/whygraph.db`, write a committab
2727
`whygraph.example.toml` documenting every tunable, and add the right `.gitignore` entries. It's
2828
idempotent - re-running on an initialized project just confirms both databases are present.
2929

30+
On a terminal, `init` runs a guided, arrow-key setup: pick the agent, the analyze/rationale LLMs
31+
(with optional API keys), and the source-control provider (with an optional GitHub token). It shows a
32+
summary that masks every secret, asks *"Write these files?"*, then writes both `whygraph.example.toml`
33+
(secret-free) and a ready-to-run `whygraph.toml` (with the secrets you entered). Every prompt is
34+
defaulted. `--yes` (and any non-TTY invocation) skips the prompts, uses defaults, and never clobbers
35+
an existing `whygraph.toml`.
36+
3037
`init` does **not** index CodeGraph. That happens on [`scan`](#whygraph-scan).
3138

3239
With `--agent X`, it also wires the WhyGraph MCP server into that agent's config. All supported
3340
agents are project-scoped, so the config file is written inside the repo.
3441

3542
| Option | Description |
3643
|---|---|
37-
| `--agent [claude\|codex\|copilot\|cursor\|vscode]` | Wire the MCP server into the named agent's config. |
44+
| `--agent [claude\|codex\|copilot\|cursor\|vscode]` | Wire the MCP server into the named agent's config. On a terminal, skips the interactive agent prompt. |
45+
| `--yes` / `-y` | Accept all defaults without prompting (also implied off a TTY). Writes a default `whygraph.toml` only if none exists. |
3846
| `--print` | Print the MCP snippet to stdout instead of writing any config file. |
3947
| `--list-agents` | List supported agents (with config-file paths) and exit. |
4048
| `--install-assets / --no-install-assets` | Copy the chosen agent's bundled assets into the project. Default: enabled. No-op for agents that ship no asset tree. |

docs/reference/configuration.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
# Configuration
22

33
WhyGraph reads an optional `whygraph.toml` at your repo root. Every field has a built-in default, so
4-
an unedited file behaves exactly as if none were present. `whygraph init` scaffolds a fully-commented
5-
`whygraph.example.toml` for you - copy it to `whygraph.toml` and edit what you need.
4+
an unedited file behaves exactly as if none were present. On a terminal, `whygraph init` walks you
5+
through the common choices (agent, analyze/rationale LLMs + keys, source-control provider + token)
6+
and writes both a fully-commented `whygraph.example.toml` (secret-free, committable) and a ready-to-run
7+
`whygraph.toml` (with any secrets you entered). You can always edit `whygraph.toml` by hand afterwards,
8+
or start from the example. `whygraph init --yes` skips the prompts and uses defaults.
69

710
!!! warning "`whygraph.toml` is gitignored - never commit a token"
811
`init` adds `whygraph.toml` to `.gitignore` precisely because it can hold API keys. Keep it that

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ dependencies = [
1414
"openai>=1.40",
1515
"ollama>=0.3",
1616
"tomli-w>=1.0",
17+
"questionary>=2.0",
1718
]
1819

1920
[project.scripts]

src/whygraph/cli/commands/init.py

Lines changed: 106 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from __future__ import annotations
44

5+
import sys
56
from pathlib import Path
67

78
import click
@@ -60,21 +61,45 @@
6061
" left alone."
6162
),
6263
)
64+
@click.option(
65+
"--yes",
66+
"-y",
67+
"yes",
68+
is_flag=True,
69+
help=(
70+
"Accept all defaults without prompting. Also implied whenever"
71+
" stdin is not a TTY (pipes, CI, the git hooks). Writes a"
72+
" default whygraph.toml if none exists and never clobbers an"
73+
" existing one."
74+
),
75+
)
6376
def init_cmd(
6477
agent_name: str | None,
6578
print_only: bool,
6679
list_agents: bool,
6780
install_assets: bool,
6881
skip_preflight: bool,
6982
force: bool,
83+
yes: bool,
7084
) -> None:
7185
"""Initialize the WhyGraph database under ``.whygraph/whygraph.db``.
7286
73-
Also writes a committable ``whygraph.example.toml`` at the project root
74-
(it documents every tunable and ships the built-in defaults — copy it
75-
to ``whygraph.toml`` and edit to customize) and ensures the project's
76-
``.gitignore`` keeps the user-owned config and generated caches out of
77-
git (``whygraph.toml``, ``.whygraph/``, ``.codegraph/``).
87+
On a terminal this runs a guided, arrow-key setup — pick the agent,
88+
the analyze/rationale LLMs (+ API keys), and the source-control
89+
provider (+ token), review a summary that masks every secret, then
90+
confirm. It writes a committable ``whygraph.example.toml`` (never any
91+
secrets) and, once confirmed, a ready-to-run ``whygraph.toml`` (with
92+
the secrets you entered). Every prompt is defaulted, so a bare Enter
93+
accepts it.
94+
95+
``--yes`` (and any non-TTY invocation: pipes, CI, the git hooks)
96+
skips all prompts and uses defaults — writing a default
97+
``whygraph.toml`` only if none exists and never clobbering an existing
98+
one. A bare non-TTY ``init`` refreshes only the example, as before.
99+
100+
Either way it ensures the project's ``.gitignore`` keeps the
101+
user-owned config and generated caches out of git (``whygraph.toml``,
102+
``.whygraph/``, ``.codegraph/``).
78103
79104
Does **not** index CodeGraph — that happens on ``whygraph scan``,
80105
which populates ``.codegraph/codegraph.db`` (and refreshes it on every
@@ -103,20 +128,28 @@ def init_cmd(
103128
if not skip_preflight:
104129
_run_preflight()
105130

131+
# Prompt only on a real terminal and only when not told to accept
132+
# defaults. Pipes / CI / the git hooks have no TTY and fall straight
133+
# through to defaults — identical to the pre-interactive behaviour.
134+
interactive = sys.stdin.isatty() and not yes
135+
answers = _gather_answers(project_root, agent_name, interactive=interactive)
136+
106137
db_path = _ensure_db_initialized()
107138
click.echo(f"Initialized WhyGraph database at {db_path}")
108139

109-
_scaffold_example_config(project_root)
140+
_scaffold_example_config(project_root, answers)
141+
_maybe_write_user_config(project_root, answers, write_user=interactive or yes)
110142
_ensure_gitignore(project_root)
111143

112-
if agent_name is None:
144+
resolved_agent = answers.agent or agent_name
145+
if resolved_agent is None:
113146
click.echo(
114147
"Tip: run `whygraph init --list-agents` to see supported agents,"
115148
" then `whygraph init --agent <name>` to wire it up."
116149
)
117150
return
118151

119-
target = agents.resolve_agent(agent_name)
152+
target = agents.resolve_agent(resolved_agent)
120153
snippet = agents.render_snippet(target)
121154

122155
if print_only or not agents.is_write_supported(target):
@@ -144,21 +177,82 @@ def _run_preflight() -> None:
144177
fail(str(exc))
145178

146179

147-
def _scaffold_example_config(project_root: Path) -> None:
180+
def _gather_answers(project_root: Path, agent_name: str | None, *, interactive: bool):
181+
"""Collect the init choices — interactively, or from defaults.
182+
183+
In interactive mode this runs the guided flow (agent + LLMs + scan +
184+
a summary panel + confirm) and returns its answers. A Ctrl-C / EOF at
185+
any prompt or a declined confirm raises :class:`InitAborted`, which we
186+
turn into a clean non-zero exit **before** any file is written or the
187+
DB is bootstrapped. Non-interactive callers get the defaults with the
188+
``--agent`` value threaded in.
189+
190+
Lazy-imports the interactive module so lightweight surfaces stay fast.
191+
"""
192+
from whygraph.core.config import DEFAULT_ANSWERS, InitAnswers
193+
194+
if not interactive:
195+
return InitAnswers(agent=agent_name, reconfigure_toml=False)
196+
197+
from whygraph.cli.interactive import InitAborted, prompt_for_init
198+
199+
try:
200+
return prompt_for_init(
201+
project_root,
202+
preset_agent=agent_name,
203+
on_summary=_render_summary_panel,
204+
)
205+
except InitAborted:
206+
fail("Aborted — no changes written.")
207+
# Unreachable (fail raises); satisfies type-checkers.
208+
return DEFAULT_ANSWERS
209+
210+
211+
def _render_summary_panel(summary: str) -> None:
212+
"""Render the pre-write review summary as a Rich panel on stderr."""
213+
from rich.panel import Panel
214+
215+
from ..console import console
216+
217+
console.print(
218+
Panel(summary, title="Review your choices", border_style="cyan", expand=False)
219+
)
220+
221+
222+
def _scaffold_example_config(project_root: Path, answers) -> None:
148223
"""Write the committable ``whygraph.example.toml`` at the project root.
149224
150-
Always refreshed so it tracks the shipped defaults. Lazy-imports the
151-
config helper so lightweight surfaces like ``--list-agents`` and
225+
Always refreshed so it tracks the shipped defaults and any non-secret
226+
choices from ``answers``. Secrets are never written here. Lazy-imports
227+
the config helper so lightweight surfaces like ``--list-agents`` and
152228
``--help`` don't pay the cost.
153229
"""
154230
from whygraph.core.config import write_example_config
155231

156-
path = write_example_config(project_root)
232+
path = write_example_config(project_root, answers)
157233
click.echo(
158234
f"Wrote example config to {path} — copy to whygraph.toml and edit to customize"
159235
)
160236

161237

238+
def _maybe_write_user_config(project_root: Path, answers, *, write_user: bool) -> None:
239+
"""Write ``whygraph.toml`` when appropriate, else preserve an existing one.
240+
241+
Writes only when ``write_user`` (interactive or ``--yes``) **and** the
242+
file is absent or the user chose to reconfigure it. A bare non-TTY
243+
``init`` never writes it (``write_user`` is ``False``), preserving the
244+
historical scaffold-only behaviour.
245+
"""
246+
from whygraph.core.config import CONFIG_FILENAME, write_user_config
247+
248+
user_path = project_root / CONFIG_FILENAME
249+
if write_user and (not user_path.exists() or answers.reconfigure_toml):
250+
path = write_user_config(project_root, answers)
251+
click.echo(f"Wrote {path} — gitignored; holds any secrets you entered")
252+
elif user_path.exists():
253+
click.echo(f"Kept existing {user_path}")
254+
255+
162256
def _ensure_gitignore(project_root: Path) -> None:
163257
"""Keep the user config and generated caches out of git.
164258

0 commit comments

Comments
 (0)