<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Tree-Sitter | Francis Du</title>
    <link>https://francisdu.com/tags/tree-sitter/</link>
      <atom:link href="https://francisdu.com/tags/tree-sitter/index.xml" rel="self" type="application/rss+xml" />
    <description>💻Data Engineer | 🦀 Rustacean | 📷 Photographer | 🤖Vibe Coder</description>
    <generator>Hugo 0.166.0</generator><language>zh-CN</language><copyright>© Francis Du</copyright><lastBuildDate>Wed, 26 Aug 2026 03:12:00 +0800</lastBuildDate>
    <item>
      <title>wcode 的 Software Graph：先承认自己不知道</title>
      <link>https://francisdu.com/blog/wcode-software-graph/</link>
      <pubDate>Wed, 26 Aug 2026 03:12:00 +0800</pubDate>
      <guid>https://francisdu.com/blog/wcode-software-graph/</guid>
      <description>&lt;p&gt;最早写 wcode 的代码索引时，我没想过要做什么 Software Graph。&lt;/p&gt;
&lt;p&gt;当时的问题很直接：模型一进大文件就喜欢整份读，几百上千行源码一股脑塞进上下文。于是我先做了 Tree-sitter，给它几个更小的入口：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;file_outline
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;find_symbol
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;symbol_context
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这套东西到现在都还在，而且是我最喜欢的一层。便宜、稳定，不用启动项目。&lt;/p&gt;
&lt;p&gt;后来开始做 Impact，我才发现“临时查一下 Symbol”不够了。&lt;/p&gt;
&lt;p&gt;我需要知道 A 和 B 的关系，还需要知道这条关系是谁告诉我的、什么时候算出来的、源码变了以后还能不能信。&lt;/p&gt;
&lt;p&gt;这才有了 Software Graph。&lt;/p&gt;
&lt;h2 id=&#34;tree-sitter-知道的没有想象中那么多&#34;&gt;Tree-sitter 知道的没有想象中那么多&lt;a class=&#34;heading-anchor&#34; href=&#34;#tree-sitter-%e7%9f%a5%e9%81%93%e7%9a%84%e6%b2%a1%e6%9c%89%e6%83%b3%e8%b1%a1%e4%b8%ad%e9%82%a3%e4%b9%88%e5%a4%9a&#34; aria-label=&#34;章节链接：Tree-sitter 知道的没有想象中那么多&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Tree-sitter 很适合做结构分析。&lt;/p&gt;
&lt;p&gt;定义在哪里、Range 是多少、Qualified Name 是什么，这些都比较稳。一些语法上能明确判断的调用关系，也可以抽出来。&lt;/p&gt;
&lt;p&gt;但它没有编译器的类型系统，也不会替我做宏展开、重载选择和动态分派。&lt;/p&gt;
&lt;p&gt;所以 Tree-sitter 产生的关系一直带着：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;provider = tree-sitter
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;precision = syntax
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;我后来越来越在意 &lt;code&gt;precision&lt;/code&gt; 这个词。&lt;/p&gt;
&lt;p&gt;做 Agent 工具时，很容易为了让返回结果“看起来更聪明”，把一个启发式结果包装得像确定事实。短期体验会很好，后面做 Impact、Risk 时却很危险，因为上层已经不知道底下到底有多靠谱。&lt;/p&gt;
&lt;p&gt;所以这里干脆先承认自己不知道。&lt;/p&gt;
&lt;h2 id=&#34;lsp-也不是装了就算-semantic&#34;&gt;LSP 也不是装了就算 semantic&lt;a class=&#34;heading-anchor&#34; href=&#34;#lsp-%e4%b9%9f%e4%b8%8d%e6%98%af%e8%a3%85%e4%ba%86%e5%b0%b1%e7%ae%97-semantic&#34; aria-label=&#34;章节链接：LSP 也不是装了就算 semantic&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;现在 wcode 有一套第一方 Semantic Provider，会探测 rust-analyzer、gopls、clangd、typescript-language-server、pyright 这些 Language Server。&lt;/p&gt;
&lt;p&gt;但“机器上有这个二进制”和“当前结果是 semantic”是两回事。&lt;/p&gt;
&lt;p&gt;只有 Language Server 真正启动、返回 Document Symbol / Call Hierarchy / Implementation，这些结果才进入 Graph，并标成：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;precision = semantic
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Repository-aware Language Server 也不是默认无条件执行。&lt;/p&gt;
&lt;p&gt;它可能加载项目配置、Build Metadata、插件，甚至间接执行仓库里的东西。现在有两种授权方式：启动 wcode 时直接用 &lt;code&gt;--allow-risky-exec&lt;/code&gt; 做进程级放行；或者让具体 Refresh 先触发本地 Authorization Request，我在 TUI 里批准这个操作后再重试。&lt;/p&gt;
&lt;p&gt;后者是我后来补的，因为很多时候我只想临时跑一次 rust-analyzer，不想顺便把整个 Runtime 后面的高风险执行都放开。&lt;/p&gt;
&lt;h2 id=&#34;stale-semantic-比没有-semantic-更糟&#34;&gt;stale semantic 比没有 semantic 更糟&lt;a class=&#34;heading-anchor&#34; href=&#34;#stale-semantic-%e6%af%94%e6%b2%a1%e6%9c%89-semantic-%e6%9b%b4%e7%b3%9f&#34; aria-label=&#34;章节链接：stale semantic 比没有 semantic 更糟&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;这里有个很容易忽略的问题。&lt;/p&gt;
&lt;p&gt;假设上午跑过 rust-analyzer，拿到一组 Call Hierarchy；下午我已经把源码改了一大轮。如果 Impact 还拿上午的结果继续推导，它会表现得很“精准”，实际上精准地错了。&lt;/p&gt;
&lt;p&gt;所以第一方 LSP Fact 会带 &lt;code&gt;source_sha256&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;源码对不上，这个 Provider Revision 就会 stale。stale 的关系不会再进新的 Software Graph，也不会继续参与 &lt;code&gt;software_context&lt;/code&gt; 和 Impact。&lt;/p&gt;
&lt;p&gt;要用就重新 Refresh。&lt;/p&gt;
&lt;p&gt;这点比“自动保持 semantic 数据永远最新”笨一点，但边界清楚。&lt;/p&gt;
&lt;h2 id=&#34;同一条关系可以有几个答案&#34;&gt;同一条关系可以有几个答案&lt;a class=&#34;heading-anchor&#34; href=&#34;#%e5%90%8c%e4%b8%80%e6%9d%a1%e5%85%b3%e7%b3%bb%e5%8f%af%e4%bb%a5%e6%9c%89%e5%87%a0%e4%b8%aa%e7%ad%94%e6%a1%88&#34; aria-label=&#34;章节链接：同一条关系可以有几个答案&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Software Graph 没有试图把所有来源揉成一个最终真相。&lt;/p&gt;
&lt;p&gt;一条 Edge 会保留自己的：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;provider
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;precision
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;revision
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;attributes
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;所以同一个 &lt;code&gt;A -&amp;gt; B&lt;/code&gt; 完全可能同时有：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Tree-sitter syntax call
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;LSP semantic call
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Runtime observed call
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;它们不互相覆盖。&lt;/p&gt;
&lt;p&gt;如果以后接 SCIP、Compiler Index 或 Runtime Trace，也还是走同一个 provider-neutral contract。&lt;/p&gt;
&lt;p&gt;这样做的好处是，上层可以自己决定信谁。&lt;/p&gt;
&lt;p&gt;Impact 遇到真实 Runtime Edge，可以用真实运行关系；只有 Syntax Edge 也能继续工作，只是结果需要更保守。&lt;/p&gt;
&lt;h2 id=&#34;graph-history-解决的是另一个问题&#34;&gt;Graph History 解决的是另一个问题&lt;a class=&#34;heading-anchor&#34; href=&#34;#graph-history-%e8%a7%a3%e5%86%b3%e7%9a%84%e6%98%af%e5%8f%a6%e4%b8%80%e4%b8%aa%e9%97%ae%e9%a2%98&#34; aria-label=&#34;章节链接：Graph History 解决的是另一个问题&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;当 Graph 开始被拿来做分析以后，我还想看结构到底怎么变的。&lt;/p&gt;
&lt;p&gt;于是加了：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;graph_history
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;graph_query
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;graph_diff
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这里没有每查询一次就存一份快照。图内容没变，就不制造历史噪音。&lt;/p&gt;
&lt;p&gt;Node 用稳定 ID 对齐。Edge 则按：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;from + to + kind + provider + precision
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;先找身份。&lt;/p&gt;
&lt;p&gt;如果只是 Revision 或 Attributes 变了，就算 &lt;code&gt;changed&lt;/code&gt;，而不是先删一条再新增一条。&lt;/p&gt;
&lt;p&gt;这类实现细节平时没什么存在感，但图一旦有几十版历史，没有稳定身份很快就看不下去了。&lt;/p&gt;
&lt;h2 id=&#34;我后来把-webui-的球图降级了&#34;&gt;我后来把 WebUI 的球图降级了&lt;a class=&#34;heading-anchor&#34; href=&#34;#%e6%88%91%e5%90%8e%e6%9d%a5%e6%8a%8a-webui-%e7%9a%84%e7%90%83%e5%9b%be%e9%99%8d%e7%ba%a7%e4%ba%86&#34; aria-label=&#34;章节链接：我后来把 WebUI 的球图降级了&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Software Graph 做完后，我也走过一个很自然的弯路：既然已经有图，那就在 WebUI 里画出来。&lt;/p&gt;
&lt;p&gt;于是有过一个能缩放、拖拽、点 Node 看 provenance 的 Graph Canvas。&lt;/p&gt;
&lt;p&gt;我自己打开几次以后发现，它更像 Debug Tool，不像项目管理页面。&lt;/p&gt;
&lt;p&gt;我真正想查的是某个 Requirement 现在由谁实现、Acceptance 在哪、设计依赖和代码依赖有没有分叉、最近改动碰到什么，而不是盯着几十个圆点猜哪条线比较重要。&lt;/p&gt;
&lt;p&gt;所以现在主 UI 已经改成 Project Observatory，按 Requirement 往下看 Feature、Component、Implementation、Verification 和 Git Change。&lt;/p&gt;
&lt;p&gt;低层 Graph 没消失。&lt;/p&gt;
&lt;p&gt;它仍然是 Context、Impact、历史 Diff 的数据来源，只是不再承担“解释整个项目”的视觉任务。&lt;/p&gt;
&lt;p&gt;做到这里我才确定一件事：底层有 Graph，不代表 UI 也应该是一团 Graph。&lt;/p&gt;
&lt;p&gt;Graph 到这里还只是底层事实。真正拿它去做 Review 时，问题会变成另一句：Diff 之外，这次到底动了什么。那部分我放在 &lt;a href=&#34;https://francisdu.com/blog/wcode-traceability/&#34;&gt;Git Diff 之外，我还想知道什么&lt;/a&gt; 里。&lt;/p&gt;
</description>
    </item>
    
  </channel>
</rss>