diff --git a/resources/scripts/nap_install.py b/resources/scripts/nap_install.py index b8ebfb0..bc29e00 100644 --- a/resources/scripts/nap_install.py +++ b/resources/scripts/nap_install.py @@ -6,6 +6,7 @@ Usage: nap init nap status nap doctor + nap reindex nap auth login nap project create nap project attach @@ -84,6 +85,7 @@ CANONICAL_COMMANDS = ( ("init", "Initialize Napseer in the current folder."), ("status", "Show concise account, project, and runtime state."), ("doctor", "Diagnose local setup without changing it."), + ("reindex", "Rebuild the current project's local memory index."), ("auth", "Authenticate an account or repair credentials."), ("project", "Create, attach, claim, or inspect a project."), ("mcp", "Install, update, inspect, or serve the local MCP runtime."), @@ -96,6 +98,7 @@ COMMAND_METADATA = { "init": {"mutates": True, "visible": True}, "status": {"mutates": False, "visible": True}, "doctor": {"mutates": False, "visible": True}, + "reindex": {"mutates": True, "visible": True}, "auth": {"mutates": True, "visible": True}, "project": {"mutates": True, "visible": True}, "mcp": {"mutates": True, "visible": True}, @@ -1066,6 +1069,10 @@ def print_command_help(command): "Usage: nap doctor\n" "Run read-only checks and print exact repair commands." ), + "reindex": ( + "Usage: nap reindex [--limit N]\n" + "Rebuild the current project's local search and graph index." + ), "auth": ( "Usage: nap auth [status|login|repair]\n" " login Authenticate an account; does not select a project.\n" @@ -1445,6 +1452,8 @@ def main(argv): if command == "doctor": print_json(doctor_status()) return 0 + if command == "reindex": + return run_wrapper("reindex", args) if command == "mcp": return handle_mcp(args) if command == "version": diff --git a/resources/scripts/napseer_mcp_server.py b/resources/scripts/napseer_mcp_server.py index 3e141c8..54e8518 100755 --- a/resources/scripts/napseer_mcp_server.py +++ b/resources/scripts/napseer_mcp_server.py @@ -17760,6 +17760,17 @@ class McpRequestRuntime: def cli_main(argv): command = argv[1] if len(argv) > 1 else "" + if command == "reindex": + args = argv[2:] + validate_cli_options( + args, + value_options={"--limit"}, + command_label="nap reindex", + ) + print(json.dumps(reindex_project({ + "limit": int(cli_option(args, "--limit", default="200")), + }), indent=2)) + return if command == "configure": print(json.dumps(cli_configure_status(), indent=2)) return diff --git a/tests/test_cli_command_system.py b/tests/test_cli_command_system.py index 5e44b93..b426942 100644 --- a/tests/test_cli_command_system.py +++ b/tests/test_cli_command_system.py @@ -33,7 +33,7 @@ def test_root_help_exposes_only_canonical_commands(capsys): output = capsys.readouterr().out visible = [name for name, metadata in module.COMMAND_METADATA.items() if metadata["visible"]] assert visible == [name for name, _summary in module.CANONICAL_COMMANDS] - assert len(visible) == 10 + assert len(visible) == 11 for name in visible: assert f" {name}" in output for hidden in ("chat", "plan", "lineage", "agent", "feedback", "where", "install"): @@ -101,6 +101,24 @@ def test_init_and_auth_repair_normalize_to_existing_handlers(monkeypatch): ] +def test_reindex_routes_to_worker_and_has_side_effect_free_help(monkeypatch, capsys): + module = load_installer() + calls = [] + monkeypatch.setattr( + module, + "run_wrapper", + lambda command, args: calls.append((command, args)) or 0, + ) + + assert module.main(["nap", "reindex", "--limit", "50"]) == 0 + assert calls == [("reindex", ["--limit", "50"])] + + calls.clear() + assert module.main(["nap", "reindex", "--help"]) == 0 + assert calls == [] + assert "Usage: nap reindex [--limit N]" in capsys.readouterr().out + + def test_update_does_not_overwrite_repository_source(monkeypatch, capsys): module = load_installer() calls = [] diff --git a/tests/test_cli_dispatch_consolidation.py b/tests/test_cli_dispatch_consolidation.py index 39291fd..dd712a9 100755 --- a/tests/test_cli_dispatch_consolidation.py +++ b/tests/test_cli_dispatch_consolidation.py @@ -36,6 +36,22 @@ def expect_unknown(mod, argv, expected_fragment): raise AssertionError(f"expected RuntimeError for {argv}") +def test_reindex_cli_rebuilds_with_bounded_page_size(): + mod = load_module() + calls = [] + mod.reindex_project = lambda args: calls.append(dict(args)) or { + "indexed": 7, + "graph_complete": True, + } + + output = io.StringIO() + with contextlib.redirect_stdout(output): + mod.cli_main(["napseer_mcp_server.py", "reindex", "--limit", "50"]) + + assert calls == [{"limit": 50}] + assert '"indexed": 7' in output.getvalue() + + def test_auth_login_rejects_history_expansion_tail_before_browser(): mod = load_module() mod.operator_account_login = lambda _args: (_ for _ in ()).throw(