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.
- package/LICENSE +21 -0
- package/README.md +189 -2
- package/bin/build-template.js +208 -0
- package/bin/index.js +1334 -0
- package/bin/platforms.js +510 -0
- package/package.json +41 -4
- package/template/.claude-plugin/plugin.json +10 -0
- package/template/.orchestra/README.md +391 -0
- package/template/.orchestra/blueprints/README.md +87 -0
- package/template/.orchestra/blueprints/api-only.md +46 -0
- package/template/.orchestra/blueprints/component-crud-resource.md +25 -0
- package/template/.orchestra/blueprints/saas-starter.md +69 -0
- package/template/.orchestra/config.yml +293 -0
- package/template/.orchestra/roles/orchestrator.md +105 -0
- package/template/.orchestra/roles/product-manager.md +439 -0
- package/template/.orchestra/scripts/compact-diff.awk +45 -0
- package/template/.orchestra/scripts/context.mjs +373 -0
- package/template/.orchestra/stacks/README.md +81 -0
- package/template/.orchestra/stacks/_lib/common.sh +3095 -0
- package/template/.orchestra/stacks/_repo/setup.sh +1113 -0
- package/template/.orchestra/stacks/backend-hono/claude/commands/endpoint.md +13 -0
- package/template/.orchestra/stacks/backend-hono/claude/skills/api-endpoint/SKILL.md +111 -0
- package/template/.orchestra/stacks/backend-hono/claude/skills/testing/SKILL.md +75 -0
- package/template/.orchestra/stacks/backend-hono/setup.sh +5945 -0
- package/template/.orchestra/stacks/backend-hono/stack.md +195 -0
- package/template/.orchestra/stacks/mobile-android-native/claude/commands/component.md +13 -0
- package/template/.orchestra/stacks/mobile-android-native/claude/skills/design-component/SKILL.md +58 -0
- package/template/.orchestra/stacks/mobile-android-native/setup.sh +607 -0
- package/template/.orchestra/stacks/mobile-android-native/stack.md +138 -0
- package/template/.orchestra/stacks/mobile-expo/claude/commands/component.md +12 -0
- package/template/.orchestra/stacks/mobile-expo/claude/skills/design-component/SKILL.md +92 -0
- package/template/.orchestra/stacks/mobile-expo/setup.sh +470 -0
- package/template/.orchestra/stacks/mobile-expo/stack.md +141 -0
- package/template/.orchestra/stacks/mobile-ios-native/claude/commands/component.md +12 -0
- package/template/.orchestra/stacks/mobile-ios-native/claude/skills/design-component/SKILL.md +58 -0
- package/template/.orchestra/stacks/mobile-ios-native/setup.sh +468 -0
- package/template/.orchestra/stacks/mobile-ios-native/stack.md +130 -0
- package/template/.orchestra/stacks/web-nextjs/claude/commands/component.md +12 -0
- package/template/.orchestra/stacks/web-nextjs/claude/skills/design-component/SKILL.md +100 -0
- package/template/.orchestra/stacks/web-nextjs/claude/skills/design-tokens/SKILL.md +61 -0
- package/template/.orchestra/stacks/web-nextjs/setup.sh +128 -0
- package/template/.orchestra/stacks/web-nextjs/stack.md +170 -0
- package/template/.orchestra/stacks/web-vite-react/claude/commands/component.md +12 -0
- package/template/.orchestra/stacks/web-vite-react/claude/skills/design-component/SKILL.md +100 -0
- package/template/.orchestra/stacks/web-vite-react/claude/skills/design-tokens/SKILL.md +61 -0
- package/template/.orchestra/stacks/web-vite-react/setup.sh +185 -0
- package/template/.orchestra/stacks/web-vite-react/stack.md +177 -0
- package/template/.orchestra/trackers/github.sh +467 -0
- package/template/CLAUDE.md +54 -0
- package/template/agents/architect.md +83 -0
- package/template/agents/engineer-high.md +17 -0
- package/template/agents/engineer-low.md +17 -0
- package/template/agents/engineer-max.md +17 -0
- package/template/agents/engineer-medium.md +17 -0
- package/template/agents/engineer-xhigh.md +17 -0
- package/template/agents/lead.md +925 -0
- package/template/agents/milestone-reviewer.md +65 -0
- package/template/agents/orchestrator.md +105 -0
- package/template/agents/product-manager.md +439 -0
- package/template/agents/re-reviewer.md +25 -0
- package/template/agents/reviewer.md +303 -0
- package/template/agents/scaffolder.md +206 -0
- package/template/agents/tracker.md +202 -0
- package/template/commands/blueprint.md +23 -0
- package/template/commands/create-role.md +64 -0
- package/template/commands/export.md +45 -0
- package/template/commands/help.md +56 -0
- package/template/commands/hotfix.md +14 -0
- package/template/commands/orchestrator.md +12 -0
- package/template/commands/pm.md +18 -0
- package/template/commands/rewind.md +63 -0
- package/template/commands/setup.md +15 -0
- package/template/commands/start.md +29 -0
- package/template/commands/status.md +11 -0
- package/template/commands/sync.md +76 -0
- package/template/commands/verifier.md +53 -0
- package/template/hooks/audit-context.sh +166 -0
- package/template/hooks/enforce-role.sh +193 -0
- package/template/hooks/protect-system-files.sh +123 -0
- package/template/hooks/review-rounds.sh +172 -0
- package/template/rules/acceptance-check.orchestra.md +11 -0
- package/template/rules/analytics-kpi.orchestra.md +56 -0
- package/template/rules/code-standards.orchestra.md +35 -0
- package/template/rules/comments.orchestra.md +77 -0
- package/template/rules/commit-format.orchestra.md +20 -0
- package/template/rules/dependency-upgrade.orchestra.md +112 -0
- package/template/rules/design-parity.orchestra.md +181 -0
- package/template/rules/observability.orchestra.md +121 -0
- package/template/rules/performance.orchestra.md +190 -0
- package/template/rules/phase-limits.orchestra.md +3 -0
- package/template/rules/pull-request.orchestra.md +218 -0
- package/template/rules/role-boundaries.orchestra.md +132 -0
- package/template/rules/security.orchestra.md +106 -0
- package/template/rules/stuck-detection.orchestra.md +25 -0
- package/template/rules/testing-standards.orchestra.md +219 -0
- package/template/rules/tracker-authority.orchestra.md +198 -0
- package/template/rules/verification-gate.orchestra.md +56 -0
- package/template/skills/accessibility/SKILL.md +31 -0
- package/template/skills/android-testing/SKILL.md +112 -0
- package/template/skills/architecture/SKILL.md +175 -0
- package/template/skills/auth-setup/SKILL.md +105 -0
- package/template/skills/best-practices/SKILL.md +29 -0
- package/template/skills/code-optimizer/SKILL.md +27 -0
- package/template/skills/core-web-vitals/SKILL.md +32 -0
- package/template/skills/crud-api/SKILL.md +32 -0
- package/template/skills/debug/SKILL.md +28 -0
- package/template/skills/deployment/SKILL.md +104 -0
- package/template/skills/error-handling/SKILL.md +154 -0
- package/template/skills/frontend-design/SKILL.md +30 -0
- package/template/skills/fullstack-infrastructure/SKILL.md +108 -0
- package/template/skills/ios-testing/SKILL.md +100 -0
- package/template/skills/mobile-testing/SKILL.md +102 -0
- package/template/skills/observability/SKILL.md +91 -0
- package/template/skills/project-structure/SKILL.md +114 -0
- package/template/skills/push-notifications/SKILL.md +146 -0
- package/template/skills/react-best-practices/SKILL.md +31 -0
- package/template/skills/realtime/SKILL.md +146 -0
- package/template/skills/review/SKILL.md +82 -0
- package/template/skills/testing/SKILL.md +45 -0
- package/template/skills/web-quality-audit/SKILL.md +47 -0
- 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.
|