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,114 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: project-structure
|
|
3
|
+
description: "Where a new file goes — the three questions that decide it, and why third-party wrappers have a fixed home. Use whenever creating a module, component, hook or utility rather than editing an existing one."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Skill: Project Structure
|
|
7
|
+
|
|
8
|
+
## The fact everything else follows from
|
|
9
|
+
|
|
10
|
+
**"Where does this go?" answered by judgement produces a different answer
|
|
11
|
+
every time.**
|
|
12
|
+
|
|
13
|
+
Nobody is wrong individually. One phase puts the Stripe call in the handler,
|
|
14
|
+
the next puts it in `utils/`, a third writes a `PaymentService`. Six
|
|
15
|
+
milestones later the same concern lives in four places, and no gate noticed —
|
|
16
|
+
because none of it is a defect, it is drift.
|
|
17
|
+
|
|
18
|
+
So the answer is a procedure, not a preference.
|
|
19
|
+
|
|
20
|
+
## Three questions, in order
|
|
21
|
+
|
|
22
|
+
Take the first one that is true.
|
|
23
|
+
|
|
24
|
+
| Question | Where | What follows |
|
|
25
|
+
|---|---|---|
|
|
26
|
+
| **Does it call a third party's SDK or HTTP API?** | `src/integrations/<provider>/` | Coverage held at **100%** |
|
|
27
|
+
| **Does more than one feature use it?** | `src/lib/` | Needs a doc comment with an example |
|
|
28
|
+
| **Otherwise** | `src/features/<domain>/` | Nothing outside may import its internals |
|
|
29
|
+
|
|
30
|
+
Feature-first, including on the backend. A layer-first tree — `controllers/`,
|
|
31
|
+
`services/`, `models/` — spreads one change across four directories and gives
|
|
32
|
+
you no place to draw a boundary: `services/` has no owner, so nothing stops
|
|
33
|
+
one domain reaching into another. See
|
|
34
|
+
`.claude/skills/architecture/SKILL.md` on what makes a boundary real.
|
|
35
|
+
|
|
36
|
+
**Before creating anything, look in `src/lib/` and the feature you are in.**
|
|
37
|
+
The duplicate that costs is the one written by someone who did not know the
|
|
38
|
+
first existed.
|
|
39
|
+
|
|
40
|
+
## Why integrations has a fixed home
|
|
41
|
+
|
|
42
|
+
Two reasons, and neither of them is swappability.
|
|
43
|
+
|
|
44
|
+
**The test suite needs something to hold.** The rule is that a function
|
|
45
|
+
calling into a third-party library has a test, so that a dependency bump which
|
|
46
|
+
changes behaviour turns the suite red — that is what makes a green Dependabot
|
|
47
|
+
PR a merge signal. Called inline across a dozen handlers, there is no function
|
|
48
|
+
to test and the bump goes green.
|
|
49
|
+
|
|
50
|
+
**The policy is applied once.** Timeout, bounded retry, circuit breaker,
|
|
51
|
+
mapping the provider's errors into ours, keeping the key out of the logs.
|
|
52
|
+
Spread across a dozen call sites you will miss one, and the one you miss is
|
|
53
|
+
the one that hangs.
|
|
54
|
+
|
|
55
|
+
### It is a seam, not an abstraction
|
|
56
|
+
|
|
57
|
+
| | Seam wrapper | Abstraction wrapper |
|
|
58
|
+
|---|---|---|
|
|
59
|
+
| Exists for | somewhere to test, one place for policy | pretending the vendor is interchangeable |
|
|
60
|
+
| Shape | thin; this app's types, this app's needs | provider-neutral, lowest common denominator |
|
|
61
|
+
| Justified by | the testing rule, today | a migration that may never happen |
|
|
62
|
+
|
|
63
|
+
A seam wrapper is allowed to look obviously like the provider it wraps. It
|
|
64
|
+
should. The abstraction version is what
|
|
65
|
+
`.claude/rules/code-standards.orchestra.md` forbids — one implementation, and
|
|
66
|
+
a guess about the future.
|
|
67
|
+
|
|
68
|
+
**Migration is the secondary benefit, and it is version upgrades, not vendor
|
|
69
|
+
swaps.** A major with a renamed method touches one file instead of twelve.
|
|
70
|
+
That happens several times a year; a vendor swap mostly never does.
|
|
71
|
+
|
|
72
|
+
### The 100% asks for exercise, not a test file
|
|
73
|
+
|
|
74
|
+
An integration test that drives the wrapper through a faked HTTP boundary
|
|
75
|
+
satisfies it completely. Do not add a unit test beside the wrapper to repeat
|
|
76
|
+
what a route test already covers — that is writing a test to move a number,
|
|
77
|
+
which the testing rule tells you to refuse rather than comply with.
|
|
78
|
+
|
|
79
|
+
Fake the provider at the **network** boundary, never by mocking the wrapper.
|
|
80
|
+
Mocking the wrapper means the wrapper is never tested, and the wrapper is the
|
|
81
|
+
thing that breaks when the provider changes.
|
|
82
|
+
|
|
83
|
+
## Components
|
|
84
|
+
|
|
85
|
+
Two levels, not four. Atoms-elements-molecules-organisms sounds precise and
|
|
86
|
+
is not: nobody places `FormField` the same way twice, and the folders drift
|
|
87
|
+
into an argument.
|
|
88
|
+
|
|
89
|
+
| Level | Contains | May import |
|
|
90
|
+
|---|---|---|
|
|
91
|
+
| **Primitives** — `src/design/` | Button, Input, Text. No business logic, no data fetching. | other primitives only |
|
|
92
|
+
| **Compositions** — beside the feature that uses them | Anything that knows what the app does | primitives, and their own feature |
|
|
93
|
+
|
|
94
|
+
The rule worth keeping from atomic design is the **import direction**: a
|
|
95
|
+
primitive never imports a composition. That one is checkable by reading a
|
|
96
|
+
diff. The taxonomy is not.
|
|
97
|
+
|
|
98
|
+
A component used by a second feature moves up to shared — on the second use,
|
|
99
|
+
not in anticipation of it.
|
|
100
|
+
|
|
101
|
+
## Types live beside what they describe
|
|
102
|
+
|
|
103
|
+
A global `types/` becomes a file everything imports, which is the opposite of
|
|
104
|
+
a boundary. A type that describes a feature's data lives in that feature; a
|
|
105
|
+
type crossing a boundary lives with the interface that defines it.
|
|
106
|
+
|
|
107
|
+
## Signs the structure has stopped working
|
|
108
|
+
|
|
109
|
+
- A change to one behaviour touches four directories.
|
|
110
|
+
- Two features import each other.
|
|
111
|
+
- `lib/` is the biggest directory in the app.
|
|
112
|
+
- The same provider's SDK is imported in more than one place.
|
|
113
|
+
- A file is named for a technical role — `helpers`, `managers`, `services` —
|
|
114
|
+
rather than for what it does.
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: push-notifications
|
|
3
|
+
description: "Delivering push to iOS and Android — permission timing, token lifecycle, payload choice, deep links, and why none of it can be tested in a JS runner. Use for any phase that sends, receives, or routes a notification."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Skill: Push Notifications
|
|
7
|
+
|
|
8
|
+
## The fact everything else follows from
|
|
9
|
+
|
|
10
|
+
**Nothing in the JS test suite can tell you push works.**
|
|
11
|
+
|
|
12
|
+
`jest-expo` runs components in Node against mocked native modules, so the
|
|
13
|
+
permission call, the token callback, the background handler and the deep link
|
|
14
|
+
are all mocked into success. The suite is green and the notification never
|
|
15
|
+
arrives.
|
|
16
|
+
|
|
17
|
+
Push needs a build with your own credentials, on a real device, with the app
|
|
18
|
+
in each of three states — foreground, background, terminated. Anything less
|
|
19
|
+
tests the mock. This is the same reason the device layer is not optional in
|
|
20
|
+
`.claude/skills/mobile-testing/SKILL.md`, on Expo.
|
|
21
|
+
|
|
22
|
+
Resolve the SDK's current API and constraints from its own docs at
|
|
23
|
+
implementation time. This area changes across SDK releases, and a handler
|
|
24
|
+
signature written from memory compiles and silently never fires.
|
|
25
|
+
|
|
26
|
+
## Permission is a product decision, not a setup step
|
|
27
|
+
|
|
28
|
+
The prompt can be shown **once**. A denial is close to permanent — the system
|
|
29
|
+
stops presenting the dialog, and the only route back is Settings.
|
|
30
|
+
|
|
31
|
+
| When you ask | Roughly what you get |
|
|
32
|
+
|---|---|
|
|
33
|
+
| On first launch, cold | 15–20% |
|
|
34
|
+
| After onboarding | 40–50% |
|
|
35
|
+
| At the moment the user turns on a feature that needs it | 70–80% |
|
|
36
|
+
|
|
37
|
+
So: explain the value, or better, ask at the moment the user does something
|
|
38
|
+
that obviously requires it. Never on launch.
|
|
39
|
+
|
|
40
|
+
- After a denial, do not re-prompt. Offer a route to Settings instead.
|
|
41
|
+
- iOS provisional authorisation delivers quietly with no prompt at all —
|
|
42
|
+
worth considering when the value is not yet obvious to the user.
|
|
43
|
+
- Android 13+ needs a runtime permission of its own. Treat it as a real
|
|
44
|
+
prompt with the same timing rules, not a manifest entry.
|
|
45
|
+
|
|
46
|
+
## The token is a lifecycle, not a value
|
|
47
|
+
|
|
48
|
+
The single most common defect: a token cached once and never refreshed, so
|
|
49
|
+
delivery quietly stops for users who reinstalled.
|
|
50
|
+
|
|
51
|
+
1. **Take it from the callback**, every launch. Never from storage as the
|
|
52
|
+
source of truth.
|
|
53
|
+
2. **Refresh replaces it.** The refresh callback is not optional — the token
|
|
54
|
+
changes on reinstall, restore, and at the platform's discretion.
|
|
55
|
+
3. **Key it by device, and associate a user.** One user has several devices;
|
|
56
|
+
one device may see several users. A token with no user cannot be targeted;
|
|
57
|
+
a token with no device id produces duplicates.
|
|
58
|
+
4. **Delete on rejection.** When the provider says the device is not
|
|
59
|
+
registered, that token is dead. Keeping it means a growing share of your
|
|
60
|
+
sends fail, and your delivery rate becomes a number nobody trusts.
|
|
61
|
+
5. **Treat it as opaque.** No parsing, no format assumptions.
|
|
62
|
+
|
|
63
|
+
A push token identifies a device belonging to a person. It never goes in a
|
|
64
|
+
log line, an error report, or an analytics property — see
|
|
65
|
+
`.claude/rules/security.orchestra.md`.
|
|
66
|
+
|
|
67
|
+
## Payload: visible or data-only
|
|
68
|
+
|
|
69
|
+
This is the choice that decides whether your handler runs at all.
|
|
70
|
+
|
|
71
|
+
| You want | Payload | Because |
|
|
72
|
+
|---|---|---|
|
|
73
|
+
| The user to see something now | **Visible** | Delivery is prioritised and the system displays it |
|
|
74
|
+
| To sync in the background | **Data-only** | A visible payload is handled by the system, not your code |
|
|
75
|
+
| Your own notification UI | **Data-only** | You build and post it |
|
|
76
|
+
|
|
77
|
+
Two constraints that bite:
|
|
78
|
+
|
|
79
|
+
- **Silent delivery is best-effort.** The OS may delay or drop it — battery,
|
|
80
|
+
Doze, Low Power Mode. Never carry something time-critical this way.
|
|
81
|
+
- **The background handler has seconds, not minutes.** Enqueue the work and
|
|
82
|
+
return. Doing the work inline gets the process killed mid-write.
|
|
83
|
+
|
|
84
|
+
On Android, a notification channel must exist before anything appears at all,
|
|
85
|
+
and a notification icon must be white-on-transparent — a coloured one renders
|
|
86
|
+
as a white square, which reads as a bug in your app.
|
|
87
|
+
|
|
88
|
+
## Foreground, background, terminated
|
|
89
|
+
|
|
90
|
+
Three states, three code paths, and they are handled in different places.
|
|
91
|
+
A notification that works in the foreground and does nothing from terminated
|
|
92
|
+
is the normal outcome of testing only one.
|
|
93
|
+
|
|
94
|
+
Deep linking is where this shows up first: opening from a terminated state
|
|
95
|
+
means the route is resolved **before** your navigation tree exists. Buffer
|
|
96
|
+
the target and apply it once navigation is ready, rather than dropping it.
|
|
97
|
+
|
|
98
|
+
Every one of the three gets tested by hand on a device before the phase
|
|
99
|
+
closes. There is no substitute.
|
|
100
|
+
|
|
101
|
+
## The send side
|
|
102
|
+
|
|
103
|
+
Sending is a third-party seam, so it has a test —
|
|
104
|
+
`.claude/rules/testing-standards.orchestra.md`. Fake the provider **at the
|
|
105
|
+
HTTP boundary**, never by mocking your own `sendPush()`, because the wrapper
|
|
106
|
+
is exactly what breaks when the provider changes.
|
|
107
|
+
|
|
108
|
+
Treat it like any other outbound call: timeout, bounded retry with jitter on
|
|
109
|
+
transient failures only, and a reported failure rather than a swallowed one.
|
|
110
|
+
See `.claude/skills/architecture/SKILL.md`.
|
|
111
|
+
|
|
112
|
+
- **A send is not a delivery.** Accepted by the provider means queued. Record
|
|
113
|
+
the two separately or your funnel is fiction.
|
|
114
|
+
- **Batch and rate-limit your own sends.** A fan-out that hits the provider
|
|
115
|
+
as fast as it can is throttled, and the throttling arrives as partial
|
|
116
|
+
failure.
|
|
117
|
+
- **Never send in a loop over users inside a request handler.** Queue it.
|
|
118
|
+
|
|
119
|
+
## Measuring it
|
|
120
|
+
|
|
121
|
+
Push is a feature with a funnel, so it is instrumented like one —
|
|
122
|
+
`.claude/rules/analytics-kpi.orchestra.md`:
|
|
123
|
+
|
|
124
|
+
- permission asked, granted, denied
|
|
125
|
+
- token registered, refreshed, rejected
|
|
126
|
+
- sent, delivered, opened
|
|
127
|
+
- and the **failure** at each step, with a reason code
|
|
128
|
+
|
|
129
|
+
Without the failure events you will know engagement dropped and never why.
|
|
130
|
+
No PII in any of those properties, and the token is never a property.
|
|
131
|
+
|
|
132
|
+
## Anti-patterns
|
|
133
|
+
|
|
134
|
+
- **Asking on first launch.** You spend the one prompt you get on a user who
|
|
135
|
+
does not yet know what the app does.
|
|
136
|
+
- **Re-prompting after denial.** The system ignores it; the user does not.
|
|
137
|
+
- **Caching the token long-term.** Delivery stops silently for reinstalls.
|
|
138
|
+
- **A visible payload when you needed the background handler.** The system
|
|
139
|
+
displays it and your code never runs.
|
|
140
|
+
- **Relying on silent push for anything time-critical.** Delivery is not
|
|
141
|
+
guaranteed and you will not be told.
|
|
142
|
+
- **Heavy work in the background handler.** The process is killed partway.
|
|
143
|
+
- **Skipping the notification channel.** Nothing appears at all on modern
|
|
144
|
+
Android, with no error anywhere.
|
|
145
|
+
- **Shipping without testing the terminated state.** It is the state most
|
|
146
|
+
users are actually in.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: react-best-practices
|
|
3
|
+
description: "React and Next.js implementation patterns — data fetching, boundaries, imports, state. Use for any phase writing React components."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Skill: React Best Practices
|
|
7
|
+
|
|
8
|
+
## When to Use
|
|
9
|
+
Phase involves React or Next.js frontend development.
|
|
10
|
+
|
|
11
|
+
## Checklist
|
|
12
|
+
- [ ] **No waterfall fetches:** use `Promise.all` for parallel data, `Suspense` boundaries for streaming
|
|
13
|
+
- [ ] **Direct imports:** never import from barrel files (`index.ts`) — import from exact module path
|
|
14
|
+
- [ ] **Server components by default:** only add `"use client"` when state/effects/browser APIs needed
|
|
15
|
+
- [ ] **Lazy load heavy components:** `next/dynamic` or `React.lazy` for below-the-fold content
|
|
16
|
+
- [ ] **Memoize expensive renders:** `React.memo` for components with stable props, `useMemo` for expensive computations
|
|
17
|
+
- [ ] **Primitive dependencies in hooks:** avoid objects/arrays in `useEffect` deps — extract primitive values
|
|
18
|
+
- [ ] **No duplicate serialization:** don't pass same data as both prop and context
|
|
19
|
+
- [ ] **Parallel route fetching:** use `Promise.all` in `loader`/`getServerSideProps`, not sequential `await`
|
|
20
|
+
- [ ] **SWR/React Query for client data:** deduplication, caching, revalidation built-in
|
|
21
|
+
- [ ] **Passive event listeners:** `{ passive: true }` for scroll/touch handlers
|
|
22
|
+
- [ ] **Hoist static JSX:** constant JSX outside component body to avoid re-creation on render
|
|
23
|
+
- [ ] **Use `Map`/`Set` for lookups:** not `Array.find()` in render paths
|
|
24
|
+
|
|
25
|
+
## Common Mistakes
|
|
26
|
+
- Sequential `await` in data fetching → blocks waterfall, use `Promise.all`
|
|
27
|
+
- Barrel file imports → prevents tree-shaking, entire module loaded
|
|
28
|
+
- `"use client"` on everything → loses server component benefits (streaming, zero bundle)
|
|
29
|
+
- Complex objects in `useEffect` dependencies → infinite re-render loop
|
|
30
|
+
- Event listeners without cleanup → memory leak in `useEffect`
|
|
31
|
+
- Fetching in `useEffect` without SWR/React Query → no dedup, no cache, race conditions
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: realtime
|
|
3
|
+
description: "Pushing updates to a connected client — choosing between polling, SSE and WebSocket, authenticating the connection, surviving reconnects, and what changes when there is more than one instance. Use for any phase adding live updates, presence, or a subscription."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Skill: Realtime
|
|
7
|
+
|
|
8
|
+
## The fact everything else follows from
|
|
9
|
+
|
|
10
|
+
**A connection is state, and state is what makes a service hard to deploy.**
|
|
11
|
+
|
|
12
|
+
A stateless service scales by adding an instance and deploys by replacing
|
|
13
|
+
one. A connection breaks both: the client is pinned to an instance, a deploy
|
|
14
|
+
drops every connection at once, and an event published on instance A never
|
|
15
|
+
reaches a subscriber on instance B.
|
|
16
|
+
|
|
17
|
+
None of that is a reason to avoid realtime. It is a reason to be sure you
|
|
18
|
+
need it, and to decide the multi-instance answer **before** the second
|
|
19
|
+
instance exists — retrofitting it is a rewrite.
|
|
20
|
+
|
|
21
|
+
## Choose the cheapest thing that works
|
|
22
|
+
|
|
23
|
+
Work down. Take the first row that is genuinely true.
|
|
24
|
+
|
|
25
|
+
| If | Use | Why |
|
|
26
|
+
|---|---|---|
|
|
27
|
+
| Updates are seconds-stale-tolerant, or rare | **Polling** | No connection state, no deploy problem, works through every proxy |
|
|
28
|
+
| Server → client only: feeds, progress, notifications, streamed output | **SSE** | Plain HTTP, reconnects and resumes by itself, no protocol to operate |
|
|
29
|
+
| Genuinely bidirectional and low latency: collaboration, presence, games | **WebSocket** | Only case that pays for the state |
|
|
30
|
+
|
|
31
|
+
Most "realtime" requirements are SSE requirements. A chat that only receives
|
|
32
|
+
is server-to-client. Reaching for WebSocket first buys you connection
|
|
33
|
+
management you did not need.
|
|
34
|
+
|
|
35
|
+
**SSE's resume is the reason to prefer it.** The browser reconnects on its
|
|
36
|
+
own and sends the last event id it saw; give every event an id and a client
|
|
37
|
+
that missed a minute can be caught up. With WebSocket you write that
|
|
38
|
+
yourself, and most implementations do not.
|
|
39
|
+
|
|
40
|
+
## Authenticate the handshake
|
|
41
|
+
|
|
42
|
+
The upgrade request is the only place authorisation happens. After it, you
|
|
43
|
+
have a socket with no request cycle to check anything against.
|
|
44
|
+
|
|
45
|
+
- Authenticate **during** the handshake, and reject before the upgrade
|
|
46
|
+
completes. Never accept first and check on the first message.
|
|
47
|
+
- **The browser WebSocket API cannot set headers.** Pass a short-lived,
|
|
48
|
+
single-use ticket obtained over normal HTTP — never the session token in
|
|
49
|
+
the query string, which lands in every access log.
|
|
50
|
+
- **A token that expires mid-connection matters.** Decide now: close and let
|
|
51
|
+
the client reconnect, or re-authenticate in-band. Both are fine. Neither
|
|
52
|
+
happening is a connection authorised by a credential that expired hours ago.
|
|
53
|
+
- Authorise **per subscription**, not once at connect. Joining a room is an
|
|
54
|
+
authorisation decision, and it is the one most often forgotten.
|
|
55
|
+
|
|
56
|
+
## Assume every connection dies
|
|
57
|
+
|
|
58
|
+
Because they do — a tunnel closes, a phone changes network, a proxy times out
|
|
59
|
+
an idle socket, or you deployed.
|
|
60
|
+
|
|
61
|
+
- **Heartbeat both ways.** A TCP connection can be dead for minutes while
|
|
62
|
+
both sides believe it is open. Ping, expect a pong, close when it does not
|
|
63
|
+
arrive.
|
|
64
|
+
- **The client reconnects with exponential backoff and jitter.** Without
|
|
65
|
+
jitter, a deploy disconnects everyone at once and they all return at once —
|
|
66
|
+
you knock over the instance that just came up.
|
|
67
|
+
- **Reconnect is not resume.** State the recovery: replay from the last event
|
|
68
|
+
id, or refetch and replace. "The client will have missed some messages" is
|
|
69
|
+
a bug that reads as flakiness.
|
|
70
|
+
- **Cap the retries, then say so.** A client that reconnects forever in the
|
|
71
|
+
background is a battery complaint. Surface a disconnected state and let the
|
|
72
|
+
user retry.
|
|
73
|
+
|
|
74
|
+
## Bound everything
|
|
75
|
+
|
|
76
|
+
An open socket is an invitation, and none of these have a safe default.
|
|
77
|
+
|
|
78
|
+
- Maximum message size, enforced on receive.
|
|
79
|
+
- Maximum messages per second, per connection — rate limiting at the socket,
|
|
80
|
+
not just at the HTTP edge.
|
|
81
|
+
- Maximum connections per user, and per instance.
|
|
82
|
+
- Maximum subscriptions per connection.
|
|
83
|
+
- A send queue that **drops or disconnects** when the client stops reading,
|
|
84
|
+
rather than buffering until the process runs out of memory. This is the
|
|
85
|
+
failure that takes the service down, and it looks like a memory leak.
|
|
86
|
+
|
|
87
|
+
Never do synchronous work per message on the event loop. One expensive
|
|
88
|
+
handler stalls every other connection on that instance.
|
|
89
|
+
|
|
90
|
+
## More than one instance
|
|
91
|
+
|
|
92
|
+
The moment there are two, an event published on one has to reach subscribers
|
|
93
|
+
on the other. Pick the mechanism deliberately:
|
|
94
|
+
|
|
95
|
+
- **A pub/sub backplane** — every instance subscribes, every instance
|
|
96
|
+
forwards to its own connections. Simple, and the usual answer.
|
|
97
|
+
- **A dedicated realtime service** — worth it when the connection count and
|
|
98
|
+
the request load have genuinely different shapes.
|
|
99
|
+
|
|
100
|
+
Either way the backplane is now a dependency the service cannot serve
|
|
101
|
+
without, so **it gets a readiness probe** — see
|
|
102
|
+
`.claude/rules/observability.orchestra.md`. A service accepting connections
|
|
103
|
+
it can never deliver to is worse than one refusing them.
|
|
104
|
+
|
|
105
|
+
Sticky sessions are a workaround, not a design. They break on deploy, they
|
|
106
|
+
unbalance load, and they hide the problem until the day you scale.
|
|
107
|
+
|
|
108
|
+
## Deploys disconnect everybody
|
|
109
|
+
|
|
110
|
+
This is normal and it is survivable, if it is designed:
|
|
111
|
+
|
|
112
|
+
- Close connections **gracefully** on SIGTERM — send a close frame so clients
|
|
113
|
+
reconnect on purpose instead of timing out. The draining window exists for
|
|
114
|
+
this; see `.claude/skills/deployment/SKILL.md`.
|
|
115
|
+
- Client backoff with jitter spreads the return.
|
|
116
|
+
- The reconnect path is the same one used after a network drop, so it is
|
|
117
|
+
already tested.
|
|
118
|
+
|
|
119
|
+
## Testing it
|
|
120
|
+
|
|
121
|
+
- The **protocol** is a third-party seam: connect, authenticate, subscribe,
|
|
122
|
+
receive, reconnect — tested against a real server, not a mocked client.
|
|
123
|
+
`.claude/rules/testing-standards.orchestra.md`.
|
|
124
|
+
- The **failure paths** are the point: rejected handshake, expired
|
|
125
|
+
credential, dropped connection mid-stream, a subscription the user is not
|
|
126
|
+
allowed to make.
|
|
127
|
+
- **Never `sleep` and assert.** Wait for the event; a sleep is a flake with a
|
|
128
|
+
delay.
|
|
129
|
+
- Instrument connect, disconnect with reason, reconnect, and messages
|
|
130
|
+
dropped. A realtime feature with no disconnect-reason metric cannot be
|
|
131
|
+
debugged from the outside.
|
|
132
|
+
|
|
133
|
+
## Anti-patterns
|
|
134
|
+
|
|
135
|
+
- **WebSocket where SSE would do.** You now operate a protocol for a feature
|
|
136
|
+
that only pushes.
|
|
137
|
+
- **Authenticating after the upgrade.** The connection existed before anyone
|
|
138
|
+
checked.
|
|
139
|
+
- **The session token in the query string.** It is in the access log of every
|
|
140
|
+
hop.
|
|
141
|
+
- **No heartbeat.** Dead connections accumulate and are counted as users.
|
|
142
|
+
- **Unbounded server-side buffering.** Reads as a memory leak, is actually a
|
|
143
|
+
slow client.
|
|
144
|
+
- **Broadcasting to everyone and filtering on the client.** Every client
|
|
145
|
+
receives data it is not allowed to see; the filter is decoration.
|
|
146
|
+
- **Assuming one instance.** Works perfectly until the day it matters.
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: review
|
|
3
|
+
description: "Reviewing a diff — as the reviewer role, or self-review before submitting. Use for any phase that reviews code rather than writing it."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Skill: Review
|
|
7
|
+
|
|
8
|
+
## Review what the gate cannot
|
|
9
|
+
|
|
10
|
+
By the time a diff exists, a great deal has already been decided
|
|
11
|
+
mechanically. Re-checking any of it spends the only thing a reader has that
|
|
12
|
+
a script does not.
|
|
13
|
+
|
|
14
|
+
**Already enforced. Do not spend attention here:**
|
|
15
|
+
|
|
16
|
+
| Concern | Enforced by |
|
|
17
|
+
|---|---|
|
|
18
|
+
| Secrets, in the diff or anywhere in history | gitleaks |
|
|
19
|
+
| String-built SQL | `scripts/policy-check.mjs` |
|
|
20
|
+
| Empty or unreported `catch` | `scripts/policy-check.mjs` |
|
|
21
|
+
| `getByTestId` in a test | `scripts/policy-check.mjs` |
|
|
22
|
+
| A mocked database | `scripts/policy-check.mjs` |
|
|
23
|
+
| Unused imports, dead code, `console.log` | biome |
|
|
24
|
+
| Contrast, ARIA, missing labels | axe, in component tests and e2e |
|
|
25
|
+
| Coverage of the lines this change touched | the diff gate |
|
|
26
|
+
| Anything wrapping a dependency, at 100% | the seam gate |
|
|
27
|
+
| Bundle growth | the bundle ratchet |
|
|
28
|
+
| It compiles, bundles, boots | typecheck, build, e2e |
|
|
29
|
+
| A known critical vulnerability | `yarn npm audit` in CI |
|
|
30
|
+
|
|
31
|
+
If one of those is wrong the commit does not exist — the gate blocked it.
|
|
32
|
+
Reporting it anyway tells the author you did not know that.
|
|
33
|
+
|
|
34
|
+
## Yours alone
|
|
35
|
+
|
|
36
|
+
Every item here needs a reader.
|
|
37
|
+
|
|
38
|
+
- [ ] **It does what the phase asked.** Re-read the acceptance criteria
|
|
39
|
+
against the diff. Working code that solves a different problem is the
|
|
40
|
+
most expensive defect there is, and no gate detects it.
|
|
41
|
+
- [ ] **The error handling is meaningful, not merely present.** A check
|
|
42
|
+
proves the `catch` is not empty. Only a reader can tell whether the
|
|
43
|
+
user gets a correct outcome or the failure was made invisible.
|
|
44
|
+
- [ ] **The tests assert behaviour.** Coverage proves a line ran. Read the
|
|
45
|
+
assertions: a test that calls a function and checks nothing about what
|
|
46
|
+
it did passes every gate we have.
|
|
47
|
+
- [ ] **Authorization is on the route**, not in a helper a future route can
|
|
48
|
+
forget to call. Named as unmechanizable in
|
|
49
|
+
`.claude/rules/security.orchestra.md` — so it is yours.
|
|
50
|
+
- [ ] **No PII in logs, errors or analytics.** Redaction covers known keys;
|
|
51
|
+
a new field carrying a person's name is invisible to it.
|
|
52
|
+
- [ ] **Query shape.** N+1, a missing index, a transaction that should exist
|
|
53
|
+
and does not.
|
|
54
|
+
- [ ] **Readiness probes what it claims.** A new dependency without a probe
|
|
55
|
+
is a broken release that gets promoted.
|
|
56
|
+
- [ ] **The abstraction earns itself.** Extracted after the second
|
|
57
|
+
duplication, not before. Was a simpler alternative available?
|
|
58
|
+
- [ ] **Loading, empty and failure states exist** for anything a user waits
|
|
59
|
+
on.
|
|
60
|
+
- [ ] **Anything that will be wrong later.** A pinned version with no reason
|
|
61
|
+
written beside it, a TODO with no owner, a workaround presented as a
|
|
62
|
+
fix.
|
|
63
|
+
|
|
64
|
+
## Severity
|
|
65
|
+
|
|
66
|
+
Defined once, in `.claude/agents/reviewer.md` under Severity — Blocking,
|
|
67
|
+
Important, Suggestion, and which of them hold the pull request. Read it
|
|
68
|
+
there; a second copy here drifted from it before.
|
|
69
|
+
|
|
70
|
+
## Common mistakes
|
|
71
|
+
|
|
72
|
+
- Reviewing the diff without opening the file. Context-dependent bugs live
|
|
73
|
+
in the lines that did not change.
|
|
74
|
+
- Re-reporting something a gate already decided.
|
|
75
|
+
- Nitpicking style a formatter owns.
|
|
76
|
+
- A vague finding. Say what, where, and why.
|
|
77
|
+
|
|
78
|
+
## Never ask for a test to be loosened
|
|
79
|
+
|
|
80
|
+
Nor a threshold lowered, nor an ignore added. If a gate is wrong, that is a
|
|
81
|
+
finding **about the gate** — a change to `.orchestra/stacks/`, not to the
|
|
82
|
+
project under review.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: testing
|
|
3
|
+
description: "The working checklist for writing tests. Strategy lives in the testing-standards rule; each platform has its own skill. Use for any phase that writes or changes tests."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Skill: Testing
|
|
7
|
+
|
|
8
|
+
## When to Use
|
|
9
|
+
Phase involves writing tests or improving coverage. The strategy lives in
|
|
10
|
+
`.claude/rules/testing-standards.orchestra.md`; this is the working checklist.
|
|
11
|
+
|
|
12
|
+
## Checklist
|
|
13
|
+
- [ ] **Detect framework:** check package.json for vitest, jest, pytest — use the project's
|
|
14
|
+
- [ ] **Read existing tests:** match style, naming, and file organisation
|
|
15
|
+
- [ ] **Pick the right layer:** pure logic → unit. Anything touching I/O → integration through the real entry point
|
|
16
|
+
- [ ] **Real database, never a mocked driver:** a fake repository returns what you told it to and cannot tell you the query is wrong
|
|
17
|
+
- [ ] **Fake external services at the network boundary** (MSW or a stub server), never by mocking our own wrapper — the wrapper is the thing that breaks on a provider change
|
|
18
|
+
- [ ] **Happy path, edge cases, and failures:** empty, null, boundary values, and the error branch with its reason
|
|
19
|
+
- [ ] **Async behaviour:** resolve and reject, timeouts, ordering
|
|
20
|
+
- [ ] **One assertion per concept:** the test name states the scenario
|
|
21
|
+
- [ ] **Run them, then break the code and confirm they fail** — a test that has never failed has not been tested
|
|
22
|
+
- [ ] **Isolation:** each test creates its own data; no shared seed fixture, no ordering dependency
|
|
23
|
+
|
|
24
|
+
## Common Mistakes
|
|
25
|
+
- Mocking the database → you test the mock. Use a real one in a container.
|
|
26
|
+
- Mocking your own module instead of the network → the seam goes untested.
|
|
27
|
+
- Snapshot tests as coverage → a snapshot passes while a handler stops firing.
|
|
28
|
+
- Writing tests to move the coverage number → the gate has started doing harm; say so.
|
|
29
|
+
- Testing implementation details → assert behaviour, not internal calls.
|
|
30
|
+
- Imposing a new style on an existing project → match what is there.
|
|
31
|
+
|
|
32
|
+
## Related
|
|
33
|
+
|
|
34
|
+
Each platform has a different shape, and the differences are not stylistic —
|
|
35
|
+
they come from what the test runner can and cannot see. Only the skills for
|
|
36
|
+
this project's `project.platforms` are installed.
|
|
37
|
+
|
|
38
|
+
- Web: `.claude/skills/web-testing/SKILL.md` — jsdom has no layout engine, so
|
|
39
|
+
component tests run in real Chromium.
|
|
40
|
+
- Mobile: `.claude/skills/mobile-testing/SKILL.md` — jest does not run React
|
|
41
|
+
Native; the device layer is not optional.
|
|
42
|
+
- iOS: `.claude/skills/ios-testing/SKILL.md` — a green unit suite does not
|
|
43
|
+
prove the app launches.
|
|
44
|
+
- Android: `.claude/skills/android-testing/SKILL.md` — a passing debug build
|
|
45
|
+
proves little about the release build.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: web-quality-audit
|
|
3
|
+
description: "End-to-end quality review of a web app — performance, accessibility, SEO, best practices. Use when auditing an existing app rather than building a feature."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Skill: Web Quality Audit
|
|
7
|
+
|
|
8
|
+
## When to Use
|
|
9
|
+
Phase involves comprehensive quality review of a web application — performance, accessibility, SEO, and best practices.
|
|
10
|
+
|
|
11
|
+
## Checklist
|
|
12
|
+
|
|
13
|
+
### Performance
|
|
14
|
+
- [ ] Core Web Vitals pass (LCP <= 2.5s, INP <= 200ms, CLS <= 0.1)
|
|
15
|
+
- [ ] Images compressed and properly sized (WebP/AVIF preferred)
|
|
16
|
+
- [ ] JavaScript minified and tree-shaken
|
|
17
|
+
- [ ] Critical CSS inlined, non-critical deferred
|
|
18
|
+
- [ ] Lazy loading for below-the-fold images and components
|
|
19
|
+
- [ ] Caching headers set for static assets (Cache-Control, ETag)
|
|
20
|
+
|
|
21
|
+
### Accessibility
|
|
22
|
+
- [ ] WCAG 2.1 AA compliant (run axe or similar audit)
|
|
23
|
+
- [ ] Keyboard navigation works for all interactive elements
|
|
24
|
+
- [ ] Screen reader tested (proper headings, landmarks, alt text)
|
|
25
|
+
- [ ] Color contrast ratios meet minimums
|
|
26
|
+
|
|
27
|
+
### SEO
|
|
28
|
+
- [ ] Unique `<title>` and `<meta name="description">` per page
|
|
29
|
+
- [ ] `robots.txt` present and correct
|
|
30
|
+
- [ ] Sitemap.xml generated and submitted
|
|
31
|
+
- [ ] Structured data (JSON-LD) for key content types
|
|
32
|
+
- [ ] Canonical URLs set for duplicate content
|
|
33
|
+
- [ ] Open Graph and Twitter Card meta tags present
|
|
34
|
+
|
|
35
|
+
### Best Practices
|
|
36
|
+
- [ ] HTTPS everywhere, no mixed content
|
|
37
|
+
- [ ] No vulnerable dependencies (`npm audit`)
|
|
38
|
+
- [ ] Error pages (404, 500) are user-friendly
|
|
39
|
+
- [ ] Console has no errors or warnings in production
|
|
40
|
+
- [ ] No deprecated APIs used
|
|
41
|
+
|
|
42
|
+
## Common Mistakes
|
|
43
|
+
- Optimizing only one metric → all four areas matter for real quality
|
|
44
|
+
- Ignoring SEO on SPAs → search engines need server-rendered content or prerendering
|
|
45
|
+
- No structured data → misses rich snippets in search results
|
|
46
|
+
- Performance testing only on fast networks → test on 3G/slow 4G too
|
|
47
|
+
- Missing error pages → users see raw error messages
|