30 KiB
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<Include>/xi:include关系- 语义引用和反向引用
- manifest 资产
.w3xart 资产
这些能力目前主要服务于 VS Code 编辑器内部。
本计划的目标是:
- 让 AI Agent、脚本和其他工具能够方便、可靠地使用该索引;
- 不需要用户注册全局 CLI / 修改 PATH;
- 安装/更新扩展后能通过一次性设置完成接入;
- 在用户持续编辑代码时,索引查询仍然足够实时且不会造成磁盘写放大;
- 为 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. 目标架构
VS Code 扩展
│
├── 内存 ModIndex
│ │
│ ├── 本地只读查询服务(可选,热数据)
│ └── 低频快照导出(冷数据)
│
├── MCP 配置助手
│ └── 写入 AI 客户端配置 + 稳定 launcher
│
└── Agent Skill 安装器
└── 安装到 ~/.agents/skills 等位置
AI Agent / 外部工具
│
├── MCP Server(推荐)
│ ├── 扩展在线 → 查询内存/本地服务
│ └── 扩展离线 → 读取最近稳定快照
│
├── CLI(可选)
└── HTTP/JSON-RPC(可选)
4. 组件设计
4.1 外部快照格式
定义一份稳定、公开的快照 schema,不直接暴露内部 workspaceStorage 缓存。
{
"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 不直接持有完整索引,而是作为查询代理:
- 扩展在线时,转发到 VS Code 扩展的本地只读查询服务;
- 扩展离线时,读取最近一次稳定快照;
- 所有工具返回结果时附带索引状态。
建议 MCP 工具集:
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 配置里。
稳定入口:
~/.ra3modxml/
ra3-mod-xml-mcp.cmd
projects.json
snapshots/
current.json.gz
meta.json
skill-install.json
MCP 配置示例:
{
"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
默认安装:
~/.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
- 从 ID 找定义 →
- 使用规则:
- 优先查询索引,而不是全项目
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 用户交互流程
安装/更新扩展后:
- 首次索引完成;
- 如果尚未启用且未被用户忽略,弹出提示:
RA3 Mod XML 索引已就绪。
是否让 AI Agent 使用索引查询能力?
- 用户点击“启用”;
- 扩展默认注册 MCP Server;
- 同时询问/默认勾选:
[✓] 同时安装 RA3 Mod XML Agent Skill(推荐)
- 写入 MCP 配置;
- 安装 Skill 到
~/.agents/skills/ra3-mod-xml/; - 提供“安装到其他位置”或“手动操作指南”。
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:findAssetfindReferencesisFileActivelistAssetsByTypegetStatus
- 新增 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. 预估文件改动
新增文件
src/agent/
types.ts
snapshot.ts
query.ts
mcpServer.ts
setup.ts
skill.ts
localServer.ts
launcher.ts
docs/
ai-agent-integration-plan.md
修改文件
package.json # commands / configuration
src/extension.ts # 注册命令、设置、初始化
src/workspace.ts # 暴露 index status / snapshot 发布钩子
src/features/agentSetup.ts # 用户引导 UI(或并入 agent/setup.ts)
7. 验收标准
- 用户安装/更新扩展并完成一次索引后,可以看到“启用 AI Agent 访问”的引导。
- 用户点击启用后,不需要手动配置 PATH,不需要手动查找 workspaceStorage。
- 常见 AI 客户端能通过 MCP 调用索引工具。
- Agent 能正确回答:
- “某 asset 定义在哪个文件?”
- “谁引用了这个 asset?”
- “这个 XML 文件是否真的被 Include?”
- 查询接口在无索引/索引更新中不会返回误导性空结果。
- 用户连续编辑保存时,不会因为频繁快照写入而产生明显卡顿或磁盘膨胀。
- Skill 安装后,Agent 知道优先使用 MCP 查询,而不是全文 grep。
- 扩展升级后,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 根为<AssetDeclaration>;使用<Includes><Include source="DATA:…"/></Includes>。 - 明确否定:无关仓库、仅含拷贝
.xsd的项目、通用 XML 配置、构建脚本、 非 SAGE 游戏项目。 - 便宜的探针:不确定时先调
get_status;它返回索引归属的projectDir。 如果与当前工作区根不一致,或状态是no_index,就停止使用索引工具, 改为直接读文件。
这样即使误触发,成本也只是一次 get_status,不会污染后续推理。
项目开发者侧:在项目根 AGENTS.md 写一句即可,例如:
## 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 需要:
- 找到雅典娜炮的 GameObject;
- 读源码,发现
<FireWeaponUpdate />/<WeaponSetUpdate />等子元素, 在这些子元素里找到武器引用; - 若
<CreateObjectDie />引用了临时物体,还要去读临时物体的 GameObject, 在它的FireWeaponUpdate里找武器; - 若雅典娜炮只有
FireWeaponUpdate、WeaponSetUpdate在inheritFrom的BaseCannon上,还要读BaseCannon并综合判断。
评估问题:
- 是否有必要提供"某个 Asset 直接或间接引用的所有 Asset"?
- 只给精炼 asset 列表,Agent 不知道是哪个子元素导致的引用,有用吗?
- 若同时提供上下文,磁盘/内存/性能允许吗?内容长度会不会反而比直接读源码更贵?
- 相对直接读源码未必有显著提升,反而增加注意力噪音?
- 间接引用会不会多项式/指数爆炸?
先指出当前实现的真实缺口
现在的索引只有反向引用(谁引用了我),没有正向查询(我引用了谁)。
find_references(AthenaCannon) 返回的是"谁引用了雅典娜炮",而不是"雅典娜炮
用了哪些武器"。所以上述 Agent 工作流目前完全无法用工具完成,只能读源码。
因此问题不是"要不要锦上添花",而是这里确实缺一个基础能力。
结论:不提供"所有可达 Asset 的扁平闭包",改提供"有界 + 带 provenance 的边列表"
不采用扁平静态闭包的理由:
- 丢失 provenance,Agent 仍然要读源码。 到底是
FireWeaponUpdate的武器、WeaponSetUpdate的武器、CreateObjectDie临时物体的武器,还是inheritFrom继承来的武器?这决定了 Agent 改哪个文件、加不加xai:joinAction。丢掉上下文,列表基本没用。 - 尺寸会失控。 一个 GameObject 的可达闭包可能是
Weapon → Projectile → Warhead → DamageNugget → FX → Particle → Texture、Model → Mesh → Material → Texture、AttributeModifier、Upgrade、Command、Button……Corona 规模下单个单位几百到上千节点完全可能。200 条边就是几万 token,比直接读 3 个小 XML 文件贵得多。 - 注意力噪音。 列表里 80% 是与当前问题无关的 Texture / FX 时,推理质量下降。
- "间接"的诱惑会持续膨胀。 一旦提供无界闭包,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 的实际架构与多窗口缺陷
当前架构(事实)
AI 客户端
│ stdio (JSON-RPC, MCP)
▼
dist/agent/mcpServer.js ← 由 AI 客户端启动,参数 --project <dir>
│ HTTP (127.0.0.1:临时端口, Bearer token)
▼
VS Code 扩展进程内的 localServer ← 仅在"启用 AI Agent 访问"后启动
│
├─ 在线 → ws.activeIndex() 实时内存索引
└─ 离线 → ~/.ra3modxml/snapshots/project-<hash>.json.gz
- AI 客户端不需要知道端口。 MCP Server 自己读
~/.ra3modxml/endpoint.json拿url+token再fetch。localhost 层是 纯实现细节。 - 端口:
server.listen(port ?? 0, "127.0.0.1"),即由操作系统分配临时端口, 不会冲突;当前未传port,所以永远是临时端口。 - 鉴权:
Authorization: Bearer <token>,token 为启动时随机生成;绑定127.0.0.1,局域网访问不到。 - 降级:live 查询失败(endpoint 不存在 / 连接被拒 / 超时 1.5s)时回退到快照。
多项目 / 多 VSCode 窗口的真实缺陷
endpoint.json 是全局唯一的一个文件,所有 VSCode 窗口都往它写入:
- 最后启用的窗口覆盖前面所有窗口。 两个窗口都启用了 Agent 访问时, 只有后启动的那个能被 MCP Server 找到。
- 可能串项目(严重)。 假设窗口 B 覆盖了 endpoint,而某个 MCP Server 是用
--project 窗口A启动的,它的tryLiveQuery会打到窗口 B 的 server, 拿到窗口 B 的索引数据,而返回值里没有任何东西能暴露这个不一致。 这比no_index危险得多 —— 会静默给出错误答案。 - 关闭任意窗口都会
clearEndpoint(),把还开着的其他窗口的 live 通道一起断掉。 - 同一窗口内多项目也不稳。 live server 用
getIndex: () => ws.activeIndex(),而activeIndex()跟着当前活动编辑器走。 用户切到另一个项目的文件,live 查询就会静默换项目;而endpoint.json里的projectDir只在启动时写一次,之后不更新。 processId虽然写进了 endpoint,但从未被校验;VSCode 崩溃后残留的 endpoint 只能靠连接失败兜底。- 离线快照路径是正确的 ——
snapshotPathForProject()按项目路径做 hash, 多项目互不干扰。问题只在 live 层。
修复方案(二期 A,按性价比排序)
- live 响应必须带
projectDir,MCP 侧必须校验它和--project一致, 不一致就直接走快照。这是最小的正确性补丁,最先做。 - endpoint 按项目存:
~/.ra3modxml/endpoints/<project-hash>.json, MCP Server 按--project找对应的那个;同时保留endpoint.json作为 "最近"的兼容入口。 - live server 支持
?project=路由,而不是只认activeIndex()。 扩展侧加indexForProject(projectDir),一个窗口的一个 server 就能服务它 所有已索引项目,也就不怕用户切编辑器。 - 关闭时只清自己的那一条,不无条件删全局 endpoint。
- 校验
processId是否存活,不存活就当 endpoint 失效。 tryLiveQuery失败后做几秒的"live 不可用"负缓存,避免每次调用都试一次。snapshotBaseName换成 sha1 前缀 + 可读 slug(与diskCacheKey一致的做法), 降低碰撞概率。
11. 二期实施清单
二期 A:多窗口 / 多项目正确性
src/agent/snapshot.ts:新增projectHash();snapshotBaseName()改为<slug>-<sha1-12>。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 通过。
新增 / 修改文件
新增:
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/<slug>-<sha1-12>.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、<AssetDeclaration>、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改为<slug>-<sha1-12>,旧的project-<hash>.json.gz快照文件不会被自动清理(该功能尚未发布,无兼容负担)。- 本沙箱禁止 spawn 子进程,因此
node --test与npm run build无法直接运行; 验证方式为:逐个node test/*.test.mjs、直接调用 esbuild CLI 构建 dist、 用管道驱动dist/agent/mcpServer.js做端到端 smoke test。