begin 是做什么的?
一次性从证据中定位代码库,无需漫无目的地探索。
此功能旨在防止的失败:智能体打开仓库,搜索一个听起来合理的词,读取恰好匹配的三个文件,然后给出一个描述系统 4% 内容的确信架构总结。begin 用度量取代猜测,告诉我们重要代码在哪里,然后只读取度量所指出的内容。
契约
- 不回忆,全推导。 重要性来自真实导入图上的 PageRank;复杂度来自计数决策点;关注点来自 Git 和 PR 日志。如果你不能指向一个
file:line或一个提交,就不要说。 - 每项声明都有锚点。 BEGIN.md 中的每条陈述都引用
path:line、一个提交或一个 PR 编号。没有锚点的句子就是猜测,而猜测正是此技能要消除的。 - 两层结构,一层是活的。
.begin/是机器输出,可重新生成且自我忽略。BEGIN.md是你编写、提交并加盖派生时所依据提交戳的散文。提交后钩子会重新派生机器层,并报告哪些散文部分已被代码超越。此技能从不编辑跟踪文件——.begin/自带.gitignore,因此仓库的.gitignore保持不变。 - 代理被明确标记为代理。
complexityProxy是决策点计数,而非解析。嵌套深度通过缩进测量。请如实说明;不要将代理粉饰为事实。 - 降级的度量会被宣布,而不会被静默使用。 对于浅克隆、无效的变更窗口、被丢弃的文件、完全无法解析的语言或可解析但解析失败的文件,扫描会打印一个
[!WARNING]块。在查看表格之前阅读该块,并在 BEGIN.md 中重复其内容。 建立在有缺陷图上的排名看起来和好的排名一模一样——这正是全部危险所在。
运行它
S=~/.claude/skills/begin/scripts
bash $S/recon.sh # 谁/什么/何时: PRs、你的提交、实时编辑、文档
node $S/scan.mjs # 结构: 导入图、热点、表面->引擎
两个脚本都写入 .begin/ 并打印摘要。从仓库中的任何位置运行——扫描会以仓库根目录为锚点。recon.sh 在没有 gh、没有网络和没有远程的情况下会干净地降级,但需要 node(编辑是强制性的)。实测热缓存:259 个文件 0.24 秒 · 3,384 个文件 1.9 秒 · 2,809 个文件 1.9 秒 · 1,819 个文件 2.5 秒 · 1,397 个文件 3.5 秒。文件数量不能预测时间,内容才能。
值得了解的选项:
--since "90 days ago"— 变更窗口(两个脚本默认 90 天)。--top 40— 要打印多少热点(默认 25)。--author "..."— 一个正则表达式。你很少需要它:它默认为仓库的user.name,并且recon.sh会回退到仓库中最活跃的作者,并在你没有任何提交时告诉你。在新克隆的仓库中,这是正常情况。--exclude 'examples/**,site/**'— 将 vendored 树和文档站点排除在表面统计之外。模式两端都锚定,并会打印被移除的文件数量,因为静默的排除最终会表现为缺失的边。--root DIR— 扫描不同的仓库。
操作顺序——严格遵循
不按顺序操作是智能体最终读错文件的原因。
1. 结构之前先做侦察
首先运行 recon.sh。它回答“这个人一直在做什么?”——未提交的编辑、分支与主分支的提交、最后约 20 个已合并的 PR 及其正文,以及每个 PR 更改的内容。这就是关注点优先级。完整阅读 .begin/recon.md;它很短。
未提交的差异通常是仓库中最具信息量的东西——它是用户当前持有的问题。侦察会标记它的三种情况:当树是干净的时候,它会显式说明(而不是省略该部分让你去找);当差异只是锁定文件和构建输出时,它会告诉你不要花第一时间阅读它;当你的检出落后于 origin 时,它会告诉你落后了多少个提交,并列出近期 PR 中你树上没有的文件。
最后一点是前提条件,而不是注意事项。 如果侦察报告你落后了,那么下面的步骤 3.2 就不安全:PR 文件列表描述的是你没有的代码。要么先 git pull,要么跳过 PR 阅读步骤,并在 BEGIN.md 中说明你跳过了。
2. 其次做结构
运行 scan.mjs 并阅读 .begin/scan.md,从任何 [!WARNING] 块开始。然后,你将获得以下度量而非假设的信息:
- 表面 — 人类或调用者实际接触系统的每个地方,按种类分组(
web-page、ui-component、http-route、cli-command、mcp-tool、worker、public-api、migration,加上example、config和test,这些是表面但非产品)。这是地图的 UI 端。 - 热点 — 排序,带有一个
why列,命名了将每个文件放在那里的术语:depended-on、churn、complex或recent。一个因churn而排在顶部的文件与因depended-on而排在顶部的文件是不同的动物;该列告诉你要进行哪种对话。 - 表面 → 引擎路径 — 从端点或按钮到困难部分的字面导入链,通过 BFS 计算。经过惰性
await import()边的路径会被标记为惰性,因为它是在调用时而不是加载时解析的。 - 导入循环组 — 基于静态值边的强连通集合,走出一条真实有向循环。类型和惰性(
await import(...))边被排除在外,因为两者都不形成真正的初始化循环。 - 不可达文件 — 死代码,或启发式方法遗漏的表面。决定是哪种,并说明。
2.5. 阅读仓库已为你编写的地图
在打开任何热点之前,检查 AGENTS.md、CLAUDE.md、ARCHITECTURE.md 或每个目录的 README——recon.md 会列出它们。一个带有子系统地图的仓库用三十行文字就能给你扫描需要两秒推断、散文需要一小时重构的内容。首先阅读这些,然后使用扫描来检查它们:文档会过时,图不会。
3. 只读取度量指向的文件
按此顺序读取,然后停止:
- 未提交的差异和当前分支的提交(他们现在在做什么)。
- 最近 3-5 个已合并 PR 更改的文件(他们刚刚决定的内容)。
scan.md中的前 8-15 个热点。- 对于前 3 个热点,向内遍历它们的
reachedVia表面,这样你就可以陈述从用户操作到引擎的完整路径。 README、来自 2.5 的面向智能体的文档,以及任何标题与热点匹配的文档。
在第一次遍历中不要打开这个集合之外的文件。如果某些东西看起来缺失了,那是一个要报告的发现,而不是浏览的许可证。
4. 在写作之前验证
表格是一组线索,而不是结论。在将每个声明放入 BEGIN.md 之前:
- 打开文件并确认你要引用的行确实是你想的那样。
- 对于表面 → 引擎路径,确认导入确实存在于源码中。
- 对于任何关于行为的内容,优先选择演示该行为的测试,而不是看起来像的函数。
- 用
grep搜索热路径中被吞噬的失败——catch {}、.catch(() => {})、as any、as never、被丢弃的返回值。错误被丢弃的地方就是 Bug 的藏身之处,而这些是你第一天就能提供的最有价值的东西。 - 至少花一次尝试来证伪你的主要声明,而不是确认它。好的 BEGIN.md 中最有价值的句子是那些与扫描相矛盾的句子。
如果扫描在某件事上错了,就在 BEGIN.md 中说出来并纠正它。图认为是死的但实际通过 HTTP 或队列到达的文件是你能够记录的最有用的东西,因为其他人也不会再找到它。在 .begin/surfaces.txt 中声明此类入口点(每行一个路径,可选 path<TAB>kind),下一次运行时它们将被视为表面。
5. 编写 BEGIN.md
使用 references/BEGIN-template.md,并先阅读 references/EXAMPLE-BEGIN.md 以校准——它是一个真实的例子,不是模拟。结构按顺序:
- 这个系统用一段话做什么——用用户的语言,而不是代码的语言。
- 基本机制——第一性原理。核心转换是什么?使其余部分变得显而易见的一个想法是什么?没有它什么会崩溃?
- 表面 → 引擎——来自
scan.md路径的表格,每行已验证。如果没有行跨越进程或网络边界,请明确说明:在拆分语言的仓库中,真正的接缝通常是 HTTP 或队列调用,任何导入图都无法看到。 - 复杂部分,排序——先发现,后度量。每个条目需要一个陷阱,引用一个不在其度量行中的
file:line。一个你无法给出这些的条目就是你实际上没有读过的条目:删除它。 - 决策及其原因——每个链接到它的 PR 或提交,以及来自 PR 正文的推理。这就是使文档成为知识图谱而不是代码导览的原因。
- 你上次停下的地方——或者,在一个你不拥有的仓库中,谁拥有它以及它在哪里增长:活跃的作者,最新的开放 PR 正在添加什么,哪些应该先阅读。
- 机器层在此处无法看到的内容——以及未解决的问题,包括承担最大负载的单一假设。永远不要省略这个。
还要记录如何运行和验证它(recon.sh 从 package.json 中提取的脚本)以及该仓库重载的任何术语的词汇表。两者都是新手首先需要的,也是每个代码导览都会忘记的东西。
使用 [[wiki-style]] 锚点进行交叉链接。为每个部分加盖印章:
<!-- begin:section id="ingestion" sha="0055f01" files="src/adapter.ts,src/identity.ts" -->
sha = 在你派生该部分时(而不是后来添加 BEGIN.md 的提交)的 git rev-parse --short HEAD。files = 该部分的声明所依赖的文件;像 src/engine/** 这样的通配符有效。一个没有 files= 或 files= 命名了不存在的路径的部分,永远不会过时,但会被报告为不可验证——这就是此技能要防止的失败,所以请确保路径正确。
6. 让它活起来
bash $S/install-hook.sh # post-commit; --pre-push 用于另一个触发器
钩子会重新运行扫描并打印一行通知,说明有多少部分过时了,详细信息在 .begin/stale.md 中。它尊重 core.hooksPath(husky)和 Git 工作树,追加到现有钩子而不损坏它,拒绝追加到非 shell 钩子,并且从不接触跟踪文件。使用 --uninstall 移除它,它只会剥离自己的块。
/begin update
当用户返回一个已经有 BEGIN.md 的仓库时:
node $S/refresh.mjs && cat .begin/stale.md
仅重写过时的部分,重新运行 recon.sh 以刷新第 6 部分,并用新的 HEAD sha 重新盖章每个重写的部分。让当前部分保持字节相同——一个重写所有内容的更新既破坏了读者对变化内容的感觉,也破坏了 Git 历史的有用性。
如果还没有 BEGIN.md,那么 /begin update 就是 /begin:编写一个。
报告
一个简短的段落:基本机制、最重要的一两个文件及其原因、用户上次停下的地方,以及你无法验证的单一事项。详细信息存在于 BEGIN.md 中,而不是回复中。
以你没有检查的内容作为结尾。一个声称完全覆盖的 begin 遍历是在撒谎。
已知限制——说明这些,不要掩饰
- 导入图是静态的。字面量
import('./x')会解析(并标记为惰性);计算的import(expr)、DI 容器或由字符串键控的注册表不会产生任何边,因此这样的文件可能看起来不可达,但实际上承担着负载。在拆分语言的仓库中,前端↔后端接缝是 HTTP 或 WebSocket 调用,在构建上是不可见的。扫描标记不可达文件正是为了让人类判断。 - 完整的 JS/TS 和 Python 解析——tsconfig
paths(树中的每个配置,不仅仅是根)、extends、项目references、NodeNext./x.js→x.ts,以及 Python 针对推断根目录的绝对、相对和子模块导入(针对 CPythonast真实数据,实测约 98% 的召回率和精确率)。Go、Rust、Ruby、Java、Kotlin、Swift、PHP、C# 和 C/C++ 完全不贡献任何边。 在这样的仓库中,每个文件的重要性都处于底层并且显示为“不可达”;扫描会发出警告,你必须重复该警告,而不是将空图报告为发现。 complexityProxy按语言统计决策点,在移除了注释和单行字符串的代码中。标记和模式文件设计为得 0 分;vendored、minified 和生成的文件会被检测并单独排名。多行模板字面量仍会计数,因此模板繁重的文件会显得有点“热”。- 变更受
--since限制,浅克隆或新克隆没有变更——扫描会大声说明。一个在两年前被重写一次的文件仍然得分接近零,并且仍然可能是仓库中最重要的文件。
测试
node $S/test-scan.mjs # 图、循环、表面、可达性、Python
node $S/test-scan.mjs --mutate pyDotDot # ...并证明断言可以变红
bash $S/test-living.sh # 过时、钩子、树清洁度
node $S/redact.mjs --self-test # 秘密被捕获且干净文本不受影响
每个断言的存在都是因为那个 Bug 是真实的并且曾经发布过。变异:alias、jsToTs、valueCycle、typeCycle、pyAbsolute、pyDotDot——每个都必须使其断言变红,如果不这样做,CI 会失败。