- name
- debug-app
- description
- Use when the user has finished building a mobile app, started Metro with `npm run dev`, and wants the running app monitored for runtime errors AND silent failures (empty lists, blank screens, swallowed network errors) and fixed autonomously. Accepts a free-text symptom (e.g., `/debug-app "todos not appearing on home screen"`) to drive persisted-log diagnostics — injects temporary console.log statements at data-path boundaries, reads the sanitized `.powernative/metro-logs/` log, and cleans up logs after the root cause is fixed. Supports port/platform filtering, configurable clean-cycle and timeout limits, and watch-only `--no-fix` mode. Otherwise polls with a durable byte cursor, fixes inline or routes appropriately, verifies each fix from new output, and exits after the configured clean checks (default 3). Foreground loop — blocks the conversation while running. Run only after the app is loaded.
- user-invocable
- true
- allowed-tools
- Read, Edit, Write, Grep, Glob, Bash, AskUserQuestion, WebFetch, mcp__plugin_mobile-app_microsoft-learn__microsoft_docs_search
- model
- sonnet
> **Plugin check**: Run `node "${PLUGIN_ROOT}/scripts/check-version.js"` - if it outputs a message, show it to the user before proceeding.
**📋 Shared instructions: [shared-instructions.md](${PLUGIN_ROOT}/shared/shared-instructions.md)** — read first.
# Debug App — Monitor & Fix
Monitor the running app through the project-local `.powernative/metro-logs/` files written by `metro.config.js`, detect runtime and bundle errors, and fix them autonomously by editing the affected files (or routing to the right skill when the fix belongs in a domain like Dataverse schema or auth registration). For silent failures, inject temporary `console.log` statements at data-path boundaries, read only newly appended log bytes, then clean the traces after the root cause is fixed. Modeled on the upstream `app-debugger.agent.md` pattern — foreground loop, bounded polling, configurable clean-check/timeout exits, and optional watch-only operation.
> **Dev-client limitation:** the standalone dev client sends app/runtime logs, React errors, host diagnostics, and Metro bundler output through Metro. The template's `metro.config.js` writes Metro terminal output and HTTP bundle failures into `.powernative/metro-logs/`; there is no separate device log source. Host diagnostics include strings such as `[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`, and `[PAHost][ErrorBoundary] Unhandled JS error:`.
## Subcommands (parsed from `$ARGUMENTS`)
| Form | Behavior |
|---|---|
| `/debug-app` (no args) | **Default — project-log-driven mode.** Run Phase 0, discover valid project-local Metro sessions, select automatically when one is live or ask when several are live, then monitor the selected log. No host terminal ID is required. |
| `/debug-app "<symptom text>"` | **Symptom-driven mode** (recommended when there's a user-visible problem). Free-text symptom such as `"todos not appearing on home screen"`, `"login button does nothing"`, `"list empty after refresh"`. Run Phase 0 → Phase 0.5 (parse symptom → ask the user to reproduce/navigate → walk the likely data path from terminal traces) → enter monitor loop. Catches silent failures (empty lists, blank screens, swallowed errors) that pure log polling misses. |
| `/debug-app status` | Discover all project-local Metro logs and print each valid session's project, platform, PID, port, start time, and log path. Mark the session referenced by the saved cursor when present, then print fixes and unresolved errors. Do NOT ask for a selection or enter the loop. |
| `/debug-app stop` | Stop only the foreground debug loop and preserve `.powernative/debug-app/` state. It does not stop Metro; the user owns the `npm run dev` process. |
| `/debug-app version` | Print the installed `mobile-app` plugin name and version from `${PLUGIN_ROOT}/.plugin/plugin.json`, then exit. |
### Monitoring options
Options may follow the default command, a symptom, or `status`:
| Option | Meaning | Default |
|---|---|---|
| `--working-dir <path>` | App root containing `package.json` and `metro.config.js`. Relative paths resolve from the current shell directory. | Current shell directory |
| `--port <1-65535>` | Monitor only the valid Metro session on this port. | Any port |
| `--platform <ios\|android>` | Consider only sessions whose recent log identifies this platform. | Any platform |
| `--cycles <1-50>` | Exit after this many consecutive clean observation intervals. | `3` |
| `--timeout <duration>` | Maximum wall-clock monitoring time. Accept `30s`–`60m` using `s`, `m`, or `h`. | `5m` |
| `--no-fix` | Watch-only mode: classify and report, but never edit project source/config, inject traces, install dependencies, regenerate files, or invoke a mutating handoff. Debug cursor/audit/health state still advances. | Fix enabled |
Examples:
```text
/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
```
**Argument parsing:**
1. Parse quoted text as one symptom value and parse recognized flags wherever they appear.
2. The first reserved token (`status`, `stop`, `help`, `--help`, `-h`, `version`, `--version`) selects the subcommand. `stop`, help, and version do not accept monitoring options. `status` accepts only `--working-dir`, `--port`, and `--platform`; reject symptoms, `--cycles`, `--timeout`, and `--no-fix` because it does not enter the loop.
3. After removing recognized options and their values, any remaining non-reserved text is the symptom and enables symptom mode.
4. Reject unknown flags, duplicate flags, missing values, invalid numbers, unsupported platforms, or more than one free-text symptom. Print the valid forms and exit without monitoring.
5. Normalize `platform` to lowercase. Convert `timeout` to `timeoutSeconds`; require `30 <= timeoutSeconds <= 3600`. For monitoring and `status`, resolve `workingDir` to an absolute path from `--working-dir` or the current shell directory. Do not search parent directories. Require `package.json` and `metro.config.js` at that root; otherwise print the invalid path and stop before reading or writing project state. After validation, `cd` to `workingDir` once and reset it from the resulting absolute `$PWD`; every relative project path and command below runs from that directory.
6. Initialize:
```text
workingDir=<absolute app root>
portFilter=<number|none>
platformFilter=<ios|android|none>
targetCleanCycles=<number, default 3>
timeoutSeconds=<number, default 300>
noFix=<true|false, default false>
monitorStartedAt=<current ISO timestamp>
```
For `help` / `--help` / `-h`, print the subcommands and monitoring-options tables and exit.
**Early-return subcommands:**
- `stop`: if received while this foreground loop owns the conversation, clean up any injected traces using Phase 0.5.5 and exit at the next safe boundary. For a standalone `stop` invocation, report that no loop is active. Never stop Metro or delete `.powernative/debug-app/` state.
- `version` / `--version`: read `${PLUGIN_ROOT}/.plugin/plugin.json`, print `<name> <version>`, and exit without resolving a project or writing state. If the manifest is missing or malformed, report that the plugin version is unavailable and exit.
- `status`: validate `workingDir`, then execute only Phase 0.0 discovery through the status branch below. Do not run project preflight, create state files, ask for a session, initialize a baseline, or enter the monitor loop.
**Tip — "play around then debug":** Metro persists recent app output in `.powernative/metro-logs/` even across chat/editor restarts. If something weird just happened, keep using the app normally, then run `/debug-app` or `/debug-app "<what you saw>"`. The first cycle reads the latest persisted log window; subsequent cycles read only bytes appended after the saved cursor.
## Core Principles
- **Foreground autonomous loop** — Once started, this skill owns the conversation until `targetCleanCycles` consecutive clean polls confirm the app is healthy, `timeoutSeconds` elapses, the user types `stop`, or the escalation rule trips. **Do not run other skills concurrently** — they'll queue behind the loop.
- **Watch-only means no project mutation** — With `--no-fix`, do not edit project source/configuration, inject diagnostic logs, install packages, regenerate schemas, switch accounts, or invoke a mutating skill. Continue to advance `.powernative/debug-app/` cursor/audit/health state, classify errors, and provide the fix/handoff that would have been used.
- **Run AFTER the app is loaded** — Metro must be running through `npm run dev` and the simulator/device must have the app open. Phase 0 verifies that a live `.powernative` log exists; the skill stops cleanly if no app is detected.
- **Native-only runtime target** — The app must be loaded in a native dev client on a device or simulator; `.powernative/metro-logs/` is the authoritative log source for that native session.
- **No web or direct Metro probes** — Do not use React Native Web, browser automation, `curl`, `fetch`, `WebFetch`, or any direct request to a Metro/localhost endpoint for runtime diagnosis. Read only the `.powernative` log and source files.
- **No screen-by-screen verification** — Do not crawl routes or validate every screen. In symptom mode, focus only on the user-reported workflow and the terminal/source evidence needed to diagnose it.
- **One fix at a time** — Fully resolve one issue (context → fix → type-check → reload → re-poll) before starting the next. No batching.
- **Working-dir state** — Debug audit state lives in `.powernative/debug-app/`: `fixes.md`, `unresolved.md`, `injected-logs.md`, `health.json`, and `metro-cursor.json`. Metro logs live in the already-ignored `.powernative/metro-logs/`. Both survive chat/editor restarts without binding state to a specific agent host.
- **Defense-in-depth redaction** — The Metro logger removes credential-like lines before writing `.powernative`, but `/debug-app` must independently minimize and sanitize every value before persisting it to `.powernative/debug-app/`. Never copy raw response bodies, record objects, tokens, headers, trace payloads, or absolute home-directory paths into debugger state.
- **The port is the log identity** — the dev-server port is the number the QR encodes, the device dials, and this skill verifies. Liveness is a socket probe (does the log's PID still hold that port?), never terminal scrollback. A `port-taken` status means the log belongs to a dead session and must not be diagnosed.
- **Never fix history** — the log is a record of the past, so an error in it is not proof of a current problem. Errors found in the baseline window must pass the Phase 0.2.1 supersession check before any code is edited. Editing working code to chase an already-resolved error is a worse outcome than reporting nothing.
- **First-party native packages are an immutable boundary** — a frame under `node_modules/@microsoft/power-apps-native-*` is evidence, not proof, of package ownership. First rule out invalid app inputs, unsupported configuration, and misuse of the package's documented API. Once the evidence confirms a defect inside an installed `@microsoft/power-apps-native-*` package, do not apply a customer-project workaround: do not edit `node_modules/`, create a `patch-package` patch or postinstall rewrite, vendor or fork package source, redirect the package through Metro/Babel/TypeScript aliases, or replace its dependency with a git, tarball, or local fork. Record a sanitized package-defect report and route the user to `/report-issue`.
- **Host terminal APIs are optional only** — If the current host already exposes Metro terminal output, it may be consulted as a low-latency convenience. Never ask the user for a terminal ID, never persist one, and never make a diagnosis from host output without advancing the authoritative `.powernative` log cursor too.
- **Context-first diagnosis** — Read `memory-bank.md` when present. Read `power.config.json` for environment, Dataverse, and connector context. Consult `native-app-plan.md` only when the failure concerns a planned screen, data model, connector, offline profile, or native capability; do not parse it for unrelated syntax/runtime errors.
- **Reference resolution order** — For Dataverse/Power Platform errors: read [skills/add-dataverse/references/dataverse-reference.md](${PLUGIN_ROOT}/skills/add-dataverse/references/dataverse-reference.md) first, inspect generated services/models and project context, then query `mcp__plugin_mobile-app_microsoft-learn__microsoft_docs_search` when behavior remains uncertain. For Expo/Expo Router/React Native errors: inspect installed versions and project code first, then use targeted `WebFetch` against official `https://docs.expo.dev/` documentation. Use package documentation next and general web search only as a last resort.
## Workflow — Task List First
Before entering the monitor loop, write a task list and keep it up to date:
```
- [ ] Discover valid `.powernative` Metro sessions and select one when needed
- [ ] 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)
```
Mark each cycle complete (clean OR fixed) before starting the next.
---
## Phase 0 — Startup Check
Before entering the loop:
### 0.preflight Read project context
Follow the shared instructions before diagnosing logs:
Use `workingDir` from argument parsing as `<working_dir>` throughout this workflow. It is already validated as an app root; do not derive it again from a later phase or silently switch projects.
1. If `<working_dir>/memory-bank.md` exists, read its Project facts, Power Platform context, Data model, Connectors, Screens, Native capabilities, and Build history. Do not create a memory bank from `/debug-app`; absence is valid.
2. Read `<working_dir>/power.config.json` when present. Capture `environmentId`, `databaseReferences`, and `connectionReferences`. `power.config.json` is the preferred environment source.
3. Read `<working_dir>/native-app-plan.md` only when the supplied symptom or a classified error concerns a planned screen, Dataverse entity/column, connector, offline behavior, navigation contract, or native capability. Treat the plan as intended design, not proof of live schema/runtime state.
4. Inspect `src/generated/services/`, `src/generated/models/`, and `src/generated/connectorSchemas.ts` only when the affected data path uses them. Never edit generated files.
Append a compact context line to `fixes.md` after Phase 0.1 creates it:
```text
[<HH:MM:SS>] Context — memory-bank=<present|absent> native-plan=<present|absent|not-needed> environment=<environmentId|none> dataverse-tables=<N> connectors=<N>
```
Do not resolve the environment or call Dataverse during ordinary bundle, React, or local JavaScript failures. When a current symptom/error is Dataverse or Power Platform related, perform the read-only Dataverse diagnostic sequence in D2 before handing off.
### 0.0 Discover and select the project-local Metro session
Use the validated `<working_dir>` from argument parsing. Enumerate **all** matching logs, newest first:
```bash
LOG_DIR="<working_dir>/.powernative/metro-logs"
ls -t "$LOG_DIR"/metro-*-pid-*-port-*.log 2>/dev/null
```
For every candidate, parse:
- `pid` and `port` from `metro-<timestamp>-pid-<pid>-port-<port>.log`;
- `startedAt` from the filename timestamp;
- `project` from `power.config.json.appDisplayName`, falling back to the basename of `<working_dir>`;
- `platform` by scanning only the latest 64 KiB for the most recent `iOS ... Bundled` / `iOS Bundling` or `Android ... Bundled` / `Android Bundling` line; use `unknown` when neither appears.
Check every recorded PID with a cross-platform Node probe: `process.kill(pid, 0)` means alive; treat `EPERM` as alive and other errors as gone. When `port` is numeric, verify ownership with `lsof -nP -iTCP:<port> -sTCP:LISTEN -t` on macOS/Linux when available, or `netstat.exe -ano -p tcp` on Git Bash/Windows. On Linux without `lsof`, use `ss -ltnp` when available. A candidate is **valid/live** only when its PID is alive and either owns the recorded port, has `port=unknown`, or no supported port-ownership probe exists. A probe that runs and reports a different PID is a contradiction, not an unavailable probe. Do not persist terminal IDs or depend on terminal output.
Selection rules:
- Apply `portFilter` and `platformFilter` to the valid/live candidates before selection. When no valid session matches, print the filters plus the available valid sessions and stop. If a platform-filtered candidate is `unknown`, do not guess; tell the user to use `--port` or omit `--platform`.
- **One valid session** — select it automatically.
- **Multiple valid sessions** — show one choice per session in this exact shape, then ask which to monitor:
```text
<project> — <platform> — port <port> — pid <pid> — started <startedAt>
```
Use `AskUserQuestion`; identify choices internally by the complete `logPath` + `pid` + `port`, not by port alone. If `metro-cursor.json` points to one of the valid sessions, make that session the default choice but still ask. Never silently choose the newest session when more than one is live.
- **No valid sessions** — apply the failure branches below. Stale or contradictory logs are evidence for the report, never selectable choices.
**`status` branch — return before selection:** After filtering and liveness checks, read `.powernative/debug-app/metro-cursor.json` only when it is nonempty valid JSON with string `logPath`, numeric `pid`, and numeric-or-`unknown` `port`. Treat a missing, empty, malformed, or shape-invalid cursor as absent and print a warning; never overwrite it from `status`. Print every valid session newest-first using the normal session display shape, marking `[saved]` only when `logPath` + `pid` + `port` match the valid cursor. Then print at most the last 20 nonempty lines from `fixes.md` and `unresolved.md`, passing the displayed text through the persistence redactor first. Print stale/contradictory candidate counts without diagnosing their log content, then exit. When no valid session exists, print that result plus the applicable no-log/stale reason and exit.
For monitoring modes only, select a session using the rules above. After selection, set `LOG_PATH`, `pid`, `port`, `project`, `platform`, and `startedAt` from the chosen candidate. This selection is sticky for the current monitor run.
When no log exists, inspect `metro.config.js` and resolve the logging module from `<working_dir>` before choosing a failure branch:
- **Current factory form:** the config uses `createPowerAppsMetroConfig` from `@microsoft/power-apps-native-host/config/metroConfig`, and that exact subpath resolves from the project's installed dependencies. The factory installs project-local logging internally.
- **Legacy direct form:** the config directly imports `@microsoft/power-apps-native-host/metro-logger`, and that exact subpath resolves from the project's installed dependencies.
- **Unavailable:** neither configured subpath resolves, the referenced form is absent, or `@microsoft/power-apps-native-host` is missing from `package.json`. A dependency version string alone does not prove that the configured export exists.
Branch as follows:
| Status | Meaning | Action |
|---|---|---|
| Exactly one valid log exists, PID is alive, and either port is unknown or the port probe is unavailable | Select it. Capture `project`, `platform`, `startedAt`, `port`, `pid`, and `logPath`, then continue. |
Voir sur GitHub