一个模糊的想法到来了,太大,一个 agent 会话装不下,而且被迷雾包裹:从这里到 destination 的路还看不见。Wayfinding 的意义在于找到那条路,而不是冲向 destination。本 skill 把这条路绘制为 repo 的 issue tracker 上的共享 map,然后逐个处理它的 decision tickets(这些问题的解决是一项 decision,而不是要执行的构建切片),直到路线清晰。
destination 因工作而异,命名它是绘制的第一个动作:它塑造每一个 ticket。它可能是一份要交接并迭代的 spec、一个在规划开始前要锁定的 decision,或一个原地进行的变更,比如数据结构迁移。map 是领域无关的:工程工作、课程内容、任何符合这种形态的东西。
规划,而不是执行
Wayfinder 默认是规划性质的:每个 ticket 解决一个 decision,当路线清晰时 map 就完成了,在某人去动手之前,没有剩下需要决定的事了。那种"干脆直接去做"的冲动,通常就是你已到达 map 边缘、该交接的信号。一项工作可以在其 Notes 里覆盖这一点(把执行本身带进 map),但若没有,就产出 decisions,而不是 deliverables。
用名称引用
每个 map 和 ticket 都是一个 issue,所以它有一个名称:它的标题。在人类阅读的一切内容里(叙述、map 的 Decisions so far),都要用这个名称来引用它,绝不用裸的 id、编号或 slug。满屏 #42, #43, #44 难以阅读;名称一眼就能看懂。id 和 URL 不会消失;名称包住了它的链接,但它们_在_名称内部,绝不代替它。
Map
map 是本 repo 的 issue tracker 上的一个 issue,标注 wayfinder:map,这是规范的工件。它的 tickets 是 map 的 child issues。
map 是一个索引,而不是存储。它列出已做出的 decisions,并指向持有其细节的 tickets;一个 decision 只存在于一个地方(它的 ticket),所以 map 从不重述它,只做摘要并链接。
map、它的 child tickets、blocking 关系和 frontier 查询在物理上位于 tracker 的哪个位置,取决于 tracker 类型。 issue tracker 应该已经提供给你了。如果没有,让用户运行 /setup-rolex-skills。查阅 tracker 文档的 "Wayfinding operations" 一节,了解 本 repo 如何表达它们。如果没有提供任何 tracker,默认使用 local-markdown tracker。
Tracker 特定的接线细节(原生关系 API、label 命名约定、frontier 查询写法)见 references/<tracker>.md。Charting 之前先用一次小探测确认 tracker 的原生关系能力(sub-issue / blocked-by / 自定义字段),再批量发 ticket,避免走 body 文字降级;如果只有 body 约定可用,停下来跟用户确认走降级方案。
Map 正文
整个 map 的低分辨率视图,每个会话加载一次。打开的 tickets 不列出:它们是打开的 child issues,通过查询找到。
## Destination
<到达此 map 终点看起来是什么样:这项工作正在通往的 spec、decision 或变更。一两行;每个会话在选择 ticket 之前都以此定向。>
## Notes
<领域;每个会话应查阅的技能;此工作的常设偏好>
## Decisions so far
<!-- 索引:每个已关闭 ticket 一行,足以判断相关性,然后放大链接查看 ticket 持有的细节 -->
- [<已关闭 ticket 的标题>](link):<一行答案摘要>
## Not yet specified
<!-- 参见 "Fog of war":范围之内、你还无法生成 ticket 的迷雾;随着 frontier 推进而 graduate -->
## Out of scope
<!-- 参见 "Out of scope":被判定超出 destination 的工作;已关闭,永不 graduate -->
Tickets
每个 ticket 都是 map 的一个 child issue;tracker 的 issue id 就是它的身份。它的 body 就是问题本身,体量适合一个 100K token 的 agent 会话:
## 问题
<此 ticket 要解决的 decision 或调查>
每个 ticket 携带一个 wayfinder:<type> label,research、prototype、grilling、task 之一(见 Ticket Types)。
一个会话通过先把 ticket 分配给驱动 map 的开发者来 claim 它,在任何工作之前,这样并发的会话会跳过它。那个 assignee 就是 claim:一个打开、未分配的 ticket 就是未 claim 的。
blocking 使用 tracker 的原生依赖关系:这至关重要,因为它让 frontier 在 tracker 自己的 UI 里_可视化地_呈现,人类不用打开 map 就能看到哪些可以领取。只有在 tracker 缺乏原生 blocking 时才回退到 body 约定。当一个 ticket 阻塞它的所有 tickets 都关闭时,它就是 unblocked 的;frontier 是那些打开、unblocked、未 claim 的 child issues,已知的边缘。
完成标准:每个 chart 出来的 ticket 都能在 tracker UI 上查到 frontier(= 已勾选 sub-issue 关系 + 阻塞边),而不是只在 map 的 Decisions so far 文本里或 issue body 文字里看到"Blocks: #N"。Tracker 原生 API / CLI / UI 任一路径都可以,但接线必须落地到原生关系,不能停在"我在 body 里写了 Blocks: #N"这种降级方案上。
答案不是 body 的一部分;它在 resolve 时记录(见 通过 Map 工作)。resolve 一个 ticket 时创建的资产从 issue 链接出来,而不是粘贴进去。
Ticket Types
每个 ticket 要么是 HITL(人在环中,与一个能为自己发言的人类_一起_工作),要么是 AFK,由 agent 独自驱动。一个 HITL ticket 只能通过那种现场交流来解决;agent 绝不代表人类那一方(一个自己回答自己问题的 grilling agent 已经破坏了这一点)。
- Research(AFK):阅读文档、第三方 API 或本地资源(如知识库),以浮现某个 decision 所等待的事实。由调用 Skill 工具并传入 "research" 的 subagent 解决。当需要当前工作目录之外的知识时使用。
- Prototype(HITL):通过调用 Skill 工具并传入 "prototype",制作一个廉价、粗糙、具体的工件(一份提纲、一个粗略想法、一个桩(stub)、或 UI/逻辑代码)来提升讨论的保真度。把 prototype 作为资产链接。当"它应该长什么样"或"它应该怎么表现"是关键问题时使用。
- Grilling(HITL):对话。默认情形。始终调用 Skill 工具两次,分别传入 "grilling" 和 "domain-modeling"。
- Task(HITL 或 AFK):在做出decision之前必须完成的动手工作:没有要决定、prototype 或 research 的内容,但讨论被阻塞直到完成。注册服务以便判断它的 API、开通访问权限、移动数据以便看到它的形状。这是唯一种产出执行方案而非 decision 的类型:它通过识别要改什么、怎么改、波及范围来解除 decision 的阻塞。它的"解决"是交付一份精确的执行规范(影响文件列表、变更要点、风险、acceptance criteria),不是实际修改代码。Agent 在能做到的地方独自驱动(AFK);否则交给人类一个精确的检查清单(HITL)。当工作完成时解决;答案记录做了什么以及任何后续 tickets 依赖的结果事实(凭据位置、新 URL、行数)。
Fog of war
map 是_故意_不完整的:不要绘制你还看不到的东西。在活跃的 tickets 之外,是 fog of war:你能判断即将到来、但还无法确定的 decisions 和调查的模糊视野,因为它们挂在仍然打开的问题上。解决一个 ticket 会清除它前方的迷雾,把现在可以 spec 化的内容 graduate 成新的 tickets,一次一个,直到通往 destination 的路清晰、不再有 tickets 剩余。
map 的 Not yet specified 一节就是写下那个模糊视野的地方:可疑的问题、以后要重新审视的领域。它是_朝向_ destination 的未发现 frontier:这里的一切都在 scope 之内,只是还不够锐化到能生成 ticket。按视野所允许的,写得粗略或完整都可以;它同时充当协作者的路标,让他们看到这项工作朝哪个方向走。
是迷雾还是 ticket? 判断标准是你现在能否精确地陈述问题,_不是_你现在能否回答它。
- 是 ticket 当问题已经锐利,即使它被阻塞、你还不能对它采取行动。
- 是 Not yet specified 当你还不能把它表述得那么锐利。不要预先将迷雾切成 ticket 大小的块:它比 ticket 更粗颗粒,一块迷雾可能 graduate 成几个 tickets,也可能一个都没有,一旦 frontier 到达它。
Not yet specified 排除了已经决定的内容(Decisions so far)、已经是活跃 ticket 的内容,以及 out of scope 的内容(下一节)。
Out of scope
迷雾只_朝向_ destination 聚集。destination 固定了 scope,所以超出它的工作就是 out of scope:它不是迷雾,不属于 Not yet specified。它在 map 上有自己独立的 Out of scope 一节:你有意识地排除在_这项_工作之外的东西。把它放在这里的是 scope,而不是锐度。
out of scope 的工作从不 graduate(frontier 在 destination 处停止),所以只有当 destination 被重新绘制时它才会回来,而且是以一项全新的工作,而不是恢复。
把某件事划为 out of scope 是一个划定 scope 的行为,不是路线上的一个步骤。当一个已经存在的 ticket 最终落在 destination 之外(绘制时误划入 scope,或由一次 resolution 暴露出来),关闭它(一个已关闭的 ticket 明确不在 frontier 上),并在 Out of scope 一节留下一行:要点加上它为什么 out of scope,并链接那个已关闭的 ticket。它不进入 Decisions so far,后者记录的是实际走过的路线;scope 边界不是路线上的一个步骤。
调用
两种模式。无论哪种,每个会话最多只解决一个 ticket,research 类型的 ticket 除外,可以在 map 被创建的同一个会话里,立即自动并行启动多个 Research tickets 的 subagents。
绘制 Map
用户用一个模糊的想法调用。
命名 destination。 调用 Skill 工具两次,分别传入 "grilling" 和 "domain-modeling",确定这个 map 正在通往什么:spec、decision 或变更。destination 固定了 scope,所以先解决它。
绘制 frontier。 再次 grilling,这次广度优先:在整个空间展开,而不是在任何一个线程上深挖,浮现出开放的 decisions 和现在可以迈出的第一步。如果这没有浮现任何迷雾(通往 destination 的路已经清晰,整个旅程小到一个会话就能装下),你就不需要 map。停下来问用户想怎么继续。
创建 map(label
wayfinder:map):填好 Destination 和 Notes,Decisions so far 为空,把迷雾勾勒进 Not yet specified。创建 tickets 并连接阻塞边。顺序:
- 探测:用一次小的 GraphQL query(或其他 tracker 等价手段)确认 tracker 暴露的原生关系能力(sub-issue / blocked-by / 自定义字段)。如果只有 body 约定可用,停下来跟用户确认走降级。详见
references/<tracker>.md。 - 批量发:用脚本(不是 shell heredoc)批量创建 map + 子 tickets,body 写到临时文件再用
--body-file,避免转义陷阱。 - 一次接线:拿到全部 id 后,一次性调用原生 mutation 把 sub-issue + blocked-by 接好。完成标准:跑一次 GraphQL query 列出子 tickets 的
blockedBy,确认真阻塞关系已落库。
接线将它们排序为 frontier 和被阻塞;所有你现在还不能明确的内容留在迷雾中:即 Not yet specified 部分。
- 探测:用一次小的 GraphQL query(或其他 tracker 等价手段)确认 tracker 暴露的原生关系能力(sub-issue / blocked-by / 自定义字段)。如果只有 body 约定可用,停下来跟用户确认走降级。详见
触发 research subagents。 对你刚创建的每个
researchticket,启动一个调用 Skill 工具并传入 "research" 的 subagent 并行解决它,把它的发现捕获到一个一次性的research/<name>分支上,并从 ticket 留下一个 context pointer。停止:绘制 map 是一个会话的工作;它不手工解决任何东西。
通过 Map 工作
用户用一个 map(URL 或编号)调用。ticket 是可选的:没有指定的话,由你来选下一个 decision,而不是用户。
约束: 这是 decision 层工作。任何 ticket 的"解决"产出都是信息(decision、方案、规范):将答案发布为 resolution comment、关闭 issue、追加到 map 的 Decisions so far,同时将新浮现的内容 graduate 为新 ticket、清除迷雾、处理 out of scope 和无效化部分。代码执行始终在 wayfinder 会话之外进行。
加载 map:低分辨率视图,不是每个 ticket 的 body。
选择 ticket。如果用户指定了一个,就用它。否则按顺序取第一个 frontier ticket。claim 它:在任何工作开始前把它分配给你自己。
解决它,按 ticket 类型决定"解决"的含义:
- Research / Grilling / Prototype:通过 research、对话或 prototype 来锁定 decision。产出记录在 resolution comment 中。
- Task:产出执行方案:分析影响文件、变更要点、风险和执行顺序,写成 resolution comment。禁止直接修改代码。执行由后续 feature 分支完成。
按需获取任何相关或已关闭 ticket 的完整正文;对
## Notes块中指定的 skill 调用 Skill 工具。如有疑问,调用 Skill 工具两次,分别传入 "grilling" 和 "domain-modeling"。记录 resolution:把答案作为 resolution comment 发布,关闭该 issue,并把 context pointer 追加到 map 的 Decisions so far。(不是以独立评论追加)
添加新浮现的 tickets(先创建、再接线);将答案已使其 spec 化的迷雾逐一 graduate,把每个已 graduate 的片段从 Not yet specified 中清除,让它只作为新 ticket 存在。如果答案揭示某个 ticket(这一个或其他)落在 destination 之外,把它划为 out of scope,而不是在路上解决它。如果该 decision 使 map 的其他部分失效,更新或删除那些 tickets。
用户可能会并行运行 unblocked 的 tickets,所以要预期其他会话在并发编辑 tracker。