关于 debug-app
debug-app 是面向移动应用开发的调试技能,通过项目本地 Metro 日志监控运行和打包错误,并可修复相关文件。它支持症状排查、启动诊断及只监视模式。
主要功能
- 01监控项目本地 Metro 日志
- 02检测运行时与打包错误
- 03自主编辑受影响文件
- 04按症状追踪数据路径
- 05启动故障诊断与状态查询
- 06支持只监视模式
适用场景
- 01排查应用启动或设备打开失败
- 02诊断界面空白或列表未显示
- 03监控 Metro 新增错误
- 04检查错误但不修改项目
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ microsoft/ power-platform-skills --skill debug-app安装后在 Claude Code 中输入/debug-app确认已加载。
附带文件references/startup-diagnostics.md
详细介绍
插件检查:运行
node "${PLUGIN_ROOT}/scripts/check-version.js"——如果输出消息,请先将其展示给用户,再继续。
📋 共享说明:shared-instructions.md——请先阅读。
启动诊断使用下方针对性的兼容性检查,而不是共享的全面版本检查;缺少依赖不得妨碍诊断。 此检查不会检查更新。明确批准启动修复/重启后,可覆盖共享的自动继续/默认回答指引。
调试应用 — 监控与修复
通过 metro.config.js 写入的项目本地 .powernative/metro-logs/ 文件监控正在运行的应用,检测运行时和打包错误,并通过自主编辑受影响文件来修复(或者在修复涉及 Dataverse 架构或身份验证注册等领域时,转交给相应技能)。对于静默故障,在数据路径边界注入临时 console.log 语句,只读取新追加的日志字节,并在修复根本原因后清除跟踪记录。此技能以上游的 app-debugger.agent.md 模式为蓝本——前台循环、有界轮询、可配置的无错误检查/超时退出,以及可选的仅监视运行模式。
当设置/启动或设备打开失败时,首先执行
startup-diagnostics.md。此有界阶段
可以在没有 Metro 日志或未加载应用的情况下运行,并且只有在其前置条件确实满足时才会返回运行时路径。仅运行 npm run dev 不会激活 Agent,此技能也不会安装后台监视器。
**开发客户端限制:**独立开发客户端会通过 Metro 发送应用/运行时日志、React 错误、主机诊断信息和 Metro 打包器输出。模板的
metro.config.js会将 Metro 终端输出和 HTTP 打包失败信息写入.powernative/metro-logs/;没有单独的设备日志来源。主机诊断信息包括如下字符串:[AuthProvider] MSAL init failed:、[bridge] fetch THREW for、[bridge] HTTP <status> for、[addAadAppToConnectionAcl] failed HTTP <status> for connection、[useConnectionRefs] could not verify connection ACLs; treating existing connections as setup-required和[PAHost][ErrorBoundary] Unhandled JS error:。
子命令(从 $ARGUMENTS 解析)
| 形式 | 行为 |
|---|---|
/debug-app(无参数) | 默认 — 由项目日志驱动的模式。 运行 Phase 0,发现有效的项目本地 Metro 会话;如果有一个会话正在运行,则自动选择;如果有多个会话正在运行,则询问用户,然后监控选定的日志。无需主机终端 ID。 |
/debug-app "<symptom text>" | 症状驱动模式(遇到用户可见的问题时推荐使用)。自由文本症状,例如 "todos not appearing on home screen"、"login button does nothing"、"list empty after refresh"。运行 Phase 0 → Phase 0.5(解析症状 → 请用户重现问题或导航 → 根据终端跟踪信息梳理可能的数据路径)→ 进入监控循环。可捕获纯日志轮询会遗漏的静默故障(空列表、空白屏幕、被吞掉的错误)。 |
/debug-app startup ["<symptom text>"] [--tunnel] [--tunnel-tenant <tenant-guid>] | 仅诊断启动问题。 检查安装、启动和 QR 码故障,提供范围受限且经批准的修复,并验证原始症状。对于获批的重试,保留明确选定的身份验证隧道选项。返回时不进入运行时监控;创建期间的故障移交也会使用此命令。 |
/debug-app status | 发现所有项目本地 Metro 日志,并打印每个有效会话的项目、平台、PID、端口、启动时间和日志路径。如果存在已保存的游标所引用的会话,则将其标记出来,然后打印修复项和未解决的错误。不要要求用户选择,也不要进入循环。 |
/debug-app stop | 仅停止前台调试循环,并保留 .powernative/debug-app/ 状态。不会停止 Metro;npm run dev 进程由用户负责。 |
/debug-app version | 从 ${PLUGIN_ROOT}/.plugin/plugin.json 打印已安装的 mobile-app 插件名称和版本,然后退出。 |
监控选项
选项可以跟在默认命令、症状或 status 后面:
| 选项 | 含义 | 默认值 |
|---|---|---|
--working-dir <path> | 包含 package.json 的应用根目录。启动诊断允许缺少 Metro 配置或依赖项;运行时监控则需要已配置且正在运行的 Metro 会话。相对路径相对于当前 Shell 目录解析。 | 当前 Shell 目录 |
--port <1-65535> | 仅监控此端口上的有效 Metro 会话。 | 任意端口 |
--platform <ios|android> | 仅考虑近期日志中标识为此平台的会话。 | 任意平台 |
--cycles <1-50> | 连续经过此次数量的无异常观察间隔后退出。 | 3 |
--timeout <duration> | 监控的最长实际耗时。使用 s、m 或 h,接受 30s–60m。 | 5m |
--no-fix | 仅检查/监视:绝不编辑项目源代码或配置、注入跟踪信息、安装、重新生成、启动/停止/重启 Metro、打开设备应用或调用会产生更改的移交。调试游标/审计/健康状态仍可能更新。 | 启用修复 |
--tunnel | 仅限启动:获批的重试调用 npm run dev 时保留 --tunnel。绝不自动将 LAN 启动切换为隧道。 | 禁用 |
--tunnel-tenant <tenant-guid> | 仅限启动:保留用于对开发隧道进行身份验证的 Entra 租户。需要 --tunnel。 | 当前 Azure CLI 租户 |
示例:
/debug-app --port 8082 --platform ios
/debug-app "orders screen is empty" --cycles 10 --timeout 15m
/debug-app --no-fix --timeout 30m
/debug-app status --platform android
/debug-app status --working-dir ../my-mobile-app
/debug-app startup "npm run dev exits before the QR appears"
/debug-app startup "tunnel launch fails" --tunnel --tunnel-tenant 11111111-1111-1111-1111-111111111111
/debug-app "QR won't open the app" --no-fix
参数解析:
- 将引号中的文本解析为一个症状值,并在任何位置解析可识别的标志。
- 第一个保留词元(
startup、status、stop、help、--help、-h、version、--version)用于选择子命令。stop、帮助和版本子命令不接受监控选项。status仅接受--working-dir、--port和--platform;拒绝症状、--cycles、--timeout、--no-fix和隧道选项,因为它不会进入循环。startup接受一个症状和除--cycles外的所有选项,因为它不会进入运行时监控。在所有其他子命令中拒绝--tunnel和--tunnel-tenant。 - 移除可识别选项及其值后,任何剩余的非保留文本都作为症状,并启用症状模式。
- 拒绝未知标志、重复标志、缺失值、无效数字、不支持的平台,或多个自由文本症状。打印有效格式并退出,不启动监控。
- 将
platform规范化为小写。将timeout转换为timeoutSeconds;要求30 <= timeoutSeconds <= 3600。将--tunnel-tenant验证为 GUID,并要求同时指定--tunnel;绝不从症状文本或不可信的诊断上下文中推导任一选项。根据显式指定的--working-dir解析workingDir,否则继承调用方的绝对根目录;仅在没有上述两项的直接调用中使用当前 shell 目录。拒绝冲突的显式根目录和继承根目录,不搜索父目录。要求该根目录下存在package.json;缺少metro.config.js、配置或已安装的依赖项时,应转到启动检查,而非运行时监控。无法读取或格式错误的清单属于受阻诊断,不允许初始化新应用。每次 shell 调用都必须显式进入选定的绝对workingDir;以下所有相对项目路径和命令都从该目录运行。 - 初始化:
workingDir=<absolute app root> portFilter=<number|none> platformFilter=<ios|android|none> targetCleanCycles=<number, default 3> timeoutSeconds=<number, default 300> noFix=<true|false, default false> devArgs=<[]|["--tunnel"]|["--tunnel","--tunnel-tenant","<tenant-guid>"]> monitorStartedAt=<current ISO timestamp>
对于 help / --help / -h,打印子命令表和监控选项表,然后退出。
仅在进入启动诊断时设置 startupStartedAt。其超时和尝试次数预算包含调用方提供的先前尝试。已验证的运行中应用继续进入监控后,为这个独立的有界循环重置 monitorStartedAt。
提前返回的子命令:
stop:如果在此循环拥有对话期间收到该命令,使用 Phase 0.5.5 清理所有注入的跟踪信息,并在下一个安全边界退出。对于独立的stop调用,报告当前没有活动循环。绝不停止 Metro 或删除.powernative/debug-app/状态。version/--version:读取${PLUGIN_ROOT}/.plugin/plugin.json,打印<name> <version>,并退出,不解析项目或写入状态。如果清单缺失或格式错误,则报告插件版本不可用并退出。status:验证workingDir,然后仅执行 Phase 0.0 发现流程中的下方状态分支。不要运行项目预检、创建状态文件、询问会话、初始化基线或进入监控循环。
如果在启动诊断期间收到 stop,则取消即将执行的诊断/修复步骤,并准确报告任何正在进行且已获批准的命令。不要自动终止安装进程或 Metro 进程,也不要声称部分恢复的安装已完成。
提示——“先试用,再调试”: Metro 会将近期应用输出持续保存到 .powernative/metro-logs/,即使聊天/编辑器重启也不例外。如果刚刚发生了异常情况,继续正常使用应用,然后运行 /debug-app 或 /debug-app "<what you saw>"。第一个周期读取最新的持久化日志窗口;后续周期仅读取保存的游标之后追加的字节。
核心原则
- 前台自主循环 — 启动后,此技能将独占对话,直到连续
targetCleanCycles次轮询确认应用运行正常、timeoutSeconds时间耗尽、用户输入stop,或触发升级规则。请勿并发运行其他技能 — 它们会排在此循环之后。 - 仅监视意味着不修改项目 — 使用
--no-fix时,请勿编辑项目源代码/配置、注入诊断日志、安装软件包、重新生成架构、更换帐户或调用会修改项目的技能。继续推进.powernative/debug-app/中的游标/审计/健康状态,分类错误,并提供原本会采用的修复方案/移交信息。 - 运行时监视需要应用处于运行状态;启动诊断则不需要。 首先通过范围受限的启动参考对安装/启动/打开失败进行分类。未经验证确认存在实时原生会话,不要进入运行时监视或声称运行时健康。
- 运行时目标仅限原生应用 — 应用必须在设备或模拟器上的原生开发客户端中加载;
.powernative/metro-logs/是该原生会话的权威日志来源。 - 禁止使用 Web 或直接探测 Metro — 不要使用 React Native Web、浏览器自动化、
curl、fetch、WebFetch,也不要向 Metro/localhost 端点直接发送请求来诊断运行时问题。只能读取.powernative日志和源文件。 - 禁止逐屏验证 — 不要遍历路由或验证每个屏幕。在症状模式下,只关注用户报告的工作流,以及诊断所需的终端/源代码证据。
- 一次只修复一个问题 — 完全解决一个问题(上下文 → 修复 → 类型检查 → 重新加载 → 再次轮询)后,再开始处理下一个问题。不得批量处理。
- 工作目录状态 — 调试审计状态位于
.powernative/debug-app/:fixes.md、unresolved.md、injected-logs.md、health.json和metro-cursor.json。Metro 日志位于已被忽略的.powernative/metro-logs/中。两者在聊天/编辑器重启后都会保留,且不会绑定到特定 Agent 宿主。 - 纵深防御式脱敏 — Metro 记录器会在写入
.powernative前移除类似凭据的行,但/debug-app必须独立处理,在将任何值保存到.powernative/debug-app/前尽量减少并清理数据。切勿将原始响应正文、记录对象、令牌、标头、跟踪负载或绝对主目录路径复制到调试器状态中。 - 端口标识日志 — 开发服务器端口是 QR 码编码、设备拨号以及此技能验证的号码。存活状态通过套接字探测判断(日志中的 PID 是否仍占用该端口?),绝不能根据终端滚屏内容判断。
port-taken状态表示日志属于已结束的会话,不得据此诊断。 - 绝不修复历史记录 — 日志记录的是过去,因此其中的错误并不能证明当前存在问题。基线窗口中发现的错误,必须先通过 Phase 0.2.1 的替代性检查,之后才能编辑代码。为追查已解决的错误而修改正常工作的代码,后果比不报告更糟。
- 第一方原生软件包是不可修改的边界 —
node_modules/@microsoft/power-apps-native-*下的栈帧是证据,但不能证明问题由软件包导致。首先排除安装损坏、Node/原生兼容性问题、无效应用输入、不受支持的配置,以及误用软件包文档所述 API 等情况。一旦证据确认已安装的@microsoft/power-apps-native-*软件包内部存在缺陷,就不要采用客户项目层面的变通方法:不要编辑node_modules/、创建patch-package补丁或 postinstall 重写、内置或分叉软件包源代码、通过 Metro/Babel/TypeScript 别名重定向软件包,或将其依赖项替换为 git、tarball 或本地分叉版本。记录经过脱敏的软件包缺陷报告,并引导用户前往/report-issue。禁止修改 Host/MSAL 源代码;经批准、使用相同锁定配置恢复安装不属于源代码补丁或升级。 - 宿主终端 API 仅为可选项 — 切勿索要或保存终端 ID。运行时监视期间,宿主输出仅供参考,诊断仍须推进权威
.powernative游标。在日志尚不存在时,范围受限的启动阶段可以使用当前失败命令的脱敏终端输出;不得伪造游标,也不得将该输出视为实时运行时会话。 - 优先结合上下文诊断 — 如果存在
memory-bank.md,请读取它。读取power.config.json以了解环境、Dataverse 和连接器上下文。只有在故障涉及计划中的屏幕、数据模型、连接器、脱机配置文件或原生功能时,才参考native-app-plan.md;与之无关的语法/运行时错误无需解析该文件。 - 参考资料查找顺序 — 对于 Dataverse/Power Platform 错误:首先读取 skills/add-dataverse/references/dataverse-reference.md,检查生成的服务/模型和项目上下文,然后在行为仍不明确时查询
mcp__plugin_mobile-app_microsoft-learn__microsoft_docs_search。对于 Expo/Expo Router/React Native 错误:首先检查已安装的版本和项目代码,然后使用WebFetch定向查阅官方https://docs.expo.dev/文档。接下来查阅软件包文档,只有在最后手段时才使用通用网页搜索。
工作流 — 先列任务清单
进入监控循环前,先写好任务清单并及时更新:
- [ ] Discover valid `.powernative` Metro sessions and select one when needed
- [ ] If startup/install/QR failure or no live session: inspect and classify; repair only with approval; verify original symptom or return pending/blocked
- [ ] Capture baseline log state from the selected session and save its byte cursor
- [ ] (Symptom mode only) Phase 0.5: parse symptom → ask user to navigate → inject console.logs → read new log bytes → walk data path → clean up logs
- [ ] Monitoring cycle 1: collect → classify → fix if needed
- [ ] Monitoring cycle 2: collect → classify → fix if needed
- [ ] Monitoring cycle 3: collect → classify → fix if needed
(add cycles as needed; after <targetCleanCycles> consecutive clean cycles, use the resolved/flagged/pending symptom outcome)
- [ ] Fix: <error summary> → <inline edit | skill route> (one task per error found)
每轮完成后(无论问题已解决还是已修复),都要标记为完成,再开始下一轮。
阶段 0 — 启动检查
进入循环前:
0.setup 启动/设置诊断分派
在提前返回的子命令处理完毕后,遇到以下情况时运行 startup-diagnostics.md:
- 子命令为
startup; - 提供的症状涉及安装、
npm run dev、Node/软件包兼容性、加载前启动,或 QR/设备打开;或 - 阶段 0.0 未找到有效的运行中会话(并非仅因端口/平台筛选不匹配,且另有会话正在运行),或阶段 0.2 未找到已连接的客户端。
对于运行正常但出现运行时症状的应用,跳过设置流程并使用现有运行时路径。对于 startup 或创建交接,返回结构化启动结果,不进入监控流程。否则,已验证运行中的应用会重新进入阶段 0.preflight、会话发现和状态初始化(0.1),然后再进入阶段 0.2;若结果为待处理/受阻/已取消,则直接返回,不给出运行正常的结果。在调用上下文中记录 startupOutcome,避免仅因原始症状提及启动而再次进入设置流程。如果只验证了安装或 Metro 启动,但没有原生应用连接,则报告运行时验证待处理并返回,而不是反复分派设置流程。不得无限次重新分派:重复的设置失败会消耗同一尝试预算。
0.preflight 读取项目上下文
诊断日志前,遵循共享说明:
在整个工作流中,将参数解析所得的 workingDir 用作 <working_dir>。该路径已验证为应用根目录;不要从后续阶段重新推导,也不要暗中切换项目。
- 如果
<working_dir>/memory-bank.md存在,读取其中的 Project facts、Power Platform context、Data model、Connectors、Screens、Native capabilities 和 Build history。不要通过/debug-app创建 memory bank;文件不存在是有效情况。 - 如果
<working_dir>/power.config.json存在,则读取该文件。记录environmentId、databaseReferences和connectionReferences。power.config.json是首选环境来源。 - 仅当提供的症状或分类后的错误涉及计划中的屏幕、Dataverse 实体/列、连接器、离线行为、导航约定或原生能力时,才读取
<working_dir>/native-app-plan.md。将该计划视为预期设计,而非实时架构/运行时状态的证明。 - 仅当受影响的数据路径使用以下内容时,才检查
src/generated/services/、src/generated/models/和src/generated/connectorSchemas.ts。绝不编辑生成的文件。
阶段 0.1 创建 fixes.md 后,追加一行简明的上下文信息:
[<HH:MM:SS>] Context — memory-bank=<present|absent> native-plan=<present|absent|not-needed> environment=<environmentId|none> dataverse-tables=<N> connectors=<N>
对于普通的 bundle、React 或本地 JavaScript 故障,不要解析环境或调用 Dataverse。当前症状/错误与 Dataverse 或 Power Platform 相关时,在交接前执行 D2 中的只读 Dataverse 诊断流程。
0.0 发现并选择项目本地的 Metro 会话
使用参数解析所得且已验证的 <working_dir>。枚举所有匹配的日志,并按时间从新到旧排序:
LOG_DIR='<working_dir>/.powernative/metro-logs'
ls -t "$LOG_DIR"/metro-*-pid-*-port-*.log 2>/dev/null
对于每个候选项,解析:
- 从
metro-<timestamp>-pid-<pid>-port-<port>.log解析pid和port; - 从文件名中的时间戳解析
startedAt; - 从
power.config.json.appDisplayName获取project;若不存在,则使用<working_dir>的基本名称; - 仅扫描最新的 64 KiB,查找最新的
iOS ... Bundled/iOS Bundling或Android ... Bundled/Android Bundling行,以确定platform;若两者均未出现,则使用unknown。 使用跨平台 Node 探测检查每个已记录的 PID:process.kill(pid, 0)表示进程存活;将EPERM视为存活,其他错误则视为进程已退出。若port为数字,在 macOS/Linux 上使用lsof -nP -iTCP:<port> -sTCP:LISTEN -t(如果可用),或在 Git Bash/Windows 上使用netstat.exe -ano -p tcp,以验证端口归属。在没有lsof的 Linux 上,如果有ss -ltnp,则使用它。只有当候选项的 PID 存活,且该进程拥有已记录的端口、port=unknown,或不存在受支持的端口归属探测方式时,该候选项才是有效/存活的。探测已运行但报告了不同的 PID,属于矛盾,而非探测方式不可用。不要持久化终端 ID,也不要依赖终端输出。
选择规则:
- 在选择前,对有效/存活的候选项应用
portFilter和platformFilter。如果没有有效会话符合条件,则打印筛选条件和可用的有效会话,然后停止。如果经过平台筛选的候选项为unknown,不要猜测;告知用户使用--port或省略--platform。 - 一个有效会话 — 自动选择。
- 多个有效会话 — 按以下精确格式为每个会话显示一个选项,然后询问要监控哪个会话:
使用<project> — <platform> — port <port> — pid <pid> — started <startedAt>AskUserQuestion;在内部使用完整的logPath+pid+port来标识选项,不能仅凭端口标识。如果metro-cursor.json指向一个有效会话,则将该会话设为默认选项,但仍须提问。存活会话不止一个时,绝不要静默选择最新会话。 - 没有有效会话 — 按以下失败分支处理。过期或矛盾的日志可作为报告依据,但绝不能作为可选项。
status 分支 — 在选择前返回: 筛选并检查存活状态后,仅当 .powernative/debug-app/metro-cursor.json 是非空的有效 JSON,且包含字符串 logPath、数字 pid 和数字或 unknown 类型的 port 时,才读取它。将缺失、为空、格式错误或形状无效的游标视为不存在,并打印警告;绝不要在 status 中覆盖它。按从新到旧的顺序打印每个有效会话,使用常规会话显示格式;只有当 logPath + pid + port 与有效游标匹配时,才标记 [saved]。然后打印 fixes.md 和 unresolved.md 中至多最后 20 行非空内容,并先对要显示的文本执行持久化脱敏。打印过期/矛盾候选项的数量,不要诊断其日志内容,然后退出。如果没有有效会话,打印该结果以及适用的无日志/过期原因,然后退出。
仅在监控模式下,根据上述规则选择会话。选择后,将所选候选项中的值赋给 LOG_PATH、pid、port、project、platform 和 startedAt。此选择在当前监控运行期间保持不变。
如果不存在日志,则先检查 metro.config.js,并从 <working_dir> 解析日志模块,然后再选择失败分支:
- 当前工厂形式: 配置使用来自
@microsoft/power-apps-native-host/config/metroConfig的createPowerAppsMetroConfig,且该确切子路径可从项目已安装的依赖中解析。工厂会在内部安装项目本地日志功能。 - 旧版直接形式: 配置直接导入
@microsoft/power-apps-native-host/metro-logger,且该确切子路径可从项目已安装的依赖中解析。 - 不可用: 两个配置的子路径都无法解析、引用的形式不存在,或
package.json中缺少@microsoft/power-apps-native-host。仅凭依赖版本字符串,无法证明配置的导出项存在。
按以下分支处理:
| 状态 | 含义 | 操作 |
|---|---|---|
| 恰好存在一个有效日志,PID 仍存活,且端口未知或端口探测不可用 | 选择该会话。记录 project、platform、startedAt、port、pid 和 logPath,然后继续。 | |
| 恰好存在一个有效日志,且 PID 仍占用日志中记录的端口 | 选择该会话。记录 project、platform、startedAt、port、pid 和 logPath,然后继续。 | |
| 存在多个有效日志 | 显示所有有效会话,并询问要监控哪一个。不要仅按时间新旧来选择。 | |
| 日志存在,但 PID 已不存在,且另一个进程占用了日志中记录的端口 | 不要根据此日志进行诊断。告知用户占用该端口的 PID,并请其重启 npm run dev。 | |
| 日志存在,但 PID/端口信息相互矛盾 | 设备可能正在连接错误的服务。请用户停止过时的 Metro 进程,然后重新运行 npm run dev。 | |
| 不存在日志,且日志记录表单不可用 | 此项目早于项目本地 Metro 日志功能,或依赖项不完整。进入启动诊断,以区分安装损坏、不支持的导出和配置不匹配;不要修改原生/Metro 配置,也不要反复重启。 | |
| 不存在日志,且当前工厂或旧版直接日志记录形式可用 | 如果可用的主机输出包含 [powernative] Metro logging instrumentation failed,保留这段有界证据。进入启动诊断,不要直接认定是软件包缺陷,也不要要求应用先加载。 |
PID/端口检查可避免基于过时日志进行诊断:日志文件可能在 Metro 进程结束后仍然存在,因此只有套接字探测才能确认该日志是否仍属于正在测试的应用。明确选择会话可避免把同一项目中某个端口上的有效会话与另一个有效会话混淆。
如果不存在 .powernative 日志,但主机提供了正在运行的 Metro 终端,则只能将其有界输出用作启动证据。不要请求或存储其终端 ID,也不要针对它启动持续监控。重试操作(如已获准)由启动阶段负责;运行时监控仍要求存在有效的项目本地日志。
在 fixes.md 中记录稳定来源:
[<HH:MM:SS>] Log source — <project> <platform> — <logPath> (pid <pid>, port <port>, started <startedAt>)
0.1 确保状态目录存在
mkdir -p .powernative/debug-app
touch .powernative/debug-app/fixes.md
touch .powernative/debug-app/unresolved.md
touch .powernative/debug-app/injected-logs.md
touch .powernative/debug-app/health.json
touch .powernative/debug-app/metro-cursor.json
rm -f .powernative/debug-app/symptom-state # per-session — Phase 0.5 rewrites it if symptom mode is active
如果 fixes.md 为空,则写入会话标题:
# Debug session — <date>
追加生效的调用设置:
[<HH:MM:SS>] Monitor config — port=<any|port> platform=<any|ios|android> clean-cycles=<targetCleanCycles> timeout=<timeoutSeconds>s no-fix=<true|false>
0.1.1 初始化健康状态
写入 .powernative/debug-app/health.json,不要包含原始日志内容:
{
"bundle": { "status": "unknown", "summary": "No evidence yet" },
"runtime": { "status": "unknown", "summary": "No evidence yet" },
"authentication": { "status": "unknown", "summary": "No evidence yet" },
"dataverse": { "status": "unknown", "summary": "No evidence yet" },
"connector": { "status": "unknown", "summary": "No evidence yet" },
"offline": { "status": "unknown", "summary": "No evidence yet" },
"navigation": { "status": "unknown", "summary": "No evidence yet" },
"nativeCapability": { "status": "unknown", "summary": "No evidence yet" },
"updatedAt": "<ISO timestamp>"
}
允许的状态为 healthy、degraded、failed、unknown 和 not-configured。每次对信号进行分类后,只更新受影响的域:
| 信号 | 健康状态更新 |
|---|---|
| 最新的构建错误之后,原生构建成功 | bundle=healthy |
| 构建/转换/导入失败 | bundle=failed |
| 未捕获的 JS/React 错误 | runtime=failed;修复后的工作流产生干净的新输出后,设为 healthy |
| 主机诊断中的身份验证成功/失败 | `authentication=healthy |
| 受影响操作的 Dataverse 2xx/错误 | `dataverse=healthy |
| 连接器解析/调用成功或失败 | `connector=healthy |
| 没有离线配置文件 | offline=not-configured;离线激活/同步警告或失败 → `degraded |
| 已确认的路由/导航错误 | navigation=failed;确认路由修复后 → healthy |
| 原生包装器成功、权限拒绝、模块缺失或原生功能失败 | `nativeCapability=healthy |
证据缺失时,状态保持 unknown;绝不要将未观察到的域改为 healthy。
0.1.2 持久化脱敏闸门
将诊断文本追加到 fixes.md 或 unresolved.md,或存储健康状态 summary 之前,请先对其进行清理:
- 将项目路径转换为相对于
<working_dir>的路径。将其他绝对主目录路径替换为[REDACTED_PATH]。 - 将完整的授权、Cookie 或密钥标头(
Authorization、Proxy-Authorization、Cookie、Set-Cookie、x-api-key、api-key、client-secret和令牌标头)替换为[REDACTED_HEADER]。 - 将 Bearer 值、类似 JWT 的三段式值、SAS/查询参数密钥(
sig、se、sp、sv、code、token、access_token、refresh_token、id_token、client_secret)、密码和 API 密钥替换为[REDACTED_SECRET]。 - 将电子邮件地址替换为
[REDACTED_EMAIL],将 GUID/记录标识符替换为[REDACTED_ID],但不包括非敏感的本地 PID/端口。 - 切勿持久化 Dataverse/连接器响应正文或任意
[TRACE]对象值。仅持久化状态、源/配置表或列名、计数、错误代码以及长度受限的错误消息。 - 再次扫描清理后的结果。如果仍包含任何敏感模式,则改为持久化
[REDACTION_BLOCKED: diagnostic omitted]。
写入前,请对每条候选诊断运行随附的验证器:
printf '%s' "$DIAGNOSTIC_SUMMARY" | node \
"${PLUGIN_ROOT}/scripts/redact-debug-diagnostic.js" \
--working-dir '<working_dir>'
仅持久化验证器的标准输出。请先根据最少字段构建 DIAGNOSTIC_SUMMARY;不要传入完整的 Metro 窗口或响应正文,再依赖脱敏来确保安全。
将 logPath 以相对于 <working_dir> 的路径存储在 metro-cursor.json 中,并在读取时根据 <working_dir> 解析该路径。即使 .powernative 日志已完成脱敏,也必须执行此闸门。
0.2 验证 Metro 已打包且应用正在运行
遥测检查点:validate_metro_session
读取一个有界基线窗口,并返回一个真实的字节游标:
node - "$LOG_PATH" 262144 <<'NODE'
const fs = require('node:fs');
const [file, maxText] = process.argv.slice(2);
const maxBytes = Number(maxText);
const size = fs.statSync(file).size;
const start = Math.max(0, size - maxBytes);
const fd = fs.openSync(file, 'r');
const buffer = Buffer.alloc(size - start);
const bytesRead = fs.readSync(fd, buffer, 0, buffer.length, start);
fs.closeSync(fd);
process.stdout.write(JSON.stringify({
cursor: start,
nextCursor: start + bytesRead,
truncated: start > 0,
output: buffer.subarray(0, bytesRead).toString('utf8')
}, null, 2));
NODE
日志记录器最初会写入 port-unknown,并且可能会将该文件原子地重命名为带数字端口的文件。如果基线读取返回 ENOENT,请重新发现一次,然后再报告失败。当现在存在具有相同文件名时间戳和 PID 的数字端口文件时,将 LOG_PATH 和 port 更新为该文件,并重试一次有界读取;不要将两个名称作为独立会话处理。
解析 output、cursor、nextCursor 和 truncated;使用阶段 0.0 中解析出的 pid、port 和 logPath。扫描 output:
- 最近一条错误类别的行是
SyntaxError、Unable to resolve module、transform failed或error: Bundling failed→ 打包已损坏。将其视为步骤 B 中“导入 / 打包”的严重错误,并立即进入步骤 D。在打包恢复正常前,不要进入稳态循环。 - 输出包含
Bundling complete/iOS Bundled/Android Bundled,且之后没有错误类别的行 → Metro 状态正常。继续。 - 输出包含 Metro 横幅(
Metro waiting on、Logs for your project或› Metro:),但尚无原生Bundled/bundling行 → Metro 已启动,但尚无原生客户端连接。告知用户:Metro 正在运行,但尚无应用连接。 如果用户报告二维码 / 打开应用的问题,则进入启动诊断;否则请用户打开原生客户端。如果用户目前无法确认,返回
verification-pending;不要仅仅因为客户端尚未连接就重新安装。 - 输出仅包含日志记录器的启动行,而所选 PID/端口会话仍处于活动状态 → Metro 尚未输出足够的状态信息。告知用户等待原生 URL 出现,然后重新运行
/debug-app;不要猜测是否已就绪。
如果 truncated: true,则基线仅涵盖最新的 256 KiB。记录较早的历史记录已被省略;不要声称已检查完整会话历史。
初始化游标前,将此初始窗口中所有可分类的条目都交由步骤 B 处理,包括 JS 运行时错误、React 警告、网络/API 故障、原生错误和主机诊断。不要仅仅因为先前的错误不是打包错误就跳过它。这能保证用户可以在症状已经出现后“先体验,再调试”。
0.2.1 错误已被后续状态覆盖——日志已显示错误解决时,绝不重复修复
基线窗口反映的是历史记录,而非当前状态。其中可能包含已修复、用户自行解决或暂时出现的错误。修复这些错误比什么都不做更糟:这会为了追逐已不存在的症状而修改正常运行的代码,而且由于错误无法复现,这种“修复”也无法验证。
因此,仅在基线窗口中(而非后续轮询中)发现的每个错误,在检查是否已被后续状态覆盖之前,都视为未确认。从错误所在行向后扫描至窗口末尾:
| 错误类别 | 后续行显示以下内容时,视为已被覆盖 |
|---|---|
| 导入 / 打包 | Bundling complete、iOS Bundled 或 Android Bundled |
| 网络 / API | 曾经失败的同一路由/资源返回了 2xx 状态 |
| JS 运行时 / React | 应用随后重新加载或成功打包,且此后错误未再次出现 |
主机诊断([AuthProvider]、[bridge] 等) | 同一子系统随后成功(例如,在 acquireTokenSilent failed 之后获取到令牌) |
然后:
- 已被覆盖 → 不要修复。记录后继续:
[<HH:MM:SS>] Observed (already resolved, not fixed) — <error summary> — superseded by <evidence line> - 未被覆盖 → 错误仍然存在。按正常流程通过步骤 D 修复。
- 有歧义(没有明确证据表明已被覆盖或仍然存在)→ 暂时不要修改代码。询问用户是否仍然遇到此问题,或等待它在下一次轮询中再次出现。游标前移后再次出现的错误,即可确认为仍然存在。
这仅适用于基线窗口。之后的 tail --cursor 轮询中出现的任何内容都属于新输出,无需检查是否已被覆盖。
症状模式优先适用。 如果用户提供了症状,这就是问题仍然存在的一手证据,因此,即使后续行看起来表明错误已被覆盖,相符的基线错误仍视为当前错误。
0.3 捕获基线
遥测检查点:capture_runtime_baseline
使用阶段 0.2 的输出。记下最近打包的原生平台(iOS / Android)以及近期的运行时日志行。追加到 fixes.md:
[<HH:MM:SS>] Baseline — last Metro activity: <1-line summary of most recent lines>
0.4 初始化持久化字节游标
使用结构化 JSON 写入 .powernative/debug-app/metro-cursor.json:
{
"logPath": ".powernative/metro-logs/metro-<timestamp>-pid-12345-port-8081.log",
"pid": 12345,
"port": 8081,
"project": "My Mobile App",
"platform": "ios",
"startedAt": "2026-08-27T12:41:32.050Z",
"monitorStartedAt": "<ISO timestamp>",
"monitorConfig": {
"port": null,
"platform": null,
"targetCleanCycles": 3,
"timeoutSeconds": 300,
"noFix": false
},
"cursor": 12345,
"updatedAt": "<ISO timestamp>"
}
设置相对于项目的 logPath,以及阶段 0.0 中的 pid、port、project、platform 和 startedAt。根据当前调用设置 monitorStartedAt 和 monitorConfig,并将 cursor 设置为阶段 0.2 中的 nextCursor。访问文件前,先根据 <working_dir> 解析 logPath。每次新调用都必须替换之前的监控配置和开始时间。在后续调用中:
-
仅当现有游标是非空的有效 JSON,且字段类型符合预期时才解析。将缺失、为空、格式错误或结构不符合要求的文件视为没有已保存的游标;向
fixes.md追加经过清理的重置警告,并根据所选会话的有限基线进行初始化。绝不能仅因之前的调试状态不完整而导致启动失败。 -
logPath、pid和port相同,且该进程仍占用该端口 → 重用已保存的游标,避免再次处理旧错误。 -
存在多个有效会话 → 询问要监控哪个会话。如果已保存的会话仍然有效,则默认选择该会话。
-
明确选择了不同会话,或已保存的会话不再有效 → 丢弃旧游标,并根据所选会话的最新日志窗口进行初始化。
-
步骤 A 返回
rotationLost: true→ 接受返回的重置游标,并在fixes.md中记录文件缩小或替换情况。
阶段 0.5 — 根据症状设置(仅当 $ARGUMENTS 是症状字符串时)
如果未提供症状,则跳过整个阶段。仅靠标准的日志轮询循环可以很好地捕获可见错误,却无法发现静默错误:例如,因为未添加连接器而导致列表为空,因为未接入 useFocusEffect 而导致屏幕空白,或因为列名与模型不匹配而导致行内容为空。阶段 0.5 用于弥补这一缺口。
**--no-fix 行为:**解析症状并让用户复现,但不要注入 [INJECTED-TRACE] 日志或编辑任何文件。只读取新增的 Metro 输出。如果症状是静默的,且没有产生可分类的输出,则将 pending 写入 symptom-state,在 unresolved.md 中追加 watch-only: traces not injected,然后进入标准循环。
0.5.1 解析症状
从用户的文本中提取三个信号:
| 信号 | 提取方法 |
|---|---|
| 受影响的屏幕 | 根据 app/ 中的路由文件名匹配关键词(例如,"todos" → app/(tabs)/todos.tsx、app/todos/index.tsx、app/(tabs)/index.tsx)。使用 Glob 枚举 app/**/*.tsx;选择最接近的子字符串匹配项。如果有多个匹配项,则询问一次。 |
| 受影响的实体 / 服务 | 使用相同的关键词匹配 src/generated/services/*Service.ts 和 src/generated/models/*Model.ts(例如,"todos" → TodosService、Todo 模型)。使用 Glob。 |
| 症状类别 | 将文本映射到以下类别之一:empty-list、blank-screen、wrong-data、unresponsive-control、stale-data、wrong-navigation、crash、pdf-viewer、pdf-report、pen-input、geolocation、dataverse-upload。对于“无法打开 PDF / PDF 预览失败”,默认为 pdf-viewer。对于“未生成报告 PDF / 打印报告失败”,默认为 pdf-report。对于“签名 / 笔迹 / 墨迹失败”,默认为 pen-input。对于“位置未跟踪 / GPS 未更新 / 后台定位已停止 / 面包屑轨迹有缺口 / 路线不一致”,默认为 geolocation。对于“签名 / 报告已保存但缺失”或“位置行未到达 Dataverse”,默认为 dataverse-upload。对于“未出现 / 未显示 / 这里什么都没有 / 缺失”,默认为 empty-list。对于“无法加载 / 冻结 / 加载指示器一直转”,默认为 blank-screen。 |
追加到 fixes.md:
[<HH:MM:SS>] Symptom — class=<class> screen=<path> entity=<service>
如果没有匹配的屏幕 / 实体:保留 screen=unknown / entity=unknown 并继续 — 阶段 0.5 仍会注入诊断日志,并根据症状文本,从最可能的数据路径读取终端输出。
0.5.2 请用户导航到受影响的屏幕
dev-player 没有用于导航的自动化 API。请用户:
"请在设备 / 模拟器上打开
<screen>屏幕,然后回复ready。"
等待用户确认后再继续。
0.5.3 注入诊断性 console.log 语句并读取终端
在疑似数据路径的边界处注入有针对性的 console.log 语句,以便 Metro 终端显示发生了什么。
注入位置 — 选择能够覆盖症状类别的最少位置:
| 症状类别 | 注入位置 |
|---|---|
empty-list | (a) 数据获取钩子的入口,记录 [TRACE items] 和原始响应的长度;(b) 屏幕组件中,在列表渲染前记录 [TRACE render] 和 items 数组的长度 |
blank-screen | 屏幕组件的入口,记录带时间戳的 [TRACE mount],并且只记录用于描述所需 auth/data props 是否存在的布尔值或数量 |
wrong-data / stale-data | 调用生成服务的钩子(不要在 src/generated/ 中),使用允许列表中的摘要记录 [TRACE service-response]:结果数量、错误是否存在、有界的错误代码/状态,以及预期字段是否存在的布尔值 |
unresponsive-control | 事件处理程序(onPress、onSubmit 等),在任何异步操作之前记录 [TRACE handler-called] |
crash | 跳过注入 — 转到监控循环(步骤 A),崩溃堆栈会出现在终端中 |
console.log 注入模式 — 所有注入行都必须使用以下确切格式:
console.log('[TRACE <tag>]', <value>); // [INJECTED-TRACE]
<tag>— 此位置的简短唯一标签(例如,items、render、service-response)- 在同一行末尾添加
// [INJECTED-TRACE]注释 — 这是清理时 grep 使用的标记 - 只记录原始类型,或新构造的允许列表摘要,其中包含数量、布尔值、有界错误/状态代码,以及配置的表/列名称。仅允许对该构造的摘要使用
JSON.stringify。 - 绝不记录原始记录、响应正文、任意对象、字段值、用户输入文本、auth/data prop 值、标识符、URL、请求头或凭据。宿主日志记录器会移除类似凭据的行,但无法证明任意应用数据不含敏感信息。
- 绝不要在
src/generated/中注入 — 应注入调用它的钩子/屏幕组件中
在 .powernative/debug-app/injected-logs.md 中记录每次注入:
[<HH:MM:SS>] Injected [INJECTED-TRACE] at <file>:<line> — tag=<tag>
然后告知用户:
“我已添加诊断性 console.log 语句。Fast Refresh 应会自动应用这些更改。请导航到
<screen>并触发该症状(例如,滚动列表或点击按钮)。如果应用未刷新,请从原生 dev-client 菜单重新加载。完成后回复done。”
等待用户回复,然后运行步骤 A 的 tail --cursor 流程,持久化 nextCursor,并筛选返回的 output,仅保留 [TRACE 行。
0.5.4 从终端输出追踪数据路径
使用 [TRACE 行追踪链路:
-
屏幕 TSX(
app/<route>.tsx)- 查找
useListData(...)/use*Data(...)调用。 - 检查服务调用选项——多余的
top: 0、过于严格的filter、绑定到永不清空的输入框的search: query,或针对缺失列的orderBy,都可能悄无声息地返回 0 行。 - 检查数据加载后是否有客户端
.filter(...)。
- 查找
-
数据钩子(
src/hooks/useListData.ts或同级文件)- **关键:**模板钩子有两条 mock 回退路径:
- 错误路径:服务返回
{ error }→ 钩子替换为 mock,并且可能调用setError。如果屏幕忽略error,就会悄无声息。 - 空结果路径:服务返回
{ data: [] }(无错误)→ 钩子悄无声息地替换为 mock。没有[TRACE]日志时,这种情况始终不可见。
- 错误路径:服务返回
- 检测:在屏幕文件中用
Grep搜索MOCK_导入。如果存在,说明已接入 mock 数据。 - 确认使用了
useFocusEffect(而不是useEffect)——返回导航时,useEffect不会重新运行。
- **关键:**模板钩子有两条 mock 回退路径:
-
生成的服务(
src/generated/services/<Name>Service.ts)- 如果是 TODO 占位实现或文件缺失 → 转到
/add-connector或/add-dataverse。不要编辑src/generated/。 - 如果文件存在,且
[TRACE service-response]摘要显示错误 → 使用其有限的状态码/错误代码;401/403 = 身份验证问题;404 = 资源名称错误。使用单独输出且已清理的主机诊断信息来了解消息上下文;不要添加原始错误跟踪。
- 如果是 TODO 占位实现或文件缺失 → 转到
-
生成的模型(
src/generated/models/<Name>Model.ts)- 确认字段名称与屏幕引用的名称一致。
item.title与cr3e9_title不匹配会导致行内容空白。
- 确认字段名称与屏幕引用的名称一致。
-
power.config.json- 确认
datasources数组包含疑似实体/连接器。如果缺失,说明从未为其运行过pa app add data-source。
- 确认
-
身份验证状态(
src/playerConfig.ts、app.config.js、auth.config.json、useAuth()钩子)- 服务返回的 401 会被包装为
{ error }——[TRACE service-response]摘要会显示状态码,但不会持久化错误对象或消息。 - OAuth 深层链接交接:确认
app.config.js→expo.scheme与src/playerConfig.ts→connectorOAuthRedirectUri一致,并且auth.config.json和 Entra ID 注册中也配置了同一个重定向 URI。如果缺少应用注册,请通过/set-app-registration-native引导用户前往 Power Apps Wrap 页面。
- 服务返回的 401 会被包装为
对 [TRACE 输出进行分类:
| 输出 | 含义 | 后续步骤 |
|---|---|---|
[TRACE items] 0 或 [] — 没有错误字段 | 服务返回空结果——检查筛选条件/查询,或确认是否尚未填充数据 | 修正查询;如果没有记录,则填充示例数据 |
[TRACE items] undefined | 钩子从未收到响应——可能是服务占位实现或缺少数据源 | 转到 /add-connector 或 /add-dataverse |
[TRACE service-response] 显示错误状态码/代码 | 服务抛出错误——401/403 = 身份验证;404 = 资源错误 | 修正身份验证配置,或重新运行 pa app add data-source |
[TRACE render] N > 0,但列表看起来为空 | 模型与屏幕之间的字段名称不匹配 | 修正屏幕字段引用,使其与模型一致 |
从未出现 [TRACE handler-called] | onPress 未绑定或组件未挂载 | 阅读 TSX,修正事件绑定 |
完全没有 [TRACE 行 | Metro 可能缓存了旧 bundle | 请用户停止 Metro,重新运行 npm run dev -- --clear,然后重新加载原生应用 |
将结果记录到 .powernative/debug-app/symptom-state(单行:resolved、flagged 或 pending)。
- 修复方案明确且局部 → 通过步骤 D3 + D4 应用(类型检查 + 重新加载 + 再次轮询)。修复后,请用户再次与屏幕交互并查看终端。如果
[TRACE items]行显示 N > 0,则写入resolved。 - 修复需要转到其他技能 → 告知用户,并记录到
unresolved.md。写入flagged。 - 原因不明显 → 将结构化说明记录到
unresolved.md。写入pending并进入监控循环。
0.5.5 清理注入的 console.log 语句
确定根本原因并应用修复(或 Phase 0.5 结束)后,移除所有注入的日志:
find app src -type f \( -name '*.js' -o -name '*.jsx' -o -name '*.ts' -o -name '*.tsx' \) \
! -path 'src/generated/*' -exec grep -nH 'INJECTED-TRACE' {} +
对于每个匹配的文件,删除 console.log(...); // [INJECTED-TRACE] 行。使用以下命令验证:
find app src -type f \( -name '*.js' -o -name '*.jsx' -o -name '*.ts' -o -name '*.tsx' \) \
! -path 'src/generated/*' -exec grep -nH 'INJECTED-TRACE' {} + # must print zero matches
清空跟踪文件:
echo '' > .powernative/debug-app/injected-logs.md
清理后运行一次 npm run type-check。
硬性规则: 代码中绝不能残留
[INJECTED-TRACE]行。将会话标记为完成前,务必清理,即使症状状态为pending或flagged也不例外。
0.5.6 重新进入标准监控循环
Phase 0.5 完成后,进入监控循环(步骤 A)。照常统计连续正常周期,但让步骤 C 根据单行 symptom-state 选择退出方式:resolved 可以正常退出,而 flagged 和 pending 在达到 targetCleanCycles 后应使用非正常退出方式。症状状态为 flagged 或 pending 时,绝不要报告正常结果。
监控循环
重复执行,直到达到 targetCleanCycles 个连续正常周期、timeoutSeconds 到期、用户输入 stop,或触发升级规则。
每个收集周期开始前以及每次修复验证后,立即将当前时间与 monitorStartedAt 比较。当经过的时间大于或等于 timeoutSeconds 时:
- 退出前清理所有
[INJECTED-TRACE]行。 - 将经过的时间、周期数、正常周期数、活动会话身份和未解决问题数追加到
fixes.md。 - 退出,但不要停止 Metro:
⚠ Monitoring timeout reached after <duration>. Session: <project> — <platform> — port <port> — pid <pid> Clean checks: <clean>/<targetCleanCycles> Details: .powernative/debug-app/fixes.md
步骤 A — 收集日志
遥测检查点:collect_runtime_logs
读取 .powernative/debug-app/metro-cursor.json,并使用 Phase 0.0 的验证逻辑重新发现所有活动的 .powernative 会话。
- 如果保存的
logPath+pid+port仍然有效,即使出现了更新的有效会话,也继续监控该会话。不要中断当前运行,也不要切换端口。 - 如果保存的会话不再有效,且恰好还剩一个有效会话,则选择该会话并返回 Phase 0.2,以建立新基线。
- 如果保存的会话不再有效,且还剩多个有效会话,则显示会话选项并询问要监控哪个会话,然后返回 Phase 0.2。
- 如果没有剩余的有效会话,则按适用的 Phase 0.0 失败情况停止,而不是读取过期日志。
否则,从固定的会话中收集新增字节。如果当前没有待处理字节,此前台收集器会监视日志目录,直到该文件发生变化或完整的 5 秒观察间隔结束:
node - "$LOG_PATH" <saved-cursor> 262144 5000 <<'NODE'
const fs = require('node:fs');
const path = require('node:path');
const [file, cursorText, maxText, observationText] = process.argv.slice(2);
const cursor = Number(cursorText);
const maxBytes = Number(maxText);
const observationMs = Number(observationText);
const observedAt = Date.now();
let watcher;
let timer;
let finished = false;
function snapshot(observation) {
try {
const size = fs.statSync(file).size;
const start = Number.isInteger(cursor) && cursor >= 0 && cursor <= size ? cursor : 0;
const fd = fs.openSync(file, 'r');
const buffer = Buffer.alloc(Math.min(maxBytes, Math.max(0, size - start)));
let bytesRead;
try {
bytesRead = fs.readSync(fd, buffer, 0, buffer.length, start);
} finally {
fs.closeSync(fd);
}
return {
cursor: start,
nextCursor: start + bytesRead,
rotationLost: start !== cursor,
truncated: start + bytesRead < size,
fileMissing: false,
readError: null,
observation,
observedMs: Date.now() - observedAt,
output: buffer.subarray(0, bytesRead).toString('utf8'),
};
} catch (error) {
return {
cursor,
nextCursor: cursor,
rotationLost: false,
truncated: false,
fileMissing: error && error.code === 'ENOENT',
readError: error && typeof error.code === 'string' ? error.code : 'UNKNOWN',
observation,
observedMs: Date.now() - observedAt,
output: '',
};
}
}
function finish(observation) {
if (finished) return;
finished = true;
if (watcher) watcher.close();
if (timer) clearTimeout(timer);
process.stdout.write(JSON.stringify(snapshot(observation), null, 2));
}
const initial = snapshot('initial');
if (initial.readError || initial.rotationLost || initial.truncated || initial.output) {
process.stdout.write(JSON.stringify(initial, null, 2));
} else {
try {
watcher = fs.watch(path.dirname(file), (_event, changedFile) => {
if (!changedFile || String(changedFile) === path.basename(file)) finish('change');
});
watcher.on('error', () => finish('watch-error'));
timer = setTimeout(() => finish('interval'), observationMs);
} catch {
finish('watch-error');
}
}
NODE
此周期仅使用返回的 output。fileMissing: true 表示日志记录器可能已重命名 port-unknown;应重新发现会话,而不是推进游标。任何其他非空的 readError 或 observation: watch-error 都不属于正常周期:报告有界错误代码并停止,而不是快速轮询。否则,立即将当前的 logPath、pid、port、nextCursor 和新的 updatedAt 持久化到 metro-cursor.json,即使 output 为空或包含错误也一样;这样可避免中断后重复处理,并安全地处理文件替换。
只有在 readError 为 null、observation: interval、observedMs >= 5000,且 output 为空或不包含可分类错误时,才计为正常周期。若提前出现 change 观察结果并带有提示性输出,则尚未达到完整的正常间隔;应处理该结果并开始下一个收集周期。如果日志文件发生变化、PID/端口检查结果相互矛盾,或因文件缩小/轮转导致游标重置,绝不要将该周期计为正常周期。
如果 truncated: true,绝不要将该结果计为干净结果。处理所有非空输出,然后在同一周期内,从返回的游标最多再调用三次 tail。如果 rotationLost: true,请在 fixes.md 中明确记录轮转警告,并要求先进行一个新的完整观察区间,再增加干净计数器。如果四个分块后数据仍被截断,请记录积压警告,并在下一个周期继续,而不是无限消耗上下文。
在新输出中,将以下内容作为可分类信号呈现,并更新相应的 health.json 域:
- Runtime 的
ERROR/WARN/LOG前缀 - 带有类似
[PAHost]、[bridge]、[AuthProvider]、[AuthContext]、[useConnectionRefs]、[useConnectionSetup]、[addAadAppToConnectionAcl]、[PAHost][ErrorBoundary]前缀的主机诊断行 - 堆栈帧(
at <fn> (<file>:<line>:<col>)) - Bundle 类错误(
Unable to resolve module、SyntaxError、transform failed)——重新归类为步骤 B“导入 / Bundle”严重问题,并通过步骤 D 处理 - 来自 Metro 请求日志的 HTTP 方法 + 状态(例如,
"GET /index.bundle?platform=ios&dev=true ..." 500 -)——.bundle上的非 200 状态属于 bundle / 转换失败;连接器 / Dataverse 主机上的非 2xx 状态归入步骤 B“网络 / API” - 包含
Bundling complete/iOS Bundled/Android Bundled的行仅供参考——以调试级别记录到fixes.md,但不要归类为问题 - 来自注入式跟踪语句的
[TRACE前缀行——按照症状排查(阶段 0.5.4)进行分类,而不是归为错误
主机诊断行的解释规则:
- 仅当行与以下已发出的主机字符串匹配时,才将其视为可分类信号:
[AuthProvider] MSAL init failed:[AuthProvider] Intune enrollment failed:[AuthContext] acquireTokenSilent failed for scopes:[AuthProvider] Intune unenroll failed:[bridge] unhandled plugin call[bridge] fetch THREW for[bridge] HTTP <status> for[addAadAppToConnectionAcl] failed HTTP <status> for connection[addAadAppToConnectionAcl] error:[useConnectionRefs] could not verify connection ACLs; treating existing connections as setup-required[useConnectionRefs] Failed to load connections:[useConnectionSetup] could not grant connection ACL: missing Power Apps token or user OID[PAHost] getConnectorToken: acquireToken threw for apiId="...":[PAHost] getConnectorToken: acquireToken returned null for apiId="..."[PAHost] getDataverseToken: acquireToken threw for orgUrl="...":[PAHost] getDataverseToken: acquireToken returned null for orgUrl="..."[PAHost][ErrorBoundary] Unhandled JS error:[PAHost][ErrorBoundary] Error stack:[PAHost][ErrorBoundary] Component stack:
- 若行仅包含生命周期 / 状态信息,例如 bridge 设置 / 就绪、令牌获取开始 / 成功、bridge 注册以及连接设置屏幕可见性,则将其视为参考信息。
步骤 B — 对每条新日志进行分类
遥测检查点:classify_runtime_failures
应用这张 8 类表。将每个唯一的堆栈跟踪/错误消息视为一个问题。
| 优先级 | 模式 | 类别 |
|---|---|---|
| 严重 | 未捕获的异常、未处理的 Promise 拒绝、应用崩溃 | JS Runtime |
| 严重 | Unable to resolve module、Cannot find module | Import / Bundle |
| 严重 | SyntaxError、Unexpected token、transform failed(仅主模式下 Metro 终端中的多行代码块) | Import / Bundle |
| 严重 | Cannot read properties of undefined、is not a function | JS Runtime |
| 高 | 来自 pdfViewer 或 penInput 封装的 NATIVE_MODULE_MISSING | Native |
| 高 | 来自 geolocation 封装的 NATIVE_MODULE_MISSING、PERMISSION_DENIED 或 TRACKING_FAILED | Native |
| 高 | 来自 pdfViewer 的 INVALID_URL,或日志中提及 content://、blob: 或 http:// 的 PDF 查看器输入 | JS Runtime |
| 高 | 来自 PDF/pen 封装的 VIEWER_FAILED 或 CAPTURE_FAILED | Native |
| 高 | ERROR 级别的运行时日志 | JS Runtime |
| 高 | 日志中显示的 HTTP 4xx/5xx | Network / API |
| 高 | Native 模块或桥接错误 | Native |
| 中 | React Warning: 组件错误 | React |
| 低 | 非已知噪声的 WARN 级别日志 | General |
解析多行打包/转换代码块(主模式): Metro 会将这些错误打印为一个横幅(例如 error: Bundling failed、iOS Bundling failed),后面跟着一个缩进代码块。与运行时堆栈不同,文件:行号位于代码块的第一条非横幅行,格式为 <绝对或相对路径>:<行>:<列>。通常没有 at <fn> 堆栈帧。识别示例:
iOS Bundling failed 412ms
SyntaxError: /Users/.../app/(tabs)/todos.tsx: Unexpected token (47:12)
45 | return (
46 | <YStack>
> 47 | <Text>{title</Text>
| ^
48 | </YStack>
49 | );
将 app/(tabs)/todos.tsx:47:12 作为修复位置。下面 D3.1 中的处理方案适用于此格式。
忽略已知噪声行:
- Metro 的
Require cycle:警告 - 没有相关崩溃的
VirtualizedList: You have a large list… - Expo SDK 信息横幅(
Starting Metro…、Connecting to…) - React Navigation 关于不可序列化参数的开发警告(除非与崩溃有关)
- 当屏幕状态未改变且未显示错误时,pen 输入产生的
USER_CANCELLED - 没有失败迹象的宿主生命周期/信息行,例如
[bridge] setupNativeHost: bridge ready、[PAHost] bridge registered、[PAHost] render: waiting for connection resolution (spinner)
步骤 C — 如果未发现问题
遥测检查点:confirm_runtime_health
递增连续干净周期计数器。
在达到 targetCleanCycles 后退出前,检查症状保护条件。 读取 .powernative/debug-app/symptom-state,并选择下方匹配的退出路径。(如果本次会话未使用症状驱动模式,阶段 0.1 已在启动时清空该文件,因此会触发“文件不存在”分支。)
每次以干净、已标记、待处理、超时、达到迭代上限或升级退出前,都要打印此健康表(数据来自 health.json,且已清理敏感信息):
Health
Bundle <status> <summary>
Runtime <status> <summary>
Authentication <status> <summary>
Dataverse <status> <summary>
Connector <status> <summary>
Offline <status> <summary>
Navigation <status> <summary>
Native capability <status> <summary>
未观察到相关证据时保留 unknown;只有在明确检测到配置缺失时才使用 not-configured。对每份摘要应用持久化脱敏门禁。日志区间干净,并不意味着所有未知领域都处于健康状态。
resolved 或文件不存在(本次会话未使用症状模式):
✓ App is running cleanly — no errors detected across <targetCleanCycles> consecutive log checks.
Symptom verification: <PASS | n/a — no symptom provided>.
Session summary written to .powernative/debug-app/fixes.md.
To resume monitoring, run /debug-app again.
flagged(阶段 0.5 发现了需要借助其他技能解决的实际问题):
⚠ Logs are clean BUT the symptom isn't fixed — it requires another skill.
Symptom: <class> on <screen>
Next step: <skill route recorded in unresolved.md by Phase 0.5> (e.g., run /add-connector)
Details: .powernative/debug-app/unresolved.md
Re-run /debug-app "<symptom>" after taking that step to verify the fix.
不要打印绿色 ✓——应用在技术上日志干净,但面向用户的问题仍然存在;用户需要采取行动后才能重新运行。
pending(达到 targetCleanCycles 个干净日志周期后仍处于活动状态):
将阶段 0.5 的链路排查发现追加到 unresolved.md,然后打印:
“⚠ 症状
<class>在<screen>上经历<targetCleanCycles>个干净日志周期后仍处于活动状态。运行时没有异常,但面向用户的问题仍然存在——可能是数据路径错误被吞掉了。请查看.powernative/debug-app/unresolved.md中的链路排查记录。建议的下一步:<根据排查结果得出>。”
退出循环。不要自动恢复。
迭代上限(两种模式均适用): 无论干净周期计数器和超时情况如何,循环最多运行 50 个周期。在 fixes.md 顶部跟踪 cycle: <N>,并在每个周期递增。达到上限时,退出并打印:
“⚠ 循环已达到迭代上限(50 个周期)。症状可能是间歇性的,或者某项修复在每次重新加载时都会导致问题复发。请查看
.powernative/debug-app/fixes.md中按周期记录的日志。建议的下一步:检查最近 3 项修复是否存在循环回归,或使用更具体的症状重新运行。”
如果计数器低于 targetCleanCycles 且尚未达到迭代上限,则返回步骤 A。其前台文件监视器会提供观察间隔;不要添加 shell sleep、特定于宿主的等待命令或快速轮询循环。
步骤 D — 如果发现问题
遥测检查点:repair_and_verify_runtime_issue
将连续干净计数器重置为 0。针对每个问题,按以下顺序逐项处理,完成一个后再处理下一个。
D0. 仅监视分支(--no-fix)
当 noFix=true 时,不要继续执行 D1–D4 中会修改内容的步骤:
- 记录类别、简短且已清理敏感信息的错误摘要、可用时项目相对路径中的顶层用户代码栈帧,以及原本会采用的修复方案或移交信息。写入前应用持久化脱敏门禁。
- 追加:
[<HH:MM:SS>] Observed (no-fix) — <category> — <file:line|no user frame> — <summary> Would do: <inline fix recipe | skill handoff | manual native action> - 不要编辑项目源代码/配置、注入跟踪信息、进行类型检查、重新加载、安装、重新生成、切换账号或调用其他技能。允许更新
.powernative/debug-app/中的游标、审计和健康文件。 - 继续处理下一个问题,然后返回步骤 A。继续监视新输出,直到达到干净目标、超时、停止条件或迭代上限。
D1. 收集上下文
使用当前 Step A 的 output 块。注意日志是否显示:
- 崩溃并附有完整堆栈跟踪(应用正在崩溃)
- React 错误边界消息(组件抛出了错误)
- 网络错误或 HTTP 状态码(API/连接器故障)
- 没有错误的空响应(静默故障——考虑注入一条
[INJECTED-TRACE]console.log;模式见 Phase 0.5.3)
对于 JS Runtime / React 错误,终端中的堆栈跟踪就是上下文。读取最顶部的用户代码堆栈帧,以定位文件。
对于崩溃或黑屏且终端没有输出的情况,请询问用户:
“屏幕上能看到什么——错误边界、纯白空白页面,还是加载指示器?请复制任何可见的错误文本。”
D2. 根因分析
阅读相关源文件。确定:
- 导致错误的确切文件和行号。对于运行时错误(JS Runtime、React、Network/API),从堆栈跟踪中选择最靠前的用户代码栈帧(跳过
node_modules/和src/generated/)。对于捆绑 / 转换错误(Import / Bundle 类别,主要模式),使用 Metro 错误块中第一条非横幅行上的 file:line——参见步骤 B 的解析说明。 - 修复涉及数据层、UI 层、路由还是架构。
- 修复是否可能导致其他屏幕出现回归问题。
Dataverse / Power Platform 诊断流程(移交前):
- 阅读 skills/add-dataverse/references/dataverse-reference.md。如果存在
memory-bank.md和相关native-app-plan.md章节,也一并使用。 - 阅读
power.config.json;从中获取environmentId,不要从其他来源获取。检查相关的生成服务 / 模型和connectorSchemas.ts。 - 以只读方式解析环境:
如果解析失败,运行node "${PLUGIN_ROOT}/scripts/resolve-environment.js" "<environmentId-or-url>"$PA auth status --json。未经用户确认,绝不要切换帐户、注销,或从/debug-app打开登录页面。 - 环境解析成功且需要实时证据时,只能通过以下方式发送只读
GET请求:
如果身份验证 / 租户身份存在疑问,请先使用node "${PLUGIN_ROOT}/scripts/dataverse-request.js" <envUrl> GET <apiPath> \ --tenant-id '<resolved-tenant-id>'WhoAmI。使用范围受限的元数据 / 实体读取来确认表名和列名。不要从/debug-app创建、更新、删除、发布、填充数据、发送大量请求,或故意使凭据失效。 - 应用运行时数据修复必须继续使用生成的
<Table>Service调用。绝不要将其替换为直接fetch/axiosDataverse 调用。 - 如果 OData 语法、查找行为、选项 / 虚拟 / 文件 / 图像字段、身份验证、节流或错误格式仍不确定,请先使用确切的错误 / 令牌查询 Microsoft Learn,再制定修复方案。
只有完成此流程并确认表 / 列 / 服务 / 架构制品缺失或不兼容后,D3 才应路由到 /add-dataverse。
Expo / Expo Router / React Native 诊断流程:
- 阅读所引用的应用文件,以及仅与问题相关的
package.json、app.json/app.config.js和路由布局。 - 优先查阅已安装软件包中与确切版本对应的类型定义 / 自述文件。
- 如果行为仍不确定,请使用有针对性的
WebFetch查阅官方https://docs.expo.dev/文档(路由问题查阅 Expo Router 页面;原生模块问题查阅 SDK 页面)。不要使用 Microsoft Learn 查询 Expo/React Native 行为。
第一方原生软件包归属判定:
每当故障堆栈、模块或导出涉及名称匹配 @microsoft/power-apps-native-* 的已安装软件包时,必须在 D3 之前执行此判定。
- 将
node_modules/栈帧仅视为线索。阅读应用调用位置、已安装软件包的公开类型定义 / 自述文件 / 导出,以及声明的版本和锁文件解析出的版本。检查应用是否使用了文档所述的 API,并传入受支持的参数和配置。 - 如果更正调用方、输入或受支持的配置即可解决契约违规,则将问题归类为客户代码问题。在这种情况下,按常规 D3 路由。
- 只有在应用调用有效且故障由软件包内部实现、缺失 / 损坏的已声明导出,或软件包内部导入导致时,才将问题归类为软件包自身问题。如果归属仍不确定,不要修改任何一方;记录不确定性并升级处理。
- 对于已确认的软件包自身缺陷,停止对此问题进行自动修复尝试。不要修改应用源代码、配置、依赖解析或已安装的软件包内容来规避问题。仅记录软件包名称、声明 / 解析的版本、平台、最小复现、预期 / 实际行为,以及经过脱敏且范围受限的堆栈 / 错误摘要。然后将用户路由到
/report-issue;如果已知官方发布的修复或文档所述的解决方法,请予以说明,但不要从/debug-app应用该修复或方法。
D3. 应用修复
对于 Import / Bundle 类别错误,请先跳转到 D3.1——这些错误有特定的处理方案,优先于下方的通用路由表。对于其他所有错误(JS Runtime、Network/API、React 等),请使用路由表:
| 错误位置 / 类别 | 操作 |
|---|---|
app/ 屏幕文件、_layout.tsx、路由段 | 使用 Edit 工具进行内联编辑 |
src/components/ | 使用 Edit 工具进行内联编辑 |
src/hooks/、src/services/ | 使用 Edit 工具进行内联编辑 |
src/generated/ | 不要编辑。 使用 CLI 刷新,将服务 / 模型修复交接给负责的数据源技能;npm run generate-schemas 仅会重新生成运行时连接器映射。不要重新添加已主动退役的数据源。 |
| Dataverse 架构(缺少列 / 表) | 先运行 D2 的只读 Dataverse 诊断流程。如果实时元数据 / 生成的构件确认架构或服务缺失,则交接给 /add-dataverse。不要修改 Dataverse,也不要从 /debug-app 编辑生成的文件。 |
Auth / MSAL(AADSTS65001、AADSTS50011) | **交接:**通过 /set-app-registration-native 引导用户前往 Power Apps Wrap 页面。不要自动编辑注册信息。 |
| 缺少连接 / 连接器引用 | **交接:**引导用户前往 /list-connections 或 /add-connector。 |
已确认是 @microsoft/power-apps-native-* 内部缺陷 | 不要在客户项目中修复。 输出下方的软件包缺陷报告,并引导至 /report-issue。此路由优先于 native、import/bundle 和无法识别错误的处理方案。 |
Native 模块、app.config.js、app.plugin.js、Podfile、build.gradle | 告知用户。 不要自动编辑 native 配置——打印错误和建议操作,然后跳到下一个问题。 |
| 无法识别的错误模式 | 尽力自主修复——见下方 D3.2。该技能会尝试一个明确命名的假设,而不是直接停止;现有的 2 次尝试后升级规则是安全保障。 |
PDF / pen / geolocation 专属路由:
- PDF 查看器输入出现
INVALID_URL时,应内联修复屏幕 / 包装器。对于 viewer 0.2.9+,允许https://和非空的file://输入。native 查看器路径中绝不要添加对content://、blob:或http://的支持。 - 生成的 PDF 本地
file://URI 可由 native PDF viewer 0.2.9+ 打开。 - PDF 查看器或 pen 输入出现
NATIVE_MODULE_MISSING,表示运行中的构建不包含 native 扩展。不要从 debug 安装软件包或编辑 native 配置;应转到/add-native pdf-viewer或/add-native pen-input验证包装器 / 软件包状态,然后如果应用构建中缺少该软件包,告知用户需要重新构建 native 或更新模板。 - 对于
geolocation,应针对实际故障维度进行调试:跟踪能否启动(startTracking、权限、native 模块)、记录是否到达 Dataverse(默认的msdyn_locationrecords是否存在、native 上传 / auth 错误、是否没有 JS 上传路径),以及行为是否符合用户预期(后台运行、重启后持久化、面包屑 / 路线连续性)。对可见的屏幕处理进行内联修复;如果缺少 native 模块 / 表,则阻止使用并转到相关的 geolocation 设置路径,而不是/add-dataverse。 - pen 输入返回
USER_CANCELLED,除非屏幕将其显示为错误,否则不属于 bug。应内联修复将取消显示为失败的屏幕。 - Dataverse 构件写入仅在架构 / 服务已存在时才属于本地应用修复。如果缺少 File/Image 列,应转到
/add-dataverse。
对于内联编辑,请保持改动精简且有针对性。不要重构周边代码、重命名符号或更改组件契约。
对于已确认的第一方 native 软件包缺陷,不要追加成功修复记录。请先通过持久化脱敏检查门,将此报告追加到 .powernative/debug-app/unresolved.md,清理注入的跟踪信息,并停止循环,以便运行 /report-issue:
Confirmed first-party native package defect — no customer-project workaround applied
Package: <@microsoft/power-apps-native-*>
Declared/resolved version: <declared> / <resolved>
Platform: <ios|android>
Reproduction: <minimal sanitized steps>
Expected: <bounded behavior>
Actual: <bounded error/status>
Ownership evidence: <why the documented caller contract is satisfied and the failure is package-internal>
Next step: /report-issue "<package>: <sanitized summary>"
追加到 .powernative/debug-app/fixes.md:
[<HH:MM:SS>] <category> — <file>:<line> — <one-line description of fix>
D3.1 Bundle / transform 错误修复方案(Import / Bundle 类别)
这些方案适用于 Step B 中归类为“Import / Bundle”的错误。它们从持久化的 .powernative Metro 日志中读取。每个方案都有明确的处理倾向:如果前置条件匹配,就执行所列操作;否则继续查看下一个方案。
应用任何方案之前,如果引用的源文件 / 导入方或失败的内部导入位于 node_modules/@microsoft/power-apps-native-* 下,请先运行第一方原生包归属检查。确认属于包自身缺陷时,应转至 /report-issue;切勿通过解析器别名、复制源文件、打补丁、重写 postinstall 或替换依赖项来修复。
| 错误模式 | 前置条件 | 操作 |
|---|---|---|
SyntaxError: <file>:<line>:<col> 出现在 app/、src/components/、src/hooks/、src/services/ 中 | 引用的行位于可编辑的用户代码中(不是 src/generated/,也不是 node_modules/) | Read 引用行附近的文件内容(±10 行),找出语法问题(未闭合的 JSX 标签、缺少右花括号/括号、多余的逗号、导入语句缺少 from、字符串未终止、语句之间缺少分号),执行一次最小化的 Edit。不要重新格式化周围的代码。 |
SyntaxError 出现在 src/generated/ 中 | 引用的文件位于 src/generated/ 下 | 不要编辑。 对于 connectorSchemas.ts,重新生成运行时映射。对于服务/模型,解析现有注册,并移交给 /add-connector 或 /add-dataverse,使用 --refresh --data-source-name "<registered-name>"、应用根目录的绝对路径,以及经批准的定向 CLI 刷新和验证范围。先检查已批准的计划:对于已移除的数据源,应通过 /edit-app 清理使用方,而不是重新注册。注册缺失或有歧义时,必须停止刷新;不要改为新增。 |
来自 <importer> 的 Unable to resolve module <name> | <name> 以 . 或 .. 开头(相对导入) | 使用 Glob 搜索导入文件所在目录,查找与 <name> 匹配且扩展名不限的文件(.ts、.tsx、.js、.jsx、.json)。如果找到了但扩展名不同 → 修正导入,移除扩展名或改为实际扩展名。如果发现拼写错误(Levenshtein ≤ 2)→ 修正拼写。如果完全找不到 → 文件确实不存在;告知用户,并询问是创建该文件还是移除导入。 |
Unable to resolve module <name> | <name> 是裸包名,且未出现在 package.json 的 dependencies / devDependencies 中 | 按照 shared/references/javascript-dependency-planning.md,根据已发布程序包的内容而非名称进行分类。如果该程序包依赖原生代码且模板中没有,则说明需要更新模板/运行时。如果确认其为纯 JavaScript,则征求用户对确切版本的同意,使用 npm install --save-exact 安装、验证,然后重试。未经同意不要安装。 |
Unable to resolve module <name> | <name> 已在 package.json 中,但打包仍失败 | 在归咎于缓存之前,先运行定向启动检查:比较锁定版本和已安装版本,并解析确切的入口点。只有在安装损坏时才经批准恢复;不受支持的导出或原生构建不匹配需要采用其他解决方案。 |
transform failed,且引用了 babel 插件(例如 [BABEL] ... unknown plugin "react-native-reanimated/plugin") | 错误引用了 babel.config.js | 移交。 babel.config.js 是项目配置(受与 app.config.js 相同的限制保护)。指出所引用的插件和建议的修复顺序(例如,“react-native-reanimated/plugin 必须是 babel.config.js 的 plugins 数组中的最后一个插件”);跳过并处理下一个问题。 |
transform failed,但未提及 babel | 通用转换失败(通常是 Metro 的转换器无法处理的 TS 特性) | 阅读所引用的文件,查找需要特定 TS lib 的语法(例如装饰器、顶层 await)。如果问题属于已知的不良模式,先说明情况并在修复前征求同意。否则移交。 |
predev 脚本失败(例如,expo start 运行前 npm run generate-schemas 出错) | 输出显示 Metro 启动前发生了失败 | 使用启动诊断确定失败阶段(安装、配置、架构生成或类型检查)。不要假定是连接器问题,也不要直接编辑 power.config.json。 |
[BABEL] ... You're trying to use the @babel/plugin-X plugin twice | babel 插件条目重复 | 出于上述相同原因移交——babel.config.js 是项目配置。指出重复项,由用户自行删除重复条目。 |
应用任何内联编辑后(上文第 1、3、4 行),Metro 会自动检测文件保存并重新打包。直接跳到 D4——不要手动触发重新加载。验证步骤会自动捕获 Metro 的 Bundling complete(或下一个错误块)。 |
追加到 .powernative/debug-app/fixes.md:
[<HH:MM:SS>] Import/Bundle — <file>:<line> — <recipe applied>
D3.2 未分类错误的尽力修复方案
如果某个错误未能匹配步骤 B 分类表和 D3.1 打包方案中的任何一行,该技能仍会尝试修复,而不是停止。以下规范可避免尽力修复沦为盲目猜测。
步骤 1 — 定位引用位置。 按顺序尝试;找到可编辑用户代码中的文件和行号后即停止:
- 堆栈跟踪中的首个用户帧 — 从堆栈顶部开始检查,跳过
node_modules/、src/generated/和 React/Hermes 内部代码(react-native/、hermes-engine/、metro/)中的帧。剩下的第一个帧就是引用位置。 - 多行打包错误块 — 参见步骤 B 的解析说明(首个非横幅行上的文件和行号)。
- 在整个代码库中逐字搜索 — 使用
Grep在app/、src/components/、src/hooks/、src/services/中搜索确切的错误消息文本(或其中最具辨识度的 4–6 个词,并转义正则表达式特殊字符)。如果匹配位置是throw new Error('...'),该处就是引用位置。 - 按模块和符号搜索 — 如果错误中提到了函数或组件名称(例如,
useFoo is not a function),使用Grep搜索该符号;唯一的声明位置就是引用位置。
如果到步骤 4 仍找不到引用位置:向 .powernative/debug-app/unresolved.md 记录结构化说明(经过最少脱敏的错误摘要,以及已执行的查找尝试),告知用户,然后转到下一个问题。不要猜测文件位置。 尽力修复仍然需要明确的目标。
步骤 2 — 深入了解(不可跳过)。
- 如果错误中包含 Microsoft 技术栈标记(
AADSTS\d+、Dataverse、Power Platform、MSAL、Entra、Graph API):先运行 D2 的项目/引用/只读诊断流程;如果行为仍不明确,再使用确切的错误代码或标记查询mcp__plugin_mobile-app_microsoft-learn__microsoft_docs_search。 - 如果错误涉及 Expo、Expo Router、Expo SDK 模块或 React Native 行为:检查已安装版本和本地包文档,然后使用有针对性的
WebFetch访问https://docs.expo.dev/。这些错误不要使用 Microsoft Learn。 - 如果错误涉及
@microsoft/power-apps-native-*:运行 D2 的一方原生包归属检查。已确认由包本身导致的缺陷不允许尽力编辑。 - 阅读引用位置前后各 15 行,了解上下文。留意近期导入、函数签名以及附近的
try/catch或useEffect依赖项。 - 如果错误提到了第三方模块(堆栈中
node_modules/里的任何模块),可以使用一次有针对性的WebFetch查看该模块的 npm 页面或 GitHub README;不要在循环中进行开放式网页搜索。
步骤 3 — 提出一个明确命名的假设。 编辑前,按以下格式将其写入 .powernative/debug-app/fixes.md:
[<HH:MM:SS>] Hypothesis (best-effort) — <file>:<line> — <one-sentence theory>
Evidence: <sanitized error code/summary + what in the cited code led you here>
Planned change: <what you'll edit, in 1 line>
可接受的假设示例:
- “
<UserAvatar>读取user.profile.image,但首次渲染时user可能未定义——添加空值保护” - “第 42 行的
useEffect捕获了过期的userId,因为依赖数组中缺少userId” - “缺少键时,
AsyncStorage.getItem会返回 null,但调用方假定返回值是可解析为 JSON 的字符串”
不可接受(拒绝应用,应改为升级处理):
- “状态管理有问题”(含糊不清——未指出具体要改什么)
- “试着用 try/catch 包起来”(只是掩盖问题,并未修复——会悄悄吞掉真正的错误)
- “或许也更新 X、Y 和 Z”(多目标修改——违反一次只修复一个问题的原则)
步骤 4 — 应用一项最小修改。 调用一次
Edit,以尽可能小的差异实现计划中的更改。不要更改无关代码、重命名符号或重构周边结构。再次确认文件路径属于可编辑的用户代码(不在src/generated/、node_modules/或 Constraints 部分受保护的路径下)。
步骤 5 — 交由 D4 验证。 现有的验证流程(类型检查 + 重新加载 + 再次轮询)将决定假设是否正确。不要为了“以防万一”而提前尝试第二个假设。
步骤 6 — 验证失败时,允许一个备选方案。 如果 D4 显示相同错误再次出现,你可以提出一个备选假设(这算是针对原始错误的第 2 次修复尝试)。如果该假设也失败,现有的升级规则即被触发,该技能将针对这个错误停止——向用户说明情况,将其追加到 unresolved.md,然后继续处理下一个问题。不要提出第三个假设。
尽力模式约束提醒(无例外):
- 绝不编辑
src/generated/、node_modules/、app.config.js、app.plugin.js、babel.config.js、metro.config.js、Podfile、build.gradle、gradle.properties、power.config.json、auth.config.json。 - 未经用户明确同意,绝不运行
npm install <pkg>、npm uninstall <pkg>、npx expo install <pkg>,或任何会修改package.json/package-lock.json的命令(与 D3.1 的裸包处理方案采用相同的授权门槛)。 - 绝不在运行时修复方案中重启 Metro,也不运行
expo prebuild。 只有启动参考中单独获批且有明确范围的启动重试,才能启动或重启所选应用的 Metro 会话。 - 如果唯一合理的假设违反其中任何一项约束,则将错误视为范围外:用一句话向用户说明并移交处理,然后继续。
D4. 验证修复
应用修复后:
-
类型检查:
npm run type-check如果存在 TS 错误,请先修复再继续。类型检查退出码为 0 之前,不要继续。
-
等待 Metro 重新打包(仅适用于导入/打包修复): 对于通过 D3.1 应用的内联编辑,Metro 会自动监视文件,并在保存时触发重新打包。在有限次数的检查中,重新运行步骤 A 的游标式尾部读取流程,并留意以下情况之一:
- 出现
Bundling complete/iOS Bundled/Android Bundled→ 成功,继续执行第 4 步。 - 出现新的打包错误块(文件:行不同,或错误消息不同)→ 将其视为新问题并返回步骤 B。
- 相同错误再次出现 → 修复未生效。将其视为针对相同错误的第 2 次修复尝试(达到 2 次后适用升级规则)。
- 经过 30s 仍无打包活动 → Metro 可能已暂停或卡住;向用户说明情况,不要自动重启 Metro(见约束)。
- 出现
-
重新加载应用(所有其他修复——JS 运行时、网络/API、React 等): 快速刷新应能应用大多数内联编辑。如果没有新的打包/运行时活动,请指示用户:
“请从原生开发客户端菜单重新加载应用,然后再次触发该工作流。”
用户确认后,运行步骤 A 的游标式尾部读取流程,仅检查新增输出。
-
通过持久化输出确认修复。 如果新追加的日志字节中不再出现先前的错误模式,且没有出现新错误,则说明修复有效。如果任何
[INJECTED-TRACE]行与问题相关,请用它们确认数据路径正常。确认后,清理注入的日志(阶段 0.5.5)。 -
将干净周期计数器重置为 0,并返回步骤 A。
升级处理
如果相同错误在 2 次修复尝试后仍然存在,请停止并报告:
⚠ Unresolved after 2 attempts: <error summary>
File: <path>
Log: <sanitized bounded error summary>
Last fix tried: <one-line description>
Suggested next step: <manual action>
将该块通过持久化脱敏门控,仅将验证器的输出追加到 .powernative/debug-app/unresolved.md,并在退出前清理任何 [INJECTED-TRACE] 日志(阶段 0.5.5 流程)。
不要针对相同错误尝试第三次自动修复。等待用户指示。
约束
- 绝不修复原生配置文件(
app.config.js、app.plugin.js、Podfile、build.gradle、gradle.properties)——向用户报告错误,注明准确行号,并建议手动操作。 - 启动恢复不属于升级。 只有启动参考中批准的相同锁文件恢复和有界 Metro 重试属于例外。 不得盲目升级、删除锁文件、安装 Node 或修补主机/MSAL。
- 绝不修改
src/generated/——这些文件是自动生成的。所属的数据源技能使用 CLI 添加/刷新/移除模型、服务和配置;npm run generate-schemas仅在之后重建运行时映射。保留批准的数据源清单;修复期间不得恢复已停用的数据源。 - 绝不在客户项目中修补或分叉第一方原生包——对于已确认的
@microsoft/power-apps-native-*缺陷,不得编辑node_modules/、生成patch-package工件或 postinstall 重写、供应/复制包源代码、生成或安装分叉版本、将依赖替换为 git/tarball/本地路径,或添加会遮蔽该包的解析器别名/垫片。将脱敏后的证据提交至/report-issue。 - Dataverse 诊断只读——
/debug-app可以解析已配置的环境,并通过捆绑脚本发送有界的 DataverseGET请求。绝不能写入元数据/数据、发布、播种记录、故意触发限流、使令牌失效、切换 CLI 账户,或用直接 HTTP 替换生成的服务。 - 不要在处理周期中途询问用户错误情况——使用上述工具自主排查。仅在以下情况下告知用户:
- 修复需要更改原生配置。
- 修复需要租户管理员操作(例如 AAD 同意)。
- 已尝试修复两次,但相同错误仍然存在(升级处理)。
- 修复需要转交给其他技能(
/add-dataverse、/set-app-registration-native、/list-connections)。 - 启动恢复/重启需要明确批准或设备验证。
- 一次只修复一个问题——彻底解决一个问题(包括类型检查、重新加载和日志验证)后,再开始处理下一个问题。
- 始终清理注入的日志——本次会话中添加的任何
// [INJECTED-TRACE]行都必须在会话结束前删除,即使症状为pending或flagged也不例外。使用 Phase 0.5.5 的find扫描可编辑的app/和src/文件,并排除src/generated/,以查找这些日志。 - 保留现有行为——修复必须尽量精简且有针对性。不得借修复缺陷之机重构、重命名或更改组件契约。
- 有界轮询——不得忙等。每次轮询都必须推进持久化的字节游标,每个周期最多处理四个 256 KiB 分块。
- 记录每项操作——每次调用工具前,打印一行说明即将执行的操作及其原因,方便用户了解进度。
故障模式
| 症状 | 可能原因 | 修复方法 |
|---|---|---|
Phase 0 报告 not-started 或 stopped | 没有正在运行的 .powernative Metro 日志 | 运行有界启动诊断;仅在证据支持时提供已批准的恢复/重试方案 |
| Phase 0 检测到近期失败日志 | Expo/Metro 在启动或运行期间退出 | 如果没有仍在运行的会话,请在启动诊断中关联失败的启动记录;否则先进行运行时基线/替代检查,再修复 |
| Phase 0 报告“Metro 正在运行,但没有应用连接” | 设备/播放器/二维码/网络问题,或用户尚未打开应用 | 诊断打开应用时出现的症状,默认不要重新安装;要求用户重试并提供最新证据 |
| 循环似乎卡住 | 修复耗时比预期更长(例如大型项目中的类型检查) | 等待——修复运行时仍应持续输出日志行。输入 stop 退出。 |
| 循环以“iteration cap reached”退出 | 症状是间歇性的,或每次重新加载都会导致修复回归 | 检查 fixes.md 中最近的 3 条记录是否存在循环;使用更具体的症状重新运行,或手动修复 |
| 修复后同一错误反复出现 | Fast Refresh 未应用更改,或修复针对了错误的文件 | 通过 git status 验证;从原生开发客户端菜单重新加载;重新运行 |
修复后同一错误仍然存在,且 git status 显示更改已保存、类型检查也通过 | Metro 转换缓存过期 | 请用户停止 Metro,重新运行 npm run dev -- --clear,然后重新加载,再运行 /debug-app |
| 立即触发升级处理 | 错误模式属于需要转交处理的类别(身份验证、架构、本机) | 执行建议的手动操作,然后重新运行 /debug-app |
.powernative/debug-app/fixes.md 未出现 | Phase 0 未运行 / 未创建状态目录 | 手动运行 mkdir -p .powernative/debug-app,然后重新运行技能 |
| “应用运行正常”,但用户仍然遇到问题 | 未使用症状驱动模式——仅轮询日志无法发现静默故障 | 使用 /debug-app "<describe what you see>" 重新运行,以触发 Phase 0.5(注入 console.log) |
Phase 0.5 报告 screen=unknown | 症状文本未匹配任何路由文件名 | 使用更具体的症状重新运行(/debug-app "todos screen empty",而不是 "data is broken"),或先导航到出现故障的屏幕,再重新运行 |
重新加载后没有 [TRACE 行 | Metro 缓存了旧的 bundle | 请用户停止 Metro,重新运行 npm run dev -- --clear,然后重新加载应用 |
会话结束后代码中仍有 [INJECTED-TRACE] 行 | 跳过了清理步骤 | 运行 Phase 0.5.5 的 find 扫描,并删除每一条匹配行 |
说明
-
专为重复运行而设计 — 每次调用都是幂等的。
.powernative/debug-app/metro-cursor.json会跳过之前读取过的字节,并在检测到新的 Metro 会话或日志轮换时安全重置。 -
如实说明局限 — 启动诊断可以在应用运行前进行,但运行时监控仍是有时限的前台循环。其他终端发生故障时,它不会唤醒处于空闲状态的 Agent。输入
stop退出监控。 -
不使用专用 Agent — 上游的
app-debugger.agent.md会委派给screen-builder、component-author、api-integration、dataverse-data-modelerAgent。此插件没有包含所有这些 Agent,因此此技能会内联修复,或路由到技能(/add-dataverse、/set-app-registration-native、/list-connections、/add-connector)。对于我们涵盖的类别,效果等同。 -
主机诊断注意事项 — 在 dev-player 会话中,带主机前缀的诊断信息(
[PAHost]、[bridge]、[AuthProvider]等)是预期行为,应视为一等遥测信息。如果非 dev-player 构建中没有这些日志行,这是预期行为,本身并非错误。 -
与上游的功能对照表:
行为 上游 此技能 日志驱动的监控循环 是 是 — 项目本地的已清理 Metro 日志是权威来源;主机终端 API 仅为可选项 8 类别分类 是 是 验证周期(类型检查 + 重新加载 + 重新轮询) 是 是 尝试 2 次后升级处理 是 是 有界轮询 是 是 — 持久化字节游标,有界分块 可配置的连续无错误退出 固定为 3 是 — 默认值为 3,可通过 --cycles配置,并且只有在症状已解决时才会退出委派给专用 Agent 是 已替换为技能路由 工作目录审计日志 否 是(额外功能 — .powernative/debug-app/fixes.md、injected-logs.md)未知错误的 MS Learn 后备方案 否 是(额外功能) 持久化的 Metro/应用日志来源(打包器错误 + Hermes 控制台 + HTTP 请求日志) 否 是 — 可在主机重启后保留;没有 MCP 后备方案 Bundle / 转换错误修复方案(D3.1) 否 是(额外功能) 感知 Bundle 的验证(轮询 Metro 以确认 Bundling complete)否 是(额外功能) 针对未分类错误尽力自主修复(D3.2) 否 是(额外功能) 症状驱动模式 — 注入 console.log + 读取终端 + 遍历数据路径 否 是(额外功能 — 捕获日志轮询无法发现的静默故障;注入 [INJECTED-TRACE]日志、读取终端,并在找到根本原因后清理日志)