La interfaz está localizada; las descripciones y documentos de las Skills se traducen automáticamente con IA a su idioma. El código, los comandos, rutas y términos técnicos se mantienen en el original, y prevalece el documento del autor.
Explorar Skills
Desarrollo · SKILL.md

wxa-skills-validate

wxa-skills-validate

校验和修复小程序 AI SKILLs 产物。在以下场景触发:对 skills/ 目录做静态校验、跑通原子接口、验证原子组件渲染、修复校验报错、输出交付文档。依托微信开发者工具进行真机验证。

Independent GitHub snapshot · Automated classification · Original documentation governs

Documentación de la Skill

GitHub

name: wxa-skills-validate description: 校验和修复小程序 AI SKILLs 产物。在以下场景触发:对 skills/ 目录做静态校验、跑通原子接口、验证原子组件渲染、修复校验报错、输出交付文档。依托微信开发者工具进行真机验证。 metadata: author: Tencent version: '0.3.0'

wxa-skills-validate

对小程序 AI SKILLs 产物执行"静态校验 → 原子接口执行 → 原子组件渲染 → 交付文档"的闭环校验,并在每一步失败时按错误类型分类就地修复 skill 源文件。

依赖

  • Node.js ≥ 18(scripts/*.mjs 用到 node:crypto / 原生 fetch)
  • 微信开发者工具(Nightly Electron Build 最新版)已安装,CLI 可执行:<DEVTOOLS_APP_PATH>/Contents/MacOS/cli -h(macOS 默认 <DEVTOOLS_APP_PATH>=/Applications/wechatwebdevtools.app)
  • 开发者工具的服务端口必须开启:手动打开工具 → 设置 → 安全设置 → 打开「服务端口」。未开启时 CLI 连不上,execute / render 全部失败
  • 调试基础库 ≥ 3.16.2;项目 AppID 已在「微信公众平台 - 基础功能 - AI 能力」申请通过「开发模式」
  • 项目 project.config.json 含 appid;app.json 含 agent.skills;每个 skill 目录含 mcp.json + SKILL.md

运行过程中开发者工具可能弹授权窗,需手动授权;未授权会导致原子接口执行失败。

触发条件

出现下列任一情况时启动本技能:

  • 显式要求对 skills 目录做 "校验 / 跑通 / 渲染 / 出交付文档" 中任一项
  • 已有 skills 产物(无论来源)需要进入验证阶段
  • 跑出 skills 的校验报错需要修复

必需信息

项说明缺失时动作
<project-path>小程序项目根目录(含 project.config.json + app.json;app.json 的 agent.skills[].path 指向 skill 分包)向用户询问
<DEVTOOLS_APP_PATH>微信开发者工具应用路径macOS 默认 /Applications/wechatwebdevtools.app,用户可覆盖
<AUTO_PORT>auto WebSocket 端口默认 9420

注:<skills-path> 已不再作为入参,脚本自动从 app.json 发现分包。

参考资料(按需加载)

进入"步骤 4:真机闭环"时必须先读 references/CLI_AGENT_REFERENCE.md,内含脚本用法、产物结构、读产物后的下一步动作、5 项核对对照表、失败回溯流程。

文件用途加载时机
references/CLI_AGENT_REFERENCE.mdCLI agent 命令参考步骤 4 执行前
references/VALIDATE_RULES.mdvalidate.mjs 内置的 V001~V021 规则详解出现校验报错需定位 id 时
references/DELIVERY_TEMPLATE.mdDELIVERY.md 交付模板最终交付时

验收目标(不可降级)

  • <project-path> 下 app.json 发现的每个 skill 分包,其 mcp.json 声明的所有原子接口必须跑通 execute(status === "ok" 且 invokeResult.isError !== true)。例外:敏感接口不真实执行——命中敏感关键词的接口由 V019 落盘 cli-agent-run/destructive-manifest.json,execute.mjs 读该 manifest 拦截(--confirm-destructive 放行)——只做静态校验,验收时视为「已跳过执行」而非未通过,需在报告标注 skipped_destructive。
  • 所有带 _meta.ui.componentPath 的原子接口,必须跑通 render 且通过 5 项核对(见 references/CLI_AGENT_REFERENCE.md 第 2.3 节)。
  • 单接口连续修复 3 轮仍不通过才允许挂起。不得跳过任何一项(敏感接口的执行跳过除外)。
  • 静态/编译通过 ≠ 验收通过:须真机 execute + render 5 项核对;execute 未跑成时不得判通过、不产出 DELIVERY.md(见「不可修复类」与「终止条件 4」)。

执行清单(复制后勾选,逐项完成)

阶段 1 — 静态校验 + 编译校验
- [ ] 运行 `node validate.mjs <project-path>`(单参数,脚本自动发现 skill 分包并决定是否跑 preview)
- [ ] summary.errors === 0(含 V001~V021),否则按 T1~T9 分类修复后重跑
- [ ] summary.buildStatus === "pass"(静态 0 error 时 preview 会自动运行;
      若为 "skipped" 说明静态未过,先按上一项修复)
- [ ] 阅读 Build 行:若 stage=compile + FAIL,说明有语法/编译错误,必须修复

阶段 2 — 准备
- [ ] 确认 CLI 可执行:<DEVTOOLS_APP_PATH>/Contents/MacOS/cli -h
- [ ] (可选)显式启动 cli auto

阶段 3 — 构建执行计划
- [ ] 解析每个 <skill>/mcp.json 的 apis[],按书写顺序 + 参数依赖做拓扑排序
- [ ] 建立"已知数据池"(空)

阶段 4 — execute 与 render(可独立执行)
对每个 {name}:
- [ ] execute 成功(status=ok 且 !isError)
- [ ] 若 mcp.json 有 _meta.ui.componentPath,render 可在任何时间点执行(不要求紧跟 execute)
- [ ] render 通过 --from-execute 复用最新的 execute 产物(args 取自 invokeResult.structuredContent);
      structuredContent 缺失时必须先重跑 execute
- [ ] 5 项核对全部通过(主要依据:`consoleMessages.snapshotCard` 中的生命周期日志 + `[ai-mode] ... overflow monitor=on` 基线日志 + 不出现 `overflowed=true`;仅在具备图像读取能力时再辅助读截图)

阶段 5 — 交付
- [ ] 写 ./cli-agent-run/report.md
- [ ] 若全部通过,按 references/DELIVERY_TEMPLATE.md 写 ./DELIVERY.md 并回贴内容

工作目录

在 <project-path> 同级建 ./cli-agent-run/ 统一存放产物:

cli-agent-run/
├── validate-report.json                   # 阶段 1 产物
├── execute-result.<apiName>.json          # 阶段 4 execute 产物(含 invokeResult.structuredContent 供 render 继承)
├── render-result.<apiName>.json           # 阶段 4 render 产物(snapshot 摘要 + consoleMessages + elementTree)
├── render-result.<apiName>.snapshot.png   # 阶段 4 render 截图
├── execute-trace.json                     # 每次尝试的回溯日志
└── report.md                              # 阶段 5 执行报告

项目根目录/
└── DELIVERY.md                            # 全部通过时的最终交付文档

同一接口重跑时必须复用 --output(文件会被覆盖);不同接口必须用不同文件名。


阶段 1 — 静态校验 + 编译校验(合并为一次运行)

运行:

node <skill-dir>/scripts/validate.mjs <miniprogram-project-path>

入参只需要一个——小程序项目根目录(含 project.config.json + app.json)。脚本自动:

  1. 读 app.json 的 agent.skills[].path 发现 skill 分包(没配置时回退到顶层 metaServicePkg/ 或 skills/)
  2. 静态规则只在 skill 分包目录内 执行,不触及主包代码
  3. 把校验产物目录 cli-agent-run/ 写入 project.config.json 的 packOptions.ignore(打包忽略)和 watchOptions.ignore(监听忽略),避免开发者工具持续监听产物变更触发循环编译(已存在不会重复追加;产物落盘前完成同步,结果挂在报告 ignoreSync 字段)
  4. 静态校验通过(errors === 0)后自动调用 cli preview 做编译校验;有 error 则跳过 preview
  5. 报告落盘到 <project>/cli-agent-run/validate-report.json(可用 --output 覆盖)

可选参数:--rules <自定义规则 json> / --cli-path <CLI 路径> / --build-timeout <ms> / --output <path>。

退出码:0 通过;1 存在 error 或 build 失败;2 运行异常。

通过判据:

  • summary.errors === 0(warning 允许带着进入阶段 2)
  • summary.buildStatus === "pass"(静态 0 error 后会自动触发 build;"skipped" 意味着静态未过,先按修复决策表修复)
  • Build 行 stage=compile + FAIL 说明有语法/编译错,必须修复

Build 编译报错时:优先检查集成配置,再动源码。对照 wxa-skills-generate SKILL.md 的"阶段 6 — 配置集成"与 references/CODE_TEMPLATES.md 的"六、app.json + project.config.json 配置"核对 app.json(agent.skills / subPackages)与 project.config.json(appid / packOptions.include)。集成无误后才按日志改源码,禁止用注释/删除源码的方式绕过集成问题。

CLI 未找到时的处理:若输出 Build: SKIPPED - 跳过:未找到微信开发者工具 CLI,说明脚本未能自动定位到 cli。自动探测顺序为:--cli-path > 环境变量 WECHAT_DEVTOOLS_CLI / WXA_CLI > macOS /Applications/wechatwebdevtools.app/Contents/MacOS/cli > 同路径的用户目录变体 > Windows C:\Program Files (x86)\Tencent\微信web开发者工具\cli.bat。此时应主动向用户询问微信开发者工具的安装路径,然后:

  • 重跑:node validate.mjs <project-path> --cli-path <用户提供的绝对 cli 路径>
  • 或建议用户设置环境变量:export WECHAT_DEVTOOLS_CLI=<绝对路径> 后重跑

CLI 缺失不影响静态规则的输出,只会让 build 阶段被 skip。

执行顺序(脚本内部闭环):

  1. 同步 project.config.json 的 packOptions.ignore + watchOptions.ignore(追加 cli-agent-run/)
  2. 发现 skill 分包 → 只在分包内跑 V001~V021
  3. 有 error → build=skipped(节省 preview 成本);0 error → 调 cli preview
  4. 到达 upload 阶段视为编译通过;即便上传失败(服务端校验、网络等),也不标记 build 失败

失败时的修复决策表(读完 validate-report.json 中 results[].id / message / fix 后匹配):

错误类型识别特征修复范围动作
T1 命名拼写字段大小写/拼写错单文件单行直接改
T2 Schema 不一致structuredContent 与 outputSchema.properties 字段不匹配(V009)apis/{name}.js + mcp.json对齐字段
T3 组件绑定不一致WXML {{}} 与 setData 字段对不上(V011)components/{x}/index.{js,wxml}对齐绑定
T4 组件取值路径错result.structuredContent.xxx 与接口返回字段不符(V010)components/{x}/index.js修访问路径
T5 合规性违规非白名单 WXML 标签 / 事件 / 图片格式 / CSS 属性(V003/V005/V006)单文件改写用白名单实现替换:collapsible-view 是允许的;transition / animation / overflow / -webkit-line-clamp 一律删除(改 -wx-line-clamp);非 tap 事件删除
T6 注册缺失mcp.json 的 name 在 index.js 未 registerAPI,或反之(V007/V008)index.js补/删注册
T7 依赖链路问题storage key 写入方/读取方对不上跨接口 + utils/util.js跨文件调整
T8 原子接口粒度错接口职责重叠mcp.json + index.js + apis/*.js拆分/合并 apis[]
T-mcp-sizemcp.json 去除 outputSchema 后超过 24000 字符(V013;后台也会拒绝)mcp.json 的 description/title/inputSchema;或重划 skill 分包压缩描述文字;接口多到难以精简时按职责拆分为多个 skill 分包,不要把示例/枚举硬塞进 outputSchema
T-auth 鉴权缺失401 / unauthorized / token 无效 等(静态阶段通常由 V007/V008 连带触发)utils/util.js / apis/{name}.js读主包还原登录流程
T-wx-jsapi 非白名单运行时 wx.<xxx> is not a function / wx.<ns> 为 undefinedapis/{name}.js / components/{x}/index.js对照 wxa-skills-generate SKILL.md D.1/D.2 白名单(完整清单见 wxa-skills-generate/references/JSAPI_WHITELIST.md),按 D.7 替换或改网络请求;无替代标 T9(详见阶段 4 C 类)
T-build 编译失败Build 行显示 FAIL 且 stage=compile项目集成 / .js / .wxml / .wxss先对照 wxa-skills-generate SKILL.md 阶段 6 "配置集成" 核对 app.json / project.config.json,集成无误后再按日志修源码
T-skill-descriptionapp.json 的 agent.skills[].description 缺失或为空(V016)app.json在该条目中补充非空的 description 字段
T-handoff接力页 _meta.ui.pagePath 格式错/页面不存在/带 query,或声明了 pagePath 却未返回 handoff(V017)mcp.json + apis/{name}.jspagePath 以 / 开头、不含 query、页面真实存在;返回值顶层补 handoff: { query, payload? }(详见 wxa-skills-generate SKILL.md D.6)
T-handoff-queryhandoff.query 的参数名与接力页 onLoad 读取的参数名不匹配(V018)apis/{name}.js读接力页 <pagePath>.js 的 onLoad(param) 确认其读取的 param.xxx 名称,将 handoff.query 的参数名改为页面实际读取的名称
T-destructive接口命中敏感关键词,V019 待模型判断(V019,destructive:null)cli-agent-run/destructive-manifest.json编辑 manifest 填 destructive=true+destructiveReason(真敏感)或 false+destructiveReason(误判如只读查询)
T-relatedPage组件未在 components[] 声明,或 relatedPage 缺失 / 缺前导 / / 页面不存在(V015)mcp.json + components/{x}/index.js在 components[] 补 { path, relatedPage }(path 与接口 _meta.ui.componentPath 严格相等,relatedPage 以 / 开头且页面真实存在,无对应业务页时填首页);运行时在收到 Result 后调 setRelatedPage({ query })
T-limitsSKILL.md / AGENTS.md / page-meta.json 超字节上限,或 SKILL 数超 30(V020)对应文件 / app.json精简内容(SKILL.md 只留 5 节结构,接口契约回 mcp.json);SKILL 过多时合并职责相近的
T-dynamic静态原子组件或其相对 JavaScript 依赖里用了 wx.login / wx.checkSession / wx.request / setTimeout / setInterval,或任意组件代码路径用了云开发(V021)<componentPath>.js、其相对 JavaScript 依赖或 mcp.json静态组件确有实时需求才在 components[] 该条目加 permissions["scope.dynamic"];云开发一律移到原子接口并经 NotificationType.Result 下发数据
T9 能力无法实现所有候选都违反硬约束—⛔ 终止,告知用户

V001~V021 规则详情见 references/VALIDATE_RULES.md。

判别口诀:文件内能改完 → T1~T6;需改 storage 清单或接口划分 → T7/T8;连修复方案都违规 → T9。

迭代规则:

情况动作
summary.errors === 0✅ 进入阶段 2
errors 数较上一轮减少继续修复,重跑
连续 3 轮相同 finding id升级为 T7/T8 跨文件调整
累计 5 轮仍未通过⛔ 终止,请求人工介入

阶段 2 — 准备 CLI agent 命令

确认 CLI 可执行:

<DEVTOOLS_APP_PATH>/Contents/MacOS/cli -h

失败则告知用户 "确认微信开发者工具已安装" 后停止,不要强行绕过。

(推荐)先 open 预热再 auto(约 10s,大项目可延长),减少 websocket 超时:

<DEVTOOLS_APP_PATH>/Contents/MacOS/cli open --project <PROJECT_PATH>
<DEVTOOLS_APP_PATH>/Contents/MacOS/cli auto \
  --project <PROJECT_PATH> --auto-port <AUTO_PORT> --trust-project

跳过此步时脚本会自动拉起 auto;遇超时或 agent compile mode is disabled 时按「不可修复类 / 工具不稳定」处理。


阶段 3 — 构建执行计划

读取 <project-path> 下 app.json 发现的每个 skill 分包的 mcp.json(validate-report.json 中的 skillDirs 字段给出了具体分包路径):

  1. 敏感接口初筛+模型判断(V019,execute 前必做):阶段 1 静态校验的 V019 脚本初筛所有 mcp.json,把 name/description 命中敏感关键词的接口写入 cli-agent-run/destructive-manifest.json(destructive:null 待判断),报 error 阻断 build。模型逐个判断后编辑 manifest 填 destructive:true(真敏感,execute 跳过)或 false(误判,execute 放行)+ destructiveReason,重跑 validate 至 V019 pass。execute.mjs 读 manifest:true 拦截 / false 放行 / null 拦截(待判断)/ 不在 manifest 放行。生成侧阶段 2 已规定敏感接口默认不收集;V019 是兜底,catch 漏网的敏感接口名。
  2. 汇总 apis[] 的 name / description / inputSchema / outputSchema / _meta.ui.componentPath。
  3. 按入参依赖排序(拓扑序):
    • 无参接口(inputSchema.properties 为空或无 required)→ 最先执行
    • 有参接口 → 排在其参数来源接口之后
    • description 或 inputSchema 含 "需要先调用 X" 类表述时,将 X 前置
  4. 维护"已知数据池":每个接口成功后把 structuredContent 存入池中,供下游参数引用。
  5. 有参接口的参数填充优先级:先查已知数据池(上游接口 structuredContent 的同义字段),池中没有才考虑用户指定或默认值——禁止在有数据池可用时直接用默认值测试有参接口。

阶段 4 — execute 与 render

术语澄清:execute 是校验阶段名(本阶段),用 CLI agent tool 调用 skills 分包的注册接口;不要与 probe 混淆——probe 是 generate 阶段 3.7 用 automator 在源项目上抓请求响应。两者工具不同(CLI agent vs automator)、对象不同(skills 分包 vs 源项目)、阶段不同(校验 vs 生成)。

execute 和 render 是两个独立可重入的命令:

  • execute 调用原子接口,产出业务数据(invokeResult.structuredContent)。
  • render 通过 --from-execute 把 execute 的 invokeResult.structuredContent 作为渲染数据源喂给组件; 也可以 --name + --args 独立指定。CLI 内部每次 render 会自动生成一次性 toolCallId / sessionId, 不依赖 execute 的运行时上下文。

执行灵活度:

  • 可以一次 execute 所有原子接口、再统一批量 render
  • 也可以"单接口 execute → render"交替进行
  • render 的数据来源优先级:--args 显式指定 > --from-execute 读到的 invokeResult.structuredContent

硬约束(仅保留真正必要的):

  • 敏感接口默认不执行——会产生不可逆副作用的接口默认拒绝执行。判定来源:优先读 cli-agent-run/destructive-manifest.json(V019 初筛+模型判断),destructive=true 拦截 / false 放行 / null 拦截(待判断,安全第一)/ 接口不在 manifest 放行;manifest 不存在时回退关键词判定。execute.mjs 未带 --confirm-destructive 时对判定的敏感接口直接拒绝(退出码 3、不落盘)。全量 execute / loop 排查中必须跳过这些接口(只做阶段 1 静态校验),报告标 skipped_destructive。严禁批量放行。仅当用户明确要求执行某个具体敏感接口时,才带 --confirm-destructive 单独执行,且执行前应向用户说明后果。
  • 执行顺序:先无参后有参——无参接口先批量 execute 成功,其 structuredContent 入数据池后,有参接口再从池中取参数值 execute。禁止在有数据池可用时直接用默认值测试有参接口
  • 按 apis[] 顺序依赖关系准备好入参(下游接口的 args 若依赖上游 structuredContent,仍需先 execute 上游)
  • 每个带 componentPath 的接口最终都要 render 通过;完整通过的判据仍然是"execute 成功 + render 5 项核对通过"
  • 同一条 CLI 调用内,render.mjs 不能并发执行(CLI 后台 auto 是串行的)
  • --from-execute 的 execute 产物必须含 invokeResult.structuredContent;若缺失,render.mjs 会直接报错,需先重新 execute 成功后再 render

4.1 execute

运行:

node <skill-dir>/scripts/execute.mjs \
  --project <PROJECT_PATH> \
  --name <name> \
  [--args '{"query":"..."}'] \
  [--auto-port <AUTO_PORT>] \
  [--skill <skill-name-or-path>] \
  [--timeout <ms>] \
  --output ./cli-agent-run/execute-result.<name>.json

execute.mjs 只接受 上述参数;toolCallId / sessionId / auto 相关票据由 CLI 内部自动处理,脚本不再暴露。

入参来源优先级:

  1. 用户指定
  2. 已知数据池(上游接口 structuredContent 的同义字段)——有参接口必须先尝试从已成功执行的无参/上游接口的 structuredContent 中提取参数值,而非直接用默认值。例:getOrderDetail 需要 orderId → 先跑 listOrders(无参),从其 structuredContent.orders[0].id 取 orderId
  3. inputSchema 允许为空 → 省略 --args
  4. 类型默认值(string ""、number 0、array []、object {}),日志标注"使用默认值"——仅当数据池无对应字段且用户未指定时才用

成功判据:status === "ok" 且 invokeResult.isError !== true 且 invokeResult.structuredContent 为非空对象 (后者是 render --from-execute 的前置条件)。

空结果排查(success 但 structuredContent 业务数据为空):isError !== true 但返回的 structuredContent 是空列表 / 空对象 / total: 0 / 只有 error 字段时,不能直接判通过——这通常是请求参数错误、鉴权未生效、URL 拼错或响应拆包路径错的症状,而非业务上真的无数据。按以下顺序排查:

  1. 读 consoleMessages 的 [ai-mode] 日志:确认请求实际发出的 URL / 参数 / header 是否正确(入口日志 → 请求前日志 → 请求后日志)
  2. 读主包源码定位真实请求:找到该接口在主包中对应的页面/请求封装,确认真实 URL / method / 参数名 / 鉴权头 / 响应拆包路径
  3. 对比主包真实请求与 apis/<name>.js 实际发出的请求:URL / method / 参数名 / 鉴权头是否一致?不一致 → 回 apis/<name>.js 或 utils/request.js 修正
  4. 鉴权排查:主包请求封装需要的登录态/token,apis/<name>.js 入口是否补齐 await ensureLogin() 等 → 鉴权缺失会导致后端返回空而非报错
  5. 排查后修正 → 重跑 execute;仍空且确认请求与主包真实请求完全一致 → 可能是后端环境差异(测试账号无数据),在 trace 记录"已排查请求正确,疑似环境无数据",允许带声明通过

execute 失败:先检查产物 _meta.diagnosis 是否为不可修复类(若是则立即停止),否则按下方"阶段 4 失败分类"的 A/B/C/D 类处理。

4.2 render(仅当 mcp.json 中该 api 有 _meta.ui.componentPath 时执行)

只要给对的 name + args(渲染数据源)就能渲染。CLI 的 render 不会重新执行原子接口,而是把 --args 作为 structuredContent 直接喂给组件渲染;--from-execute 只是一个语法糖,用来把 execute 产物里的 invokeResult.structuredContent 直接喂给 render。

推荐运行方式(从 execute 产物继承 name / args,args 来源为 invokeResult.structuredContent):

node <skill-dir>/scripts/render.mjs \
  --project <PROJECT_PATH> \
  --from-execute ./cli-agent-run/execute-result.<name>.json \
  [--timeout 90000] \
  --output ./cli-agent-run/render-result.<name>.json

若 execute 产物缺 invokeResult.structuredContent,脚本会直接 exit 2 报错—— 此时必须先重跑 execute 并确认 status=ok + invokeResult.isError!==true + structuredContent 为非空对象。

独立指定上下文(没有 execute 产物,或需要手动指定 args):

node <skill-dir>/scripts/render.mjs \
  --project <PROJECT_PATH> \
  --name <tool-name> \
  --args '{"<字段>":"..."}' \
  [--timeout 90000] \
  --output ./cli-agent-run/render-result.<name>.json

render.mjs 自动从 --from-execute 继承 name / args;任一字段被 --name / --args 等显式参数提供时以显式值为准。CLI 下发的参数仅限 --project / --name / --args / --output / --trust-project (及必要时的 --timeout),其它上下文由 CLI 内部自动生成,无需也无法从脚本显式传入。

render cold start 通常比 execute 慢(需要创建 container + 渲染组件),首次调用或 CI 环境建议 --timeout 90000。 详细参数、产物结构、读产物后下一步动作见 references/CLI_AGENT_REFERENCE.md 第 2 节。

必须读取的产物(仅靠 render.mjs 退出码 0 不足以判通过):

  • console 日志(主要依据):render-result.<name>.json 的 consoleMessages.snapshotCard。必看:
    • [ai-mode] ... created → [ai-mode] ... 收到接口返回 → [ai-mode] ... setData 三条生命周期日志(缺任何一条 → 组件初始化或 Result 监听有问题)
    • [ai-mode] <component> overflow monitor=on(基线日志,必存在):组件已绑定 NotificationType.Overflow 监听。缺失 → 视为未接入监听,回 wxa-skill-generate 的组件 JS 骨架补齐
    • [ai-mode] <component> overflow overflowed=true data=<JSON>(或 data.overflowHeight > 0):有裁剪,核对 ③ 不通过。只要出现一次就判失败;只有 monitor=on、没有 overflowed=true 记录则视为未裁剪通过
    • 任何 ERROR 级日志基本意味着业务组件初始化失败,截图会是空白
  • 组件树 elementTree(辅助核对,原样透传):render-result.<name>.json 的 elementTree 完全由 CLI render 返回,是一段缩进格式的字符串(非 JSON 对象),序列化了卡片的 shadow tree,形如 <view:view class="addr-row">...、<text:default-component class="temp">... 28°、 <(virtual):wx:if> 等节点。render.mjs / lib.mjs 不做任何加工或占位回填——CLI 没下发就没有该字段。 它不参与 pass/fail 判定,仅作为辅助信号:用来核对字段文案是否命中绑定、列表节点数量、 wx:if 空状态是否生效等(对字符串做 grep 即可)
  • 截图(辅助):render-result.<name>.snapshot.png。仅在当前运行环境具备图像读取能力时,以图像方式 read_file 读入,辅助核对样式还原度(核对 ④)。若当前环境不具备图像读取能力,跳过截图读取,不视为失败;核对 ③(裁剪)完全以 overflow 日志为准,不回退到基于截图的视觉判断

5 项核对见 references/CLI_AGENT_REFERENCE.md 第 2.3 节。任一不通过 → 留在本接口继续修复。

4.3 闭环自检(整体判通过前的硬门闩)

每个带 componentPath 的接口都满足下列全部才允许标为通过:

  • 存在 execute-result.<name>.json,其 status === "ok" 且 invokeResult.isError !== true
  • 存在 render-result.<name>.json
  • 5 项核对全部通过(含 consoleMessages.snapshotCard 中存在 [ai-mode] ... overflow monitor=on 基线日志、且不出现 overflowed=true;截图仅在具备图像读取能力时作为辅助信号)

阶段 4 失败分类与修复流程

不可修复类:环境 / 权限问题(非代码错误)

先检查 _meta.diagnosis:非 null 即环境/权限问题,停止改代码,把 hint 原样转述给用户(勿笼统断言"无权限")。此情形不得判通过、不产出 DELIVERY.md。

按 _meta.diagnosis.type 区分处理:

miniprogram_not_runnable(CLI 返回 agent compile mode is disabled):表示小程序主包/分包未能正常编译运行——agent 能力要在小程序能正常跑起来时才自动就绪(cli preview 走到 upload 的"编译通过"只代表能打包,不代表运行时不白屏)。按 hint 逐条排查:

  1. 在开发者工具打开项目,确认能正常运行、无白屏,控制台无 app.js / hack.js 运行时报错(如 Cannot set properties of undefined、appServiceSDKScriptError)
  2. regeneratorRuntime 类报错通常源于 project.config.json 的 es6 / enhance 编译设置与线上不一致,按能正常运行的配置对齐。
  3. appid missing / cloud init error 说明 project.config.json 缺 appid 或云开发未初始化,补齐后重试
  4. 确认 app.json 的 agent.skills / subPackages 配置正确,skill 目录含 mcp.json
  5. 首次打开可能尚未就绪,重开一次项目预热后再重试

agent_env_unreachable:CLI stdout 含 timeout waiting for auto websocket 且 stderr 含 Fetching AppID (wx...) detailed information ✖。多种可能,不可直接归因为无权限,须把 hint 里的可能性逐条转述给用户,排查顺序:

  1. 开发者工具 / 基础库 agent 运行时异常或版本过旧 → 切线上基础库、用 --debug 重试
  2. 自动化通道未连上 / 端口不一致 → 确认服务端口已开启,必要时指定 --auto-port
  3. 工具未登录或账号不是该 AppID 成员 → 重新登录并确认账号权限
  4. 网络无法访问微信后台 → 检查代理 / VPN / 防火墙
  5. 以上均正常仍失败,才考虑 AppID 未开通 AI 开发模式权限

工具不稳定(_meta.diagnosis 为 null 但 execute 超时/掉线/Adapter wait timeout/runtime 未 attach):非代码 bug,禁止改代码判通过。处理:cli open 预热 → --timeout 120000+ → 工具保持前台,必要时重启;仍失败则停止,不产出 DELIVERY.md。

diagnosis === null ≠ 都是"工具不稳定"——execute.mjs/render.mjs 仅在识别到确定性环境信号(agent compile mode is disabled / Fetching AppID ✖ 等)时写 diagnosis;其余一律留 null。diagnosis === null 时按 error / consoleMessages 的具体错误特征判定归类:

  • 超时 / 掉线 / Adapter wait timeout / runtime 未 attach → 工具不稳定(本节)
  • missing required parameter / xxx is undefined → A 类(下方)
  • no data / getStorageSync 返回 null → B 类(下方)
  • network / 500 / unauthorized / wx.<xxx> is not a function / JS 抛异常 → C 类(下方)
  • status: ok + isError: false + 数据空 → E 类(下方)

禁止把 A/B/C/E 类业务错误伪装成"工具不稳定"逃避修复。


修复范式(确认非不可修复类后,每次失败按此顺序走)

- [ ] 步骤 1:读运行时产物(execute-result / render-result 的 `error` / `consoleMessages`;
      `elementTree` 由 CLI 返回时可辅助定位字段/绑定问题,缺失时跳过;
      `snapshot.png` 仅当环境具备图像读取能力时再辅助核对,否则跳过)
   - 先检查 `_meta.diagnosis` —— 若非 null → 不可修复类,立即停止
   - **若报错形如 `wx.<xxx> is not a function` / `Cannot read property '<xxx>' of undefined`
     → 直接跳到 C.1 子类按白名单比对处理**
- [ ] 步骤 2:回到主包源码定位真实逻辑(页面 .js / utils/request.js / app.js / cloudfunctions/*)
- [ ] 步骤 3:对比分包实现,列出差异点再改(`apis/<name>.js` / `utils/util.js` / `components/<name>/*`)
- [ ] 步骤 4:若涉及接口划分 / storage 链路,改 mcp.json 的 apis[] 并同步 index.js 注册
- [ ] 步骤 5:重跑 execute 验证数据正确;涉及 UI/WXML/WXSS 改动时单独跑 render 验证渲染
      (render 可通过 --from-execute 复用之前的 execute 产物,前提是该产物仍含 invokeResult.structuredContent)
- [ ] 步骤 6:仍失败重复步骤 1~5,单接口上限 5 次

核心原则:真相只在主包里。分包是独立运行的拷贝,逻辑差异以主包为准。禁止在未读主包源码的情况下臆测修改。


A 类:execute 参数失败

特征:missing required parameter / xxx is undefined / 参数格式错误。

  1. 依赖图找上游接口;未跑则先跑上游,提取字段后重拼 --args(上限 3 次)
  2. 字段名不一致 → 做字段映射后重试
  3. 找不到上游来源 → 读主包确认真实依赖 → 改 apis/<name>.js 入参拼装 → 重跑
  4. 3 次仍失败 → 转 C 类

B 类:execute 读取 storage 失败

特征:no data / getStorageSync 返回 null。

  1. 在 <skill>/SKILL.md 的 storage 清单中找写入方接口,先跑一次再重跑当前接口(上限 2 次)
  2. 若 key 应由主包 app.js 初始化 → 读主包 → 在 utils/util.js 的 ensureStorageInit() 补初始化逻辑 → 重跑
  3. 2 次仍失败 → 转 C 类

C 类:execute 代码/网络失败

特征:network / timeout / 500 / unauthorized / not registered / JS 抛异常 / 返回字段与 outputSchema 不一致。

  1. 读 execute-result.<name>.json 的 invokeResult.error + consoleMessages([ai-mode] 前缀日志)锁定失败步骤
  2. 读主包必读清单:
    • 源页面 .js:拼装入参(headers / token / 签名 / 查询串)
    • utils/request.js / utils/http.js / api/*.js:baseUrl / 鉴权头 / 错误码 / 返回结构(是否包了 data/code/msg)
    • app.js:wx.cloud.init({ env }) / 全局 token / globalData
    • 云开发项目:cloudfunctions/<fn>/index.js 的真实字段名
  3. 列差异点后只改 apis/<name>.js / utils/util.js,不重写整个文件
  4. 若返回字段变了 → 同步改 components/<name>/index.js 的访问路径与 index.wxml 绑定
  5. 重跑 execute;仍失败重复 1~4,上限 5 次
  6. 5 次仍失败:
    • 涉及接口划分 / storage 依赖 → 改 mcp.json 的 apis[] 结构 + ensureStorageInit,重跑阶段 1
    • 源码无对应能力或依赖非白名单 → 标记 T9,在 report.md 记录后终止本接口

C.1 子类:wx JSAPI 未定义(非白名单)

特征:运行时报 wx.<xxx> is not a function / Cannot read property '<xxx>' of undefined(wx.<ns> 为 undefined)。

优先假设不是代码写错,而是该 JSAPI 不在技能分包白名单内。按以下顺序处理:

  1. 从报错提取 API 名,对照 wxa-skills-generate SKILL.md 的 D.1(接口侧)/ D.2(组件侧)白名单(完整清单见 wxa-skills-generate/references/JSAPI_WHITELIST.md)
  2. 在白名单内 → 检查调用上下文是否错位(组件/接口侧专属)、wx.request 在组件侧是否漏声明 permissions["scope.dynamic"]
  3. 不在白名单 → 按 D.7 替换(如 chooseImage → chooseMedia)或改网络请求实现
  4. 无等价替代 → 标 T9 终止。禁止用 if (wx.x) / try/catch 吞异常当修好

C.2 子类:Skill 模块加载失败(分包未注册)

特征:运行时报 Skill code loading failed: module 'skills/<skill>/index.js' is not defined, require args is 'skills/<skill>/index.js'(或类似 module ... is not defined / require args is ... 的模块解析错)。

优先假设不是 JS 代码错,而是分包集成没接对。按以下顺序核对(不要动 apis/ 或 components/):

  1. app.json 的 subPackages 是否把 skills 声明为独立分包(缺此项是最常见原因):
    "subPackages": [
      { "root": "skills", "name": "skills", "pages": [], "independent": true }
    ]
    
    • root 必须是 skills 目录的相对路径;independent 必须为 true;pages 可为 []
  2. app.json 的 agent.skills[].path 是否指向 skills/<skill>(与 subPackages.root 一致)
  3. project.config.json 的 packOptions.include 是否包含 { "type": "folder", "value": "skills" }(否则 CLI 构建时不打包该目录)
  4. 目录自身:skills/<skill>/index.js 实际存在,且其中通过 wx.modelContext.registerAPI('<name>', fn) 注册了报错对应的 <name>
  5. 以上四项完整且正确 → 才按 C 类主流程去读 apis/<name>.js 的代码

对照 wxa-skills-generate SKILL.md 阶段 6 与 references/CODE_TEMPLATES.md 第六节 的配置片段做核对,不要乱写。

E 类:execute success 但业务数据为空

特征:status === "ok" 且 invokeResult.isError !== true,但 structuredContent 是空列表 / 空对象 / total: 0 / 只有 error 字段。不能直接判通过——这通常是请求参数错误、鉴权未生效、URL 拼错或响应拆包路径错的症状。

按以下顺序排查(详见 references/CLI_AGENT_REFERENCE.md E 类排查流程):

  1. 读 consoleMessages 的 [ai-mode] 日志,确认请求实际发出的 URL / 参数 / header
  2. 读主包源码定位该接口真实请求结构(URL / method / 参数名 / 鉴权头 / 响应拆包路径)
  3. 对比主包真实请求与 apis/<name>.js 实际发出的请求:URL / method / 参数名 / 鉴权头是否一致
  4. 鉴权排查:主包请求封装需要的登录态/token,apis/<name>.js 入口是否补齐 await ensureLogin() 等
  5. 排查后修正 → 重跑 execute;仍空且确认请求与主包真实请求完全一致 → 可能是后端环境差异,在 trace 记录"已排查请求正确,疑似环境无数据",允许带声明通过

上限 3 轮;排查后确认是代码问题 → 转 C 类修 apis/<name>.js / utils/request.js。

D 类:render 核对不通过(含"被裁剪"/"样式还原度不达标")

  1. 读产物定位:consoleMessages.snapshotCard(主要依据,尤其是 [ai-mode] ... overflow 日志)+ elementTree(若 CLI 下发,辅助核对字段绑定 / 列表长度 / 空状态文案);仅在环境具备图像读取能力时,再以图像方式 read_file 读 snapshot.png 作为样式还原度的辅助信号
  2. 按问题类型改(只改 components/<name>/,不动 mcp.json / apis/*):
    • 被裁剪([ai-mode] ... overflow overflowed=true 或 data.overflowHeight > 0):index.wxss 压缩 item 高度、用 -wx-line-clamp:1~2 截断长文本;根节点不要写 overflow / max-height / min-height / height(外层尺寸与裁剪都由宿主施加,组件自行设高会让 NotificationType.Overflow 回调失效);单纯列表数据超量时可用半屏原子组件 collapsible-view 让卡片自行展开,不要误改成另开半屏页面;只有需要卡片外详情或用户补充信息时才用独立半屏页面。否则在 index.js 计算 visibleItems + omittedCount = total - visibleItems.length,WXML 渲染"还有 {{omittedCount}} 条未展示"
    • 未接入溢出监听(consoleMessages.snapshotCard 中找不到 [ai-mode] ... overflow monitor=on 基线日志):回 wxa-skill-generate 的组件 JS 骨架,在 created 中通过 wx.modelContext.getViewContext(this).on(NotificationType.Overflow, ...) 绑定监听,并在绑定后同步 console.info('[ai-mode] {componentName} overflow monitor=on') 打出基线日志
    • 样式与源页面不一致(还原度不达标):回主包读 .wxml / .wxss + app.wxss,重提视觉 token(主色、字号、圆角、间距、分割线、图片比例)覆盖 index.wxss。单位推荐 vw(1vw ≈ 7.5rpx)
    • 图片不展示(软性优化):在 index.js 的 NotificationType.Result 分支做字段归一化(如 imageUrl: item.imageUrl || item.cover || item.pic || item.thumb || item.image)
    • 组件上行协议违规(点击"活"按钮但小程序 AI 拿不到下一跳、或组件自己调业务接口):上行合法形态有两种:① 单 text(自然语言 followUp);② text + api/call 组合(结构化 toolCall)—— wx.modelContext.getContext(this).sendFollowUpMessage({ content: [{ type: 'text', text }, { type: 'api/call', data: { name, arguments } }] }), text 是用户视角的简短中文(≤ 12 字)、name 必须在当前 skill mcp.json.apis[].name 中存在、arguments 字段与目标接口 inputSchema.properties 对齐、值从 e.currentTarget.dataset / this.data 取。 违规形态包括:content 只含 api/call 不含前导 text(缺用户上下文,小程序 AI 拿不到意图描述)、缺 content 数组直接 { type: 'api/call', ... }、name 在 mcp.json 中不存在、arguments 字段名错或带占位值、组件内直接 wx.request 业务接口。setRelatedPage 使用在 created 中保存的 this._viewCtx 是合法形式,不应按缓存引用报错。 只改 components/<name>/index.js 的 tap handler,不改 apis/ 和 mcp.json;每次上行前补一行 console.info('[ai-mode] {componentName} send api/call name=... args=...') 便于下次 render 在 consoleMessages.snapshotCard 中核验
  3. 改完 components/<name>/ 后重跑 render(UI 类改动不需要重新 execute,可通过 --from-execute 复用现有产物)→ 再次读 render-result.<name>.json 的 consoleMessages.snapshotCard:基线 overflow monitor=on 日志必须存在、且不出现 overflowed=true(具备图像读取能力时可附加读取截图作为样式还原度的辅助信号),不得仅依据退出码 0 放行
  4. 上限 5 轮,仍不通过按 C 类退出条件处理

禁止动作

  • 禁止用 curl / fetch / HTTP 工具直接验证网络接口。原因:① 小程序沙箱的鉴权上下文(session / cookie / 签名 / wx.login code)在终端无法复现;② 验证结果对 skill 分包无参考价值。网络请求改动只能通过 execute.mjs 验证。
  • 禁止在未读主包源码的情况下臆测修改分包代码。
  • render 可通过 --from-execute 复用已有 execute 产物(args 取自 invokeResult.structuredContent);但 render 关心的是 UI 渲染正确,因此若改动会影响接口返回数据(改 apis/ / outputSchema),需要先重新 execute 再 render,不应用旧产物;仅改 UI(wxml/wxss/components/index.js)时可复用。
  • 禁止多个接口的 CLI 命令并发执行。
  • 禁止改动 skill 的总体目录结构(<skill>/apis/ / <skill>/components/ / mcp.json / SKILL.md),只在文件内容层面做最小修复。

回溯记录

每次 execute / render 追加写入 ./cli-agent-run/execute-trace.json:

{
  "skill": "<skill-dir>",
  "api": "<name>",
  "attempt": 1,
  "argumentsUsed": { },
  "argumentsSource": "user | upstream:<apiName> | default | empty",
  "executeStatus": "ok | error",
  "executeError": null,
  "renderChecks": { "rendered": true, "fieldsComplete": true, "overflow": false, "style": true, "ellipsis": true },
  "renderFailReason": null,
  "recovery": null
}

终止条件

满足任一即终止:

  1. 阶段 1 通过 + 每个声明的 api execute 成功 + 有 componentPath 的接口 5 项核对全部通过
  2. 阶段 1 连续 5 轮未通过 → 停止,输出失败报告
  3. 阶段 4 累计 5 轮仍有接口未通过 → 停止,输出失败报告
  4. 不可修复类(_meta.diagnosis 非 null)或工具不稳定经预热/加 timeout/重启仍失败 → 停止,转述 hint,不产出 DELIVERY.md
  5. T9 类问题 → 立即终止,告知用户

阶段 5 — 交付产物

1. 执行报告 ./cli-agent-run/report.md(每次终止都输出)

# CLI `agent` 命令校验报告

- 执行时间:<ISO>
- project-path:<abs-path>
- skill 分包:<metaServicePkg, ...>(validate-report.json 中 skillDirs 字段)
- devtools:<DEVTOOLS_APP_PATH>

## 接口结果

| skill | api | componentPath | execute | render 5 项 | 产物 |
|-------|-----|---------------|---------|------------|------|
| business | searchItems | components/item-list/index | ✔ | ✔✔✔✔✔ | execute-result.searchItems.json / render-result.searchItems.snapshot.png |

## 未通过接口

- <apiName>:<原因简述>,详见 <产物路径>

## 修复摘要

- `skills/<skill>/apis/<name>.js`:<一行摘要>

2. 交付文档 ./DELIVERY.md(仅终止条件 1 成立时产出)

终止条件 1 成立时必须产出:

  • 写入路径:./DELIVERY.md(项目根;用户指定其它路径时以用户为准,但必须是 .md)
  • 模板:严格套用 references/DELIVERY_TEMPLATE.md,所有 {占位符} 必须替换为实际值
  • execute-trace.json 存在时在"已知限制"节引用
  • 写入后必须在对话中同时贴出完整 MD 内容,不能只说"文件已生成"
  • 无法写入(权限)→ 将 MD 内容直接输出在对话中作为替代

仅输出 report.md 不算任务完成;DELIVERY.md 才是最终交付物。

3. 未通过时的修复建议(追加到 report.md 末尾)

  • 阶段 4 不可修复类 / 工具不稳定 → 禁止改代码,按 diagnosis.hint 转述;提示用户工具恢复后重跑 execute,不产出 DELIVERY.md
  • 阶段 1 T1~T6 → 直接修对应文件,重跑 validate
  • 阶段 1 T7/T8 → 调整 mcp.json 的 apis[] / utils/util.js 的 storage 逻辑 / index.js 的 registerAPI,重跑 validate
  • 阶段 4 A/B 类 → 修 apis/<name>.js 入参拼装或 utils/util.js 的 ensureStorageInit,重跑 execute
  • 阶段 4 C 类 → 对照主包源码修 apis/<name>.js / utils/util.js,必要时调整 mcp.json 的 outputSchema 与组件取值路径,重跑 execute
  • 阶段 4 D 类 → 修 components/<name>/ 的 wxml/wxss/js,重跑 render
  • T9 → 终止,告知用户功能不支持或建议更换实现路径

关键约束(再次强调)

  • 验收目标不可降级:所有原子接口与带 componentPath 的原子组件都必须通过;挂起仅限"连续 5 轮仍未通过"硬上限
  • render 必须读取 consoleMessages.snapshotCard 做判断,不能只看 execute-result;具备图像读取能力时再辅助读截图
  • "未裁剪 + 样式还原"是硬判据:未裁剪以 consoleMessages.snapshotCard 中存在 [ai-mode] ... overflow monitor=on 基线日志且不出现 overflowed=true 为准(缺 monitor=on = 未接入监听,按不通过处理);样式还原度在具备图像读取能力时再读 snapshot.png 作为辅助信号,否则以 elementTree 字段完整性兜底
  • 修复必须跨主包 + 分包联动,真相只在主包里
  • 根据 mcp.json 的 apis[] 依赖关系安排 execute 顺序;存在上游依赖时,上游 execute 必须先于下游。render 无此顺序约束
  • 不要新增依赖、不要重写整个文件
Skills guardadas