diff --git a/CLAUDE.md b/CLAUDE.md index 985b0ad..b861ef9 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -87,7 +87,7 @@ CodeGraph indexing belongs to **`whygraph scan`, not `whygraph init`** — `init ### Auto-rescan git hooks -`whygraph hooks install` (opt-in; `cli/commands/hooks.py`) wires `post-commit` / `post-merge` / `post-rewrite` to keep the DBs current as the developer works — no daemon. Each hook execs a shared helper (`.whygraph/hooks/whygraph-scan`, gitignored) that runs `whygraph scan --no-llm-descriptions --no-remote` (git history + `codegraph sync` only — fast, offline, no token; LLM descriptions stay on lazy backfill). The helper is **detached** (commits return instantly) and **single-flight + coalescing** (portable `mkdir` lock + a `pending` flag, since macOS has no `flock`), so rapid commits neither stack nor drop the latest `HEAD`. Installs are **sentinel-guarded** (`# >>> whygraph managed >>>`) and append to a foreign hook rather than clobber it. The `--no-remote` flag on `scan` exists for this path; `db/engine.py` sets `PRAGMA busy_timeout` so a background rescan and a manual scan don't collide. +`whygraph hooks install` (opt-in; `cli/commands/hooks.py`) wires `post-commit` / `post-merge` / `post-rewrite` to keep the DBs current as the developer works — no daemon. Each hook execs a shared helper (`.whygraph/hooks/whygraph-scan`, gitignored) that runs `whygraph scan --skip-analyze --no-remote` (git history + `codegraph sync` only — fast, offline, no token; LLM descriptions stay on lazy backfill). The helper is **detached** (commits return instantly) and **single-flight + coalescing** (portable `mkdir` lock + a `pending` flag, since macOS has no `flock`), so rapid commits neither stack nor drop the latest `HEAD`. Installs are **sentinel-guarded** (`# >>> whygraph managed >>>`) and append to a foreign hook rather than clobber it. The `--no-remote` flag on `scan` exists for this path; `db/engine.py` sets `PRAGMA busy_timeout` so a background rescan and a manual scan don't collide. Deferred (net-new, not built yet): a project registry for cross-repo orchestration, a persistent/server mode, and per-branch CodeGraph/WhyGraph databases. diff --git a/docs/getting-started/quickstart.md b/docs/getting-started/quickstart.md index 55fc849..a454ef8 100644 --- a/docs/getting-started/quickstart.md +++ b/docs/getting-started/quickstart.md @@ -39,7 +39,7 @@ evidence WhyGraph serves. For a fast, offline pass - no remote calls, no LLM - skip both phases: ```bash -whygraph scan --no-remote --no-llm-descriptions +whygraph scan --no-remote --skip-analyze ``` Descriptions backfill lazily later, so this is a fine way to get started quickly. See diff --git a/docs/guide/editors.md b/docs/guide/editors.md index abdb728..e63a45e 100644 --- a/docs/guide/editors.md +++ b/docs/guide/editors.md @@ -21,7 +21,7 @@ inside the repo, so you can commit it and every teammate's editor picks it up. | `vscode` (alias `copilot`) | VS Code / GitHub Copilot | `.vscode/mcp.json` | | `codex` | OpenAI Codex | `.codex/config.toml` | -Run `whygraph init --list-agents` to print these paths for your own checkout. +Run `whygraph init --help` to see the supported agents. The generated config launches `whygraph-mcp` by bare command name, so the same checked-in file works for everyone who has WhyGraph installed - no absolute paths to scrub. @@ -32,19 +32,16 @@ for everyone who has WhyGraph installed - no absolute paths to scrub. leaves your existing files alone; pass `--force` to overwrite them. ```bash -whygraph init --agent claude --no-install-assets # MCP wiring only, skip the .claude/ copy -whygraph init --agent claude --force # overwrite existing .claude/ files +whygraph init --agent claude # wire MCP + copy the .claude/ assets +whygraph init --agent claude --force # overwrite existing .claude/ files ``` ## Useful flags | Flag | What it does | |---|---| -| `--print` | Print the MCP snippet to stdout instead of writing any file. Good for pasting by hand. | -| `--list-agents` | List supported agents and their config paths, then exit. | -| `--install-assets / --no-install-assets` | Copy (or skip) the agent's bundled assets. Default: copy. No-op for agents with no asset tree. | -| `--skip-preflight` | Skip the host-tool diagnostics. For known-good scripted environments. | | `--force` | Overwrite existing asset files in the destination directory. | +| `--yes` / `-y` | Accept all defaults without prompting (also implied off a TTY). | ## Verify diff --git a/docs/guide/scanning.md b/docs/guide/scanning.md index 962ad3e..905e7e5 100644 --- a/docs/guide/scanning.md +++ b/docs/guide/scanning.md @@ -29,7 +29,7 @@ without polluting area history. | Flag | Default | What it does | |---|---|---| -| `--no-llm-descriptions` | off | Skip the per-commit LLM phase. Git and GitHub crawlers still run; descriptions backfill lazily and on a later full scan. | +| `--skip-analyze` | off | Skip the per-commit LLM phase. Git and GitHub crawlers still run; descriptions backfill lazily and on a later full scan. | | `--codegraph / --no-codegraph` | on | Refresh the CodeGraph index concurrently with the crawl. | | `--codegraph-image TEXT` | pinned tag | Override the Docker image for the CodeGraph fallback. Ignored when a local `codegraph` binary is found. | | `--remote / --no-remote` | on | Crawl the remote for PRs and issues per `[scan].provider`. `--no-remote` is a fast, offline, token-free scan. | @@ -38,7 +38,7 @@ without polluting area history. A common fast pass while iterating: ```bash -whygraph scan --no-remote --no-llm-descriptions +whygraph scan --no-remote --skip-analyze ``` !!! tip "Lazy backfill" @@ -55,7 +55,7 @@ whygraph hooks install ``` This wires `post-commit`, `post-merge`, and `post-rewrite` to run -`whygraph scan --no-remote --no-llm-descriptions` **in the background**. Git history and a CodeGraph +`whygraph scan --no-remote --skip-analyze` **in the background**. Git history and a CodeGraph `sync` only - no LLM, no remote calls - so commits stay instant and the scan is offline and token-free. diff --git a/docs/reference/cli.md b/docs/reference/cli.md index 01d3267..8b2c8e7 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -41,14 +41,12 @@ agents are project-scoped, so the config file is written inside the repo. | Option | Description | |---|---| -| `--agent [claude\|codex\|copilot\|cursor\|vscode]` | Wire the MCP server into the named agent's config. On a terminal, skips the interactive agent prompt. | +| `--agent [claude\|codex\|copilot\|cursor\|vscode]` | Wire the MCP server into the named agent's config. On a terminal, skips the interactive agent prompt. Run `whygraph init --help` for the full list of supported agents. | | `--yes` / `-y` | Accept all defaults without prompting (also implied off a TTY). Writes a default `whygraph.toml` only if none exists. | -| `--print` | Print the MCP snippet to stdout instead of writing any config file. | -| `--list-agents` | List supported agents (with config-file paths) and exit. | -| `--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. | -| `--skip-preflight` | Skip the host-tool diagnostics that normally run first. For known-good scripted environments. | | `--force` | When installing assets, overwrite existing files in the agent's destination directory. | +Preflight diagnostics and asset install both always run — the chosen agent's bundled assets are copied into the repo automatically (use `--force` to overwrite local edits). + See [Wiring your editor](../guide/editors.md) for the per-agent paths. ## `whygraph scan` @@ -59,7 +57,7 @@ picks up new commits and backfills what's missing. | Option | Default | Description | |---|---|---| -| `--no-llm-descriptions` | off | Skip the per-commit LLM description phase. The git and GitHub crawlers still run; descriptions backfill lazily on demand and on a later full scan. | +| `--skip-analyze` | off | Skip the per-commit LLM description phase. The git and GitHub crawlers still run; descriptions backfill lazily on demand and on a later full scan. | | `--codegraph / --no-codegraph` | on | Refresh the CodeGraph index concurrently with the crawl - `codegraph sync` when an index exists, `codegraph init -i` on first run. A failure here warns rather than aborting. | | `--codegraph-image TEXT` | pinned tag | Override the Docker image used for the CodeGraph refresh fallback. Ignored when a local `codegraph` binary is found. | | `--remote / --no-remote` | on | Crawl the source-control remote (GitHub PRs / issues) per `[scan].provider`. `--no-remote` skips it for a fast, offline, token-free scan. | @@ -99,5 +97,5 @@ whygraph hooks install ``` The hooks wire `post-commit`, `post-merge`, and `post-rewrite` to run -`whygraph scan --no-remote --no-llm-descriptions` in the background. See +`whygraph scan --no-remote --skip-analyze` in the background. See [Keep it fresh](../guide/scanning.md#keep-it-fresh) for the details. diff --git a/src/whygraph/agents.py b/src/whygraph/agents.py index 46f6282..6a2c363 100644 --- a/src/whygraph/agents.py +++ b/src/whygraph/agents.py @@ -68,7 +68,7 @@ class AgentTarget: Serialization format of the target file. Determines which renderer :func:`render_snippet` uses. description : str - Short one-line description shown by ``whygraph init --list-agents``. + Short one-line description shown in ``whygraph init --help``. assets_subdir : str or None Name of the source directory under ``src/whygraph/assets/`` that holds this agent's bundled asset tree, or ``None`` if the agent @@ -268,8 +268,8 @@ def write_snippet(target: AgentTarget, project_root: Path) -> Path: other servers and top-level keys are preserved. * If the file exists but is unparseable, a fresh minimal config replaces it. This is a conscious trade-off: we surface the new - config rather than refuse to proceed. Callers can offer - ``--print`` for users who'd rather merge by hand. + config rather than refuse to proceed. Users who'd rather merge by + hand can render the snippet with ``render_snippet()`` instead. Parameters ---------- @@ -293,8 +293,8 @@ def write_snippet(target: AgentTarget, project_root: Path) -> Path: Comments and incidental formatting in the existing config file are not preserved across the read-modify-write cycle (``tomllib`` strips comments on parse; ``json.load`` collapses whitespace). Users who - care about preserving their hand-formatted config should use - ``--print`` and merge the snippet manually. + care about preserving their hand-formatted config should render the + snippet with ``render_snippet()`` and merge it manually. """ if target.scope != "project": raise ValueError( diff --git a/src/whygraph/analyze/backfill.py b/src/whygraph/analyze/backfill.py index c0b6ffc..405516d 100644 --- a/src/whygraph/analyze/backfill.py +++ b/src/whygraph/analyze/backfill.py @@ -2,7 +2,7 @@ The :class:`~whygraph.scan.analyze_crawler.AnalyzeCrawler` describes every undescribed commit in bulk at scan time. When a scan is run with -``whygraph scan --no-llm-descriptions`` (or when a commit was added to +``whygraph scan --skip-analyze`` (or when a commit was added to the database before its description could be generated), the ``commit.llm_description`` column is left ``NULL``. The MCP tools :func:`whygraph.mcp.evidence.whygraph_evidence_for` and diff --git a/src/whygraph/cli/commands/hooks.py b/src/whygraph/cli/commands/hooks.py index 81a3b73..1c317fc 100644 --- a/src/whygraph/cli/commands/hooks.py +++ b/src/whygraph/cli/commands/hooks.py @@ -55,7 +55,7 @@ if mkdir "$lock" 2>/dev/null; then trap 'rmdir "$lock" 2>/dev/null' EXIT INT TERM rm -f "$pending" - whygraph scan --no-llm-descriptions --no-remote >> "$log" 2>&1 + whygraph scan --skip-analyze --no-remote >> "$log" 2>&1 rmdir "$lock" 2>/dev/null trap - EXIT INT TERM else diff --git a/src/whygraph/cli/commands/init.py b/src/whygraph/cli/commands/init.py index d480542..b5ea25a 100644 --- a/src/whygraph/cli/commands/init.py +++ b/src/whygraph/cli/commands/init.py @@ -12,7 +12,33 @@ from ..console import fail -@click.command(name="init") +def _agents_epilog() -> str: + """Render the supported-agents block for ``whygraph init --help``. + + Lists each agent's name, aliases, scope, format, and one-line + description — the discoverability the old ``--list-agents`` command + provided, minus its cwd-relative config path (help text isn't + cwd-anchored; the write step still echoes the real path). Built at + import from :data:`whygraph.agents.AGENTS`. + + The leading ``\\b`` marker tells Click's help formatter not to + re-wrap the block, so the indented per-agent layout survives. Click + ends a "no-rewrap" paragraph at the first blank line, so the whole + block is kept as a single paragraph with no interior blank lines. + """ + lines = ["\b", "Supported agents (use with --agent):"] + for name in sorted(agents.AGENTS): + target = agents.AGENTS[name] + aliases = f" (aliases: {', '.join(target.aliases)})" if target.aliases else "" + scope = "project" if target.scope == "project" else "user" + lines.append( + f" {target.name}{aliases} — scope: {scope}, format: {target.format}" + ) + lines.append(f" {target.description}") + return "\n".join(lines) + + +@click.command(name="init", epilog=_agents_epilog()) @click.option( "--agent", "agent_name", @@ -20,37 +46,6 @@ default=None, help="Wire the WhyGraph MCP server into the named LLM agent's config.", ) -@click.option( - "--print", - "print_only", - is_flag=True, - help="Print the MCP snippet to stdout instead of writing any config file.", -) -@click.option( - "--list-agents", - "list_agents", - is_flag=True, - help="List supported agents (with config-file paths) and exit.", -) -@click.option( - "--install-assets/--no-install-assets", - "install_assets", - default=True, - help=( - "Copy the chosen agent's bundled assets (if any) into the project." - " Default: enabled. No-op for agents that ship no asset tree." - ), -) -@click.option( - "--skip-preflight", - "skip_preflight", - is_flag=True, - help=( - "Skip the host-tool diagnostics that normally run at the top of" - " `whygraph init`. Use only in scripted environments where the" - " environment is known-good." - ), -) @click.option( "--force", "force", @@ -75,10 +70,6 @@ ) def init_cmd( agent_name: str | None, - print_only: bool, - list_agents: bool, - install_assets: bool, - skip_preflight: bool, force: bool, yes: bool, ) -> None: @@ -106,27 +97,22 @@ def init_cmd( subsequent run). With ``--agent X``, registers the WhyGraph MCP server with the named - agent. All supported agents are project-scoped — their MCP config - file is written / merged in the repo. Pass ``--print`` to skip the - write and emit the snippet for manual pasting. + agent (run ``whygraph init --help`` for the list of supported + agents). All supported agents are project-scoped — their MCP config + file is written / merged in the repo. Agents whose config can't be + written automatically get the snippet printed for manual pasting. If the chosen agent ships a bundled asset tree (see :attr:`whygraph.agents.AgentTarget.has_assets`), the tree is copied into the matching destination directory under the repo. Pre-existing - files are left alone unless ``--force`` is passed; pass - ``--no-install-assets`` to skip the copy entirely. + files are left alone unless ``--force`` is passed. Idempotent — re-running on an already-initialized project just confirms both databases are present and at head. """ - if list_agents: - _print_agent_list() - return - project_root = Path.cwd() - if not skip_preflight: - _run_preflight() + _run_preflight() # Prompt only on a real terminal and only when not told to accept # defaults. Pipes / CI / the git hooks have no TTY and fall straight @@ -144,7 +130,7 @@ def init_cmd( resolved_agent = answers.agent or agent_name if resolved_agent is None: click.echo( - "Tip: run `whygraph init --list-agents` to see supported agents," + "Tip: run `whygraph init --help` to see supported agents," " then `whygraph init --agent ` to wire it up." ) return @@ -152,13 +138,13 @@ def init_cmd( target = agents.resolve_agent(resolved_agent) snippet = agents.render_snippet(target) - if print_only or not agents.is_write_supported(target): + if not agents.is_write_supported(target): _print_snippet(target, project_root, snippet) else: path = agents.write_snippet(target, project_root) click.echo(f"Wrote whygraph MCP entry to {path}") - if install_assets and target.has_assets: + if target.has_assets: result = assets.install_assets(target, project_root, force=force) _print_install_summary(target, project_root, result, force=force) @@ -166,8 +152,8 @@ def init_cmd( def _run_preflight() -> None: """Echo the diagnostics block; ``fail`` with a clean error on missing tools. - Imported here (not at module top) so ``--list-agents`` and ``--help`` - don't pay the import cost. + Imported here (not at module top) so ``--help`` doesn't pay the + import cost. """ from whygraph.cli.preflight import PreflightError, run_preflight @@ -224,8 +210,8 @@ def _scaffold_example_config(project_root: Path, answers) -> None: Always refreshed so it tracks the shipped defaults and any non-secret choices from ``answers``. Secrets are never written here. Lazy-imports - the config helper so lightweight surfaces like ``--list-agents`` and - ``--help`` don't pay the cost. + the config helper so lightweight surfaces like ``--help`` don't pay + the cost. """ from whygraph.core.config import write_example_config @@ -275,27 +261,14 @@ def _ensure_db_initialized() -> Path: """Bootstrap the WhyGraph DB, lazy-importing the heavy chain. Imported here (not at module top) so that lightweight CLI surfaces - like ``--list-agents`` and ``--help`` don't fail when the DB layer - or its dependencies are mid-rewrite. + like ``--help`` don't fail when the DB layer or its dependencies are + mid-rewrite. """ from whygraph.db import ensure_initialized return ensure_initialized() -def _print_agent_list() -> None: - click.echo("Supported agents:") - for name in sorted(agents.AGENTS): - target = agents.AGENTS[name] - aliases = f" (aliases: {', '.join(target.aliases)})" if target.aliases else "" - path = agents.config_path_for(target, Path.cwd()) - scope = "project" if target.scope == "project" else "user" - click.echo(f" {target.name}{aliases}") - click.echo(f" scope: {scope} format: {target.format}") - click.echo(f" path: {path}") - click.echo(f" note: {target.description}") - - def _print_snippet( target: agents.AgentTarget, project_root: Path, snippet: str ) -> None: diff --git a/src/whygraph/cli/commands/scan.py b/src/whygraph/cli/commands/scan.py index ac28be3..3907d9e 100644 --- a/src/whygraph/cli/commands/scan.py +++ b/src/whygraph/cli/commands/scan.py @@ -49,8 +49,8 @@ @click.command(name="scan") @click.option( - "--no-llm-descriptions", - "no_llm_descriptions", + "--skip-analyze", + "skip_analyze", is_flag=True, default=False, help=( @@ -109,7 +109,7 @@ ), ) def scan_cmd( - no_llm_descriptions: bool, + skip_analyze: bool, refresh_codegraph: bool, codegraph_image: str | None, remote: bool, @@ -135,12 +135,12 @@ def scan_cmd( else: github_client = None - if no_llm_descriptions: + if skip_analyze: # Bypass the LlmDescriptor probe entirely so a broken `[analyze]` # config still lets users run a fast scan and rely on the MCP # tools' lazy backfill for descriptions. descriptor = None - analyze_skip: str | None = "--no-llm-descriptions" + analyze_skip: str | None = "--skip-analyze" else: try: descriptor = LlmDescriptor.from_config(config.analyze) @@ -334,7 +334,7 @@ def _optional_phase_cells( """Return the (status, summary, timing) cells for an optional phase. A crawler that never ran (phase skipped via ``--no-remote`` / - ``--no-llm-descriptions``) renders a dim ``— skipped`` status with no + ``--skip-analyze``) renders a dim ``— skipped`` status with no summary or timing. """ if crawler is None: diff --git a/src/whygraph/mcp/rationale.py b/src/whygraph/mcp/rationale.py index f239efa..ede2212 100644 --- a/src/whygraph/mcp/rationale.py +++ b/src/whygraph/mcp/rationale.py @@ -110,7 +110,7 @@ def whygraph_rationale_brief( return _format_response(target, rationale, evidence, cached_at) # Cache miss — lazily backfill any commit whose `llm_description` is - # NULL (e.g. after `whygraph scan --no-llm-descriptions`) so the + # NULL (e.g. after `whygraph scan --skip-analyze`) so the # rationale prompt sees the richer per-commit summaries. Bulk commits # are described per-file against the target's path instead. The cache # fingerprint is sha256-over-sorted-SHAs, so backfilling here does diff --git a/tests/test_cli_scan_phases.py b/tests/test_cli_scan_phases.py index 08cc320..9294ef5 100644 --- a/tests/test_cli_scan_phases.py +++ b/tests/test_cli_scan_phases.py @@ -165,7 +165,7 @@ def test_three_phases_run_in_order_with_llm_last( assert "done in" in result.output -def test_no_llm_descriptions_drops_the_llm_phase( +def test_skip_analyze_drops_the_llm_phase( isolated_db: Path, monkeypatch: pytest.MonkeyPatch ) -> None: order: list[tuple[str, str]] = [] @@ -174,7 +174,7 @@ def test_no_llm_descriptions_drops_the_llm_phase( scan_mod, "_select_github_client", lambda *a, **k: _DummyClient() ) - result = CliRunner().invoke(whygraph_main, ["scan", "--no-llm-descriptions"]) + result = CliRunner().invoke(whygraph_main, ["scan", "--skip-analyze"]) assert result.exit_code == 0, result.output # Two phases: structural + pr-origin recovery; no LLM phase. @@ -191,7 +191,7 @@ def test_single_phase_when_remote_and_llm_disabled( stubs = _patch_crawlers(monkeypatch, order) result = CliRunner().invoke( - whygraph_main, ["scan", "--no-remote", "--no-llm-descriptions"] + whygraph_main, ["scan", "--no-remote", "--skip-analyze"] ) assert result.exit_code == 0, result.output diff --git a/tests/test_cli_scan_no_llm_descriptions.py b/tests/test_cli_scan_skip_analyze.py similarity index 93% rename from tests/test_cli_scan_no_llm_descriptions.py rename to tests/test_cli_scan_skip_analyze.py index c82fd6c..5a213c6 100644 --- a/tests/test_cli_scan_no_llm_descriptions.py +++ b/tests/test_cli_scan_skip_analyze.py @@ -1,4 +1,4 @@ -"""Tests for ``whygraph scan --no-llm-descriptions``. +"""Tests for ``whygraph scan --skip-analyze``. The crawlers themselves have dedicated tests (``test_git_crawler.py``, ``test_scan_analyze_crawler.py``). This module pins the flag wiring: @@ -115,7 +115,7 @@ def no_github(monkeypatch: pytest.MonkeyPatch) -> None: ) -def test_scan_no_llm_descriptions_skips_phase_two( +def test_scan_skip_analyze_skips_phase_two( isolated_db: Path, stub_crawlers: dict[str, type], no_github: None, @@ -132,7 +132,7 @@ def from_config(cls, _cfg): monkeypatch.setattr("whygraph.analyze.LlmDescriptor", _FailIfProbed) - result = CliRunner().invoke(whygraph_main, ["scan", "--no-llm-descriptions"]) + result = CliRunner().invoke(whygraph_main, ["scan", "--skip-analyze"]) assert result.exit_code == 0, result.output assert stub_crawlers["git"].constructed == 1 @@ -140,10 +140,10 @@ def from_config(cls, _cfg): assert probe_calls == [] # descriptor probe bypassed entirely # Panel text — split because Rich may insert soft wraps. assert "skipped" in result.output - assert "--no-llm-descriptions" in result.output + assert "--skip-analyze" in result.output -def test_scan_no_llm_descriptions_tolerates_broken_analyze_config( +def test_scan_skip_analyze_tolerates_broken_analyze_config( isolated_db: Path, stub_crawlers: dict[str, type], no_github: None, @@ -158,7 +158,7 @@ def from_config(cls, _cfg): monkeypatch.setattr("whygraph.analyze.LlmDescriptor", _AlwaysFails) - result = CliRunner().invoke(whygraph_main, ["scan", "--no-llm-descriptions"]) + result = CliRunner().invoke(whygraph_main, ["scan", "--skip-analyze"]) assert result.exit_code == 0, result.output assert stub_crawlers["analyze"].constructed == 0 diff --git a/tests/test_init_agents.py b/tests/test_init_agents.py index c365833..cf2a78c 100644 --- a/tests/test_init_agents.py +++ b/tests/test_init_agents.py @@ -263,23 +263,31 @@ def _invoke_in(cwd: Path, *args: str): return runner.invoke(whygraph_main, list(args)), Path.cwd() -def test_init_list_agents_does_not_touch_db( - monkeypatch: pytest.MonkeyPatch, tmp_path: Path +def test_init_help_lists_agents_without_touching_db( + monkeypatch: pytest.MonkeyPatch, ) -> None: + """``init --help`` lists the supported agents and never bootstraps the DB. + + The supported-agents block moved from the removed ``--list-agents`` + command into the ``--help`` epilog. ``--help`` short-circuits in + Click before the command body, so the DB is never touched. + """ called = {"n": 0} def _fake() -> Path: called["n"] += 1 - return tmp_path / "x.db" + return Path("x.db") monkeypatch.setattr("whygraph.cli.commands.init._ensure_db_initialized", _fake) runner = CliRunner() - result = runner.invoke(whygraph_main, ["init", "--list-agents"]) + result = runner.invoke(whygraph_main, ["init", "--help"]) assert result.exit_code == 0, result.output assert called["n"] == 0 + assert "Supported agents" in result.output assert "claude" in result.output assert "cursor" in result.output assert "codex" in result.output + assert "vscode" in result.output def test_init_no_flag_writes_no_agent_config(stub_init, tmp_path: Path) -> None: @@ -357,18 +365,6 @@ def test_init_agent_claude_writes_mcp_json_and_installs_assets( assert "Installed assets for claude" in result.output -def test_init_agent_claude_no_install_assets_skips_dot_claude( - stub_init, tmp_path: Path -) -> None: - result, cwd = _invoke_in( - tmp_path, "init", "--agent", "claude", "--no-install-assets" - ) - assert result.exit_code == 0, result.output - assert (cwd / ".mcp.json").exists() - assert not (cwd / ".claude").exists() - assert "Installed assets for" not in result.output - - def test_init_agent_claude_force_overwrites_existing(stub_init, tmp_path: Path) -> None: # Pre-seed a user edit at the install destination. runner = CliRunner() @@ -566,19 +562,6 @@ def test_init_agent_codex_merges_existing_agents_md(stub_init, tmp_path: Path) - assert merged.find("Our team rules") < merged.find("") -def test_init_agent_claude_with_print_skips_mcp_write_but_installs_assets( - stub_init, tmp_path: Path -) -> None: - """``--print`` suppresses the MCP write; asset install runs normally.""" - result, cwd = _invoke_in(tmp_path, "init", "--agent", "claude", "--print") - assert result.exit_code == 0, result.output - assert not (cwd / ".mcp.json").exists() - # JSON snippet still printed for the user to paste. - assert '"whygraph-mcp"' in result.output - # Assets are governed by --no-install-assets, not --print. - assert (cwd / ".claude" / "agents" / "planner.md").is_file() - - def test_init_unknown_agent_errors(stub_init, tmp_path: Path) -> None: result, _ = _invoke_in(tmp_path, "init", "--agent", "emacs") assert result.exit_code != 0 @@ -667,7 +650,7 @@ def _abort(*_a, **_k): runner = CliRunner() with runner.isolated_filesystem(temp_dir=tmp_path): cwd = Path.cwd() - result = runner.invoke(whygraph_main, ["init", "--skip-preflight"]) + result = runner.invoke(whygraph_main, ["init"]) assert result.exit_code != 0 assert "Aborted" in result.output # No files written, and the DB was never bootstrapped.