


Change a Device Automation Tool and Check It on Real Devices
Use when an AI changes a device automation tool that runs on several platforms. It keeps changes small, keeps command info in one registry, picks tests that fit the change, and checks behavior on real devices or simulators with fresh builds before reporting.
To use this AI rule
## Classify the Request and Read Only the Code That Owns the Change
Classify the request first. An information-only review, triage, or question is read-only unless edits or execution are explicitly requested. A code-change request authorizes a minimal implementation and the checks required for that surface.
State assumptions and define observable success before editing. Read the durable domain vocabulary, applicable architecture decisions, and the owning configuration or contract. Do not read the whole repository by default.
Start with a bounded search:
- Locate the symbol, command, flag, or error with a fast text search.
- Read the owning module, one shared helper, and one downstream caller or adapter.
- For a large file, find the relevant type or function before opening a bounded range.
- Trace imports and tests outward only when the evidence requires it.
Keep the change inside one command family, platform seam, or module group unless the task explicitly crosses boundaries. Do not inspect two platform implementations merely because both exist. If scope must expand, stop and confirm the new boundary.
Decide documentation, help, schema, and skill impact before coding. Prefer durable source-of-truth pointers over file inventories that drift.
## Keep Each Command's Name and Options in One Registry
Give every command one canonical descriptor that owns its identity, capabilities, timeout policy, batch policy, and public projections. Keep daemon or service routing in a route registry, interaction-path guarantees in a typed matrix, and command names in one catalog. Do not recreate string sets inside handlers.
Keep top-level routers thin and orchestration-only. Route to an owning handler family; do not accumulate command logic in the entrypoint. Capability decisions belong in the capability source of truth, not scattered conditionals.
Let each public option flow only through layers that understand it:
1. Define the public flag and grammar.
2. Add it to the owning command metadata and schema.
3. Parse it into command input.
4. Project it into a service request only if runtime execution needs it.
5. Extend typed clients and normalizers only when those interfaces expose it.
6. Thread it into dispatch and platform modules only after the public contract proves it belongs there.
7. Classify every public option in the architecture-progress or coverage model.
Centralize selector parsing, matching, resolution, and predicate evaluation. An element-targeting action should accept stable selectors or recorded references, preserve its selector chain, and surface replay divergence as a suggestion rather than silently rewriting user state.
## Check That Fast and Cached Paths Act Like the Main Path
Assume guarantees erode at path boundaries. When adding a fast path, delegated path, cache path, or platform-specific path, classify every affected command-and-path cell before claiming parity.
A registry entry is not semantic proof. Read the implementation and verify it satisfies the guarantee's definition. A fallback that delegates only after an error does not prove success-path parity; the fast path may accept an input the shared path would reject.
Before measuring an experiment, prove that each arm can execute. Two green runs are meaningless when the changed branch never fired. Record the path, backend, reason code, and other typed evidence needed to show which implementation handled the request.
Use structured signals rather than parsing human messages. Branch on reason codes, typed details, and explicit timeout values; do not copy error-text sniffing. Treat any unavoidable message match as documented debt at the decision site.
Compose warnings through the shared response builder instead of overwriting earlier warnings. Store response-wide metadata once rather than repeating bytes for every node or item.
Before preserving a compatibility field, determine whether it shipped. An unreleased surface can be removed cleanly; a released surface needs migration and compatibility handling.
## Keep Files Small and Mirror the Source Layout in Tests
Use `unknown` only at trust boundaries such as parsed input, runtime payloads, caught errors, and generic I/O. Validate once, then narrow to a domain type or focused parser instead of carrying uncertainty through internal helpers and formatters.
Prefer type-level completeness checks when the language can make missing cases impossible. Colocate machine-readable claims with the code they describe: registry cells beside enforcement pointers, coverage manifests beside contract tests, and decision comments at the branch they justify.
Keep each module responsible for one question. As a practical tripwire, target roughly 300 lines for implementation files; after 500, improve locality before adding behavior; beyond 1,000, treat the file as architecture debt unless it is generated data or a fixture snapshot.
Name files after the domain concept they answer. Use package-boundary barrels only; internal barrels add navigation hops. Extract a deep module when it improves conceptual ownership, not merely to reduce line count.
Mirror source topology in tests. When a source module splits, split its tests in the same change. Put reusable fixtures in a named sibling fixture module rather than duplicating large inline objects. Do not add production exports solely to make a private detail easy to test.
Before finalizing, tighten the touched and directly adjacent code: remove obsolete branches, redundant tests, unused imports, stale fixtures, and duplication made unnecessary by the change. Avoid unrelated cleanup.
## Close Every Device Session and Keep Logs Apart from Diagnostics
Route process execution through the shared process abstraction. Extend that abstraction when a capability is missing instead of importing low-level process primitives throughout the codebase.
Treat every manually opened device session as a resource. Record its session name, platform, device identifier, and state directory, then close it with the same values before finishing—even after a failed experiment. Stop only exact stale processes that belong to the verification run; never kill broad process groups by guess.
Keep application logs, runner output, and request diagnostics distinct. One backend should own application-log capture. Store request diagnostics under the resolved session and request identifier; reserve top-level daemon logs for lifecycle and failures that happen before session resolution.
Centralize diagnostic scopes, timers, event emission, redaction, and artifact-path construction. Do not hand-build log paths or add ad hoc files when the diagnostics service owns that data.
Normalize user-facing failures into a stable payload with code, concise message, recovery hint, diagnostic identifier, log path, and typed details. Preserve those fields when wrapping errors. State what failed, why when known, and the next recovery action.
Treat optional probes, cache checks, and preflights as best-effort unless the feature contract makes them mandatory. Give them a shorter timeout than the required operation and fall back to the established path when they fail or return an unusable shape.
## Pick the Tests That Match What Changed
Start with the repository's affected-check planner when available. It should derive focused tests and remaining gates from canonical metadata rather than from a handwritten list.
Map checks to the changed surface:
- Any typed source change requires type checking and formatting.
- Toolchain or compiler configuration requires the tooling gate and declaration build.
- Shared service or handler changes require the focused unit bundle.
- Response-shape or provider-boundary changes require provider integration and coverage checks.
- Native runner changes require rebuilding the native test artifact.
- Cross-platform behavior requires the cross-platform integration suite.
- Documentation-only work needs runtime tests only when a more specific contract says so.
Do not run broad integration tests by default when focused evidence is sufficient. Conversely, do not call a device-facing change ready from unit tests alone.
Keep tests behavioral. Do not assert what static types already prove. Unit tests must not wait real time; inject a time budget, derive cadence from it, or assert that the budget was wired correctly.
When an enforcement gate finds an unclassified flag, missing guarantee cell, uncovered scenario, response-construction bypass, or rule-parity gap, classify or cover the new behavior. Never suppress the gate or add an unexplained allowlist entry.
## Rebuild Before Checking on Real Devices or Simulators
Before manual verification, rebuild every artifact that the runtime actually consumes, restart services that do not hot-reload, and invalidate or replace any adopted runner that can keep serving an older native binary. A test against stale output is a false negative, not product evidence.
Prove which backend handled the run. Capture a machine-readable snapshot or status response and verify the backend identifier and helper version against the current package. A silent fallback is valid evidence only when fallback behavior is the feature under test.
Use different devices, simulators, or emulators for concurrent worktrees because isolated daemon state does not isolate physical targets. Follow the test application's own setup guide; an installed application is not enough unless build, development-server, and surface checks prove the intended version is running.
Classify environment noise before debugging product code. Reproduce contention failures in isolation and on the unchanged base under the same load. A changing failure set that passes alone is likely contention; a stable failure in the changed path is stronger regression evidence.
If a sandbox prevents a required local listener, device bridge, or native tool, rerun through the approved elevated path rather than diagnosing the sandbox denial as a product bug. If live verification remains blocked, report the exact device, permission, command, or service needed and retain the residual risk.
## Review the Real Command Route and Report with Device Results
Review against the motivating issue and accepted architecture decisions, not only the diff. Trace the production route from command surface through request routing to the platform backend. A test that bypasses the router or mocks the critical seam does not prove shipped behavior.
For every key regression test, identify the old implementation, deletion, or revert that should make it fail. If the test stays green after the bug returns, it is vacuous and must be strengthened.
Separate intended refactoring from hidden behavior changes in output shape, warning propagation, error details, artifact paths, retry tiers, and fallbacks. Verify sibling changes and branch ordering when the implementation depends on another change.
Gate external publication or push behind a single success chain so it cannot run after a failed format, type, lint, or test command. Keep commits and pull requests scoped, use the project's conventional title style, and open a ready review by default only when the work is complete.
Summarize user-visible behavior, meaningful validation, live-device evidence, and real tradeoffs. Do not call CI green from a local unit run, and do not call a platform change merge-ready without current live evidence. State docs or help impact, touched scope, any unverified path, and the exact blocker.
Before handoff, close every session and account for every resource opened during verification. Report any session, process, state directory, or artifact that could not be cleaned up.
