[Feature]: 多 git 子仓库的大型 workspace(如 vllm + 插件)下 Flashgrep 无法正常工作:根目录无 git 基线导致索引不可用,进入子仓库则丢失关联仓库搜索范围
- Dominant language
- Rust
- Stars
- 2.2k
- Forks
- 229
- Avg merge
- 2h 46m
- Merged PRs (30d)
- 577
Description
### Summary
当一个 workspace 是**大型聚合目录**——本身不是 git 仓库,内部包含多个独立 git 仓库(例如 vllm 主仓库 + 若干插件/关联仓库,统一在一个 workspace 下开发):
```
workspace/ ← 非 git 仓库(聚合根)
├── git仓库1/ ← 独立 repo(如 vllm)
├── git仓库2/ ← 独立 repo(如 vllm-engine 插件)
└── git仓库3/ ← 独立 repo(如推理插件)
```
此时 Flashgrep 索引搜索两条路都走不通:
1. **以整个 workspace 为 repo 打开**:flashgrep 的索引模型依赖 git 基线(`base-snapshot` 绑定 commit),而聚合根不是 git 仓库 → 无法建立基线 → 索引不可用(Grep 退化为全量扫描,Glob 无 fallback 直接受影响)。
2. **进入某个子仓库打开**:搜索 scope 被强制限定在该 repo 内(越界直接报 "Search path is outside workspace root")→ 搜不到同 workspace 下其他关联仓库 → 丢失跨仓库搜索能力。
根因是 flashgrep 的集成模型是**单仓库**的(一次搜索请求只带一个 `repo_id`,scope 必须落在该 repo 内),而产品需要的是**多仓库聚合 workspace** 场景。
- **类型:** Bug / 能力缺失(对多 git 子仓库 workspace 布局的支持)
- **严重度:** 中~高(影响 monorepo/多仓库布局用户的 Grep/Glob,表现为搜不到或降级)
- **影响面:** 桌面端 Glob/Grep 工具(本地 workspace),多 git 子仓库布局用户
### Area
Desktop app
### Reproduction or evidence
复现步骤:
1. 建立如上目录结构:聚合根 `workspace/` 不是 git 仓库,内部 `git仓库1/`、`git仓库2/` 各自 `git init`。
2. 开启 `app.ai_experience.enable_workspace_search`,以 `workspace/` 为工作目录打开。
3. 调用 Grep/Glob 搜索跨仓库内容(如同时分布在两个子仓库的符号)。
4. 观察:
- 以 workspace 根为 repo 打开时,索引停留在 `MissingBaseSnapshot`/扫描降级(Grep 慢或结果不完整;Glob 依赖索引目录,行为异常,与 #TODO 关联的超时问题互相放大)。
- 把 workspace 切到 `git仓库1/` 打开时,能搜到仓库 1 内部,但搜索 `git仓库2/` 路径直接报越界/无结果——丢失关联仓库权限。
### 代码证据(单仓库模型的绑定点)
| 位置 | 事实 |
| --- | --- |
| `flashgrep/protocol.rs:105-119` | `SearchParams { repo_id, ... }`、`GlobParams { repo_id, scope }`——一次搜索请求只携带**单个** `repo_id`,无跨仓库聚合字段 |
| `flashgrep/protocol.rs:357-374` | `RepoStatus` 的索引状态是单仓库语义:`base_snapshot_root` / `snapshot_key` / `dirty_files{modified,deleted,new}` / `rebuild_recommended`——基线绑定 git commit |
| `flashgrep/protocol.rs:378-386` | `RepoPhase::MissingBaseSnapshot`——无可用 git 基线时索引不可用,是显式状态 |
| `workspace_search/service.rs:78,435` | `sessions: HashMap`、`get_or_open_session(repo_root)`——session 以**单个 repo_root 为键**,一次搜索只打开/查询一个 repo |
| `workspace_search/service.rs:906-921` | `normalize_scope_path`:`normalized.starts_with(repo_root)` 否则报 **"Search path is outside workspace root"**——scope 硬性限定在单 repo 内,跨仓库路径被拒绝 |
| `workspace_search/service.rs:226` | Grep 搜索带 `with_scan_fallback(true)`——可降级扫描;`GlobParams`(protocol.rs:115-119)**无 fallback 字段**,Glob 无降级路径 |
| `grep_tool.rs:270-274` | 工具层把 `context.workspace.root_path_string()` 整体当作 repo_root 传参——**整个 workspace 被当成一个 repo**,多子仓库结构被压平 |
| `glob_tool.rs:419-426` | `GlobSearchRequest { repo_root: workspace_root, ... }`——同上,整个 workspace 当单个 repo |
| `flashgrep/rpc_client.rs:340` | daemon 能力声明 `"workspace_list": false`——daemon 不支持列出 workspace 内多仓库并聚合 |
| `resources/flashgrep/README.md:5` | daemon 为外部项目 `wgqqqqq/flashgrep` v0.2.10,BitFun 侧为集成方;多仓库能力需在集成层解决 |
### 根因调用链
```
工具层 Grep/Glob
→ repo_root = workspace 聚合根(非 git 仓库)
→ WorkspaceSearchService::search_content/glob
→ get_or_open_session(workspace_root) // 单 repo session
→ normalize_scope_path:scope 必须 starts_with(repo_root) // 跨子仓库路径被拒
→ daemon:无 git 基线 → MissingBaseSnapshot → 索引不可用/降级
```
两个方向的矛盾:
1. **把 workspace 根当 repo**:flashgrep 索引模型要求 git 基线(base-snapshot 绑定 commit、dirty 分类来自 git status、语料遵循 .gitignore),聚合根不是 git 仓库时基线缺失;即便强行索引,也会重复扫描内部子仓库并撞上子仓库各自的 .gitignore 边界。
2. **把子仓库当 repo**:scope 被 `starts_with(repo_root)` 强制锁死在单仓库内,同 workspace 的其他关联仓库(插件等)不可搜索——正是「进入某个仓库就失去外部其他关联仓库的权限」。
工具层没有任何**跨仓库聚合**逻辑:不枚举 workspace 下的子仓库、不开多个 repo session 并发查询、不合并结果。一次 Grep/Glob 只能命中一个 repo_root。
## Expected behavior
多 git 子仓库的聚合 workspace 应被当作**仓库集合**处理,而不是单个 repo:
1. **仓库集合抽象**:workspace 是多个 git 仓库的组合。搜索请求支持 `repo_ids: Vec`(或聚合根 + 子仓库发现),daemon 或服务层按子仓库建索引并聚合结果,去重后返回。
2. **子仓库自动发现**:服务层识别 workspace 内的 git 子仓库边界(`git rev-parse --show-toplevel` 或 `.git` 目录发现),每个子仓库一个 repo session;跨仓库 Grep/Glob 并发查询各 session 后合并。
3. **scope 语义扩展**:搜索路径允许跨多个子仓库,越界检查从「单 repo 内」放宽为「任一子仓库内」,并保留权限边界(子仓库外仍拒绝)。
4. **无 git 基线的降级路径**:聚合根非 git 时,Grep 的 scan fallback 需显式告知用户「索引不可用、正在全量扫描」;Glob 需要补齐 fallback(当前无)。
5. **避免重复扫描**:子仓库边界识别后,聚合根扫描应跳过嵌套 `.git` 目录与子仓库内部,防止重复索引。
### 建议方案
- **短期(集成层)**:`WorkspaceSearchService` 增加 workspace → 子仓库集合的映射;Grep/Glob 工具对跨仓库请求并发调用各子仓库 session 并合并结果;scope 越界检查按仓库集合判定。
- **中期(协议层)**:与外部 `wgqqqqq/flashgrep` 上游对齐多仓库能力(`workspace_list`、多 repo 搜索参数),或引入聚合根索引模式(非 git 目录也可建不依赖 commit 的文件快照基线)。
- **配套**:与本仓库 #TODO(Flashgrep RPC 调用链无超时)联动——多仓库并发查询放大了 daemon 单请求挂起的影响,修复时须一并加上超时与降级。
### Environment, if relevant
```
BitFun commit: 4d3869859 (2026-08-14)
OS: Windows x64
FLASHGREP_DAEMON_BIN: C:\Users\wfwt\AppData\Local\BitFun\flashgrep\flashgrep-x86_64-pc-windows-msvc.exe
flashgrep daemon: wgqqqqq/flashgrep v0.2.10(外部项目,本仓库集成)
布局: workspace 聚合根(非 git)+ 多个独立 git 子仓库(主项目 + 插件)
```
## Related files
- `src/crates/services/services-integrations/src/workspace_search/flashgrep/protocol.rs`
- `src/crates/services/services-integrations/src/workspace_search/service.rs`
- `src/crates/services/services-integrations/src/workspace_search/flashgrep/client.rs`
- `src/crates/services/services-integrations/src/workspace_search/flashgrep/rpc_client.rs`
- `src/crates/assembly/core/src/agentic/tools/implementations/grep_tool.rs`
- `src/crates/assembly/core/src/agentic/tools/implementations/glob_tool.rs`
- `src/crates/assembly/core/src/service/search/service.rs`
- `resources/flashgrep/README.md`
Contributor guide
Assessment
This issue has not been assessed yet.