# AST 符号解析 — Code Intelligence 设计 > 日期:2026-06-24 | 状态:设计深化(2026-06-24:信息密度三态 + 主题压缩),Phase1 待实施 > 关联:memory [[devflow-info-density-concept]] / [[devflow-aichat-session-analysis-2026-06-22]] / plan gentle-gliding-book ## Context(为什么) **起因**:实测会话 e46f5605「DevFlow上下文管理不足分」——LLM `read_file` 读 8 个源码文件(context.rs/anthropic_compat/intent.rs/prompt.rs/compress 等)各 500 行**全文回灌**,prompt 累积 **360K token 超 glm-5.2 上下文上限** → LLM 失败 → 末尾 tool 后无 assistant(卡)。8dfe0b94 更滚到 5M token 爆炸。 **已否决的治标方向**: - ❌ read_file 限制行数(500→150):"等于没读",LLM 被迫 offset 翻页,更多轮次 + 仍累积 - ❌ read_file content 字节截断(8KB):"不讲武德",LLM 要全文被砍 **正路**:AST 语义解析,精准提取**调用链 + 函数代码**,替代物理读全文。 **双目标**(用户明确): 1. 精准获取调用链 + 函数代码,避免物理读文件的 LLM 上下文成本 2. AST 树作后期 ai coding 编码支撑工具(符号检索/依赖图/生成上下文/向量编码) **根本目标(2026-06-24 深化)**:提高上下文**信息密度**——使进 prompt 的每一行与**当前关注主题**紧密结合,去噪、去多余。这是所有读取/压缩设计的判据。 ## 核心原则:信息密度 ≠ 压缩(2026-06-24) **密度 = 只给与当前任务相关的内容;压缩 = 无差别减体积。两者不同。** 压缩减了体积,密度不一定升——减什么是预设规则或盲猜,和"当前任务关注什么"无关。 **分水岭:压缩器带不带当前任务上下文。** - 不带 → 通用摘要(治标,可能砍掉恰是任务关键的细节) - 带(任务焦点 focus)→ 相关性提炼(治本,留相关去无关) **被否的治标方向**: - ❌ 物理行数截断上限(500→150→3):武断,任何值都不合理(3 行砍废、3 万行等于没设);且按行返本身错——该按**语义结构层级**返 - ❌ 算法裁剪(砍注释/doc/空行):格式去噪,留下的不一定和任务相关,密度提升有限 - ❌ 通用 LLM 摘要(不带 focus):压出"这个函数做什么"的通用摘要,可能丢任务关键行 **正路(本设计采纳)**: 1. **结构层级供给**(拉模式/Progressive Disclosure):read_symbol 默认返骨架 → LLM 看骨架下钻局部 → 精准取。进 prompt 的都是 LLM 主动要的,天然相关。 2. **主题驱动压缩**(带 focus):大块必须给时,压缩 LLM 接收当前任务焦点,做相关性提炼(非通用摘要)。 → 不按物理行返,按**语义层级**返;压缩必须**带任务上下文**。 ## 一、技术定位:AST vs LSP(两个不同层面) | | AST(tree-sitter) | LSP(rust-analyzer) | |---|---|---| | 本质 | 数据结构(语法树,解析产物) | 通信协议(语义服务,JSON-RPC) | | 层面 | 语法层 | 语义层 | | 提供什么 | 符号结构/调用语法 | 类型/精准引用/重构/诊断 | | 形态 | 库(链接进进程) | 独立服务器(子进程) | | 成本 | 轻(ms/MB) | 重(二进制 30-50MB/平台 + 索引 + 内存) | 抽象层级:源码 → [tree-sitter] → AST(语法,Phase1-3 停此) → [类型推断] → 语义 → [LSP 协议] → 客户端。**LSP 依赖 AST**(rust-analyzer 内部也用),但停在更深(语义层)。 ## 二、目标论证(能力边界,不夸大) ### 目标1:调用链 + 函数代码 | 能力 | tree-sitter | Phase | |---|---|---| | 函数结构(骨架·默认) | ✅ `function_definition` → 签名+分支结构+调用图+行数(极小,高密度) | 1 | | 函数代码(定义体) | ✅ `full=true` / `drill` 取完整或局部(语法边界 100% 准) | 1 | | 同文件调用点 | ✅ `call_expression` 命中符号 → 调用位置 | 2 | | 跨文件调用链 | ✅ 符号索引 + import 解析 | 3 | | **动态调度**(`dyn Trait`/虚函数) | ❌ 静态解析不能(需类型推断,LSP 级) | 局限 | | 回调/闭包/函数指针/宏 | ❌ 需数据流/编译器 | 局限 | **覆盖度**:静态直接调用占实际 ~70-80%(绝大多数业务代码);动态/间接/宏 ~20-30% 需 LSP。 **避免上下文成本**:`read_symbol(compress)` → 函数体几十行 + 静态调用点,非 `read_file` 全文 500 行 → prompt 降一个量级。对 e46f5605 类(读源码理解实现,静态为主)直接见效。 ### 目标2:ai coding 编码支撑 AST 是语法层(parse tree)。**语法层能**: - 符号级上下文(LLM 编码拿相关函数定义+调用关系,非全文 chunk) - 结构化检索(符号/调用链/依赖,替代文本 grep) - 变更影响分析(改函数 X → 影响哪些调用方,精准 review/测试范围) - 代码生成约束(签名/调用语法) **语义层不能**(需 LSP/编译器):类型检查/推断、安全跨文件重构、数据流/借用分析。 → AST 够 aichat 的"精准读代码 + 调用链 + 上下文 + 影响分析"(95% 读代码场景);深层语义是 LSP 的事。 ## 三、方案:tree-sitter + read_symbol ### 选型:tree-sitter - GitHub 出品,增量 AST 解析,几十种语言 grammar - Rust 生态 `tree-sitter` + `tree-sitter-rust`/`tree-sitter-typescript` 等(编译期 crate) - **按语言按需 enable grammar**(非全装,控体积) - 增量解析:文件改只重解变更(<1ms) ### 三态读取(read_symbol,密度驱动) read_symbol **不按物理行返、不截断**,按**语义结构层级**返(消解"截断多少行"争论): | 态 | 触发 | 返回 | 密度 | |---|---|---|---| | **骨架**(默认) | `read_symbol(path, symbol)` | 签名 + 内部分支结构 + 调用列表 + 行数(不返函数体) | 极高(极小+全相关) | | **下钻**(LLM 拉) | `read_symbol(..., drill=分支/调用名)` | 该局部分支/调用的精准定义 | 高(LLM 主动要) | | **全文**(显式) | `read_symbol(..., full=true)` | 完整定义体(小函数常态直通) | 全 | ``` read_symbol(path, symbol, kind?, drill?, full?) → tree-sitter 解析 path,提取 symbol 定义节点 → 默认:返骨架(签名 + 分支结构 + 调用图 + 行数) → drill=局部名:返该分支/调用的精准定义 → full=true:返完整定义体 → 返回 {symbol, kind, signature, skeleton|drill|def_body, line_range, file_hash, calls?} ``` ### 主题压缩(compress_with_focus,大块兜底) 大块必须整体给时(复杂函数 / 大文件段),经**带焦点压缩**: ``` compress_with_focus(content, focus) → focus = 当前任务/问题(LLM 显式传) → LLM 相关性提炼:留与 focus 相关的路径/逻辑,去无关分支/防御/日志 → 返提炼结果 + 原 hash(丢细节时可回取原始) ``` - **focus 必传**(分水岭:不带 focus 的通用摘要被否) - hash 兜底:LLM 可凭 hash 回取原始(类似下钻) - 与现有 `compress_via_llm`:现有压历史对话(P0-1 未真降 prompt),本工具压"工具返回入 prompt 当轮"——扩展触发点非新造 ### 注册 / schema / 兜底 - read_symbol 注册 `register_file_tools`(与 read_file/grep 同域) - schema:`object_schema([("path",string,req),("symbol",string,req),("kind",string,opt),("drill",string,opt),("full",bool,opt)])` - handler:捕获 `allowed_dirs`,调 `resolve_workspace_path_with_allowed` 校验(复用 read_file 模式) - RiskLevel::Low(只读) - **不报错底线**(见七):无 grammar / 解析失败 / symbol 未找到 / 任何大小 → 走兜底或正常返,不 panic / 不阻断 ## 四、Phase 路线 **两条线**:读取层(read_symbol 三态,tree-sitter)/ 压缩层(主题压缩,LLM,与 P0-1)。 **读取层**(tree-sitter): | Phase | 能力 | 成本 | 缓存 | |---|---|---|---| | **1 单文件符号骨架+下钻+全文** | read_symbol 三态 | 低-中 | 实时解析(弃 AST) | | **2 同文件调用点** | drill 跨分支调用图 | 中 | 实时 | | **3 跨文件调用链** | 符号索引 + import | 高 | 符号表内存缓存 | **压缩层**(LLM,与 P0-1): | Phase | 能力 | 成本 | 缓存 | |---|---|---|---| | **C1 compress_with_focus** | 带 focus 相关性提炼 | 中(LLM 调用) | hash 缓存(同内容不重压) | | **C2 触发管线接入** | 工具返回→压缩→入 prompt | 中 | 阈值(小不压)+ 缓存 | **远期**:Encoding(符号检索/依赖图/向量编码)。 ## 五、缓存策略(分情况,对齐 tree-sitter 增量特性) **Phase1/2(单文件)**:**情况1 实时解析** - tree-sitter 单文件 ms 级,解析完提取符号后**丢弃 AST**(不缓存) - 每次 read_symbol 实时解析 → 最新最准(零缓存失效) - 速度 ~1-10ms(几千行),增量 <1ms **Phase3(跨文件索引)**:**情况2 缓存** - 全项目符号表(file→symbol→pos+签名),累积大 - **2a 内存缓存**(devflow ~4000 符号 ~0.5-2MB,内存够) - 大项目(几千文件/几十 MB)转 **2b 文件缓存**(`.devflow/symbols.cache`,LRU 按需加载) - **失效**:文件 mtime/sha256 变 → tree-sitter 增量重解析该文件(<1ms)→ 更新该文件项(不全扫) **增量解析是命脉**:让"实时"(情况1)够快、"缓存失效"(情况2)够便宜。 ## 六、估算(devflow 实测基数) - 源码:280 文件 / 82,444 行(Rust 142/52.7K,TS 87/12K,Vue 51/17.6K) - 符号:~4000(Rust ~2840 + TS ~1044 + Vue ~510) - 符号表:Phase1 ~400KB / Phase2 ~1MB / Phase3(含调用链)~1.6-2MB - → **内存缓存够**(情况2a) ## 七、兜底(不报错底线,不破坏现有) **底线:任何情况不 panic / 不阻断 / 不因大小失败。** - 无 grammar(语言不支持)/ 解析失败(语法错误)/ symbol 未找到 → 兜底提示 `grep` + `read_file`(offset 精准读) - **任何大小函数** → 正常返(不截断天然不因大小失败;大函数走骨架+下钻/主题压缩,非报错) - read_symbol 失败不阻断(返兜底提示让 LLM 回退 grep) - compress_with_focus 失败 → 回原始内容(LLM 仍能用,降级非阻断) ## 八、LSP 后置结论(2026-06-24 评估) **LSP 成本高,重 tree-sitter 100x**: - 二进制 ~30-50MB/平台(跨平台 3 份,bundle 进安装包体积大) - 首次索引 30s-2min(全项目类型推断),内存几百 MB-1GB,后台常驻 - JSON-RPC 通信复杂(初始化/能力协商/文件同步/多语言 server 生命周期) - 分发:bundle 重 / 要求用户装(rustup/tsserver)门槛高 **收益**:语义层(动态调度/类型/重构/精准引用),tree-sitter 天花板外 ~20%。 **结论**:**Phase1-3 tree-sitter 先**(语法层,覆盖 ~80% 读代码/调用链/上下文,轻量零分发负担);**LSP 后置/按需**——检测用户装了 rust-analyzer 才启用深层语义,不强制 bundle,保分发轻。别一上来 LSP(成本/分发过重),tree-sitter 拿 80% 红利,LSP 留作"深层语义可选增强"。 ## 九、与现有方向的关系(密度驱动,四层互补) ``` ① 结构层级供给(read_symbol 骨架→下钻) ← 本设计,语义结构精准+密度 ↓ 大块兜底 ② 主题压缩(compress_with_focus) ← 带 focus 相关性提炼(非通用摘要) ↓ 兜底 ③ grep+offset(read_file) ← 现有,文本精准 ↓ 历史治理 ④ 历史压缩(P0-1 compress_via_llm) ← prompt 层已读历史摘要 ``` - LLM 默认 read_symbol 骨架 → 要细节下钻 → 大块经主题压缩 → 不支持回退 grep - 已读历史由 P0-1 压缩 - **密度判据**:①②③④ 每层都服务"只给任务相关内容",非"减体积" ## 十、风险 / 改动面 | 风险 | 缓解 | |---|---| | tree-sitter 依赖体积 | 按语言按需 enable grammar(非全装) | | 跨语言覆盖(小众语言 grammar 弱) | 兜底 grep+read | | 动态调度解析不准 | 文档标注局限,需时评估 LSP(后置) | | Phase3 跨文件索引重 | 渐进,Phase1/2 先(实时,无索引) | ## 十一、验证(Phase1 落地后) - **解析速度**:tree-sitter 解析 devflow 大文件(context.rs 1552 行等),测 ms - **符号表大小**:全项目解析后序列化字节数(验估算 ~400KB) - **token 对比**:read_symbol(compress) vs read_file(context.rs) 返回 token 量(验降一个量级) - **单测**:read_symbol 提取函数定义体 + 调用点(Phase2) ## 十二、落地步骤 1. **本文档**(设计定稿) 2. **Phase1 实施**:tree-sitter-rust 集成 + read_symbol 工具 + 单文件符号提取 + 兜底 + 实测 3. Phase2/3/4 + LSP 后置评估,按需 ## 关键文件(Phase1 实施) - `src-tauri/Cargo.toml`:加 tree-sitter + tree-sitter-rust - `src-tauri/src/commands/ai/tool_registry.rs`:`register_file_tools` 内加 read_symbol(对齐 read_file handler) - 新模块 `src-tauri/src/commands/ai/code_intel.rs`:tree-sitter 解析 + 符号提取纯函数 - 单测:符号提取 + 兜底