agentic-team 0.0.0-stage → 6.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +189 -2
  3. package/bin/build-template.js +208 -0
  4. package/bin/index.js +1334 -0
  5. package/bin/platforms.js +510 -0
  6. package/package.json +41 -4
  7. package/template/.claude-plugin/plugin.json +10 -0
  8. package/template/.orchestra/README.md +391 -0
  9. package/template/.orchestra/blueprints/README.md +87 -0
  10. package/template/.orchestra/blueprints/api-only.md +46 -0
  11. package/template/.orchestra/blueprints/component-crud-resource.md +25 -0
  12. package/template/.orchestra/blueprints/saas-starter.md +69 -0
  13. package/template/.orchestra/config.yml +293 -0
  14. package/template/.orchestra/roles/orchestrator.md +105 -0
  15. package/template/.orchestra/roles/product-manager.md +439 -0
  16. package/template/.orchestra/scripts/compact-diff.awk +45 -0
  17. package/template/.orchestra/scripts/context.mjs +373 -0
  18. package/template/.orchestra/stacks/README.md +81 -0
  19. package/template/.orchestra/stacks/_lib/common.sh +3095 -0
  20. package/template/.orchestra/stacks/_repo/setup.sh +1113 -0
  21. package/template/.orchestra/stacks/backend-hono/claude/commands/endpoint.md +13 -0
  22. package/template/.orchestra/stacks/backend-hono/claude/skills/api-endpoint/SKILL.md +111 -0
  23. package/template/.orchestra/stacks/backend-hono/claude/skills/testing/SKILL.md +75 -0
  24. package/template/.orchestra/stacks/backend-hono/setup.sh +5945 -0
  25. package/template/.orchestra/stacks/backend-hono/stack.md +195 -0
  26. package/template/.orchestra/stacks/mobile-android-native/claude/commands/component.md +13 -0
  27. package/template/.orchestra/stacks/mobile-android-native/claude/skills/design-component/SKILL.md +58 -0
  28. package/template/.orchestra/stacks/mobile-android-native/setup.sh +607 -0
  29. package/template/.orchestra/stacks/mobile-android-native/stack.md +138 -0
  30. package/template/.orchestra/stacks/mobile-expo/claude/commands/component.md +12 -0
  31. package/template/.orchestra/stacks/mobile-expo/claude/skills/design-component/SKILL.md +92 -0
  32. package/template/.orchestra/stacks/mobile-expo/setup.sh +470 -0
  33. package/template/.orchestra/stacks/mobile-expo/stack.md +141 -0
  34. package/template/.orchestra/stacks/mobile-ios-native/claude/commands/component.md +12 -0
  35. package/template/.orchestra/stacks/mobile-ios-native/claude/skills/design-component/SKILL.md +58 -0
  36. package/template/.orchestra/stacks/mobile-ios-native/setup.sh +468 -0
  37. package/template/.orchestra/stacks/mobile-ios-native/stack.md +130 -0
  38. package/template/.orchestra/stacks/web-nextjs/claude/commands/component.md +12 -0
  39. package/template/.orchestra/stacks/web-nextjs/claude/skills/design-component/SKILL.md +100 -0
  40. package/template/.orchestra/stacks/web-nextjs/claude/skills/design-tokens/SKILL.md +61 -0
  41. package/template/.orchestra/stacks/web-nextjs/setup.sh +128 -0
  42. package/template/.orchestra/stacks/web-nextjs/stack.md +170 -0
  43. package/template/.orchestra/stacks/web-vite-react/claude/commands/component.md +12 -0
  44. package/template/.orchestra/stacks/web-vite-react/claude/skills/design-component/SKILL.md +100 -0
  45. package/template/.orchestra/stacks/web-vite-react/claude/skills/design-tokens/SKILL.md +61 -0
  46. package/template/.orchestra/stacks/web-vite-react/setup.sh +185 -0
  47. package/template/.orchestra/stacks/web-vite-react/stack.md +177 -0
  48. package/template/.orchestra/trackers/github.sh +467 -0
  49. package/template/CLAUDE.md +54 -0
  50. package/template/agents/architect.md +83 -0
  51. package/template/agents/engineer-high.md +17 -0
  52. package/template/agents/engineer-low.md +17 -0
  53. package/template/agents/engineer-max.md +17 -0
  54. package/template/agents/engineer-medium.md +17 -0
  55. package/template/agents/engineer-xhigh.md +17 -0
  56. package/template/agents/lead.md +925 -0
  57. package/template/agents/milestone-reviewer.md +65 -0
  58. package/template/agents/orchestrator.md +105 -0
  59. package/template/agents/product-manager.md +439 -0
  60. package/template/agents/re-reviewer.md +25 -0
  61. package/template/agents/reviewer.md +303 -0
  62. package/template/agents/scaffolder.md +206 -0
  63. package/template/agents/tracker.md +202 -0
  64. package/template/commands/blueprint.md +23 -0
  65. package/template/commands/create-role.md +64 -0
  66. package/template/commands/export.md +45 -0
  67. package/template/commands/help.md +56 -0
  68. package/template/commands/hotfix.md +14 -0
  69. package/template/commands/orchestrator.md +12 -0
  70. package/template/commands/pm.md +18 -0
  71. package/template/commands/rewind.md +63 -0
  72. package/template/commands/setup.md +15 -0
  73. package/template/commands/start.md +29 -0
  74. package/template/commands/status.md +11 -0
  75. package/template/commands/sync.md +76 -0
  76. package/template/commands/verifier.md +53 -0
  77. package/template/hooks/audit-context.sh +166 -0
  78. package/template/hooks/enforce-role.sh +193 -0
  79. package/template/hooks/protect-system-files.sh +123 -0
  80. package/template/hooks/review-rounds.sh +172 -0
  81. package/template/rules/acceptance-check.orchestra.md +11 -0
  82. package/template/rules/analytics-kpi.orchestra.md +56 -0
  83. package/template/rules/code-standards.orchestra.md +35 -0
  84. package/template/rules/comments.orchestra.md +77 -0
  85. package/template/rules/commit-format.orchestra.md +20 -0
  86. package/template/rules/dependency-upgrade.orchestra.md +112 -0
  87. package/template/rules/design-parity.orchestra.md +181 -0
  88. package/template/rules/observability.orchestra.md +121 -0
  89. package/template/rules/performance.orchestra.md +190 -0
  90. package/template/rules/phase-limits.orchestra.md +3 -0
  91. package/template/rules/pull-request.orchestra.md +218 -0
  92. package/template/rules/role-boundaries.orchestra.md +132 -0
  93. package/template/rules/security.orchestra.md +106 -0
  94. package/template/rules/stuck-detection.orchestra.md +25 -0
  95. package/template/rules/testing-standards.orchestra.md +219 -0
  96. package/template/rules/tracker-authority.orchestra.md +198 -0
  97. package/template/rules/verification-gate.orchestra.md +56 -0
  98. package/template/skills/accessibility/SKILL.md +31 -0
  99. package/template/skills/android-testing/SKILL.md +112 -0
  100. package/template/skills/architecture/SKILL.md +175 -0
  101. package/template/skills/auth-setup/SKILL.md +105 -0
  102. package/template/skills/best-practices/SKILL.md +29 -0
  103. package/template/skills/code-optimizer/SKILL.md +27 -0
  104. package/template/skills/core-web-vitals/SKILL.md +32 -0
  105. package/template/skills/crud-api/SKILL.md +32 -0
  106. package/template/skills/debug/SKILL.md +28 -0
  107. package/template/skills/deployment/SKILL.md +104 -0
  108. package/template/skills/error-handling/SKILL.md +154 -0
  109. package/template/skills/frontend-design/SKILL.md +30 -0
  110. package/template/skills/fullstack-infrastructure/SKILL.md +108 -0
  111. package/template/skills/ios-testing/SKILL.md +100 -0
  112. package/template/skills/mobile-testing/SKILL.md +102 -0
  113. package/template/skills/observability/SKILL.md +91 -0
  114. package/template/skills/project-structure/SKILL.md +114 -0
  115. package/template/skills/push-notifications/SKILL.md +146 -0
  116. package/template/skills/react-best-practices/SKILL.md +31 -0
  117. package/template/skills/realtime/SKILL.md +146 -0
  118. package/template/skills/review/SKILL.md +82 -0
  119. package/template/skills/testing/SKILL.md +45 -0
  120. package/template/skills/web-quality-audit/SKILL.md +47 -0
  121. package/template/skills/web-testing/SKILL.md +120 -0
