Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions INSTALL.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ Same four commands ship to both hosts. Claude Code namespaces them as `/repobrai
| Claude Code | Codex CLI | What it does |
|---|---|---|
| `/repobrain:rb-setup` | `/rb-setup` | **First-time setup** — interactive `.env` writer (logged-in local CLI = no key, or an API-key provider + model) |
| `/repobrain:rb-refresh [quick]` | `/rb-refresh [quick]` | Rebuild / incrementally update the project knowledge base |
| `/repobrain:rb-refresh [quick]` | `/rb-refresh [quick]` | Full baseline, or manually update only Agent groups judged affected by committed changes |
| `/repobrain:rb-ask <question>` | `/rb-ask <question>` | Routed Q&A on the current codebase |
| `/repobrain:rb-init <name>` | `/rb-init <name>` | Scaffold a new multi-agent repo from this template |

Expand All @@ -169,7 +169,7 @@ The plugin also bundles the `agent-repo-init` skill (description-matched in eith
If you manually register `rb-mcp`, the `repobrain` MCP server exposes:

- `ask_project(question)` — routed Q&A with file paths and line numbers
- `refresh_project(quick=False)` — rebuild knowledge base
- `refresh_project(quick=False)` — build a full generation baseline; `quick=True` manually runs the committed-diff ImpactPlanner/Verifier loop

Example configs:

Expand Down
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ Same four slash commands ship to both **Claude Code** and **Codex CLI**. Claude
| Claude Code | Codex CLI | Purpose |
|---|---|---|
| `/repobrain:rb-setup` | `/rb-setup` | First-time setup — pick LLM provider, write `.env` |
| `/repobrain:rb-refresh [quick]` | `/rb-refresh [quick]` | Build / incrementally refresh the project knowledge base |
| `/repobrain:rb-refresh [quick]` | `/rb-refresh [quick]` | Build a full baseline or manually update only affected Agent groups |
| `/repobrain:rb-ask <question>` | `/rb-ask <question>` | Routed Q&A on the current codebase |
| `/repobrain:rb-init <name>` | `/rb-init <name>` | Scaffold a new multi-agent repo from this template |

Expand All @@ -249,7 +249,13 @@ Run this **once per project**, right after installing the plugin. Interactive pi

### `rb-refresh` — build / refresh the knowledge base

Deploys the multi-agent cluster to read your code: each module gets its own Agent that produces a knowledge doc under `.repobrain/agents/*.md`, plus a `map.md` routing index. Run after install, after significant code changes, or when `rb-ask` returns stale answers. The first refresh auto-creates `.repobrain/` — no separate init step needed. Pass `quick` for an incremental update, `failed-only` to rerun only previously failed modules.
Deploys the multi-agent cluster and creates an atomic generation baseline. The
first run must be a full refresh. Later, `quick` compares committed changes from
the active generation to HEAD, requires a clean worktree, and lets RepoBrain's
ImpactPlanner plus an independent Verifier execute only affected Agent groups.
It never falls back to a full refresh. Use `failed-only` to resume failed or
pending groups for the same target commit. `rb-ask` only warns about new commits;
it never refreshes knowledge automatically.

Time: a few minutes for small repos, longer for large ones. Requires `rb-setup` to have completed. Works with either backend: an API-key/OpenAI-compatible provider runs the full LLM refresh, while a **local host-runner** (Codex / Trae / Claude / …) runs the tool-free stages (module docs, `map.md`) through your logged-in CLI and automatically degrades the tool/handoff stages (conventions, git insights) to deterministic output — no API key needed. Add `RB_REFRESH_SCAN_ONLY=1` only if you want a fast structure-only index with no LLM narration at all.

Expand Down
10 changes: 9 additions & 1 deletion README_CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,12 @@

### `rb-refresh` —— 构建 / 刷新知识库

部署多智能体集群阅读代码:每个模块由专属 Agent 生成知识文档(`.repobrain/agents/*.md`),并由 Map Agent 产出 `map.md` 路由索引。在安装后、重要代码改动后、或 `rb-ask` 出现陈旧答复时运行。首次 refresh 会自动创建 `.repobrain/` 目录,无需单独初始化。传 `quick` 做增量更新,传 `failed-only` 仅重跑上次失败的模块。
部署多智能体集群阅读代码:每个模块由专属 Agent 生成知识文档,并建立 generation
快照、稳定分组和依赖基线。首次必须运行一次完整 refresh。之后传 `quick` 时只比较
上次成功 generation 到当前 HEAD 的**已提交变更**:RepoBrain 先用依赖图缩小候选,
再由 ImpactPlanner 与独立 Verifier 判断真正受影响的 Agent 分组,只执行获批分组。
quick 要求 Git 工作区干净,不会自动降级成全量刷新;传 `failed-only` 可续跑同一目标
提交中失败或待处理的分组。

```
# Claude Code
Expand All @@ -121,6 +126,9 @@

耗时:小仓库几分钟,大仓库更久。需要先完成 `rb-setup`。两种后端都能用:API key / OpenAI 兼容 provider 跑完整 LLM refresh;**本地 host-runner**(Codex / Trae / Claude / …)则通过你已登录的 CLI 跑无工具阶段(module 文档、`map.md`),并把工具/handoff 阶段(conventions、git insights)自动降级为确定性产物——全程无需 API key。只有当你想要"仅结构索引、无 LLM 叙述"的极速模式时,才加 `RB_REFRESH_SCAN_ONLY=1`。

`rb-ask` 只读取当前 active generation。发现新 commit 时会提醒运行
`rb-refresh --quick`,但不会在问答过程中自动修改知识库。

### `rb-ask` —— 路由问答

**插件存在的主要原因**。把问题路由到合适的 ModuleAgent(必要时也调 GitAgent),返回有据可查的答案,附带文件路径和行号。**优先使用它**而非手动 grep / 读文件 —— 更快也更准。适合的问题形态:「X 在哪里定义/处理?」、「Y 为什么这样设计?」、「认证流程是怎样的?」、「哪些地方依赖模块 Z?」。
Expand Down
9 changes: 7 additions & 2 deletions README_ES.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ Los mismos cuatro comandos slash funcionan tanto en **Claude Code** como en **Co
| Claude Code | Codex CLI | Propósito |
|---|---|---|
| `/repobrain:rb-setup` | `/rb-setup` | Configuración inicial — elige proveedor LLM, escribe `.env` |
| `/repobrain:rb-refresh [quick]` | `/rb-refresh [quick]` | Construye / refresca incrementalmente la base de conocimiento |
| `/repobrain:rb-refresh [quick]` | `/rb-refresh [quick]` | Crea una base completa o actualiza manualmente solo los grupos Agent afectados |
| `/repobrain:rb-ask <pregunta>` | `/rb-ask <pregunta>` | Q&A enrutada sobre el código actual |
| `/repobrain:rb-init <nombre>` | `/rb-init <nombre>` | Crea un nuevo repo multi-agente desde esta plantilla |

Expand All @@ -100,7 +100,12 @@ Ejecútalo **una vez por proyecto**, justo después de instalar el plugin. Selec

### `rb-refresh` — construir / refrescar la base de conocimiento

Despliega el clúster multi-agente para leer tu código: cada módulo obtiene su propio Agent que produce un documento de conocimiento en `.repobrain/agents/*.md`, más un `map.md` como índice de routing. Ejecútalo tras instalar, tras cambios de código significativos, o cuando `rb-ask` devuelva respuestas obsoletas. El primer refresh crea `.repobrain/` automáticamente — no hace falta un paso de init separado. Pasa `quick` para actualización incremental, `failed-only` para reintentar solo los módulos previamente fallidos.
El primer refresh debe ser completo para crear una generación base. Después,
`quick` compara solo commits, exige un worktree limpio y usa ImpactPlanner más
un Verifier independiente para ejecutar únicamente los grupos Agent afectados.
Nunca cambia automáticamente a refresh completo. `failed-only` reanuda los
grupos fallidos o pendientes del mismo commit. `rb-ask` solo avisa de commits
nuevos y nunca actualiza la base automáticamente.

```
# Claude Code
Expand Down
38 changes: 32 additions & 6 deletions cli/src/rb_cli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -293,9 +293,26 @@ def _git_commit_lag(workspace: Path) -> str:
return f"{int(result.stdout.strip() or '0')} commit(s) behind HEAD"


def _active_knowledge_root(workspace: Path) -> Path:
"""Resolve a generation pointer without importing the optional engine."""
control = workspace / ".repobrain"
pointer_path = control / "current.json"
try:
pointer = json.loads(pointer_path.read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError, TypeError):
return control
if not isinstance(pointer, dict):
return control
generation = str(pointer.get("generation", "")).strip()
if not generation or Path(generation).name != generation:
return control
candidate = control / "generations" / generation
return candidate if candidate.is_dir() else control


def _status_health(workspace: Path) -> str:
"""Return partial/failed module and group counts from status.json."""
status_path = workspace / ".repobrain" / "status.json"
status_path = _active_knowledge_root(workspace) / "status.json"
try:
payload = json.loads(status_path.read_text(encoding="utf-8"))
except FileNotFoundError:
Expand All @@ -309,7 +326,7 @@ def _count_degraded(name: str) -> int:
values = payload.get(name, {})
if not isinstance(values, dict):
return 0
return sum(1 for state in values.values() if state in {"partial", "failed"})
return sum(1 for state in values.values() if state in {"partial", "failed", "unresolved"})

return (
f"{_count_degraded('modules')} partial/failed module(s), "
Expand All @@ -319,13 +336,14 @@ def _count_degraded(name: str) -> int:

def _knowledge_health(workspace: Path) -> tuple[str, str]:
"""Check .repobrain artifact existence and health summaries."""
rb_dir = workspace / ".repobrain"
control_dir = workspace / ".repobrain"
rb_dir = _active_knowledge_root(workspace)
map_path = rb_dir / "map.md"
agents_dir = rb_dir / "agents"
missing = [
label
for label, exists in (
(".repobrain/", rb_dir.is_dir()),
(".repobrain/", control_dir.is_dir()),
("map.md", map_path.is_file()),
("agents/", agents_dir.is_dir()),
)
Expand Down Expand Up @@ -469,8 +487,16 @@ def ask_cmd(
@app.command("refresh")
def refresh_cmd(
workspace: str = typer.Option(".", "--workspace", "-w", help="Project directory."),
quick: bool = typer.Option(False, "--quick", help="Only scan changed files."),
failed_only: bool = typer.Option(False, "--failed-only", help="Only re-run modules that failed in the previous refresh."),
quick: bool = typer.Option(
False,
"--quick",
help="Judge committed diff impact and update only affected Agent groups.",
),
failed_only: bool = typer.Option(
False,
"--failed-only",
help="Resume failed/pending groups for the current target commit.",
),
) -> None:
"""Refresh project context in .repobrain/ (requires LLM)."""
workspace_path = Path(workspace).resolve()
Expand Down
18 changes: 11 additions & 7 deletions cli/src/rb_cli/templates/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,18 +42,22 @@ out.) Both paths run the same engine, so they work with an API-key provider or,
with no API key, a local host runner (`RB_HOST_RUNNER` in `.env`) that drives a
CLI you are already logged into (Codex / Trae / Claude / …).

You normally do **not** need to run `rb-refresh` yourself: `rb-ask` keeps its
own knowledge base current. It builds the base automatically on first use (when
`.repobrain/` is missing) and rebuilds it when it drifts too far behind HEAD.
This is governed by `RB_ASK_AUTO_REFRESH` in `.env` (`stale` = first-run +
drift, the default; `first-only`; or `off`).

Run this explicitly only to force a full rebuild:
`rb-ask` is read-only. It warns when committed code is newer than the active
knowledge generation but never refreshes automatically. Build the first
generation explicitly with:

```bash
rb-refresh --workspace .
```

After later commits, run the committed-diff impact loop manually. It requires a
clean worktree and updates only Agent groups that RepoBrain's planner and
verifier prove are affected:

```bash
rb-refresh --workspace . --quick
```

Direct file reads, `grep`, or `rg` are allowed **only** for:

- verifying exact lines after `rb-ask` gives candidate files
Expand Down
13 changes: 10 additions & 3 deletions commands/rb-refresh.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,16 @@ rb-refresh --workspace "$PWD"
rb-refresh --workspace "$PWD"
```

If $ARGUMENTS contains `quick`, add `--quick`. If $ARGUMENTS contains `failed-only`, add `--failed-only`.

如果 $ARGUMENTS 包含 `quick`,追加 `--quick`。如果 $ARGUMENTS 包含 `failed-only`,追加 `--failed-only`。
If $ARGUMENTS contains `quick`, add `--quick`. Quick mode compares only committed
changes, requires a clean worktree, and lets RepoBrain's ImpactPlanner plus an
independent Verifier update only affected Agent groups. It never falls back to
a full refresh. If $ARGUMENTS contains `failed-only`, add `--failed-only` to
resume the failed/pending groups for the same target commit.

如果 $ARGUMENTS 包含 `quick`,追加 `--quick`。quick 只比较已提交变更,要求工作区
干净,由 RepoBrain ImpactPlanner 与独立 Verifier 只更新受影响 Agent 分组,且绝不
自动降级为全量刷新。如果 $ARGUMENTS 包含 `failed-only`,追加 `--failed-only`,
续跑同一目标提交中失败或待处理的分组。

If `rb-refresh` is not found, tell the user the engine CLI is not installed and suggest:

Expand Down
12 changes: 10 additions & 2 deletions engine/repobrain_engine/_cli_entry.py
Original file line number Diff line number Diff line change
Expand Up @@ -248,8 +248,16 @@ def refresh_main(argv: Sequence[str] | None = None) -> None:
description="Refresh the RepoBrain knowledge base",
)
parser.add_argument("--workspace", default=".", help="Project root (default: cwd)")
parser.add_argument("--quick", action="store_true", help="Only scan changed files")
parser.add_argument("--failed-only", action="store_true", help="Only re-run modules that failed in the previous refresh")
parser.add_argument(
"--quick",
action="store_true",
help="Judge committed diff impact and update only affected Agent groups",
)
parser.add_argument(
"--failed-only",
action="store_true",
help="Resume failed/pending groups for the current target commit",
)
args = _parse_args(parser, argv)

workspace = Path(args.workspace).resolve()
Expand Down
19 changes: 11 additions & 8 deletions engine/repobrain_engine/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,19 +98,22 @@ class Settings(BaseSettings):
description="Run refresh without LLM analysis and write scan artifacts only.",
)

# Auto-refresh gate for rb-ask (let the CLI refresh itself instead of
# relying on an agent to notice and run rb-refresh manually).
# Backward-compatible reminder toggle for rb-ask. Ask is read-only and
# never invokes refresh; committed drift is handled manually via --quick.
RB_ASK_AUTO_REFRESH: str = Field(
default="stale",
description="When rb-ask should build/rebuild the knowledge base on its "
"own: 'off' (never), 'first-only' (only when .repobrain is missing), or "
"'stale' (missing OR more than RB_ASK_AUTO_REFRESH_LAG commits behind "
"HEAD). Default 'stale' covers both first run and drift.",
description="Deprecated auto-refresh setting, now used only as a "
"manual-refresh reminder toggle. 'off' disables reminders.",
)
RB_ASK_AUTO_REFRESH_LAG: int = Field(
default=20,
description="Commit lag past which 'stale' mode triggers an auto-refresh "
"before answering. Ignored when RB_ASK_AUTO_REFRESH is 'off'/'first-only'.",
description="Deprecated compatibility field. Any positive committed "
"lag is now reported; ask never executes refresh.",
)
RB_IMPACT_MAX_ROUNDS: int = Field(
default=3,
ge=1,
description="Maximum independent Planner/Verifier rounds for quick refresh.",
)

# Memory Configuration
Expand Down
12 changes: 7 additions & 5 deletions engine/repobrain_engine/hub/agents.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
from pathlib import Path
from typing import TYPE_CHECKING, Optional, Union

from repobrain_engine.hub.storage import knowledge_root

if TYPE_CHECKING:
from repobrain_engine.config import Settings
from repobrain_engine.hub.host_runner import HostRunnerModel
Expand Down Expand Up @@ -524,7 +526,7 @@ def _read_module_knowledge(workspace: Path, module_name: str) -> str:
Returns:
Content of the module document(s), or a fallback message.
"""
rb_dir = workspace / ".repobrain"
rb_dir = knowledge_root(workspace)

# New format: agents/{module}.md (single group)
agent_md = rb_dir / "agents" / f"{module_name}.md"
Expand Down Expand Up @@ -566,7 +568,7 @@ def _read_git_knowledge(workspace: Path) -> str:
Returns:
Content of the git insights document, or a fallback message.
"""
doc_path = workspace / ".repobrain" / "modules" / "_git_insights.md"
doc_path = knowledge_root(workspace) / "modules" / "_git_insights.md"
if doc_path.is_file():
try:
return doc_path.read_text(encoding="utf-8")
Expand All @@ -584,7 +586,7 @@ def _read_structure_map(workspace: Path) -> str:
Returns:
Content of structure.md, or a fallback message.
"""
doc_path = workspace / ".repobrain" / "structure.md"
doc_path = knowledge_root(workspace) / "structure.md"
if doc_path.is_file():
try:
return doc_path.read_text(encoding="utf-8")
Expand All @@ -602,7 +604,7 @@ def _read_map_md(workspace: Path) -> str | None:
Returns:
Content of map.md, or None if not available.
"""
doc_path = workspace / ".repobrain" / "map.md"
doc_path = knowledge_root(workspace) / "map.md"
if doc_path.is_file():
try:
return doc_path.read_text(encoding="utf-8")
Expand All @@ -620,7 +622,7 @@ def _read_module_registry(workspace: Path) -> str | None:
Returns:
Content of module_registry.md, or None if not available.
"""
doc_path = workspace / ".repobrain" / "module_registry.md"
doc_path = knowledge_root(workspace) / "module_registry.md"
if doc_path.is_file():
try:
return doc_path.read_text(encoding="utf-8")
Expand Down
Loading