* chore(deps): add Renovate config and enforce packageManager pnpm version in CI Refs #1422 Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * chore: bump pnpm to 11.17.0 and format the whole repo with oxfmt format/format:check drop their hand-maintained path list: oxfmt already skips node_modules and honors .gitignore, so the only exclusion list is .oxfmtrc.json ignorePatterns. Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(mutation): accept either quote style in the affected-lane path filter Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * chore(deps): keep fixture-app runtime deps as individual Renovate PRs Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --------- Co-authored-by: Michał Pierzchała <thymikee@gmail.com> Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
8.2 KiB
Contributing
Thanks for your interest in contributing to agent-device.
Development
Requirements:
- Node.js 22+
- pnpm, activated from
package.jsonpackageManager(corepack enable pnpm) - Android SDK tools (
adb) for Android support - Xcode (
simctl/devicectl) for iOS support
Setup:
corepack enable pnpm
pnpm install
package.json packageManager is the single source of truth for the pnpm version. Corepack
activates that exact version locally, and .github/actions/setup-node-pnpm reads the same field
and fails the job when the installed pnpm --version disagrees — so the CI and local package
managers cannot drift apart silently, and the "Ignoring...pnpm" drift warning stays quiet locally.
Build all CLIs and Xcode projects:
pnpm build:all
Apple XCTest builds now share a common helper script. pnpm build:xcuitest:ios and
pnpm build:xcuitest:tvos keep their existing cleanup behavior, while
pnpm build:xcuitest:macos reuses the existing DerivedData by default for faster local
iteration. Set AGENT_DEVICE_IOS_CLEAN_DERIVED=1 when you need a clean macOS runner rebuild.
Before pushing, run the aggregate gate:
pnpm check
That is check:tooling && check:fallow && check:unit, and it is the only command that covers
every non-device CI job. pnpm check:tooling on its own is not the gate — it stops before
check:fallow, so a dead export or a complexity finding your diff introduces still fails CI
after a clean check:tooling run. pnpm test likewise runs the unit projects only. What
pnpm check cannot cover is the device/smoke matrix, which needs real devices.
Run tests:
pnpm test
Targeted checks, while iterating:
pnpm check:quick
pnpm check:unit
pnpm exec vitest run src/compat/maestro/__tests__/replay-flow.test.ts src/compat/__tests__/replay-input.test.ts
Code quality (fallow): CI runs pnpm check:fallow --base "$FALLOW_BASE", a diff-based
audit of dead code, duplication, and complexity in the files your PR changes, compared
against the grandfathered baselines in fallow-baselines/. Locally, pnpm fallow runs
the same kind of audit against origin/main and is expected to pass on a clean tree;
pnpm fallow:all shows the full-project picture, including known legacy findings that
the baselines grandfather, so it reporting issues is normal. CRAP scores depend on
estimated test coverage, so a finding can occasionally be exposed — not introduced — by
your change. Run pnpm fallow:baseline to regenerate the baselines only when you are
intentionally accepting a finding.
pnpm fallow— diff-based audit vsorigin/main(what CI runs, with CI picking the PR base)pnpm fallow:all— full-tree summary, includes grandfathered legacy findingspnpm fallow:baseline— regenerate baselines (only to intentionally accept a finding)
Code quality (production exports): pnpm check:production-exports runs Fallow's native
production graph, which excludes test/story/dev files, and fails when a new export has no
production consumer. This includes the test-only-export bug class that shipped in #1199's first
revision, while also catching exports that are unreachable from every graph. It is intentionally
baseline-free: there is no grandfather file, so a new unused production export fails loudly. Fallow's
ignoreExportsUsedInFile option in the gate's inherited config keeps exports with a real
same-file consumer out of this report without weakening the general Fallow audit.
Fix a finding by wiring the export into production or removing the unnecessary export/code. For
an intentional test seam or other non-production consumer, add a JSDoc @internal tag with a short
justification beside the declaration. An inline
// fallow-ignore-next-line unused-export is not suitable here: the general test-inclusive graph
sees the test consumer and correctly reports that suppression as stale. Production usage reached
only through dynamic property access remains invisible to a static import graph, so register those
exports in .fallowrc.json ignoreExports instead (as with the daemon route handlers loaded
through typeof import()).
Optional device selectors for tests:
ANDROID_DEVICE=Pixel_9_Pro_XLorANDROID_SERIAL=emulator-5554IOS_DEVICE="iPhone 17 Pro"orIOS_UDID=<udid>
Test App and Maestro Compatibility
The Expo test app lives in examples/test-app. Install its dependencies once:
pnpm test-app:install
For Maestro compatibility, we currently have 15 parser/compat unit tests and one
top-level test-app Maestro flow, examples/test-app/maestro/checkout-form.yaml,
which includes examples/test-app/maestro/helpers/open-checkout-form.yaml.
Run only the parser/compat tests:
pnpm exec vitest run src/compat/maestro/__tests__/replay-flow.test.ts src/compat/__tests__/replay-input.test.ts
Run the Expo test-app flow on iOS:
pnpm test-app:ios -- --device "iPhone 17 Pro"
pnpm ad --session test-app-maestro open "Agent Device Tester" --platform ios --device "iPhone 17 Pro"
pnpm ad --session test-app-maestro wait "Agent Device Tester" 30000 --platform ios --device "iPhone 17 Pro"
pnpm test-app:maestro:ios -- --session test-app-maestro -- --device "iPhone 17 Pro"
pnpm test-app:ios keeps Metro in the foreground after launching the app. Leave
that terminal running and run the agent-device and Maestro commands from a
separate terminal.
When targeting a specific Android emulator or device, build and install the development client on that same target before running Maestro:
pnpm test-app:android -- --device "$ANDROID_DEVICE"
pnpm test-app:maestro:android -- --session test-app-maestro -- --device "$ANDROID_DEVICE"
Guidelines
- Keep dependencies minimal.
- Preserve the CLI’s agent-friendly JSON output.
- Ensure tests open and close sessions explicitly.
- Add/adjust integration tests when introducing new commands.
- Prefer built-in Node APIs over new packages.
Conservative Code Comments
When code deliberately chooses a slower or more conservative path, leave a short inline comment at the decision site. The comment should name:
- the failure or regression the conservative path prevents; and
- the condition that should trigger a revisit.
Use a grep-able CONSERVATIVE: prefix when the choice is expected to outlive the current change.
This applies to defensive fallbacks, temporary guards, disabled fast paths, serialization, retries,
over-preservation, and teardown-to-be-safe behavior.
Examples:
// CONSERVATIVE: Keep the preflight for non-allowlisted runner commands because only the
// allowlist has proven healthy-mutation recovery. Revisit when lifecycle status coverage can
// distinguish every mutating command's terminal state.
// CONSERVATIVE: Preserve external runner artifacts because the checkout does not own their cache
// root. Revisit only if external artifacts get an ownership marker that makes cleanup safe.
Dependency Updates
Renovate (.github/renovate.json) proposes dependency updates: weekly lockfile maintenance, one
grouped PR for devDependencies, one PR per runtime dependency, and digest bumps for GitHub Actions.
Security updates are enabled explicitly (vulnerabilityAlerts plus osvVulnerabilityAlerts) and
are the only updates exempt from the 7-day minimum release age.
Renovate PRs are gated exactly like human PRs: automerge is off everywhere, so every branch needs
a green CI run and a human review before merge. Review one the way you would review any dependency
change — read the release notes in the PR body, and check that the affected-check plan for the diff
(pnpm check:affected --base origin/main --run, which fails open to the full set for lockfile and
workflow changes) is green. A green Renovate PR is a merge candidate, not a merge: no rubber-stamp
automerge path exists, and none should be added without also deciding which gate is trusted to
replace the reviewer.
Issue Labels
Issue labels describe workflow state, not who will do the work. See
docs/agents/triage-labels.md for the label meanings and state flow.
Reporting issues
Please include:
- OS and Node version
- Xcode/Android SDK versions (if relevant)
- Exact command and output
Thanks for helping improve agent-device.