@@ -0,0 +1,154 @@
1
+ ---
2
+ name: error-handling
3
+ description: "What to do when something fails — typed errors, propagation, what the user sees, and degrading instead of crashing. Use for any phase that adds a failure path, a catch, or an error response."
4
+ ---
5
+
6
+ # Skill: Error Handling
7
+
8
+ The rule is in `.claude/rules/observability.orchestra.md` and a check
9
+ enforces it: **a catch either handles the error meaningfully or reports it and
10
+ rethrows.** `scripts/policy-check.mjs` fails the gate on an empty catch and on
11
+ a catch whose only body is a log.
12
+
13
+ That check proves a catch is not empty. It cannot tell whether the user got a
14
+ correct outcome. This skill is about the part that needs a person.
15
+
16
+ ## The fact everything else follows from
17
+
18
+ **"Handled" means the caller gets a correct outcome — not that the crash was
19
+ hidden.**
20
+
21
+ ```ts
22
+ try {
23
+ await chargeCard(order);
24
+ } catch (e) {
25
+ reportError(e);
26
+ return { ok: true }; // the gate passes. the customer was not charged.
27
+ }
28
+ ```
29
+
30
+ Reported, rethrown-shaped, and wrong. Nothing downstream can tell the
31
+ difference between a payment that succeeded and one that vanished. Deciding
32
+ which of those two this is, is the entire job.
33
+
34
+ ## Three kinds of failure, three different answers
35
+
36
+ | Kind | Example | Answer |
37
+ |---|---|---|
38
+ | **Expected** | invalid input, not found, conflict | Not exceptional. Return it as a value or throw a typed error the boundary maps to a status. |
39
+ | **Transient** | timeout, 503 from a provider, deadlock | Retry if idempotent, with backoff and a bound. Then give up deliberately. |
40
+ | **A bug** | undefined is not a function, a broken invariant | Do not catch it here. Let it reach the boundary, be reported with its stack, and answer 500. |
41
+
42
+ The most common mistake is treating the third as the first: a broad `catch`
43
+ around a whole handler turns every bug into a polite message and the defect
44
+ is never seen again.
45
+
46
+ ## Typed at the boundary
47
+
48
+ The backend stack ships `AppError` with a closed `ErrorCode` union, and
49
+ `toErrorBody` maps it to a status and a body. Use it.
50
+
51
+ ```ts
52
+ throw new AppError("not_found", "Order not found");
53
+ ```
54
+
55
+ - **Never throw a bare `Error` across a route boundary.** It becomes a 500,
56
+ which tells the caller nothing and pages someone for a 404.
57
+ - **Never let internal detail into the response.** `toErrorBody` returns the
58
+ minimum for anything unrecognised, deliberately — a stack trace in a body
59
+ is an information leak, and the message often contains a query.
60
+ - The code is a **closed union**, not a string. A new failure mode is a new
61
+ member and a new status, decided once, rather than a literal invented at a
62
+ call site.
63
+
64
+ Adding a code is a real decision: it is part of the API contract, and a
65
+ client will branch on it.
66
+
67
+ ## Validate once, at the edge
68
+
69
+ Parse input at the boundary and let everything inside work with a type that
70
+ cannot be invalid. A function that re-checks its arguments is a function that
71
+ does not trust its own signature — and the check will disagree with the
72
+ schema eventually.
73
+
74
+ That is why the boundary owns validation and the interior does not:
75
+ `.claude/rules/security.orchestra.md` puts it in the same place for the same
76
+ reason.
77
+
78
+ ## Keep the cause
79
+
80
+ ```ts
81
+ throw new AppError("internal", "could not load order"); // no
82
+ throw new Error("could not load order", { cause: original }); // yes
83
+ ```
84
+
85
+ Wrapping is fine; discarding is not. The original stack is the only thing
86
+ that says which line failed. When you rethrow, carry the cause; when you
87
+ report, report the cause too.
88
+
89
+ ## What the user sees
90
+
91
+ Two audiences, two texts, and they are rarely the same string.
92
+
93
+ - **The user** gets something actionable: what happened, and what they can do.
94
+ Not an error code alone, and never a stack.
95
+ - **The operator** gets the detail, in the log, with the correlation id — so
96
+ a user quoting `x-request-id` from a bug report leads straight to the line.
97
+ The request logger echoes that header back for exactly this reason.
98
+
99
+ If a screen can fail, it has a failure state, and the failure state has a way
100
+ forward. `.claude/skills/review/SKILL.md` treats a missing one as a finding.
101
+
102
+ ## Degrading instead of failing
103
+
104
+ Some dependencies are not required to answer. Say so explicitly, and say what
105
+ the answer is without them.
106
+
107
+ ```ts
108
+ // Recommendations are a nice-to-have; the page renders without them.
109
+ const recommended = await getRecommendations(userId).catch((e) => {
110
+ report(e);
111
+ return [];
112
+ });
113
+ ```
114
+
115
+ This is the one place an error is swallowed, and it carries a comment saying
116
+ why failure is acceptable — the exception named in the observability rule.
117
+ Two conditions, both required:
118
+
119
+ 1. The feature is genuinely optional to the outcome.
120
+ 2. The failure is still **reported**, so "optional" does not quietly become
121
+ "permanently broken".
122
+
123
+ For anything gating a risky path the fallback is **false** — the same
124
+ direction the feature-flag registry falls, and for the same reason.
125
+
126
+ ## Failure paths are tested, not assumed
127
+
128
+ - Every catch you write has a test that reaches it. An untested error path is
129
+ usually a broken error path — it is the code least likely to have been run
130
+ even once.
131
+ - Provider failures are faked **at the HTTP boundary**, never by mocking your
132
+ own wrapper: the wrapper is where a provider's change bites you, so mocking
133
+ it tests nothing. `.claude/rules/testing-standards.orchestra.md`.
134
+ - The cases worth writing: a 500 from the provider, a timeout, a malformed
135
+ body, and the retry that eventually gives up.
136
+
137
+ Coverage will not tell you these exist. Mutation testing would, which is why
138
+ it is wired even while it waits on the compiler.
139
+
140
+ ## Anti-patterns
141
+
142
+ - **`catch (e) { report(e) }` in a write path.** The caller now believes the
143
+ write happened.
144
+ - **Catching to convert everything to `null`.** The caller has no idea
145
+ whether "nothing" means empty or broken.
146
+ - **One broad try/catch wrapping an entire handler.** It catches your bugs
147
+ along with the expected failures, and turns both into the same response.
148
+ - **Retrying a non-idempotent write.** This is how a customer gets charged
149
+ twice.
150
+ - **A message that names the wrong actor.** "Something went wrong" when the
151
+ input was invalid sends the user looking in the wrong place.
152
+ - **Logging and rethrowing at every level.** One failure, six lines, and none
153
+ of them the one with the stack. Report where you handle it, not on the way
154
+ past.
@@ -0,0 +1,30 @@
1
+ ---
2
+ name: frontend-design
3
+ description: "Building UI that looks deliberate rather than generic — typography, colour, spacing, motion. Use for any phase where visual quality is part of the acceptance criteria."
4
+ ---
5
+
6
+ # Skill: Frontend Design
7
+
8
+ ## When to Use
9
+ Phase involves building UI that needs to look polished, professional, and distinctive — not generic.
10
+
11
+ ## Checklist
12
+ - [ ] **Bold aesthetic direction:** commit to a clear visual concept — not timid, not generic
13
+ - [ ] **Distinctive typography:** avoid overused fonts (Inter, Roboto, Arial) — choose fonts with character
14
+ - [ ] **Cohesive color scheme:** limited palette (3-5 colors) with sharp accent colors
15
+ - [ ] **Intentional layout:** use asymmetry, overlap, or diagonal elements where appropriate — not everything grid-aligned
16
+ - [ ] **Layered shadows:** multiple transparent shadows instead of solid borders (`box-shadow: 0 1px 2px rgba(0,0,0,0.1), 0 4px 12px rgba(0,0,0,0.05)`)
17
+ - [ ] **Concentric border radius:** outer radius = inner radius + padding (nested elements)
18
+ - [ ] **Micro-interactions:** hover states, focus transitions, loading animations — all < 300ms
19
+ - [ ] **Staggered enter animations:** split elements, ~100ms delay between each
20
+ - [ ] **Font smoothing:** `-webkit-font-smoothing: antialiased` for crisp text
21
+ - [ ] **Tabular numbers:** `font-variant-numeric: tabular-nums` for data tables and counters
22
+ - [ ] **Optical alignment:** icons and text optically centered, not geometrically
23
+
24
+ ## Common Mistakes
25
+ - Generic AI-slop aesthetics → every AI tool produces the same look, be distinctive
26
+ - Solid borders everywhere → layered transparent shadows look more refined
27
+ - Geometric centering for icons → optical centering looks correct to the eye
28
+ - Harsh easing functions → use `cubic-bezier(0.4, 0, 0.2, 1)` for natural motion
29
+ - Missing exit animations → enter with animation, exit abruptly looks broken
30
+ - System fonts without thought → typography is 80% of visual quality
@@ -0,0 +1,108 @@
1
+ ---
2
+ name: fullstack-infrastructure
3
+ description: "TypeScript and React conventions that hold in any app — types, hooks, rendering, data flow, performance, i18n. Which libraries, which structure and which tooling are decided by .orchestra/stacks/, not here. Use when writing TypeScript or React in any app."
4
+ ---
5
+
6
+ # TypeScript & React Conventions
7
+
8
+ What is true regardless of which stack scaffolded the app.
9
+
10
+ ## What this file does not decide
11
+
12
+ It used to carry a tech stack table and a monorepo tree of its own, naming an
13
+ ORM, an auth scheme, an HTTP client and a component kit. Every one of those is
14
+ decided somewhere that can enforce it, and a skill restating a choice is a
15
+ second source of truth: whichever the agent reads last wins, and nothing
16
+ notices when they drift apart.
17
+
18
+ | Question | Answered by |
19
+ |---|---|
20
+ | Which libraries, package manager, tooling | `.orchestra/stacks/<stack>/setup.sh` — it installs them |
21
+ | Where a new file goes | `.claude/skills/project-structure/SKILL.md` |
22
+ | Where a component comes from | the web `stack.md`, and `src/components/README.md` |
23
+ | What "handled" means for an error | `.claude/skills/error-handling/SKILL.md` |
24
+ | Testing, commits, security, observability | `.claude/rules/*.orchestra.md` |
25
+
26
+ What the stacks scaffold today, for orientation only: Drizzle rather than
27
+ Prisma, server-side sessions rather than JWT, `fetch` in
28
+ `src/integrations/<provider>/` rather than an Axios wrapper class, feature-first
29
+ `src/features/<domain>/` rather than a layer-first tree.
30
+
31
+ **If this file ever disagrees with a stack, the stack is right** — it is the
32
+ thing that actually ran.
33
+
34
+ ## TypeScript
35
+
36
+ - `strict`, with no per-file exemptions.
37
+ - No `any`. Use `unknown` and narrow it — `any` disables the checker at
38
+ exactly the boundary you added it to guard.
39
+ - `interface` for object shapes, `type` for unions and intersections.
40
+ - Discriminated unions for anything with states. A boolean pair that can
41
+ express `loading && error` is a bug waiting for a screenshot.
42
+ - Explicit return types on exported functions. Inference is fine inside a
43
+ module and a liability across one — a return type that changes silently is
44
+ a breaking change nobody reviewed.
45
+ - Prefer a union of literals to an enum. It erases at compile time and
46
+ narrows without an import.
47
+
48
+ ## React
49
+
50
+ - Function components only.
51
+ - Custom hooks are prefixed `use` — the linter's rules-of-hooks check keys
52
+ off the name, so a mis-named hook is simply unchecked.
53
+ - `useMemo` and `useCallback` where a measurement says they help. Applied by
54
+ reflex they add allocation and a dependency array to get wrong.
55
+ - Composition over prop drilling. Context for what is genuinely ambient
56
+ (theme, locale, session), not to avoid passing two props.
57
+ - Stable keys from the data. An array index makes React reuse the wrong node
58
+ the first time the list reorders, and the bug looks like corrupted state.
59
+ - `React.lazy` with `Suspense` at route boundaries.
60
+
61
+ ## Data flow
62
+
63
+ - **Server cache and client state are different things.** Copying a response
64
+ into a client store gives you two truths and a stale one.
65
+ - Every backend call goes through a wrapper in `src/integrations/<provider>/`
66
+ — never a raw client call from a component. That is where the timeout, the
67
+ retry bound and the error mapping live, and it is the only place a test can
68
+ hold. See `.claude/skills/project-structure/SKILL.md`.
69
+ - Which cache or store library, if any, is the stack's decision. If a project
70
+ adopts one, it goes in the stack's `setup.sh` so it is installed and
71
+ covered — not named here.
72
+
73
+ ## Classes vs functions
74
+
75
+ **Class** when there is lifecycle to hold: an SDK that needs init and
76
+ teardown, or shared configuration across calls.
77
+ **Function** when it is stateless and single-purpose — `formatDate`,
78
+ `slugify`.
79
+
80
+ Prefer writing a small utility to adding a dependency for it. Reach for a
81
+ package when the implementation is genuinely hard — dates, time zones,
82
+ collation — not when it is twelve lines.
83
+
84
+ ## Performance
85
+
86
+ - Lazy load routes and anything heavy below the fold.
87
+ - Virtualize long lists.
88
+ - Debounce or throttle high-frequency events at the source, not in the
89
+ handler.
90
+ - Derive loading and error state from the request, not from booleans set by
91
+ hand in three places.
92
+
93
+ ## Environment variables
94
+
95
+ Validated at startup against a schema, so the process refuses to boot on bad
96
+ configuration rather than failing on the first request that needs it.
97
+
98
+ `.env.example` is **generated from that schema**, never written beside it —
99
+ see `.claude/rules/security.orchestra.md`.
100
+
101
+ ## i18n
102
+
103
+ Ask whether it is needed before the first string is written; retrofitting
104
+ means touching every component.
105
+
106
+ When it is: every user-facing string is a key, namespaced by feature. No
107
+ concatenated sentences — word order is not universal, and a sentence built
108
+ from three fragments cannot be translated correctly into most languages.
@@ -0,0 +1,100 @@
1
+ ---
2
+ name: ios-testing
3
+ description: "Testing strategy for native iOS apps — Swift, SwiftUI, XCTest and XCUITest. Use for any phase that writes or changes tests in a native iOS app."
4
+ ---
5
+
6
+ # Skill: iOS Testing
7
+
8
+ ## The fact everything else follows from
9
+
10
+ **A unit test does not prove the app launches.**
11
+
12
+ An iOS app dies for reasons no XCTest sees: a missing Info.plist key, an
13
+ entitlement the provisioning profile lacks, a `@main` that throws before the
14
+ first view, a bundle resource that was never copied. The suite is green and
15
+ the app crashes on the splash screen.
16
+
17
+ So the launch test is not the top of a pyramid you get to later. It is the
18
+ first test worth writing.
19
+
20
+ ## The layers
21
+
22
+ | Layer | What belongs here | Runs on |
23
+ |---|---|---|
24
+ | Unit | Pure logic and view models — no UIKit, no SwiftUI | Host, fast |
25
+ | View | SwiftUI views: state transitions and the accessibility tree | Simulator |
26
+ | Integration | The real networking stack, stubbed at the transport | Simulator |
27
+ | UI (XCUITest) | 3–5 journeys, one of them launch | Real simulator |
28
+ | Build | `xcodebuild` for a device architecture | The gate |
29
+
30
+ ## Stub the transport, not your client
31
+
32
+ ```swift
33
+ final class StubProtocol: URLProtocol { /* … */ }
34
+
35
+ let config = URLSessionConfiguration.ephemeral
36
+ config.protocolClasses = [StubProtocol.self]
37
+ let session = URLSession(configuration: config)
38
+ ```
39
+
40
+ Register a `URLProtocol` subclass and the real `URLSession` runs — your
41
+ request building, your headers, your decoding, your error mapping.
42
+
43
+ Injecting a `MockAPIClient` instead tests the mock. The client is the thing
44
+ that breaks when the API changes, so it is the thing that must stay in the
45
+ test.
46
+
47
+ ## Query the accessibility tree
48
+
49
+ ```swift
50
+ app.buttons["Add water"].tap() // yes — the accessible name
51
+ app.buttons["btn_add_water"].tap() // no — an identifier nobody hears
52
+ ```
53
+
54
+ XCUITest resolves elements through the accessibility tree, which is the same
55
+ tree VoiceOver uses. Query by label and **a control with no accessible name
56
+ fails the test** — accessibility becomes something the suite enforces.
57
+
58
+ Use `accessibilityIdentifier` only where the visible label genuinely changes
59
+ with locale or data, and keep the label correct anyway.
60
+
61
+ ## Swift Testing for units, XCTest for UI
62
+
63
+ `import Testing` with `@Test` and `#expect` is the current framework for unit
64
+ and view tests — clearer failures, real parameterised cases, native async.
65
+
66
+ XCUITest is still XCTest. Do not try to unify them.
67
+
68
+ Both need `await` discipline: a `@MainActor` view model tested from a
69
+ non-isolated context passes locally and races in CI.
70
+
71
+ ## Never
72
+
73
+ - **Snapshot tests as the main assertion.** A recorded PNG changes with every
74
+ OS point release, gets re-recorded without being read, and passes while the
75
+ button does nothing. Assert state and the accessibility tree instead.
76
+ - Testing that SwiftUI re-renders, or that `NavigationStack` navigates.
77
+ Assert that the action fires with the right argument.
78
+ - Sleeping to wait. `XCTestExpectation` and `waitForExistence(timeout:)`
79
+ exist; a `sleep` is a flake with a delay.
80
+ - A simulator-only definition of done. The simulator has no memory pressure,
81
+ no thermal throttling, no real camera and a different keyboard.
82
+
83
+ ## The build is part of the suite
84
+
85
+ Build for a **device** architecture in the gate, not only the simulator.
86
+ Simulator builds are x86/arm64-host and skip code signing, so a missing
87
+ entitlement, an unsupported architecture or a bad bitcode setting only
88
+ appears when a real build is attempted.
89
+
90
+ A release build additionally runs optimisation and dead-code stripping,
91
+ which is where `#if DEBUG` mistakes surface.
92
+
93
+ ## Release risk
94
+
95
+ An App Store release cannot be rolled back. There is no equivalent of an
96
+ over-the-air JavaScript update unless one was deliberately built.
97
+
98
+ That makes phased release and a server-side kill switch part of the testing
99
+ strategy rather than an operational afterthought — see
100
+ `.claude/rules/observability.orchestra.md`.
@@ -0,0 +1,102 @@
1
+ ---
2
+ name: mobile-testing
3
+ description: "Testing strategy for React Native and Expo apps. Use for any phase that writes or changes tests in a mobile app, or that adds an e2e flow."
4
+ ---
5
+
6
+ # Skill: Mobile Testing
7
+
8
+ ## The fact everything else follows from
9
+
10
+ **Jest does not run React Native.** The `jest-expo` preset runs components in
11
+ Node against mocked native modules.
12
+
13
+ On a backend, an integration test exercises the real thing. Here, the
14
+ equivalent test exercises a simulation. The whole JS suite can be green while
15
+ the app fails to build, crashes on launch, or works on iOS and not Android.
16
+
17
+ So the device layer is not the optional top of a pyramid. It is where the
18
+ risk actually lives.
19
+
20
+ ## The layers
21
+
22
+ | Layer | What belongs here | Runs against |
23
+ |---|---|---|
24
+ | Pure logic | Calculations, conversions, date and streak logic, reducers — extracted **out** of components into plain modules | Node |
25
+ | Components (RNTL) | Render, interact, assert behaviour | Node, **mocked natives** |
26
+ | API seam (MSW) | Our own backend, faked at the network layer | Node |
27
+ | E2E (Maestro) | 5–10 flows, no more | **Real simulator/emulator** |
28
+ | The build | `expo-doctor`, `expo install --check`, a native build | CI |
29
+
30
+ ## Where mobile bugs actually come from
31
+
32
+ | Failure | Caught by |
33
+ |---|---|
34
+ | Does not build, or crashes on launch | the build, then e2e |
35
+ | Native module version off the SDK | `expo install --check` |
36
+ | Fine on iOS, broken on Android | e2e on both |
37
+ | Permission denied, backgrounded mid-request | e2e |
38
+ | Offline or flaky network | MSW |
39
+ | Stored data breaks on app upgrade | a dedicated migration test |
40
+ | Small screen, large accessibility font | e2e or manual |
41
+ | A notification that never arrives | nothing automated — a device |
42
+
43
+ Very little of that column says "component test". Weight the effort
44
+ accordingly.
45
+
46
+ The last row is the honest one: permission, token refresh, the background
47
+ handler and a deep link from a terminated app are all mocked into success by
48
+ `jest-expo`. Depth in `.claude/skills/push-notifications/SKILL.md`.
49
+
50
+ ## Query by role, never by testID
51
+
52
+ ```ts
53
+ screen.getByRole("button", { name: "Add water" }); // yes
54
+ screen.getByTestId("add-water-btn"); // no
55
+ ```
56
+
57
+ RNTL resolves roles from `accessibilityRole` and the accessible name. Query
58
+ that way and **a missing accessibility prop becomes a failing test** —
59
+ accessibility stops being a review checklist item and becomes enforced by the
60
+ suite. A `testID` proves nothing about whether a screen reader can use the
61
+ screen.
62
+
63
+ ## Extract the logic out of the component
64
+
65
+ A component test that exercises clamping, rounding or formatting is testing
66
+ a pure function through three layers of render. Move the function to a plain
67
+ module: it gets faster, it can be mutation-tested, and the component test
68
+ shrinks to "renders what the module returned".
69
+
70
+ ## E2E — Maestro, not Detox
71
+
72
+ Maestro is YAML flows and needs no native build knowledge. Detox is more
73
+ capable and a great deal more setup; not worth it below a large team. Maestro
74
+ is a CLI, installed by its own installer — not an npm package.
75
+
76
+ Keep the suite small and end-to-end in the real sense:
77
+
78
+ 1. launch → sign in
79
+ 2. the core action of the app, once
80
+ 3. the screen reflects it
81
+ 4. kill and relaunch — the data is still there
82
+
83
+ Step 4 catches storage and migration bugs that nothing else sees.
84
+
85
+ ## Never
86
+
87
+ - Snapshot tests. Huge, meaningless, and "updated" reflexively. A snapshot
88
+ passes while the handler stops firing.
89
+ - Testing that React Navigation navigates. Assert that pressing the control
90
+ calls navigate with the right target; the library's own behaviour is not
91
+ ours to test.
92
+ - Chasing coverage through screen composition files. The value sits in the
93
+ logic modules and the e2e flows.
94
+
95
+ ## Release risk splits in two
96
+
97
+ - **JS-only change** — ships over the air (EAS Update). The JS suite really
98
+ is the gate, and a bad one rolls back in minutes.
99
+ - **Native change** — SDK, a plugin, a native dependency. Needs a build and a
100
+ device test, and it **cannot be rolled back** once it is in a store.
101
+
102
+ Treat the second kind as a different category of change, not a bigger one.
@@ -0,0 +1,91 @@
1
+ ---
2
+ name: observability
3
+ description: "Wire error monitoring, structured logging, and analytics into an app. Use when setting up a new app, before first release, or when instrumenting a milestone's KPIs."
4
+ ---
5
+
6
+ # Wiring observability
7
+
8
+ Mandatory before first release. See
9
+ `.claude/rules/observability.orchestra.md` and
10
+ `.claude/rules/analytics-kpi.orchestra.md` for the rules this implements.
11
+
12
+ ## Order
13
+
14
+ 1. **Error monitoring first.** It is the thing you need most on the day
15
+ something breaks, and the thing hardest to add under pressure.
16
+ 2. **Structured logging** — server side.
17
+ 3. **Analytics** — once the KPIs are known. Instrumenting before you know
18
+ what you are measuring produces events nobody reads.
19
+
20
+ ## Error monitoring checklist
21
+
22
+ - [ ] SDK initialised as early as the app's lifecycle allows
23
+ - [ ] release tagged with version **and** commit sha
24
+ - [ ] symbols uploaded in CI — source maps, dSYMs, mapping files
25
+ - [ ] environment set, so staging noise does not drown production signal
26
+ - [ ] PII scrubbing configured **before** the first event is sent
27
+ - [ ] a deliberate test error verified end to end, in the dashboard
28
+ - [ ] alerting routed somewhere a human actually reads
29
+
30
+ An error monitor nobody has seen an event from is not wired. Prove it.
31
+
32
+ ## Logging checklist
33
+
34
+ - [ ] JSON output, one event per line
35
+ - [ ] correlation id per request, on every line for that request
36
+ - [ ] levels used honestly — `error` means someone should look
37
+ - [ ] no secrets, no PII, no full request bodies
38
+ - [ ] `debug` off in production
39
+
40
+ ## Health checklist
41
+
42
+ Two endpoints, because they answer different questions. `/health` says the
43
+ process is up; `/ready` says it can serve. The platform healthcheck points at
44
+ readiness, so this is the thing that decides whether a release is promoted.
45
+
46
+ - [ ] `/health` cheap, no dependencies
47
+ - [ ] `/ready` probes every dependency the service cannot serve without
48
+ - [ ] it answers 503 and **names** the dependency that failed
49
+ - [ ] a test makes it fail — a readiness check that cannot fail is worse
50
+ than none
51
+ - [ ] a new dependency adds its probe and its failing test in the same change
52
+ - [ ] the platform's `healthcheckPath` points at `/ready`
53
+
54
+ ## Feature flag checklist
55
+
56
+ A flag exists to protect the service, so the lookup must never be able to
57
+ take it down.
58
+
59
+ - [ ] one typed registry — no string literal at a call site
60
+ - [ ] every lookup bounded by a timeout
61
+ - [ ] a failed lookup is reported and answered with the flag's fallback,
62
+ never thrown into a handler
63
+ - [ ] a kill switch falls back to **false**: if we cannot ask whether the
64
+ risky path is allowed, the answer is no
65
+
66
+ ## Analytics checklist
67
+
68
+ - [ ] event names and payloads in one typed schema module per app
69
+ - [ ] call sites import from the schema — no string literals
70
+ - [ ] naming convention picked once: `object_action`
71
+ - [ ] view event per screen or route
72
+ - [ ] start, success, **and failure with a reason code** on critical flows
73
+ - [ ] no PII in properties
74
+ - [ ] consent gate respected before anything is sent
75
+ - [ ] tests assert the event fires once with the right shape
76
+ - [ ] events observed actually arriving before the milestone closes
77
+
78
+ ## Common failures
79
+
80
+ **Instrumenting the wrapper, not the action.** An event fired three layers
81
+ from the user eventually fires for the wrong reason. Instrument where the
82
+ action happens.
83
+
84
+ **Only tracking success.** A funnel with no failure events tells you people
85
+ left but never why — which is the only thing you wanted to know.
86
+
87
+ **Swallowing the error you just reported.** Report and rethrow, or handle it
88
+ so the user gets a correct outcome. Hiding a crash is not handling it.
89
+
90
+ **Shipping without verifying.** Untested instrumentation is usually broken
91
+ instrumentation. Send one real event and look at it.