Skip to content

Commit 9cf311a

Browse files
committed
feat(cli): wire paths + bootstrap + proxy + degraded mode
Also updates test_smoke.py to mock bootstrap/proxy now that main() is real orchestration rather than a print-and-return stub.
1 parent 8f6b4ac commit 9cf311a

3 files changed

Lines changed: 103 additions & 12 deletions

File tree

src/codegraphagent/cli.py

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,29 @@
11
"""CLI entry point for codegraphagent.
22
3-
This is the [project.scripts] target. It defers the real work to a function
4-
in this module so that `python -m codegraphagent` and the `codegraphagent`
5-
command share the same entrypoint.
3+
Orchestrates the three pieces of the shim:
4+
1. paths — resolve the project root and ensure the .biorouter/codegraph
5+
state dir + .codegraph symlink exist.
6+
2. bootstrap — ensure the vendored engine bundle is downloaded, verified,
7+
and extracted.
8+
3. proxy — spawn the engine and pump MCP traffic.
9+
10+
If either of the first two fails, hand control to the degraded-mode error
11+
shim so the agent gets a structured error frame rather than an opaque crash.
612
"""
713

14+
from __future__ import annotations
15+
16+
from codegraphagent import bootstrap, error_shim, paths, proxy
17+
from codegraphagent.errors import CodeGraphAgentError
18+
819

920
def main() -> int:
10-
"""Run the CodeGraphAgent MCP server.
21+
try:
22+
root = paths.resolve_project_root()
23+
paths.ensure_layout(root)
24+
launcher = bootstrap.ensure_engine()
25+
except CodeGraphAgentError as exc:
26+
error_shim.serve(exc)
27+
return 0
1128

12-
Returns the process exit code (0 on clean shutdown, non-zero otherwise).
13-
Real implementation lands in later tasks; for now this is a stub.
14-
"""
15-
print("codegraphagent: stub — wiring lands in Task E2")
16-
return 0
29+
return proxy.run(launcher=launcher, cwd=root)

tests/test_cli.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
"""cli.main — orchestrates paths.ensure_layout → bootstrap.ensure_engine → proxy.run."""
2+
3+
from __future__ import annotations
4+
5+
from pathlib import Path
6+
from unittest.mock import patch, MagicMock
7+
8+
import pytest
9+
10+
from codegraphagent import cli
11+
from codegraphagent.errors import BootstrapError, LayoutConflictError
12+
13+
14+
def test_main_happy_path(tmp_path: Path, monkeypatch: pytest.MonkeyPatch):
15+
monkeypatch.setenv("BIOROUTER_WORKING_DIR", str(tmp_path))
16+
(tmp_path / ".git").mkdir()
17+
18+
fake_launcher = tmp_path / "fake-launcher"
19+
fake_launcher.write_text("")
20+
21+
proxy_run = MagicMock(return_value=0)
22+
23+
with patch.object(cli.bootstrap, "ensure_engine", return_value=fake_launcher), \
24+
patch.object(cli.proxy, "run", proxy_run):
25+
rc = cli.main()
26+
27+
assert rc == 0
28+
proxy_run.assert_called_once()
29+
kwargs = proxy_run.call_args.kwargs
30+
assert kwargs["launcher"] == fake_launcher
31+
assert kwargs["cwd"] == tmp_path.resolve()
32+
33+
34+
def test_main_falls_back_to_error_shim_on_bootstrap_error(
35+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
36+
):
37+
monkeypatch.setenv("BIOROUTER_WORKING_DIR", str(tmp_path))
38+
(tmp_path / ".git").mkdir()
39+
40+
err = BootstrapError("nope", url="https://example.com/x")
41+
error_shim_serve = MagicMock()
42+
43+
with patch.object(cli.bootstrap, "ensure_engine", side_effect=err), \
44+
patch.object(cli.error_shim, "serve", error_shim_serve):
45+
rc = cli.main()
46+
47+
assert rc == 0
48+
error_shim_serve.assert_called_once()
49+
assert error_shim_serve.call_args.args[0] is err
50+
51+
52+
def test_main_falls_back_to_error_shim_on_layout_conflict(
53+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
54+
):
55+
monkeypatch.setenv("BIOROUTER_WORKING_DIR", str(tmp_path))
56+
(tmp_path / ".git").mkdir()
57+
(tmp_path / ".codegraph").mkdir()
58+
59+
error_shim_serve = MagicMock()
60+
with patch.object(cli.error_shim, "serve", error_shim_serve):
61+
rc = cli.main()
62+
63+
assert rc == 0
64+
error_shim_serve.assert_called_once()
65+
assert isinstance(error_shim_serve.call_args.args[0], LayoutConflictError)

tests/test_smoke.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,20 @@ def test_version_is_set():
77
assert codegraphagent.__version__ == "0.1.0"
88

99

10-
def test_cli_entrypoint_runs():
11-
from codegraphagent.cli import main
12-
rc = main()
10+
def test_cli_entrypoint_runs(tmp_path, monkeypatch):
11+
"""main() returns 0; full bootstrap is mocked to avoid network I/O."""
12+
from pathlib import Path
13+
from unittest.mock import patch, MagicMock
14+
from codegraphagent import cli
15+
16+
monkeypatch.setenv("BIOROUTER_WORKING_DIR", str(tmp_path))
17+
(tmp_path / ".git").mkdir()
18+
19+
fake_launcher = tmp_path / "fake-launcher"
20+
fake_launcher.write_text("")
21+
22+
with patch.object(cli.bootstrap, "ensure_engine", return_value=fake_launcher), \
23+
patch.object(cli.proxy, "run", MagicMock(return_value=0)):
24+
rc = cli.main()
25+
1326
assert rc == 0

0 commit comments

Comments
 (0)