2121REPO_ROOT = os .path .dirname (os .path .dirname (os .path .abspath (__file__ )))
2222sys .path .insert (0 , REPO_ROOT )
2323
24+ from app import build_cli_parser
2425from scripts .export import build_parser
2526
2627
@@ -61,6 +62,25 @@ def _debug_kwarg_uses_args(call: ast.Call) -> bool:
6162 return False
6263
6364
65+ def _ast_mentions_args_debug (node : ast .AST ) -> bool :
66+ for child in ast .walk (node ):
67+ if (
68+ isinstance (child , ast .Attribute )
69+ and child .attr == "debug"
70+ and isinstance (child .value , ast .Name )
71+ and child .value .id == "args"
72+ ):
73+ return True
74+ return False
75+
76+
77+ def _use_reloader_kwarg_tied_to_debug (call : ast .Call ) -> bool :
78+ for kw in call .keywords :
79+ if kw .arg == "use_reloader" :
80+ return _ast_mentions_args_debug (kw .value )
81+ return False
82+
83+
6484# ---------------------------------------------------------------------------
6585# export.py argument tests
6686# ---------------------------------------------------------------------------
@@ -252,37 +272,26 @@ def test_help_exits_zero(self):
252272# ---------------------------------------------------------------------------
253273
254274class TestAppArgparse :
255- """app.py __main__ block must expose the same flags as cursor's app.py."""
256-
257- def _build_parser (self ) -> argparse .ArgumentParser :
258- """Re-create the argparse parser from app.py without importing Flask."""
259- parser = argparse .ArgumentParser (description = "Claude Code Chat Browser" )
260- parser .add_argument ("--port" , type = int , default = 5000 )
261- parser .add_argument ("--host" , default = "127.0.0.1" )
262- parser .add_argument ("--debug" , action = "store_true" , default = False )
263- parser .add_argument ("--base-dir" , default = None )
264- parser .add_argument ("--exclude-rules" , "-e" , default = None ,
265- metavar = "PATH" , dest = "exclude_rules" )
266- return parser
275+ """app.py CLI must expose the same flags as cursor's app.py."""
267276
268277 def test_host_default_is_localhost (self ):
269278 """Default host must be 127.0.0.1 to match cursor which binds to localhost only."""
270- parser = self . _build_parser ()
279+ parser = build_cli_parser ()
271280 args = parser .parse_args ([])
272281 assert args .host == "127.0.0.1"
273282
274283 def test_host_override (self ):
275- parser = self . _build_parser ()
284+ parser = build_cli_parser ()
276285 args = parser .parse_args (["--host" , "127.0.0.1" ])
277286 assert args .host == "127.0.0.1"
278287
279288 def test_debug_default_is_false (self ):
280- parser = self . _build_parser ()
289+ parser = build_cli_parser ()
281290 args = parser .parse_args ([])
282291 assert args .debug is False
283292
284293 def test_debug_explicit_true (self ):
285- parser = self . _build_parser ()
294+ parser = build_cli_parser ()
286295 args = parser .parse_args (["--debug" ])
287296 assert args .debug is True
288297
@@ -297,38 +306,38 @@ def test_app_py_debug_not_hardcoded_true(self):
297306 assert any (_debug_kwarg_uses_args (c ) for c in app_run_calls )
298307
299308 def test_port_default (self ):
300- parser = self . _build_parser ()
309+ parser = build_cli_parser ()
301310 args = parser .parse_args ([])
302311 assert args .port == 5000
303312
304313 def test_port_override (self ):
305- parser = self . _build_parser ()
314+ parser = build_cli_parser ()
306315 args = parser .parse_args (["--port" , "8080" ])
307316 assert args .port == 8080
308317
309318 def test_base_dir_default_none (self ):
310- parser = self . _build_parser ()
319+ parser = build_cli_parser ()
311320 args = parser .parse_args ([])
312321 assert args .base_dir is None
313322
314323 def test_base_dir_override (self ):
315- parser = self . _build_parser ()
324+ parser = build_cli_parser ()
316325 args = parser .parse_args (["--base-dir" , "/tmp/projects" ])
317326 assert args .base_dir == "/tmp/projects"
318327
319328 def test_exclude_rules_default_none (self ):
320- parser = self . _build_parser ()
329+ parser = build_cli_parser ()
321330 args = parser .parse_args ([])
322331 assert args .exclude_rules is None
323332
324333 def test_exclude_rules_long_form (self ):
325- parser = self . _build_parser ()
334+ parser = build_cli_parser ()
326335 args = parser .parse_args (["--exclude-rules" , "/tmp/rules.txt" ])
327336 assert args .exclude_rules == "/tmp/rules.txt"
328337
329338 def test_exclude_rules_short_form (self ):
330339 """Cursor's app.py uses -e as the short form; claude must too."""
331- parser = self . _build_parser ()
340+ parser = build_cli_parser ()
332341 args = parser .parse_args (["-e" , "/tmp/rules.txt" ])
333342 assert args .exclude_rules == "/tmp/rules.txt"
334343
@@ -353,11 +362,15 @@ def test_app_py_host_default_is_localhost(self):
353362 assert '"127.0.0.1"' in src
354363
355364 def test_app_py_use_reloader_is_platform_aware (self ):
356- """use_reloader must depend on sys.platform, not be hardcoded False ."""
365+ """use_reloader must be opt-in via --debug and gated on non-Windows platforms ."""
357366 app_path = os .path .join (REPO_ROOT , "app.py" )
358- with open (app_path , "r" , encoding = "utf-8" ) as f :
367+ with open (app_path , encoding = "utf-8" ) as f :
368+ tree = ast .parse (f .read (), filename = app_path )
369+ app_run_calls = [n for n in ast .walk (tree ) if _is_app_run_call (n )]
370+ assert app_run_calls
371+ assert all (_use_reloader_kwarg_tied_to_debug (c ) for c in app_run_calls )
372+ with open (app_path , encoding = "utf-8" ) as f :
359373 src = f .read ()
360374 assert "sys.platform" in src
361375 assert "win32" in src
362- # Must NOT have unconditional use_reloader=False
363376 assert "use_reloader=False" not in src
0 commit comments