# RA3 Mod XML AI Agent 接入计划与进度追踪 > 状态:实施中 > 创建时间:2026-09-09 > 目标:让 AI Agent / 其他工具能够方便、可靠地使用 RA3 Mod XML 扩展构建的语义索引。 --- ## 进度追踪 | 阶段 | 内容 | 状态 | |---|---|---| | Phase 0 | 计划与进度文件 | ✅ 完成 | | Phase 1 | 外部快照格式与导出命令 | ✅ 完成 | | Phase 2 | 查询核心与 CLI | ✅ 完成 | | Phase 3 | MCP Server | ✅ 完成(基础 stdio MCP) | | Phase 4 | MCP 配置助手 / 稳定 launcher | ✅ 完成(launcher + 启用命令 + Claude/Cursor 配置写入 + 复制配置) | | Phase 5 | Agent Skill 安装器 | ✅ 完成(默认安装 + 多位置选择 + 安装记录 + 卸载/同步函数) | | Phase 6 | Live 查询与快照合并策略 | ✅ 完成(本地只读 HTTP live server + endpoint 文件 + MCP 在线优先/离线兜底 + final 索引静默 5 秒自动快照) | | Phase 7 | 测试、文档、发布 | 🟡 部分完成(README + 计划文档已更新;全部 31 个 test/*.test.mjs 逐个直接运行通过;已用直接 esbuild CLI 验证 dist 构建与 MCP smoke test;`node --test` 受沙箱限制,尚未 VSIX 打包/发布) | ### 二期进度(2026-09-10) | 阶段 | 内容 | 状态 | |---|---|---| | 二期 A1 | live 响应携带 `projectDir`,MCP 侧校验 | ✅ 完成 | | 二期 A2 | endpoint 按项目分文件(保留全局兜底) | ✅ 完成 | | 二期 A3 | live server 支持 `?project=` 路由(不再只看活动编辑器) | ✅ 完成 | | 二期 A4 | 关闭窗口只清理自己写的 endpoint | ✅ 完成 | | 二期 A5 | `processId` 存活校验 | ✅ 完成 | | 二期 A6 | live 失败负缓存 | ✅ 完成 | | 二期 A7 | `snapshotBaseName` 改为 sha1 前缀 + 可读 slug | ✅ 完成 | | 二期 B1 | Skill 适用范围段落(正/负信号 + `get_status` 探针) | ✅ 完成 | | 二期 C1 | `get_asset_references`:有界、带 provenance 的正向引用查询 | ✅ 完成 | | 二期 C2 | 本地 HTTP `/get_asset_references` 与 MCP 工具接入 | ✅ 完成 | | 二期 C3 | 二期测试与文档 | ✅ 完成(264/264 测试通过) | --- ## 1. 背景与目标 当前 RA3 Mod XML 扩展已经能构建大型项目的语义索引,包括: - asset 定义:`type / id / file / line / origin / stream` - `$DEFINE` - `` / `xi:include` 关系 - 语义引用和反向引用 - manifest 资产 - `.w3x` art 资产 这些能力目前主要服务于 VS Code 编辑器内部。 本计划的目标是: 1. 让 AI Agent、脚本和其他工具能够方便、可靠地使用该索引; 2. 不需要用户注册全局 CLI / 修改 PATH; 3. 安装/更新扩展后能通过一次性设置完成接入; 4. 在用户持续编辑代码时,索引查询仍然足够实时且不会造成磁盘写放大; 5. 为 AI Agent 提供工具 + 引导 Skill,让它知道何时、如何调用索引。 --- ## 2. 核心设计决策 ### 2.1 不采用“全局 CLI + PATH”作为主入口 CLI 可以作为高级/调试工具保留,但不是默认路径。 ### 2.2 以 MCP Server 作为 AI Agent 主接口 MCP Server 使用本地 stdio 启动,通过绝对路径配置即可,不需要 PATH。 ### 2.3 内存索引为主,磁盘快照为辅 - 实时查询尽量走 VS Code 扩展内存中的 `ModIndex`; - 磁盘只保存低频、合并后的“当前稳定快照”作为离线兜底; - 避免用户连续编辑保存时产生大量磁盘 IO。 ### 2.4 Agent Skill 是可选增强层 MCP 注册解决“工具可用”,Skill 解决“Agent 知道怎么用好工具”。 Skill 不作为启用 MCP 的强制条件。 ### 2.5 所有查询结果都携带索引状态 外部接口必须返回:索引是否存在、是否正在构建、是否完整、是否 stale、数据版本/时间。 --- ## 3. 目标架构 ```text VS Code 扩展 │ ├── 内存 ModIndex │ │ │ ├── 本地只读查询服务(可选,热数据) │ └── 低频快照导出(冷数据) │ ├── MCP 配置助手 │ └── 写入 AI 客户端配置 + 稳定 launcher │ └── Agent Skill 安装器 └── 安装到 ~/.agents/skills 等位置 AI Agent / 外部工具 │ ├── MCP Server(推荐) │ ├── 扩展在线 → 查询内存/本地服务 │ └── 扩展离线 → 读取最近稳定快照 │ ├── CLI(可选) └── HTTP/JSON-RPC(可选) ``` --- ## 4. 组件设计 ### 4.1 外部快照格式 定义一份稳定、公开的快照 schema,不直接暴露内部 workspaceStorage 缓存。 ```json { "schemaVersion": 1, "project": "D:/Mods/CoronaMod/mods/mods/corona", "generatedAt": "...", "buildId": 128, "phase": "art", "complete": true, "stale": false, "stats": { "assetCount": 38304, "referenceCount": 97862, "defineCount": 240, "streamCount": 12 }, "assets": [ { "type": "GameObject", "id": "...", "file": "Data/...", "line": 10, "origin": "project", "stream": "static", "viaInstance": false } ], "defines": [], "references": [], "includeGraph": [], "sourceCandidates": [] } ``` 生成方式: - 从 `ModIndex` 导出; - 使用数组而不是 JS Map; - 原子写入:`temp + rename`; - 同一项目只维护 `current` 快照,不保留每次历史版本; - 可选维护 `previous` 用于构建期间回退。 ### 4.2 MCP Server MCP Server 不直接持有完整索引,而是作为查询代理: 1. 扩展在线时,转发到 VS Code 扩展的本地只读查询服务; 2. 扩展离线时,读取最近一次稳定快照; 3. 所有工具返回结果时附带索引状态。 建议 MCP 工具集: ```text get_status() list_projects() find_asset(id, type?) find_references(id, type?) list_assets_by_type(type, prefix?, limit?) get_definition(type, id) resolve_include(source) is_file_active(path) find_define(name) get_usage_guide() ``` ### 4.3 稳定 launcher 与 MCP 配置助手 扩展安装路径会随版本变化,所以不能直接写死在 MCP 配置里。 稳定入口: ```text ~/.ra3modxml/ ra3-mod-xml-mcp.cmd projects.json snapshots/ current.json.gz meta.json skill-install.json ``` MCP 配置示例: ```json { "mcpServers": { "ra3-mod-xml": { "command": "C:\\Users\\lanyi\\.ra3modxml\\ra3-mod-xml-mcp.cmd", "args": ["--project", "D:\\Mods\\CoronaMod\\mods\\mods\\corona"] } } } ``` 扩展每次激活/升级时刷新 launcher 内容,指向当前扩展安装目录。 这样 MCP 配置只需要写一次,升级不断。 ### 4.4 Agent Skill 默认安装: ```text ~/.agents/skills/ra3-mod-xml/ ├── SKILL.md └── references/ └── query-guide.md ``` Skill 内容原则: - 只描述功能本身; - 说明“什么情况下该用这个 Skill”以及“怎么用”; - 不引用各个工作区里的具体文档,例如 `docs/codebase-navigation-guide.md`,避免注意力噪音; - 保持通用,不绑定某个 mod。 Skill 内容建议包括: - 索引覆盖范围和状态含义; - MCP 工具用途; - 推荐查询工作流: - 从 ID 找定义 → `find_asset` - 找所有引用 → `find_references` - 确认文件是否 active → `is_file_active` - 追继承链 → `find_references` + `find_asset` - 找未引用资产 → `unreferenced` - 使用规则: - 优先查询索引,而不是全项目 `rg`; - 查询尽量带 type,避免同名不同类型混淆; - 不要把整个索引快照读进上下文; - 如果索引状态不是 ready/stale,应查询状态或重试。 ### 4.5 索引状态与一致性 定义索引状态: | 状态 | 含义 | |---|---| | `no_index` | 从未构建 | | `building` | 正在构建/重建 | | `ready_xml` | XML phase 已发布,art 未完成 | | `ready` | 完整 final 索引 | | `stale` | 构建期间有文件变化 | | `error` | 构建失败但保留 last good | 查询接口支持两种模式: - 默认 / eventual:有可用快照就返回,同时附带 `stale/incomplete` 标记; - 严格 / consistent:调用方传 `wait_for_index=true` / `allow_stale=false`,等待新 final 索引或超时。 ### 4.6 持久化策略 - 热数据:内存 `ModIndex`; - 实时查询:本地查询服务 / MCP 转发; - 冷数据:低频快照,例如: - 用户手动导出; - 索引稳定一段时间后自动写一次; - 扩展退出前写一次(尽力而为); - 不每次 rebuild 都写全量快照。 ### 4.7 用户交互流程 安装/更新扩展后: 1. 首次索引完成; 2. 如果尚未启用且未被用户忽略,弹出提示: ```text RA3 Mod XML 索引已就绪。 是否让 AI Agent 使用索引查询能力? ``` 3. 用户点击“启用”; 4. 扩展默认注册 MCP Server; 5. 同时询问/默认勾选: ```text [✓] 同时安装 RA3 Mod XML Agent Skill(推荐) ``` 6. 写入 MCP 配置; 7. 安装 Skill 到 `~/.agents/skills/ra3-mod-xml/`; 8. 提供“安装到其他位置”或“手动操作指南”。 ### 4.8 安全设计 - 本地查询服务只监听 `127.0.0.1`; - 使用随机 token; - MCP Server 默认只读,不提供文件修改/命令执行工具; - 写 MCP 配置前必须用户同意; - Skill 安装目录记录 managed marker,避免覆盖用户自定义内容。 --- ## 5. 实施阶段 ### Phase 1:外部快照格式与导出命令 - 新增 `src/agent/snapshot.ts` - 新增命令:`ra3modxml.exportIndexSnapshot` - 定义 `schemaVersion` - 从 `ModIndex` 生成可序列化快照 - 原子写入 `~/.ra3modxml/snapshots/current.json.gz` - 输出状态元数据 ### Phase 2:查询核心与 CLI - 复用纯 TS `indexer` 模块; - 新增 `src/agent/query.ts`: - `findAsset` - `findReferences` - `isFileActive` - `listAssetsByType` - `getStatus` - 新增 CLI 入口(可选): - 不注册 PATH; - 通过绝对路径调用; - 供脚本/调试使用。 ### Phase 3:MCP Server - 新增 `src/agent/mcpServer.ts` - 支持 stdio 协议; - 启动时读取连接信息/快照; - 提供前述 MCP 工具; - 每个响应带索引状态。 ### Phase 4:MCP 配置助手 - 新增 `src/agent/setup.ts` - 创建稳定 launcher; - 检测/写入常见客户端配置: - Claude Desktop - Cursor - Codex - 通用 JSON - 提供“复制 MCP 配置”; - 提供手动操作指南。 ### Phase 5:Agent Skill 安装器 - 新增 `src/agent/skill.ts` - 生成 `SKILL.md` + `references/query-guide.md` - 默认安装到 `~/.agents/skills/ra3-mod-xml/` - 可选安装到: - `~/.claude/skills/ra3-mod-xml/` - 项目 `.agents/skills/` - 项目 `.claude/skills/` - 自定义位置 - 记录已安装位置,扩展升级时自动同步; - 不覆盖用户自定义内容。 ### Phase 6:Live 查询与快照合并 - 在扩展内启动本地只读查询服务; - MCP Server 在线时优先转发到扩展; - 离线时读取 `current.json.gz`; - 实现快照 quiet-period 合并写入,避免频繁全量落盘; - 实现状态机:`no_index / building / ready_xml / ready / stale / error`。 ### Phase 7:测试、文档、发布 - 测试快照导出/导入一致性; - 测试 MCP 工具查询; - 测试 Skill 安装/更新/卸载; - 测试持续编辑场景下的状态与 stale 行为; - 更新 README / docs; - 发布新版本。 --- ## 6. 预估文件改动 ### 新增文件 ```text src/agent/ types.ts snapshot.ts query.ts mcpServer.ts setup.ts skill.ts localServer.ts launcher.ts docs/ ai-agent-integration-plan.md ``` ### 修改文件 ```text package.json # commands / configuration src/extension.ts # 注册命令、设置、初始化 src/workspace.ts # 暴露 index status / snapshot 发布钩子 src/features/agentSetup.ts # 用户引导 UI(或并入 agent/setup.ts) ``` --- ## 7. 验收标准 1. 用户安装/更新扩展并完成一次索引后,可以看到“启用 AI Agent 访问”的引导。 2. 用户点击启用后,不需要手动配置 PATH,不需要手动查找 workspaceStorage。 3. 常见 AI 客户端能通过 MCP 调用索引工具。 4. Agent 能正确回答: - “某 asset 定义在哪个文件?” - “谁引用了这个 asset?” - “这个 XML 文件是否真的被 Include?” 5. 查询接口在无索引/索引更新中不会返回误导性空结果。 6. 用户连续编辑保存时,不会因为频繁快照写入而产生明显卡顿或磁盘膨胀。 7. Skill 安装后,Agent 知道优先使用 MCP 查询,而不是全文 grep。 8. 扩展升级后,MCP 配置仍然有效,Skill 能自动同步到已安装位置。 --- --- # 第二部分:二期计划(讨论结论 + 实施) > 讨论时间:2026-09-10 > 主题:Skill 的项目识别边界、正向/间接引用查询的取舍、MCP 与 localhost 架构现状 --- ## 8. 讨论结论一:谁来判定"这是不是一个红警 3 模组项目" ### 问题 Skill 需要说明"何时应该被使用"和"适用范围"。这要求 Agent 能理解什么是 "红警 3 模组项目里的 XML",同时避免把无关项目误判为 RA3 模组。 问题在于:这个判断应该由扩展/Skill 自己解释(甚至给出推断规则),还是应该 由项目开发者在自己的项目文档 / `AGENTS.md` 里声明? ### 结论:分层,且 Skill 不做复杂推断 | 层 | 职责 | 理由 | |---|---|---| | **Skill** | 说明能力覆盖范围 + 少量正/负信号 + 便宜的探测方式 | Skill 是用户全局的,会在**所有**对话里加载,必须简短且能自我限制 | | **项目开发者**(`AGENTS.md` / README / 项目标记) | 声明"这个仓库是什么项目、要不要用这些工具" | 只有项目自己知道自己是什么,这是每个仓库的局部事实 | | **MCP 工具自身** | 用响应证明自己是否可用 | `get_status` 是最便宜的判定,不应靠读文档猜 | ### 为什么不把判断规则写进 Skill - Skill 在 `~/.agents/skills/` 下全局生效;如果写一堆判定规则,会在与 RA3 无关的项目里制造"要不要用这个工具"的注意力噪音。 - 规则永远不完备。真实反例: - 目录里有 `Schemas/xsd/CnC3Types.xsd` → 可能只是 SDK 本身,不是 mod; - 只有一堆从 SDK 抄来的 `.xsd` → 是"RA3 相关",但没有 mod 数据; - `CnC3Types.xsd` 里的 `CnC3` 指 **C&C3**(命令与征服 3 / 凯恩之怒), 所以"存在 CnC3Types.xsd" **不等于** "是 RA3 模组"; - 有 `Data/Mod.xml` / `*.babproj` / `Data/additionalmaps/mapmetadata_*.xml` → 基本可确认是 **SAGE / BinaryAssetBuilder** 系项目(RA3 只是其中之一)。 - 一旦误判,Agent 会拿 RA3 的索引数据去回答另一个项目的问题,比不使用更糟。 ### 落地方案 **Skill 侧**(`src/agent/skill.ts` 的 `SKILL_MD`)新增 applicability 段落: - 正向信号(任一命中即可尝试): `Data/Mod.xml`;`Data/additionalmaps/mapmetadata_*.xml`;`*.babproj`; XML 根为 ``;使用 ``。 - 明确否定:无关仓库、仅含拷贝 `.xsd` 的项目、通用 XML 配置、构建脚本、 非 SAGE 游戏项目。 - 便宜的探针:不确定时先调 `get_status`;它返回索引归属的 `projectDir`。 如果与当前工作区根不一致,或状态是 `no_index`,就**停止使用索引工具**, 改为直接读文件。 这样即使误触发,成本也只是一次 `get_status`,不会污染后续推理。 **项目开发者侧**:在项目根 `AGENTS.md` 写一句即可,例如: ```markdown ## RA3 Mod tooling This repository is a Red Alert 3 mod (SAGE XML). When locating asset definitions or references, prefer the `ra3-mod-xml` MCP tools (`find_asset`, `find_references`, `is_file_active`) over full-text search. ``` 扩展**不自动**往用户仓库写标记文件(如 `.agents/ra3-mod-xml-project.json`) 作为二期内容;若将来提供,必须显式确认。 --- ## 9. 讨论结论二:要不要提供"某个 Asset 直接/间接引用的所有 Asset" ### 场景 用户要求 Agent"查阅或修改雅典娜炮的武器"。Agent 需要: 1. 找到雅典娜炮的 GameObject; 2. 读源码,发现 `` / `` 等子元素, 在这些子元素里找到武器引用; 3. 若 `` 引用了临时物体,还要去读临时物体的 GameObject, 在它的 `FireWeaponUpdate` 里找武器; 4. 若雅典娜炮只有 `FireWeaponUpdate`、`WeaponSetUpdate` 在 `inheritFrom` 的 `BaseCannon` 上,还要读 `BaseCannon` 并综合判断。 评估问题: - 是否有必要提供"某个 Asset 直接或间接引用的所有 Asset"? - 只给精炼 asset 列表,Agent 不知道是哪个子元素导致的引用,有用吗? - 若同时提供上下文,磁盘/内存/性能允许吗?内容长度会不会反而比直接读源码更贵? - 相对直接读源码未必有显著提升,反而增加注意力噪音? - 间接引用会不会多项式/指数爆炸? ### 先指出当前实现的真实缺口 现在的索引只有**反向**引用(谁引用了我),没有**正向**查询(我引用了谁)。 `find_references(AthenaCannon)` 返回的是"谁引用了雅典娜炮",而不是"雅典娜炮 用了哪些武器"。所以上述 Agent 工作流**目前完全无法用工具完成**,只能读源码。 因此问题不是"要不要锦上添花",而是这里确实缺一个基础能力。 ### 结论:不提供"所有可达 Asset 的扁平闭包",改提供"有界 + 带 provenance 的边列表" **不采用扁平静态闭包的理由:** 1. **丢失 provenance,Agent 仍然要读源码。** 到底是 `FireWeaponUpdate` 的武器、 `WeaponSetUpdate` 的武器、`CreateObjectDie` 临时物体的武器,还是 `inheritFrom` 继承来的武器?这决定了 Agent 改哪个文件、加不加 `xai:joinAction`。丢掉上下文,列表基本没用。 2. **尺寸会失控。** 一个 GameObject 的可达闭包可能是 `Weapon → Projectile → Warhead → DamageNugget → FX → Particle → Texture`、 `Model → Mesh → Material → Texture`、`AttributeModifier`、`Upgrade`、`Command`、 `Button`……Corona 规模下单个单位几百到上千节点完全可能。200 条边就是几万 token,**比直接读 3 个小 XML 文件贵得多**。 3. **注意力噪音。** 列表里 80% 是与当前问题无关的 Texture / FX 时,推理质量下降。 4. **"间接"的诱惑会持续膨胀。** 一旦提供无界闭包,Agent 会习惯性调用它, 然后被淹没。 **但以下特性确实值得工具化:** - 多跳遍历是**纯机械**的,正是工具该做的事; - `inheritFrom` 链 + `xai:joinAction` 语义很微妙,Agent 手推容易错; - 每跳都 read 一个文件,Corona 下成本和 token 都很高; - 工具可以做到**确定性、不遗漏**(按 XSD 判定"哪些属性是引用", 而不是靠 Agent 逐个注意)。 **设计要点(二期实现遵循):** - 返回**边列表**而不是节点集合。每条边携带: - `from`(effective asset)、`to`(解析出的定义位置)、 - `via.kind`(`attribute` / `content` / `inheritFrom`)、 - `via.element`、`via.parent`、`via.attribute`, - `source.file` / `source.line`(XML 实际所在位置), - `definedIn`(当该边来自 `inheritFrom` 祖先的 XML 时,标明真正写它的资产)。 - **默认 `depth: 1`,硬上限 3。** Agent 拿到直接引用后自己决定是否对某个具体 节点递归,比一次性 dump 闭包更省 token、更有针对性。 - **`targetTypes` 过滤**是抑制噪音最有效的手段。雅典娜炮场景就是 `targetTypes: ["WeaponTemplate"]`(可选再带 `GameObject` 给 `CreateObjectDie`), 返回可能只有 3–10 条边。 - **`inheritFrom` 不消耗 depth**:继承是资产自身定义的一部分,不是运行时引用。 因此在同一 depth 内递归遍历继承链(链长上限 8),并把 `definedIn` 标出来。 - **截断时返回 `omittedByTargetType` 摘要**,让 Agent 知道被砍掉了什么形状的数据, 可以精确下钻,而不是无脑重试或放弃。 - **不提供"合并后的有效值"**:`joinAction` 的 Replace/Remove/merge 语义算错的风险 太高,一期不做。 **关于爆炸和成本:** - 算法上不是指数:带 visited-set 的 BFS 是 `O(V+E)`。真正的风险是**语义宽度**, 不是复杂度,因此用 `depth` + `targetTypes` + `maxEdges` 三重限制。 - **只做 live 查询**(VS Code 打开时可用)。要拿元素上下文,最干净的做法是按需 解析定义所在文件的 DOM,而不是往 records 缓存里塞元素名/属性名 —— Corona 有 ~98k 条引用记录,再塞 5 个字符串字段会让常驻内存明显膨胀,为一个默认 `depth=1` 的功能付出这个成本不划算。 - 离线快照路径明确返回"需要 live index",而不是静默返回空结果。 --- ## 10. 讨论结论三:MCP / localhost 的实际架构与多窗口缺陷 ### 当前架构(事实) ```text AI 客户端 │ stdio (JSON-RPC, MCP) ▼ dist/agent/mcpServer.js ← 由 AI 客户端启动,参数 --project │ HTTP (127.0.0.1:临时端口, Bearer token) ▼ VS Code 扩展进程内的 localServer ← 仅在"启用 AI Agent 访问"后启动 │ ├─ 在线 → ws.activeIndex() 实时内存索引 └─ 离线 → ~/.ra3modxml/snapshots/project-.json.gz ``` - **AI 客户端不需要知道端口。** MCP Server 自己读 `~/.ra3modxml/endpoint.json` 拿 `url` + `token` 再 `fetch`。localhost 层是 纯实现细节。 - **端口**:`server.listen(port ?? 0, "127.0.0.1")`,即由操作系统分配临时端口, 不会冲突;当前未传 `port`,所以永远是临时端口。 - **鉴权**:`Authorization: Bearer `,token 为启动时随机生成;绑定 `127.0.0.1`,局域网访问不到。 - **降级**:live 查询失败(endpoint 不存在 / 连接被拒 / 超时 1.5s)时回退到快照。 ### 多项目 / 多 VSCode 窗口的真实缺陷 `endpoint.json` 是**全局唯一**的一个文件,所有 VSCode 窗口都往它写入: 1. **最后启用的窗口覆盖前面所有窗口。** 两个窗口都启用了 Agent 访问时, 只有后启动的那个能被 MCP Server 找到。 2. **可能串项目(严重)。** 假设窗口 B 覆盖了 endpoint,而某个 MCP Server 是用 `--project 窗口A` 启动的,它的 `tryLiveQuery` 会打到**窗口 B** 的 server, 拿到窗口 B 的索引数据,而返回值里没有任何东西能暴露这个不一致。 这比 `no_index` 危险得多 —— 会静默给出错误答案。 3. **关闭任意窗口都会 `clearEndpoint()`**,把还开着的其他窗口的 live 通道一起断掉。 4. **同一窗口内多项目也不稳。** live server 用 `getIndex: () => ws.activeIndex()`,而 `activeIndex()` 跟着**当前活动编辑器**走。 用户切到另一个项目的文件,live 查询就会静默换项目;而 `endpoint.json` 里的 `projectDir` 只在启动时写一次,之后不更新。 5. `processId` 虽然写进了 endpoint,但**从未被校验**;VSCode 崩溃后残留的 endpoint 只能靠连接失败兜底。 6. 离线快照路径是**正确**的 —— `snapshotPathForProject()` 按项目路径做 hash, 多项目互不干扰。问题只在 live 层。 ### 修复方案(二期 A,按性价比排序) 1. **live 响应必须带 `projectDir`,MCP 侧必须校验它和 `--project` 一致**, 不一致就直接走快照。这是最小的正确性补丁,最先做。 2. **endpoint 按项目存**:`~/.ra3modxml/endpoints/.json`, MCP Server 按 `--project` 找对应的那个;同时保留 `endpoint.json` 作为 "最近"的兼容入口。 3. **live server 支持 `?project=` 路由**,而不是只认 `activeIndex()`。 扩展侧加 `indexForProject(projectDir)`,一个窗口的一个 server 就能服务它 所有已索引项目,也就不怕用户切编辑器。 4. **关闭时只清自己的那一条**,不无条件删全局 endpoint。 5. **校验 `processId` 是否存活**,不存活就当 endpoint 失效。 6. `tryLiveQuery` 失败后做几秒的"live 不可用"负缓存,避免每次调用都试一次。 7. `snapshotBaseName` 换成 sha1 前缀 + 可读 slug(与 `diskCacheKey` 一致的做法), 降低碰撞概率。 --- ## 11. 二期实施清单 ### 二期 A:多窗口 / 多项目正确性 - `src/agent/snapshot.ts`:新增 `projectHash()`;`snapshotBaseName()` 改为 `-`。 - `src/agent/endpoint.ts`:新增 `endpointPathForProject()` / `writeEndpointForProject()` / `readEndpointForProject()` / `clearEndpointForProject()` / `isProcessAlive()`。 - `src/workspace.ts`:新增 `indexForProject(projectDir)`。 - `src/agent/liveQuery.ts`:`liveStatus()` 在没有索引时回落到请求的 `projectDir`。 - `src/agent/localServer.ts`:`getIndex(projectDir?)` 路由、异步 handler、 `/projects` 端点、所有响应携带 `index.projectDir`。 - `src/agent/mcpServer.ts`:按项目读 endpoint、校验 `projectDir` 与 `processId`、 `?project=` 透传、live 失败负缓存、响应 `projectDir` 二次校验。 - `src/extension.ts`:启动时按项目写 endpoint、关闭时只清自己写过的那些。 ### 二期 B:Skill 适用范围 - `src/agent/skill.ts`:`SKILL_MD` 增加 applicability 段落(正向信号、明确否定、 `get_status` 探针);保持"不引用工作区文档"的原则。 ### 二期 C:`get_asset_references` - `src/agent/forwardRefs.ts`(新增):DOM-based、有界、带 provenance 的正向引用遍历。 - `src/agent/localServer.ts`:新增 `GET /get_asset_references`。 - `src/agent/mcpServer.ts`:新增 `get_asset_references` 工具(live-only, 离线时返回明确的"需要 live index"说明)。 - `src/extension.ts`:为 live server 提供 `loadFile()`(读文本 + 解析 + LineMap)。 ### 二期 D:测试 - `test/agentEndpoint.test.mjs`:按项目 endpoint、进程存活、清理隔离。 - `test/agentLocalServer.test.mjs`:`?project=` 路由、`projectDir` 校验、 `/get_asset_references`。 - `test/agentForwardRefs.test.mjs`:直接引用、继承链、`CreateObjectDie` 内容引用、 `targetTypes` 过滤、`maxEdges` 截断、`depth` 上限。 - `test/agentSkill.test.mjs`:Skill 文本包含 applicability 段落且不含工作区文档引用。 - `test/agentMcpRouting.test.mjs`:live URL 固定 `project`、跨项目响应拒绝。 --- ## 12. 二期实施结果 全部完成,`npx tsc --noEmit` 通过,`test/*.test.mjs` 共 **264/264** 通过。 ### 新增 / 修改文件 ```text 新增: src/agent/forwardRefs.ts 有界、带 provenance 的正向引用遍历 test/agentForwardRefs.test.mjs test/agentMcpRouting.test.mjs 修改: src/agent/endpoint.ts 按项目 endpoint + isProcessAlive() src/agent/snapshot.ts projectHash() / projectSlug() / snapshotBaseName() src/agent/liveQuery.ts liveStatus() 回落请求的 projectDir src/agent/localServer.ts 异步 handler、?project= 路由、/projects、/get_asset_references src/agent/mcpServer.ts 按项目 endpoint、projectDir 校验、负缓存、get_asset_references src/agent/skill.ts applicability 段落 + get_asset_references 用法 src/workspace.ts 新增 indexForProject() src/extension.ts 按项目写/清 endpoint、loadFile、endpoint 刷新 test/agentEndpoint.test.mjs test/agentLocalServer.test.mjs test/agentSkill.test.mjs ``` ### 关键行为 **多窗口 / 多项目** - `~/.ra3modxml/endpoints/-.json` 一项目一文件;MCP Server 先读自己项目的 文件,读不到才退回全局 `endpoint.json`,且仅在 `projectDir` 匹配时使用。 - 每个 live 请求带 `?project=`;server 用 `ModWorkspace.indexForProject()` 路由, 不再依赖"当前活动编辑器"。 - MCP 侧对响应里的 `index.projectDir` 做二次校验,不匹配就拒答并提示重新启用, 而不是给一个看起来合理但属于别的项目的答案。 - 关闭窗口只删除本窗口写过的 endpoint 文件。 - `processId` 不存活时 endpoint 视为失效。 - live 连接失败后有 5 秒负缓存,避免每次工具调用都探一次。 **Skill** - 新增 `When this skill applies` 段落:正向信号(`Data/Mod.xml`、 `mapmetadata_*.xml`、`*.babproj`、``、 `uri:ea.com:eala:asset`)、明确否定规则(无关仓库、仅含拷贝 `.xsd` 的项目)、 以及 `CnC3Types.xsd` 属于 C&C3 而非 RA3 的反例。 - 规定用 `get_status` 做便宜探针:状态为 `no_index` 或 `projectDir` 不匹配时 停止使用索引工具,改为直接读文件。 - 仍然不引用任何工作区文档(有测试断言)。 **`get_asset_references`(正向引用)** - 返回**边**:`from` / `to` / `via{kind,element,parent,attribute}` / `source{file,line,character}` / `definedIn` / `value`。 - `depth` 默认 1、上限 3;`inheritFrom` 不消耗 depth,祖先 XML 在同层遍历, 用 `definedIn` 标出真正写这条边的资产(解决"雅典娜炮自己没有 WeaponSetUpdate, 但 BaseCannon 有"的场景)。 - `targetTypes` 按 XSD 可赋值性过滤,但 `inheritFrom` 边始终保留,因为 它解释了其余边写在哪里。 - `maxEdges` 截断时返回 `truncated` + `omittedByTargetType`(只统计因上限被丢弃的边, 已被 `targetTypes` 过滤的不计入)。 - 只走 live 路径;不可用时返回明确的错误说明,而不是空结果。 ### 已知限制 - `get_asset_references` 不计算 `xai:joinAction`(`Replace`/`Remove`)合并后的 有效值,需要调用方自行确认。 - 只有 live 路径支持正向引用;离线快照路径明确返回"需要 live index"。 - `snapshotBaseName` 改为 `-`,旧的 `project-.json.gz` 快照文件不会被自动清理(该功能尚未发布,无兼容负担)。 - 本沙箱禁止 spawn 子进程,因此 `node --test` 与 `npm run build` 无法直接运行; 验证方式为:逐个 `node test/*.test.mjs`、直接调用 esbuild CLI 构建 dist、 用管道驱动 `dist/agent/mcpServer.js` 做端到端 smoke test。