# Agent Note: 跟随符号链接指向的指令文件

Status: implemented

[English](2026-07-21-follow-instruction-symlinks.md) | 中文

## 问题

[agent-instructions 插件](2026-06-24-workspace-context.zh.md)在解析前用 `ctx.fs.lstat` 探测每个指令候选，拒绝任何末段的符号链接，从而使仓库自有的链接无法把指令加载指向工作区之外的内容。这条「不跟随」不变式挡住了一个有意为之、且受支持的配置：用户若把 `$DSH_HOME/AGENTS.md`（或某个项目的 `AGENTS.md`）符号链接到别处保存的一个规范指令文件，以便在多个工具与多个 home 之间共享同一份规范文件，就会看到该链接被悄悄忽略。它还迫使内容去重把无处不在的 `CLAUDE.md → AGENTS.md` 镜像当作一个被跳过的特例来处理，而非一个普通的重复文件。仓库所有者要求在每个 scope 上无条件跟随符号链接指向的指令文件，并接受下文记录的残余信任边界风险。

## 决策

指令发现不再用 `lstat` 检查末段。每个候选（用户全局的 `$DSH_HOME/AGENTS.md`、每个基础候选，以及每个本地覆盖候选）都会被解析，并对其解析后的目标做 stat，基线组合时与每一轮 `tools/post-execute` 协调时一视同仁。一个目标为常规文件的符号链接会加载该目标的内容；一个解析后的非文件目标（包括指向目录的链接）是被确认的缺失，会像缺失文件一样移除该 scope；一个 `resolve` 或 `stat` 异常被归类为暂时不可用，且从不移除已加载的 scope。`nodeStatFile` 调用 `stat`（宿主路径），`fsStatFile` 先 `resolve` 再 `stat`（提供方路径）；两者都不调用 `lstat`。

一个被跟随的符号链接对下游每一步都是普通文件。它参与按目录的内容去重（[加载全部并去重说明](2026-07-21-instruction-load-all-dedup.zh.md)），因此一个符号链接指向其同级 `AGENTS.md` 的 `CLAUDE.md` 现在会解析到相同内容，并像任何逐字节相同的真实副本一样被合并，而不再作为特例被跳过。

### 信任边界与残余风险

跟随仓库自有的链接会越过插件的信任边界：一个被克隆的、不受信任的仓库可以携带一个 `AGENTS.md`，其符号链接目标是该进程能读取的任意文件，从而把树外内容作为工作区指导暴露出来。该内容仅作为一条被 system-reminder 模式框定、权威性较低的 user 角色前缀进入；它绝不覆盖 system、developer 或用户的直接指令，并被当作数据而非权威依据。起缓解作用的边界在文件系统层，而非本插件：在部署加载不受信任的仓库时，用 `dsh-fs-observation-policy` 防护机制或一个操作系统沙箱（[跨家族 fs 沙箱](2026-07-14-cross-family-fs-sandbox.zh.md)）约束 `ctx.fs`。这是一个明确的、由所有者接受的取舍，而非疏漏。

## 备选方案

**保留 `lstat` 的「不跟随」不变式。** 被仓库所有者否决：它挡住了受支持的「符号链接到规范文件」配置，并迫使符号链接镜像场景成为一个被跳过的特例而非普通重复。它所近似的读取权限边界属于文件系统策略与沙箱层，那里能更精确地遏制同一风险。

**只跟随用户全局的 `$DSH_HOME` 候选，项目文件保持不跟随。** 否决：所有者要求在每个 scope 上行为一致，而一条分裂的规则比一条一致应用的策略加一条有文档记录的边界更难推理。用户选择打开的项目并不比用户自己的 home 更值得信任。

**跟随符号链接，但拒绝解析到项目根之外的目标。** 否决：这会在错误的层（路径几何而非读取权限）重新引入一条局部的信任边界，破坏合理的「`$DSH_HOME` 指向别处」场景，并重复文件系统策略门已经拥有的遏制。

## 后果

一个符号链接指向的指令文件现在会像其目标一样被加载和渲染，从而支持在多个工具与多个 home 之间共享规范指令文件，而 `CLAUDE.md → AGENTS.md` 镜像会通过内容去重而非被跳过。指令加载不再依赖 `ctx.fs.lstat`；一个解析后的非文件是被确认的缺失，只有提供方异常才是暂时不可用。信任边界从本插件移出，进入文件系统策略与沙箱层。当部署加载不受信任的仓库时，它们必须约束 `ctx.fs`。[agent-instructions note](2026-06-24-workspace-context.zh.md) 与包 README 承载相同的跟随行为与残余风险声明。
