agentic-team 0.0.0-stage → 6.1.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 (118) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +184 -2
  3. package/bin/build-template.js +208 -0
  4. package/bin/index.js +1139 -0
  5. package/package.json +41 -4
  6. package/template/.claude-plugin/plugin.json +10 -0
  7. package/template/.orchestra/README.md +401 -0
  8. package/template/.orchestra/blueprints/README.md +87 -0
  9. package/template/.orchestra/blueprints/api-only.md +46 -0
  10. package/template/.orchestra/blueprints/component-crud-resource.md +25 -0
  11. package/template/.orchestra/blueprints/saas-starter.md +69 -0
  12. package/template/.orchestra/config.yml +277 -0
  13. package/template/.orchestra/roles/orchestrator.md +105 -0
  14. package/template/.orchestra/roles/product-manager.md +438 -0
  15. package/template/.orchestra/scripts/compact-diff.awk +45 -0
  16. package/template/.orchestra/stacks/README.md +81 -0
  17. package/template/.orchestra/stacks/_lib/common.sh +3095 -0
  18. package/template/.orchestra/stacks/_repo/setup.sh +1109 -0
  19. package/template/.orchestra/stacks/backend-hono/claude/commands/endpoint.md +13 -0
  20. package/template/.orchestra/stacks/backend-hono/claude/skills/api-endpoint/SKILL.md +111 -0
  21. package/template/.orchestra/stacks/backend-hono/claude/skills/testing/SKILL.md +75 -0
  22. package/template/.orchestra/stacks/backend-hono/setup.sh +5945 -0
  23. package/template/.orchestra/stacks/backend-hono/stack.md +195 -0
  24. package/template/.orchestra/stacks/mobile-android-native/claude/commands/component.md +13 -0
  25. package/template/.orchestra/stacks/mobile-android-native/claude/skills/design-component/SKILL.md +58 -0
  26. package/template/.orchestra/stacks/mobile-android-native/setup.sh +607 -0
  27. package/template/.orchestra/stacks/mobile-android-native/stack.md +138 -0
  28. package/template/.orchestra/stacks/mobile-expo/claude/commands/component.md +12 -0
  29. package/template/.orchestra/stacks/mobile-expo/claude/skills/design-component/SKILL.md +92 -0
  30. package/template/.orchestra/stacks/mobile-expo/setup.sh +470 -0
  31. package/template/.orchestra/stacks/mobile-expo/stack.md +141 -0
  32. package/template/.orchestra/stacks/mobile-ios-native/claude/commands/component.md +12 -0
  33. package/template/.orchestra/stacks/mobile-ios-native/claude/skills/design-component/SKILL.md +58 -0
  34. package/template/.orchestra/stacks/mobile-ios-native/setup.sh +468 -0
  35. package/template/.orchestra/stacks/mobile-ios-native/stack.md +130 -0
  36. package/template/.orchestra/stacks/web-nextjs/claude/commands/component.md +12 -0
  37. package/template/.orchestra/stacks/web-nextjs/claude/skills/design-component/SKILL.md +100 -0
  38. package/template/.orchestra/stacks/web-nextjs/claude/skills/design-tokens/SKILL.md +61 -0
  39. package/template/.orchestra/stacks/web-nextjs/setup.sh +128 -0
  40. package/template/.orchestra/stacks/web-nextjs/stack.md +170 -0
  41. package/template/.orchestra/stacks/web-vite-react/claude/commands/component.md +12 -0
  42. package/template/.orchestra/stacks/web-vite-react/claude/skills/design-component/SKILL.md +100 -0
  43. package/template/.orchestra/stacks/web-vite-react/claude/skills/design-tokens/SKILL.md +61 -0
  44. package/template/.orchestra/stacks/web-vite-react/setup.sh +185 -0
  45. package/template/.orchestra/stacks/web-vite-react/stack.md +177 -0
  46. package/template/.orchestra/trackers/github.sh +467 -0
  47. package/template/CLAUDE.md +54 -0
  48. package/template/agents/architect.md +83 -0
  49. package/template/agents/engineer-high.md +17 -0
  50. package/template/agents/engineer-low.md +17 -0
  51. package/template/agents/engineer-max.md +17 -0
  52. package/template/agents/engineer-medium.md +17 -0
  53. package/template/agents/engineer-xhigh.md +17 -0
  54. package/template/agents/lead.md +870 -0
  55. package/template/agents/milestone-reviewer.md +63 -0
  56. package/template/agents/orchestrator.md +105 -0
  57. package/template/agents/product-manager.md +438 -0
  58. package/template/agents/reviewer.md +301 -0
  59. package/template/agents/scaffolder.md +180 -0
  60. package/template/agents/tracker.md +202 -0
  61. package/template/commands/blueprint.md +23 -0
  62. package/template/commands/create-role.md +64 -0
  63. package/template/commands/export.md +45 -0
  64. package/template/commands/help.md +55 -0
  65. package/template/commands/hotfix.md +12 -0
  66. package/template/commands/orchestrator.md +12 -0
  67. package/template/commands/pm.md +18 -0
  68. package/template/commands/rewind.md +61 -0
  69. package/template/commands/setup.md +14 -0
  70. package/template/commands/start.md +29 -0
  71. package/template/commands/status.md +11 -0
  72. package/template/commands/sync.md +73 -0
  73. package/template/commands/verifier.md +53 -0
  74. package/template/hooks/audit-context.sh +153 -0
  75. package/template/hooks/enforce-role.sh +175 -0
  76. package/template/hooks/protect-system-files.sh +123 -0
  77. package/template/hooks/review-rounds.sh +172 -0
  78. package/template/rules/acceptance-check.orchestra.md +11 -0
  79. package/template/rules/analytics-kpi.orchestra.md +56 -0
  80. package/template/rules/code-standards.orchestra.md +34 -0
  81. package/template/rules/comments.orchestra.md +77 -0
  82. package/template/rules/commit-format.orchestra.md +20 -0
  83. package/template/rules/dependency-upgrade.orchestra.md +112 -0
  84. package/template/rules/design-parity.orchestra.md +170 -0
  85. package/template/rules/observability.orchestra.md +118 -0
  86. package/template/rules/performance.orchestra.md +186 -0
  87. package/template/rules/phase-limits.orchestra.md +3 -0
  88. package/template/rules/pull-request.orchestra.md +213 -0
  89. package/template/rules/role-boundaries.orchestra.md +117 -0
  90. package/template/rules/security.orchestra.md +106 -0
  91. package/template/rules/stuck-detection.orchestra.md +25 -0
  92. package/template/rules/testing-standards.orchestra.md +214 -0
  93. package/template/rules/tracker-authority.orchestra.md +190 -0
  94. package/template/rules/verification-gate.orchestra.md +56 -0
  95. package/template/skills/accessibility/SKILL.md +31 -0
  96. package/template/skills/android-testing/SKILL.md +112 -0
  97. package/template/skills/architecture/SKILL.md +175 -0
  98. package/template/skills/auth-setup/SKILL.md +105 -0
  99. package/template/skills/best-practices/SKILL.md +29 -0
  100. package/template/skills/code-optimizer/SKILL.md +27 -0
  101. package/template/skills/core-web-vitals/SKILL.md +32 -0
  102. package/template/skills/crud-api/SKILL.md +32 -0
  103. package/template/skills/debug/SKILL.md +28 -0
  104. package/template/skills/deployment/SKILL.md +104 -0
  105. package/template/skills/error-handling/SKILL.md +154 -0
  106. package/template/skills/frontend-design/SKILL.md +30 -0
  107. package/template/skills/fullstack-infrastructure/SKILL.md +108 -0
  108. package/template/skills/ios-testing/SKILL.md +100 -0
  109. package/template/skills/mobile-testing/SKILL.md +102 -0
  110. package/template/skills/observability/SKILL.md +91 -0
  111. package/template/skills/project-structure/SKILL.md +114 -0
  112. package/template/skills/push-notifications/SKILL.md +146 -0
  113. package/template/skills/react-best-practices/SKILL.md +31 -0
  114. package/template/skills/realtime/SKILL.md +146 -0
  115. package/template/skills/review/SKILL.md +82 -0
  116. package/template/skills/testing/SKILL.md +44 -0
  117. package/template/skills/web-quality-audit/SKILL.md +47 -0
  118. package/template/skills/web-testing/SKILL.md +120 -0
@@ -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.
@@ -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`.
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.