From rn-dev-agent
Authors reusable Maestro actions by recording or hand-writing parameterized YAML flows with M7 headers. Dedups first, grounds selectors in evidence, diagrams, validates, then promotes for replay.
How this skill is triggered — by the user, by Claude, or both
Slash command
/rn-dev-agent:creating-actionsThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
An **action** is a parameterised Maestro flow at `<project>/.rn-agent/actions/<id>.yaml` with an M7 metadata header, replayable via `/rn-dev-agent:run-action` or `cdp_run_action` (auto-repair-aware). A well-authored action turns a ~minutes interactive walk into a ~seconds deterministic replay. Authoring one well means: **dedup first, ground every selector in evidence, design the flow as an ASCI...
An action is a parameterised Maestro flow at <project>/.rn-agent/actions/<id>.yaml with an M7 metadata header, replayable via /rn-dev-agent:run-action or cdp_run_action (auto-repair-aware). A well-authored action turns a ~minutes interactive walk into a ~seconds deterministic replay. Authoring one well means: dedup first, ground every selector in evidence, design the flow as an ASCII diagram before writing YAML, validate, then replay to promote.
/rn-dev-agent:test-feature verification passed and the walk should be persisted.When NOT to author an action:
maestro_run with inlineYaml and throw it away.appId by contract.Before authoring anything, check what already exists:
node "${CLAUDE_PLUGIN_ROOT}/scripts/learned-actions.mjs" --json --section b \
--workspace-root "$PWD" --memory-cwd "$PWD" --filter <keyword>
(or /rn-dev-agent:list-learned-actions <keyword>). If a match covers the goal, replay it. If a near-match exists (same flow, hardcoded values), parameterise THAT action with ${VAR} placeholders rather than creating a sibling — duplicate actions rot independently and split the repair history.
| Situation | Path |
|---|---|
| About to walk the flow live on a device anyway | Recorder: cdp_record_test_start → drive UI (cdp_interact / device_*) → cdp_record_test_stop → cdp_record_test_save_as_action (writes header + sidecar; pass intent/tags/mutates/produces yourself — the recorder cannot infer them) |
| Flow and selectors already known (prior exploration, existing test) | Direct authoring — Steps 2–6 below |
| Structured steps in hand, want generated YAML | maestro_generate — then verify the M7 header per Step 4 and continue with Steps 5–6 |
The recorder path still benefits from Steps 3 (diagram) and 5–6 (validate, replay to promote): add the diagram to the generated YAML's header before first replay.
Collect evidence for every element the flow will touch:
cdp_component_tree(filter="<screen-or-component>") per screen — filtered, never the full treedevice_snapshot for what is actually on the native screengrep -r 'testID=' src/ for static discoverycdp_nav_graph / cdp_navigation_state for exact route names (used in expectedRouteSequence)If an element the flow needs has no testID, stop and add one to the app source first (see the rn-testing skill for testID conventions). Text-based selectors break on i18n and copy edits; an action built on them generates repair churn.
Map the flow screen-by-screen with the exact selectors. This is the design-review artifact: parameter gaps, missing assertion anchors, and wrong start-state assumptions are cheap to fix here and expensive to fix after the YAML exists.
Canonical format — one [RouteName] box line per screen, │-arrow lines for interactions, each labelled with the exact selector; one anchor (the assertVisible proving arrival) per screen; ${PARAMS} marked where caller data flows in:
[any screen]
│ launchApp (stopApp: false)
│ tapOn tab-home
▼
[Home] anchor: product-list
│ scrollUntilVisible product-card-${PRODUCT_ID} (if off-screen)
│ tapOn product-add-btn-${PRODUCT_ID}
▼
[Home] cart-badge increments
│ tapOn tab-cart
▼
[Cart] anchor: cart-list
verify: cart-item-${PRODUCT_ID}
Review the diagram against Step-2 evidence before continuing:
${PARAM}; everything else is fixedEmbed the diagram in the YAML header (below the M7 block) so the action documents itself and repair reviews can see intended structure. Safety rules for embedding — the M7 parser trims each comment line and treats a leading word: value as metadata:
# — a fully blank line ends the header block.[, │, ▼, (, indentation is NOT enough). A line like # status: shows spinner would silently overwrite the action's status metadata.appId: com.example.shop
---
# id: add-product-to-cart
# intent: From any screen, add product PRODUCT_ID to the cart and verify it landed.
# tags: [cart, add, smoke]
# mutates: true
# status: experimental
# params: [PRODUCT_ID]
# appId: com.example.shop
#
# [diagram from Step 3 — every line #-prefixed, glyph-first]
- launchApp:
stopApp: false
- tapOn:
id: "tab-home"
- assertVisible:
id: "product-list"
# ... steps mirror the diagram 1:1
Contract rules (violations break replay, repair, or inventory):
^[a-z0-9][a-z0-9-]*$; file is .rn-agent/actions/<id>.yaml.id, intent, tags, mutates, status are the 5 inventory keys — a missing mutates renders as ? in /list-learned-actions. Always status: experimental at creation; promotion to active is earned by a clean replay, never hand-set. Full field glossary (incl. produces, expectedRouteSequence, author): references/m7-header-reference.md.[A-Z_][A-Z0-9_]*; every ${VAR} in the steps is listed in # params, and vice versa. The inventory scanner counts ${...} occurrences anywhere in the file, comments included — so the diagram may mark real step params as ${PRODUCT_ID}, but prose (e.g. the intent line) uses bare names, and no comment may mention a ${VAR} the steps don't use.launchApp: { stopApp: false } self-bootstrap (works cold or warm, preserves login); conditional prologues via runFlow: { when: { visible: ... } }; waitForAnimationToEnd after transitions; the diagram's anchor assertVisible after each screen change; scrollUntilVisible for potentially off-screen targets.clearState: true on an Expo Dev Client build — it wipes the Metro URL and strands the launcher (GH #8)..rn-agent/state/<id>.state.json) — it is created lazily on first load/replay.Copy-adapt the complete worked example: examples/add-product-to-cart.yaml.
--filter <id> — confirm intent, tags, mutates, status come back exactly as written (not ?). This also proves the embedded diagram didn't corrupt the header.grep -o '\${[A-Z_]*}' <file> over the steps ↔ # params list, both directions.id: in the YAML appears in the Step-2 evidence.check_flow_syntax on the body; otherwise the first replay doubles as the syntax check.Replay through the orchestrator — not raw maestro_run — so the run is recorded and auto-repair-aware:
cdp_run_action({ actionId: "<id>", params: { PRODUCT_ID: "7" }, trigger: "agent" })
experimental → active and materialises the sidecar.cdp_store_state, expect_redux / expect_route / expect_visible_by_testid.mutates: true actions leave residue — clean up between runs or use timestamp-suffixed param values so repeated replays stay deterministic.PRODUCT_ID) before trusting the action.status: experimental and say so explicitly — never hand-promote.After any later auto-repair or manual selector edit, update the embedded diagram to match — a stale diagram misleads the next repair review.
| What | Where / Rule |
|---|---|
| Action file | <project>/.rn-agent/actions/<id>.yaml |
| Sidecar (auto-created) | <project>/.rn-agent/state/<id>.state.json |
| id regex | ^[a-z0-9][a-z0-9-]*$ |
| param key regex | [A-Z_][A-Z0-9_]* |
| Inventory / dedup | scripts/learned-actions.mjs or /rn-dev-agent:list-learned-actions |
| Replay | cdp_run_action / /rn-dev-agent:run-action <id> -e KEY=VAL |
| Lifecycle | experimental → (clean replay) → active; repair demotes back to experimental; deprecated = never replay |
| Repair budget | 3 auto-repairs per rolling 24h per action |
| Mistake | Consequence |
|---|---|
| Skipping the Step-0 inventory scan | Duplicate actions that drift apart; repair history split |
| Inventing a testID that "should" exist | SELECTOR_NOT_FOUND on first replay; wasted repair budget |
Hardcoding values instead of ${PARAMS} | Action only replays one scenario; near-duplicates multiply |
status: active at creation | Unvalidated flow treated as production-quality by replay-first routing |
Diagram line starting with a bare word: | Silently overwrites M7 metadata (e.g. status) |
Blank (non-#) line inside the header | Parser stops early; later M7 keys ignored |
${VAR} in a comment that no step uses | Inventory synthesizes a phantom -e VAR=...; replay pre-flight demands a param the flow ignores |
clearState: true on Dev Client | App strands on the Dev Client launcher (GH #8) |
Raw maestro_run for a saved action | No RunRecord, no auto-repair, no promotion |
| Hand-writing the sidecar | Stale lastSeenMtimeMs → false EXTERNAL_EDIT repair refusals |
references/m7-header-reference.md — every M7 field with semantics, parser behavior, lifecycle transitionsexamples/add-product-to-cart.yaml — complete worked example with embedded diagram/rn-dev-agent:run-action — replay-side pre-flight (mutates confirmation, appId match, param coverage)Provides UI/UX resources: 50+ styles, color palettes, font pairings, guidelines, charts for web/mobile across React, Next.js, Vue, Svelte, Tailwind, React Native, Flutter. Aids planning, building, reviewing interfaces.
Fetches up-to-date documentation from Context7 for libraries and frameworks like React, Next.js, Prisma. Use for setup questions, API references, and code examples.
npx claudepluginhub lykhoyda/rn-dev-agent --plugin rn-dev-agent