diff --git a/docs/compile.md b/docs/compile.md index bae72c6c..766cfdb0 100644 --- a/docs/compile.md +++ b/docs/compile.md @@ -6,7 +6,7 @@ start compiling*: sessions and sources feed claims through the review gate; compile distills those claims into the small, durable wiki a future agent (or human) reads first. -``` +```text sessions / sources claims topic pages ───────────────────▶ gate ───────────▶ compile ───────────▶ gate ──▶ wiki (capture) (approve) (LLM drafts) (approve) diff --git a/src/vouch/compile.py b/src/vouch/compile.py index df7b6c2d..8dbf4a86 100644 --- a/src/vouch/compile.py +++ b/src/vouch/compile.py @@ -314,6 +314,10 @@ def compile_kb( "e.g.\ncompile:\n llm_cmd: \"claude -p --model sonnet\"" ) cap = max_pages if max_pages is not None else cfg.max_pages + if cap < 1: + # a zero/negative cap would drop every draft after spending the LLM + # run; refuse up front instead of silently producing nothing. + raise CompileError(f"max_pages must be >= 1, got {cap}") prompt = build_prompt(store, max_pages=cap) drafts = parse_drafts(run_llm(cmd, prompt, timeout_seconds=cfg.timeout_seconds)) diff --git a/tests/test_compile.py b/tests/test_compile.py index e4fc80ee..64137a0d 100644 --- a/tests/test_compile.py +++ b/tests/test_compile.py @@ -202,6 +202,13 @@ def test_empty_kb_raises_before_running_llm(store: KBStore) -> None: compile_kb(store, config=_cfg("false")) +def test_non_positive_max_pages_raises(store: KBStore) -> None: + # `false` as llm_cmd: the guard must fire before the LLM is spent + _approved_claim(store, "a fact") + with pytest.raises(CompileError, match="max_pages must be >= 1"): + compile_kb(store, config=_cfg("false"), max_pages=0) + + def test_llm_failure_raises(store: KBStore) -> None: _approved_claim(store, "a fact") with pytest.raises(CompileError, match="failed"):