Skip to content

Commit bb380a5

Browse files
authored
Add reasoning mode configuration (#175)
1 parent dc0da26 commit bb380a5

35 files changed

Lines changed: 778 additions & 75 deletions

.env.example

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,12 @@ REPOWISE_DEFAULT_MODEL=claude-sonnet-4-6
7272
# Max concurrent LLM calls during generation
7373
REPOWISE_MAX_CONCURRENT=5
7474

75+
# Reasoning mode for supported docs-generation providers: auto | off | minimal
76+
# auto = provider default; off = disable Qwen3/OpenRouter effort reasoning when supported;
77+
# minimal = request the lowest supported OpenAI/OpenRouter reasoning effort.
78+
# Unsupported explicit modes fail before an API call.
79+
REPOWISE_REASONING=auto
80+
7581
# Max pages to regenerate per maintenance run
7682
REPOWISE_CASCADE_BUDGET=30
7783

README.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -577,9 +577,10 @@ repowise reindex # rebuild vector store (no LLM calls)
577577
`repowise init` generates `.repowise/config.yaml`. Key options:
578578

579579
```yaml
580-
provider: anthropic # anthropic | openai | ollama | litellm
580+
provider: anthropic # anthropic | openai | openrouter | gemini | deepseek | ollama | litellm | mock
581581
model: claude-sonnet-4-5
582582
embedding_model: voyage-3
583+
reasoning: auto # auto | off | minimal
583584

584585
git:
585586
co_change_commit_limit: 500
@@ -594,6 +595,10 @@ maintenance:
594595
background_regen_schedule: "0 2 * * *"
595596
```
596597
598+
`reasoning` applies to documentation generation. `auto` preserves provider
599+
defaults; explicit `off` / `minimal` modes are translated only by providers and
600+
models that support them, otherwise repowise fails before making an API call.
601+
597602
Full configuration reference: [docs/CONFIG.md](docs/CONFIG.md)
598603

599604
---

docs/CLI_REFERENCE.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ In workspace mode, adds: repo scanning, per-repo indexing, cross-repo analysis (
5050

5151
| Flag | Description |
5252
|------|-------------|
53-
| `--provider` | LLM provider: `anthropic`, `openai`, `gemini`, `ollama`, `mock` |
53+
| `--provider` | LLM provider: `anthropic`, `openai`, `openrouter`, `gemini`, `deepseek`, `ollama`, `litellm`, `mock` |
5454
| `--model` | Model name override (e.g., `claude-sonnet-4-6`) |
5555
| `--embedder` | Embedder for semantic search: `gemini`, `openai`, `mock` |
5656
| `--index-only` | Skip LLM generation. Only parse, build graph, and index git. Free. |
@@ -61,6 +61,7 @@ In workspace mode, adds: repo scanning, per-repo indexing, cross-repo analysis (
6161
| `--exclude / -x` | Gitignore-style exclusion patterns. Repeatable. |
6262
| `--include-submodules` | Include git submodule directories. |
6363
| `--concurrency` | Max concurrent LLM calls (default: 5). |
64+
| `--reasoning` | Reasoning mode for supported providers: `auto`, `off`, or `minimal` (default: `auto`). |
6465
| `--resume` | Resume from the last checkpoint if interrupted. |
6566
| `--force` | Regenerate all pages even if they exist. |
6667
| `--commit-limit` | Max commits to analyze per file (default: 500). |
@@ -76,6 +77,8 @@ repowise init --provider anthropic --yes # automated
7677
repowise init --index-only # free, no LLM
7778
repowise init --dry-run # preview cost
7879
repowise init --test-run # quick test (10 files)
80+
repowise init --provider openai --model qwen3 --reasoning off
81+
repowise init --provider openrouter --model openai/gpt-5 --reasoning minimal
7982
repowise init -x vendor/ -x "*.gen.go" # exclude patterns
8083
repowise init --include-submodules # include submodules
8184
repowise init . # workspace mode
@@ -95,6 +98,7 @@ Incrementally update wiki pages for files changed since the last sync.
9598
| `--provider` | Override LLM provider for this run |
9699
| `--model` | Override model |
97100
| `--since` | Git ref to diff from (overrides `state.json`) |
101+
| `--reasoning` | Reasoning mode for supported providers: `auto`, `off`, or `minimal` |
98102
| `--cascade-budget` | Max pages to regenerate (default: auto) |
99103
| `--dry-run` | Show what would be updated without regenerating |
100104
| `--workspace` | Update all stale repos in the workspace + cross-repo analysis |
@@ -109,6 +113,7 @@ Incrementally update wiki pages for files changed since the last sync.
109113
repowise update # diff since last sync
110114
repowise update --dry-run # preview
111115
repowise update --since v1.0.0 # diff from a tag
116+
repowise update --reasoning off # one-off supported-provider thinking-off run
112117
repowise update --workspace # all workspace repos (incl. first-time indexing)
113118
repowise update --repo backend # specific workspace repo
114119
repowise update --no-workspace # force single-repo mode in a workspace root

docs/USER_GUIDE.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ repowise init [PATH]
162162

163163
| Flag | Description |
164164
|------|-------------|
165-
| `--provider` | LLM provider: `anthropic`, `openai`, `gemini`, `ollama`, `mock`. Auto-detected from env vars if not set. |
165+
| `--provider` | LLM provider: `anthropic`, `openai`, `openrouter`, `gemini`, `deepseek`, `ollama`, `litellm`, `mock`. Auto-detected from env vars if not set. |
166166
| `--model` | Model name override (e.g., `claude-sonnet-4-6`, `gpt-5.4-nano`) |
167167
| `--embedder` | Embedder for semantic search: `gemini`, `openai`, `mock`. Auto-detected from env vars. |
168168
| `--index-only` | Skip LLM generation entirely. Only parse, build graph, and index git. Free. |
@@ -172,6 +172,7 @@ repowise init [PATH]
172172
| `--skip-infra` | Exclude infrastructure files (Dockerfiles, Makefiles, Terraform, shell scripts). |
173173
| `--exclude / -x` | Gitignore-style exclusion patterns. Repeatable: `-x vendor/ -x "*.generated.*"` |
174174
| `--concurrency` | Max concurrent LLM calls (default: 5). Higher = faster but more API pressure. |
175+
| `--reasoning` | Reasoning mode for supported providers: `auto`, `off`, or `minimal` (default: `auto`). |
175176
| `--resume` | Resume from the last checkpoint if a previous run was interrupted. |
176177
| `--force` | Regenerate all pages even if they already exist. |
177178
| `--commit-limit` | Max commits to analyze per file (default: 500, max: 5000). Saved to config. |
@@ -197,6 +198,9 @@ repowise init --provider openai --dry-run
197198
# Quick test with 10 files
198199
repowise init --provider gemini --test-run
199200

201+
# OpenRouter with minimal reasoning effort
202+
repowise init --provider openrouter --model openai/gpt-5 --reasoning minimal
203+
200204
# Exclude vendor and generated code
201205
repowise init -x vendor/ -x "*.gen.go" -x "**/__generated__/**"
202206
```
@@ -228,6 +232,7 @@ Much faster and cheaper than a full `init` — only regenerates pages for change
228232
| `--provider` | Override LLM provider for this run |
229233
| `--model` | Override model |
230234
| `--since` | Git ref to diff from (overrides `state.json`). Example: `--since v1.0.0` |
235+
| `--reasoning` | Reasoning mode for supported providers: `auto`, `off`, or `minimal`. |
231236
| `--cascade-budget` | Max pages to regenerate per run (default: 30). Prevents runaway regeneration. |
232237
| `--dry-run` | Show what would be updated without regenerating. |
233238

@@ -246,6 +251,9 @@ repowise update --since v2.0.0
246251

247252
# Limit regeneration scope
248253
repowise update --cascade-budget 10
254+
255+
# Disable reasoning for a supported provider/model for this run
256+
repowise update --reasoning off
249257
```
250258

251259
---

docs/architecture/ARCHITECTURE.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1617,7 +1617,7 @@ Key files:
16171617
Full configuration with defaults (`.repowise/config.yaml`):
16181618

16191619
```yaml
1620-
provider: anthropic # anthropic | openai | ollama | litellm
1620+
provider: anthropic # anthropic | openai | openrouter | gemini | deepseek | ollama | litellm | mock
16211621
model: claude-sonnet-4-5 # passed through to the provider
16221622
embedding_provider: anthropic
16231623
embedding_model: voyage-3
@@ -1694,4 +1694,4 @@ rate_limits:
16941694
ollama:
16951695
rpm: 999999
16961696
tpm: 999999
1697-
```
1697+
```

packages/cli/src/repowise/cli/commands/init_cmd.py

Lines changed: 68 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
load_config,
2222
load_state,
2323
resolve_provider,
24+
resolve_reasoning,
2425
resolve_repo_path,
2526
run_async,
2627
save_config,
@@ -277,7 +278,9 @@ async def _persist_result(
277278

278279
# Record a completed GenerationJob so the web UI can show
279280
# "last synced" / "last re-indexed" timestamps.
280-
from datetime import datetime, UTC as _UTC
281+
from datetime import UTC as _UTC
282+
from datetime import datetime
283+
281284
from repowise.core.persistence.crud import upsert_generation_job
282285

283286
now = datetime.now(_UTC)
@@ -318,6 +321,7 @@ def _run_workspace_generation(
318321
skip_tests: bool,
319322
skip_infra: bool,
320323
test_run: bool,
324+
reasoning: str = "auto",
321325
) -> list[Any]:
322326
"""Run LLM generation for a single repo in the workspace init flow.
323327
@@ -326,20 +330,28 @@ def _run_workspace_generation(
326330
workspace run.
327331
"""
328332
from repowise.cli.cost_estimator import build_generation_plan, estimate_cost
333+
from repowise.cli.helpers import get_db_url_for_repo
329334
from repowise.cli.ui import BRAND, RichProgressCallback
330335
from repowise.core.generation import GenerationConfig
331336
from repowise.core.generation.cost_tracker import CostTracker
332-
from repowise.core.persistence.vector_store import InMemoryVectorStore
333-
from repowise.core.providers.embedding.base import MockEmbedder
334-
from repowise.core.pipeline import run_generation
335-
from repowise.cli.helpers import get_db_url_for_repo
336337
from repowise.core.persistence import (
337338
create_engine as _ce,
339+
)
340+
from repowise.core.persistence import (
338341
create_session_factory as _csf,
342+
)
343+
from repowise.core.persistence import (
339344
get_session as _gs,
345+
)
346+
from repowise.core.persistence import (
340347
init_db as _idb,
348+
)
349+
from repowise.core.persistence import (
341350
upsert_repository as _ur,
342351
)
352+
from repowise.core.persistence.vector_store import InMemoryVectorStore
353+
from repowise.core.pipeline import run_generation
354+
from repowise.core.providers.embedding.base import MockEmbedder
343355

344356
# Build embedder
345357
embedder_impl: Any
@@ -371,7 +383,10 @@ def _run_workspace_generation(
371383
vector_store = InMemoryVectorStore(embedder_impl)
372384

373385
# Cost estimate
374-
gen_config = GenerationConfig(max_concurrency=concurrency)
386+
gen_config = GenerationConfig(
387+
max_concurrency=concurrency,
388+
reasoning=resolve_reasoning(reasoning),
389+
)
375390
plans = build_generation_plan(
376391
result.parsed_files, result.graph_builder, gen_config, skip_tests, skip_infra
377392
)
@@ -475,6 +490,7 @@ def _workspace_init(
475490
skip_infra: bool = False,
476491
concurrency: int = 5,
477492
test_run: bool = False,
493+
reasoning: str | None = None,
478494
yes: bool = False,
479495
dry_run: bool = False,
480496
resume: bool = False,
@@ -574,6 +590,7 @@ def _workspace_init(
574590
if adv.get("exclude"):
575591
exclude_patterns = list(exclude_patterns) + list(adv["exclude"])
576592
test_run = adv.get("test_run", test_run)
593+
reasoning = adv.get("reasoning") or reasoning
577594
embedder_name_resolved = _resolve_embedder(adv.get("embedder") or embedder_name)
578595
elif not index_only:
579596
# "full" mode
@@ -582,6 +599,8 @@ def _workspace_init(
582599
)
583600

584601
# Resolve provider once (shared across all repos for generation)
602+
primary_cfg = load_config(primary_repo.path)
603+
resolved_reasoning = resolve_reasoning(reasoning, primary_cfg)
585604
provider = None
586605
if not index_only:
587606
try:
@@ -591,6 +610,8 @@ def _workspace_init(
591610
f"Model: [cyan]{provider.model_name}[/cyan]"
592611
)
593612
console.print(f" Embedder: [cyan]{embedder_name_resolved}[/cyan]\n")
613+
if resolved_reasoning != "auto":
614+
console.print(f" Reasoning: [cyan]{resolved_reasoning}[/cyan]\n")
594615
except Exception as exc:
595616
console.print(f" [yellow]Provider setup failed ({exc}); falling back to index-only.[/yellow]")
596617
index_only = True
@@ -693,6 +714,7 @@ def _workspace_init(
693714
skip_tests=skip_tests,
694715
skip_infra=skip_infra,
695716
test_run=test_run,
717+
reasoning=resolved_reasoning,
696718
)
697719
result.generated_pages = generated_pages
698720
total_pages += len(generated_pages)
@@ -753,6 +775,7 @@ def _workspace_init(
753775
embedder_name_resolved,
754776
exclude_patterns=exclude_patterns if exclude_patterns else None,
755777
commit_limit=resolved_commit_limit,
778+
reasoning=resolved_reasoning,
756779
)
757780

758781
# Save workspace config with updated timestamps
@@ -867,7 +890,10 @@ def _workspace_init(
867890
"--provider",
868891
"provider_name",
869892
default=None,
870-
help="LLM provider name (anthropic, openai, gemini, deepseek, ollama, litellm, mock).",
893+
help=(
894+
"LLM provider name (anthropic, openai, openrouter, gemini, "
895+
"deepseek, ollama, litellm, mock)."
896+
),
871897
)
872898
@click.option("--model", default=None, help="Model identifier override.")
873899
@click.option(
@@ -888,6 +914,12 @@ def _workspace_init(
888914
"--force", is_flag=True, default=False, help="Regenerate all pages, ignoring existing."
889915
)
890916
@click.option("--concurrency", type=int, default=5, help="Max concurrent LLM calls.")
917+
@click.option(
918+
"--reasoning",
919+
type=click.Choice(["auto", "off", "minimal"]),
920+
default=None,
921+
help="Reasoning mode for supported providers: auto, off, or minimal. Default: auto.",
922+
)
891923
@click.option(
892924
"--test-run",
893925
is_flag=True,
@@ -951,6 +983,7 @@ def init_command(
951983
resume: bool,
952984
force: bool,
953985
concurrency: int,
986+
reasoning: str | None,
954987
test_run: bool,
955988
index_only: bool,
956989
exclude: tuple[str, ...],
@@ -1012,6 +1045,7 @@ def init_command(
10121045
skip_tests=skip_tests,
10131046
skip_infra=skip_infra,
10141047
concurrency=concurrency,
1048+
reasoning=reasoning,
10151049
test_run=test_run,
10161050
yes=yes,
10171051
dry_run=dry_run,
@@ -1073,6 +1107,7 @@ def init_command(
10731107
skip_tests = adv["skip_tests"]
10741108
skip_infra = adv["skip_infra"]
10751109
concurrency = adv["concurrency"]
1110+
reasoning = adv.get("reasoning") or reasoning
10761111
exclude = adv["exclude"]
10771112
test_run = adv["test_run"]
10781113
embedder_name = adv.get("embedder") or embedder_name
@@ -1090,6 +1125,7 @@ def init_command(
10901125
# Merge exclude_patterns from config.yaml and --exclude/-x flags
10911126
config = load_config(repo_path)
10921127
language = config.get("language", "en")
1128+
resolved_reasoning = resolve_reasoning(reasoning, config)
10931129
exclude_patterns: list[str] = list(config.get("exclude_patterns") or []) + list(exclude)
10941130

10951131
# Resolve commit limit: CLI flag → config.yaml → default (500)
@@ -1153,13 +1189,22 @@ def init_command(
11531189
console.print(f" Embedder: [cyan]{embedder_name_resolved}[/cyan]")
11541190
if language != "en":
11551191
console.print(f" Language: [cyan]{language}[/cyan]")
1192+
if resolved_reasoning != "auto":
1193+
console.print(f" Reasoning: [cyan]{resolved_reasoning}[/cyan]")
11561194

11571195
# Validate provider connection
11581196
from repowise.core.providers.llm.base import ProviderError
11591197

11601198
with console.status(" Verifying provider connection…", spinner="dots"):
11611199
try:
1162-
run_async(provider.generate("You are a test.", "Reply with OK.", max_tokens=50))
1200+
run_async(
1201+
provider.generate(
1202+
"You are a test.",
1203+
"Reply with OK.",
1204+
max_tokens=50,
1205+
reasoning=resolved_reasoning,
1206+
)
1207+
)
11631208
except ProviderError as exc:
11641209
raise click.ClickException(f"Provider validation failed: {exc}") from exc
11651210
console.print(" [green]✓[/green] Provider connection verified")
@@ -1269,7 +1314,11 @@ def init_command(
12691314

12701315
# Cost estimation
12711316
from repowise.core.generation import GenerationConfig
1272-
gen_config = GenerationConfig(max_concurrency=concurrency, language=language)
1317+
gen_config = GenerationConfig(
1318+
max_concurrency=concurrency,
1319+
language=language,
1320+
reasoning=resolved_reasoning,
1321+
)
12731322
plans = build_generation_plan(
12741323
result.parsed_files, result.graph_builder, gen_config, skip_tests, skip_infra
12751324
)
@@ -1365,13 +1414,21 @@ def init_command(
13651414
# is persisted to the llm_costs table. We need the repo_id from the
13661415
# database row that was created/upserted during _persist_result
13671416
# (which has not run yet), so we look it up or fall back to in-memory.
1368-
from repowise.core.generation.cost_tracker import CostTracker
13691417
from repowise.cli.helpers import get_db_url_for_repo
1418+
from repowise.core.generation.cost_tracker import CostTracker
13701419
from repowise.core.persistence import (
13711420
create_engine as _create_engine,
1421+
)
1422+
from repowise.core.persistence import (
13721423
create_session_factory as _create_sf,
1424+
)
1425+
from repowise.core.persistence import (
13731426
get_session as _get_session,
1427+
)
1428+
from repowise.core.persistence import (
13741429
init_db as _init_db,
1430+
)
1431+
from repowise.core.persistence import (
13751432
upsert_repository as _upsert_repo,
13761433
)
13771434

@@ -1522,6 +1579,7 @@ async def _count_db_pages() -> int:
15221579
embedder_name_resolved,
15231580
exclude_patterns=exclude_patterns if exclude_patterns else None,
15241581
commit_limit=resolved_commit_limit if commit_limit is not None else None,
1582+
reasoning=resolved_reasoning,
15251583
)
15261584

15271585
# ---- Completion panel ----

0 commit comments

Comments
 (0)