From 2f57c5871cc57a312e27b4a542cc45925ecb9c58 Mon Sep 17 00:00:00 2001 From: Marc-cn <130138935+Marc-cn@users.noreply.github.com> Date: Sat, 10 Oct 2026 11:36:16 -0400 Subject: [PATCH] fix(cli): make darnit serve default to openssf-baseline With no --framework, cmd_serve took the first name from a sorted list of installed frameworks. On a full darnit-mcp install that is gittuf, so the MCP entry darnit install writes (uvx --from darnit-mcp darnit serve) gave clients the gittuf tools. Use the same default as darnit audit: openssf-baseline when it is installed, otherwise the only installed framework. With several installed and no baseline, exit with an error that asks for --framework. Signed-off-by: Marc-cn <130138935+Marc-cn@users.noreply.github.com> Assisted-by: Claude:claude-fable-5-1 --- CHANGELOG.md | 6 ++++ packages/darnit/src/darnit/cli.py | 33 +++++++++++++++++---- tests/darnit/test_cli.py | 48 +++++++++++++++++++++++++++++++ 3 files changed, 82 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d54d77f..c896d1ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -572,6 +572,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 error now gives the real cause. Both guards said "not installed" for any import error, which was wrong for an installed package with a missing dependency or a broken release (#573). +- `darnit serve` with no `--framework` now serves OpenSSF Baseline when it + is installed, the same default as `darnit audit`. It used to take the + first installed framework by name, which on a full `darnit-mcp` install + is `gittuf`, so the MCP entry that `darnit install` writes gave clients + the gittuf tools. With several frameworks installed and no baseline it + now asks for `--framework` instead of guessing. - Installs without the development dependencies work again (`uv tool install` from a checkout, and any install from a built wheel). Two things broke them: `tree-sitter-language-pack` 1.6.3, the newest release the old diff --git a/packages/darnit/src/darnit/cli.py b/packages/darnit/src/darnit/cli.py index 20fd9668..d056c9d4 100644 --- a/packages/darnit/src/darnit/cli.py +++ b/packages/darnit/src/darnit/cli.py @@ -1097,6 +1097,24 @@ def _emit_exit_summary(reason: str, exit_code: int) -> None: harness_logger.info("harness: %s, exit %d", reason, int(exit_code)) +DEFAULT_FRAMEWORK = "openssf-baseline" + + +def _default_framework(frameworks: list[str]) -> str | None: + """Pick the framework `darnit serve` uses when none is named. + + The same default as `darnit audit` (``load_effective_config_auto``): + OpenSSF Baseline when it is installed. Otherwise the only installed + framework. With several installed and no baseline there is no safe guess, + so the caller has to ask for ``--framework``. + """ + if DEFAULT_FRAMEWORK in frameworks: + return DEFAULT_FRAMEWORK + if len(frameworks) == 1: + return frameworks[0] + return None + + def cmd_serve(args: argparse.Namespace) -> int: """Start the MCP server. @@ -1147,14 +1165,19 @@ def cmd_serve(args: argparse.Namespace) -> int: allowed = operator_config.config.plugins.allowed if allowed: frameworks = [f for f in frameworks if f in allowed] - if frameworks: - framework_name = frameworks[0] # Default to first available - else: + if not frameworks: logger.error( "No framework specified and none found. " "Use 'darnit serve config.toml' or install a framework package." ) return 1 + framework_name = _default_framework(frameworks) + if framework_name is None: + logger.error( + f"No framework specified and several are installed: {', '.join(frameworks)}. " + "Choose one with --framework." + ) + return 1 # Get framework path and use it as config try: @@ -1395,7 +1418,7 @@ def create_parser() -> argparse.ArgumentParser: "Usage:\n" " darnit serve config.toml # Use TOML config file\n" " darnit serve --framework NAME # Use named framework\n" - " darnit serve # Auto-detect framework", + " darnit serve # OpenSSF Baseline, if installed", formatter_class=argparse.RawDescriptionHelpFormatter, ) serve_parser.add_argument( @@ -1405,7 +1428,7 @@ def create_parser() -> argparse.ArgumentParser: ) serve_parser.add_argument( "-f", "--framework", - help="Framework to use (default: auto-detect). Ignored if config file is provided.", + help="Framework to use (default: openssf-baseline if installed, else the only installed framework). Ignored if config file is provided.", ) _add_operator_config_args(serve_parser) serve_parser.set_defaults(func=cmd_serve) diff --git a/tests/darnit/test_cli.py b/tests/darnit/test_cli.py index 13472815..8c6f5a47 100644 --- a/tests/darnit/test_cli.py +++ b/tests/darnit/test_cli.py @@ -135,6 +135,54 @@ def test_install_preserves_existing_settings(tmp_path, monkeypatch): assert "darnit" in data["mcpServers"] +@pytest.mark.parametrize( + ("frameworks", "expected"), + [ + (["gittuf", "openssf-baseline", "reproducibility"], "openssf-baseline"), + (["community-spec", "gittuf", "hello", "openssf-baseline"], "openssf-baseline"), + (["gittuf"], "gittuf"), + (["community-spec", "gittuf"], None), + ], +) +def test_default_framework(frameworks, expected): + from darnit.cli import _default_framework + + assert _default_framework(frameworks) == expected + + +def _serve_with(monkeypatch, frameworks): + """Run `darnit serve` with a fake framework list; return (exit code, served names).""" + served = [] + + class _Server: + def run(self): + pass + + def fake_create_server(path, **kwargs): + served.append(path) + return _Server() + + monkeypatch.setattr("darnit.config.list_available_frameworks", lambda: list(frameworks)) + monkeypatch.setattr("darnit.config.resolve_framework_path", lambda name: name) + monkeypatch.setattr("darnit.server.create_server", fake_create_server) + return main(["serve"]), served + + +def test_serve_defaults_to_the_baseline_not_the_first_name(monkeypatch): + code, served = _serve_with(monkeypatch, ["gittuf", "openssf-baseline", "reproducibility"]) + + assert code == 0 + assert served == ["openssf-baseline"] + + +def test_serve_refuses_to_guess_between_other_frameworks(monkeypatch, caplog): + code, served = _serve_with(monkeypatch, ["community-spec", "gittuf"]) + + assert code == 1 + assert served == [] + assert any("--framework" in record.message for record in caplog.records) + + class TestCreateParser: """Tests for CLI argument parsing."""