@terreno/agent-plugin 0.0.0-stage → 57.21.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 (98) hide show
  1. package/.claude-plugin/plugin.json +32 -0
  2. package/CHANGELOG.md +2124 -0
  3. package/LICENSE +21 -0
  4. package/README.md +57 -2
  5. package/agents/pre-commit.md +65 -0
  6. package/agents/ui-verifier.md +93 -0
  7. package/package.json +32 -4
  8. package/references/async-review-bots.md +76 -0
  9. package/references/documentation-contract.md +61 -0
  10. package/references/execution-state.schema.json +77 -0
  11. package/references/github-attention-contract.md +165 -0
  12. package/references/independent-review.md +31 -0
  13. package/references/lifecycle-contract.md +211 -0
  14. package/references/loop-engineering.md +125 -0
  15. package/references/pick-roast-loop.md +112 -0
  16. package/references/pr-deployments.md +69 -0
  17. package/references/product-ci.md +164 -0
  18. package/references/push-ownership.md +91 -0
  19. package/references/stage-result.schema.json +82 -0
  20. package/references/subagent-briefing.md +43 -0
  21. package/references/testing-plan.md +163 -0
  22. package/references/user-guide.md +171 -0
  23. package/skills/1-grow/SKILL.md +145 -0
  24. package/skills/1-grow/references/grilling.md +243 -0
  25. package/skills/2-pick/SKILL.md +170 -0
  26. package/skills/2-pick/references/mocking.md +34 -0
  27. package/skills/2-pick/references/testing.md +43 -0
  28. package/skills/3-roast/SKILL.md +168 -0
  29. package/skills/4-brew/SKILL.md +201 -0
  30. package/skills/5-taste/SKILL.md +311 -0
  31. package/skills/ai-prompt-governance/SKILL.md +100 -0
  32. package/skills/attach-pr-media/SKILL.md +89 -0
  33. package/skills/attach-pr-media/scripts/upload-pr-media.sh +151 -0
  34. package/skills/backend-test-env/SKILL.md +64 -0
  35. package/skills/backfill-regression/SKILL.md +101 -0
  36. package/skills/backfill-user-guide/SKILL.md +96 -0
  37. package/skills/bootstrap-mcp-server/SKILL.md +244 -0
  38. package/skills/building-admin-interfaces/SKILL.md +83 -0
  39. package/skills/building-admin-interfaces/agents/openai.yaml +4 -0
  40. package/skills/building-admin-interfaces/references/nav-and-shell.md +42 -0
  41. package/skills/building-admin-interfaces/references/patterns-and-tools.md +73 -0
  42. package/skills/building-admin-interfaces/references/screens-and-routes.md +50 -0
  43. package/skills/building-terreno-apps/SKILL.md +280 -0
  44. package/skills/building-terreno-apps/agents/openai.yaml +4 -0
  45. package/skills/building-terreno-apps/references/navigation.md +102 -0
  46. package/skills/building-terreno-apps/references/route-structure.md +91 -0
  47. package/skills/building-terreno-apps/references/screen-patterns.md +191 -0
  48. package/skills/debug-syncdb-with-mcp/SKILL.md +75 -0
  49. package/skills/deploy-gcp/SKILL.md +73 -0
  50. package/skills/deploy-gcp/references/troubleshooting.md +29 -0
  51. package/skills/generate-release-announcements/SKILL.md +145 -0
  52. package/skills/generate-sdk/SKILL.md +156 -0
  53. package/skills/mongoose-schema-safety/SKILL.md +164 -0
  54. package/skills/pick-roast-loop/SKILL.md +152 -0
  55. package/skills/planning-loop/SKILL.md +161 -0
  56. package/skills/prd/SKILL.md +138 -0
  57. package/skills/set-up-social-login/SKILL.md +76 -0
  58. package/skills/store-assets/SKILL.md +221 -0
  59. package/skills/store-assets/examples/screenshots.yaml +31 -0
  60. package/skills/store-assets/examples/storeAssets.config.json +44 -0
  61. package/skills/store-assets/scripts/banner.ts +165 -0
  62. package/skills/store-assets/scripts/config.ts +255 -0
  63. package/skills/store-assets/scripts/devClient.ts +635 -0
  64. package/skills/store-assets/scripts/devices.ts +286 -0
  65. package/skills/store-assets/scripts/icon.ts +81 -0
  66. package/skills/store-assets/scripts/maestro/goHome.yaml +21 -0
  67. package/skills/store-assets/scripts/maestro/login.yaml +27 -0
  68. package/skills/store-assets/scripts/maestro/openApp.yaml +26 -0
  69. package/skills/store-assets/scripts/screenshots.ts +848 -0
  70. package/skills/taste-sweep/SKILL.md +172 -0
  71. package/skills/terreno-backend-api/SKILL.md +355 -0
  72. package/skills/terreno-backend-api/agents/openai.yaml +4 -0
  73. package/skills/terreno-backend-api/references/custom-routes.md +132 -0
  74. package/skills/terreno-backend-api/references/model-router.md +116 -0
  75. package/skills/terreno-backend-api/references/server-setup.md +116 -0
  76. package/skills/terreno-backend-tests/SKILL.md +176 -0
  77. package/skills/terreno-data-fetching/SKILL.md +276 -0
  78. package/skills/terreno-data-fetching/agents/openai.yaml +4 -0
  79. package/skills/terreno-data-fetching/references/auth-and-tokens.md +93 -0
  80. package/skills/terreno-data-fetching/references/realtime.md +45 -0
  81. package/skills/terreno-data-fetching/references/sdk-customization.md +84 -0
  82. package/skills/terreno-frontend-tests/SKILL.md +190 -0
  83. package/skills/terreno-mobile-tests/SKILL.md +127 -0
  84. package/skills/terreno-test-setup/SKILL.md +81 -0
  85. package/skills/terreno-test-setup/references/backend.md +131 -0
  86. package/skills/terreno-test-setup/references/frontend.md +169 -0
  87. package/skills/terreno-test-setup/references/mobile.md +134 -0
  88. package/skills/terreno-ui/SKILL.md +252 -0
  89. package/skills/terreno-ui/agents/openai.yaml +4 -0
  90. package/skills/terreno-ui/references/data-display.md +85 -0
  91. package/skills/terreno-ui/references/feedback.md +122 -0
  92. package/skills/terreno-ui/references/forms.md +119 -0
  93. package/skills/terreno-ui/references/layout.md +124 -0
  94. package/skills/terreno-ui/references/theming.md +87 -0
  95. package/skills/update-docs/SKILL.md +82 -0
  96. package/skills/upgrading-terreno/SKILL.md +86 -0
  97. package/skills/upgrading-terreno/references/ordering.md +40 -0
  98. package/skills/verify-ui-changes/SKILL.md +605 -0
@@ -0,0 +1,191 @@
1
+ # Screen Patterns — Terreno Apps
2
+
3
+ ## List screen
4
+
5
+ ```tsx
6
+ import {Box, Button, Card, Heading, Page, Spinner, Text} from "@terreno/ui";
7
+ import {useCallback} from "react";
8
+ import {useGetTodosQuery} from "@/store/sdk";
9
+
10
+ const TodoListScreen: React.FC = () => {
11
+ const {data, isLoading, error, refetch} = useGetTodosQuery({});
12
+
13
+ const handleCreate = useCallback((): void => {
14
+ // navigate or open modal
15
+ }, []);
16
+
17
+ if (isLoading) {
18
+ return <Page title="Todos"><Spinner /></Page>;
19
+ }
20
+ if (error) {
21
+ return (
22
+ <Page title="Todos">
23
+ <Text color="error">Failed to load</Text>
24
+ <Button text="Retry" onClick={refetch} />
25
+ </Page>
26
+ );
27
+ }
28
+
29
+ const items = data?.data ?? [];
30
+
31
+ return (
32
+ <Page title="Todos" scroll>
33
+ <Box gap={3} padding={4}>
34
+ <Box direction="row" justifyContent="between" alignItems="center">
35
+ <Heading size="lg">Todos</Heading>
36
+ <Button text="Add" onClick={handleCreate} iconName="plus" />
37
+ </Box>
38
+ {items.length === 0 ? (
39
+ <Text color="secondaryLight">No todos yet</Text>
40
+ ) : (
41
+ items.map((todo) => (
42
+ <Card key={todo.id} padding={3}>
43
+ <Text>{todo.title}</Text>
44
+ </Card>
45
+ ))
46
+ )}
47
+ </Box>
48
+ </Page>
49
+ );
50
+ };
51
+ ```
52
+
53
+ ## Profile screen
54
+
55
+ Use `TapToEdit` for per-field profile edits. Save each field with `usePatchMeMutation` — do not collect every field behind one Save button.
56
+
57
+ ```tsx
58
+ import {Box, Card, Page, TapToEdit} from "@terreno/ui";
59
+ import {useCallback, useEffect, useState} from "react";
60
+ import {useGetMeQuery, usePatchMeMutation} from "@/store/sdk";
61
+
62
+ const ProfileScreen: React.FC = () => {
63
+ const {data: profile} = useGetMeQuery();
64
+ const [updateProfile] = usePatchMeMutation();
65
+ const [name, setName] = useState<string>("");
66
+
67
+ // Seed only this field from the server so saving another TapToEdit does not wipe the draft.
68
+ useEffect(() => {
69
+ if (!profile) {
70
+ return;
71
+ }
72
+ setName(profile.name || "");
73
+ }, [profile?.name]);
74
+
75
+ const handleSaveName = useCallback(
76
+ async (value: string): Promise<void> => {
77
+ await updateProfile({name: value}).unwrap();
78
+ },
79
+ [updateProfile]
80
+ );
81
+
82
+ return (
83
+ <Page title="Profile" scroll>
84
+ <Box gap={4} padding={4}>
85
+ <Card>
86
+ <TapToEdit
87
+ onSave={handleSaveName}
88
+ setValue={setName}
89
+ title="Name"
90
+ type="text"
91
+ value={name}
92
+ />
93
+ </Card>
94
+ </Box>
95
+ </Page>
96
+ );
97
+ };
98
+ ```
99
+
100
+ ## Form screen
101
+
102
+ ```tsx
103
+ import {Box, Button, Page, TextField} from "@terreno/ui";
104
+ import {useCallback, useState} from "react";
105
+ import {useRouter} from "expo-router";
106
+ import {usePostTodosMutation} from "@/store/sdk";
107
+
108
+ const CreateTodoScreen: React.FC = () => {
109
+ const router = useRouter();
110
+ const [title, setTitle] = useState<string>("");
111
+ const [titleError, setTitleError] = useState<string | undefined>();
112
+ const [createTodo, {isLoading}] = usePostTodosMutation();
113
+
114
+ const handleSubmit = useCallback(async (): Promise<void> => {
115
+ if (!title.trim()) {
116
+ setTitleError("Title is required");
117
+ return;
118
+ }
119
+ try {
120
+ await createTodo({title: title.trim(), completed: false}).unwrap();
121
+ router.back();
122
+ } catch (err) {
123
+ console.error("Create failed", err);
124
+ }
125
+ }, [createTodo, router, title]);
126
+
127
+ return (
128
+ <Page title="New Todo" scroll>
129
+ <Box gap={4} padding={4}>
130
+ <TextField
131
+ title="Title"
132
+ value={title}
133
+ onChange={setTitle}
134
+ errorText={titleError}
135
+ />
136
+ <Button text="Save" onClick={handleSubmit} loading={isLoading} fullWidth />
137
+ </Box>
138
+ </Page>
139
+ );
140
+ };
141
+ ```
142
+
143
+ ## Detail screen with params
144
+
145
+ ```tsx
146
+ import {Page, Spinner, Text} from "@terreno/ui";
147
+ import {useLocalSearchParams} from "expo-router";
148
+ import {useGetTodosByIdQuery} from "@/store/sdk";
149
+
150
+ const TodoDetailScreen: React.FC = () => {
151
+ const {id} = useLocalSearchParams<{id: string}>();
152
+ const {data, isLoading, error} = useGetTodosByIdQuery({id: id ?? ""}, {skip: !id});
153
+
154
+ if (isLoading) {
155
+ return <Page title="Todo"><Spinner /></Page>;
156
+ }
157
+ if (error || !data) {
158
+ return <Page title="Todo"><Text color="error">Not found</Text></Page>;
159
+ }
160
+
161
+ return (
162
+ <Page title={data.title} scroll>
163
+ <Text>{data.title}</Text>
164
+ </Page>
165
+ );
166
+ };
167
+ ```
168
+
169
+ ## Admin table screen
170
+
171
+ Prefer `@terreno/admin-frontend` over hand-rolled tables. Use `AdminScreenRouter` so
172
+ custom screens and models share `[model]/index`. Full nav and custom-screen rules:
173
+ skill `building-admin-interfaces`.
174
+
175
+ ```tsx
176
+ import {AdminScreenRouter} from "@terreno/admin-frontend";
177
+ import {useLocalSearchParams} from "expo-router";
178
+ import {terrenoApi} from "@/store/sdk";
179
+
180
+ const AdminModelScreen: React.FC = () => {
181
+ const {model} = useLocalSearchParams<{model: string}>();
182
+ return (
183
+ <AdminScreenRouter
184
+ api={terrenoApi}
185
+ apiBase="/admin"
186
+ name={model ?? ""}
187
+ routeBase="/admin"
188
+ />
189
+ );
190
+ };
191
+ ```
@@ -0,0 +1,75 @@
1
+ ---
2
+ name: debug-syncdb-with-mcp
3
+ description: >-
4
+ Invoke when debugging SyncDB local state, stale data, outbox stalls,
5
+ conflicts, cursor/stream drift, reconciliation, or offline behavior in a
6
+ running Terreno app. Uses terreno-mcp-local to inspect, snapshot, compare,
7
+ mutate, flush, merge, and repair SyncDB state.
8
+ ---
9
+ # Debug SyncDB with MCP
10
+
11
+ Use the fixed SyncDB MCP tools instead of arbitrary `evaluate` snippets.
12
+
13
+ ## 1. Establish the live client
14
+
15
+ 1. Confirm the app creates its SyncDB client with `debug: true` in development.
16
+ 2. Start the app and authenticate the affected user.
17
+ 3. Call `get_syncdb_state` without filters.
18
+
19
+ Continue only when the response names a registered client and includes `status`,
20
+ `collections`, `outbox`, `conflicts`, `cursors`, `knownStreams`, `needsRepair`,
21
+ and `debug`.
22
+
23
+ ## 2. Capture the baseline
24
+
25
+ Call `syncdb_snapshot` with `action: "capture"`. Keep the returned snapshot id.
26
+ Use `collection`, `entityId`, or `limit` only when the full state is too large.
27
+
28
+ The capture retains mergeable TinyBase content inside the local MCP process.
29
+ Returned JSON redacts sensitive fields and does not expose that merge payload.
30
+
31
+ ## 3. Reproduce and inspect
32
+
33
+ Reproduce the issue once, then call:
34
+
35
+ - `get_syncdb_state` for the current state and debugger event stream
36
+ - `syncdb_snapshot` with `action: "capture"` for the post-reproduction state
37
+ - `syncdb_snapshot` with `action: "compare"` and both snapshot ids
38
+
39
+ Use the diff plus debug events to identify whether divergence begins at the
40
+ local mutation, outbox send, ack/nack, delta, cursor, or reconcile phase.
41
+
42
+ ## 4. Change state only when required
43
+
44
+ Restart `terreno-mcp-local` with `TERRENO_MCP_EVAL=1`, then use
45
+ `syncdb_action`:
46
+
47
+ | Action | Purpose |
48
+ | --- | --- |
49
+ | `mutate` | Normal optimistic create/update/delete through the outbox |
50
+ | `setLocalEntity` / `deleteLocalEntity` | Direct local-only fault injection or repair |
51
+ | `flush` | Drain the durable outbox now |
52
+ | `reconcile` / `forceResync` | Catch up or purge and rebuild from server snapshots |
53
+ | `resolveConflict` / `retryFailed` | Unblock conflicted or terminally failed entities |
54
+ | `goOffline` / `goOnline` | Exercise queueing and reconnect behavior |
55
+ | `mergeSnapshot` | CRDT-merge a prior capture into the current TinyBase store; later HLCs win, including deletes |
56
+ | `clearDebug` | Reset retained debugger events and counters |
57
+
58
+ Prefer `mutate` over direct local edits. Capture another snapshot before
59
+ `setLocalEntity`, `deleteLocalEntity`, or `mergeSnapshot`; those actions bypass
60
+ normal server validation and outbox semantics.
61
+
62
+ ## 5. Prove the diagnosis
63
+
64
+ Capture and compare a final snapshot, then verify:
65
+
66
+ - the expected entity payload, tombstone, seq, and stream
67
+ - no unintended pending mutation remains
68
+ - queued/conflict/failed counts match the expected outcome
69
+ - cursors and known streams are coherent
70
+ - debugger events show the expected send/ack/delta/reconcile sequence
71
+
72
+ The workflow is complete when the state diff and event sequence explain the
73
+ failure or prove the repair. See
74
+ [`docs/how-to/debug-with-mcp.md`](../../../../docs/how-to/debug-with-mcp.md) for
75
+ setup and examples.
@@ -0,0 +1,73 @@
1
+ ---
2
+ name: deploy-gcp
3
+ description: Deploy a Terreno app to GCP — Cloud Run backend and GCS+CDN static web. Trigger phrases include "deploy to GCP", "deploy to Cloud Run", and "ship the backend".
4
+ targets: ['*']
5
+ ---
6
+
7
+ # Deploy to GCP
8
+
9
+ Deploy `example-backend` (or a bootstrapped `backend/`) to Cloud Run and the web export to GCS + CDN.
10
+
11
+ **Never deploy to a project the user did not name in the request.** Never create or modify secrets without echoing which secret names will change.
12
+
13
+ ## When to use
14
+
15
+ - User asks to deploy a Terreno backend or full stack to Google Cloud
16
+ - User names a specific GCP `$PROJECT_ID` and wants Cloud Run + static web hosting
17
+
18
+ ## When not to use
19
+
20
+ - User has not named a target GCP project
21
+ - User wants Vercel, Netlify-only, or non-GCP hosting
22
+ - User wants Terraform apply without explicit confirmation
23
+
24
+ ## Preflight checks
25
+
26
+ 1. **Project layout** — detect monorepo (`example-backend/`, `example-frontend/`) vs bootstrapped app (`backend/`, `frontend/`).
27
+ 2. **gcloud** — `gcloud config get-value project` must match the user-named project (or set it with user confirmation).
28
+ 3. **APIs** — verify `run.googleapis.com`, `artifactregistry.googleapis.com`, `secretmanager.googleapis.com` are enabled.
29
+ 4. **MongoDB** — `MONGO_URI` must point at a replica set (Atlas or `replSet=`). Standalone `mongod` breaks change streams.
30
+ 5. **Web build** — confirm `EXPO_PUBLIC_API_URL` will be set to the Cloud Run URL **before** `bun run export`.
31
+
32
+ ## Plan and confirm (required)
33
+
34
+ Before any mutating `gcloud`, `docker push`, or `gsutil` command, print:
35
+
36
+ - Target `$PROJECT_ID`, `$REGION`, `$SERVICE_NAME`
37
+ - Web bucket `$WEB_BUCKET` and CDN site name
38
+ - Every secret to create or update (`MONGO_URI`, `TOKEN_SECRET`, etc.)
39
+ - Every resource to create or update (Artifact Registry repo, Cloud Run service, GCS bucket, CDN resources)
40
+
41
+ **Stop and require explicit user confirmation** before proceeding.
42
+
43
+ ## Backend deploy
44
+
45
+ Follow [docs/how-to/deploy-backend-to-cloud-run.md](../../../../docs/how-to/deploy-backend-to-cloud-run.md):
46
+
47
+ 1. Create Artifact Registry repository
48
+ 2. `docker build -f example-backend/Dockerfile .` from repo root
49
+ 3. Push image to `$REGION-docker.pkg.dev/$PROJECT_ID/$REPOSITORY/$SERVICE_NAME`
50
+ 4. Create Secret Manager secrets (values out of band)
51
+ 5. Create runtime service account with `secretmanager.secretAccessor`
52
+ 6. `gcloud run deploy` with `--session-affinity`, `--timeout=3600`, `--min-instances=1`, `--set-secrets`, `--port=3000`
53
+
54
+ ## Frontend deploy
55
+
56
+ Follow [docs/how-to/deploy-web-to-gcs-cdn.md](../../../../docs/how-to/deploy-web-to-gcs-cdn.md):
57
+
58
+ 1. `EXPO_PUBLIC_API_URL=$SERVICE_URL bun run export` in the frontend package
59
+ 2. Upload `dist/` to `$WEB_BUCKET` with correct cache headers
60
+ 3. Run `./scripts/setup-gcs-hosting.sh --project ... --site-name ... --bucket ... --domain ...` if CDN not provisioned
61
+ 4. Invalidate CDN cache on the URL map
62
+
63
+ ## Verification
64
+
65
+ ```bash
66
+ curl -s "$SERVICE_URL/health" | jq .
67
+ ```
68
+
69
+ Expect `"healthy": true`. Fetch the web root URL and confirm the SPA loads and API calls hit `$SERVICE_URL`.
70
+
71
+ ## Troubleshooting
72
+
73
+ See [references/troubleshooting.md](references/troubleshooting.md).
@@ -0,0 +1,29 @@
1
+ # GCP deploy troubleshooting
2
+
3
+ Symptom → cause → fix for Terreno on Cloud Run + GCS/CDN.
4
+
5
+ ## Runtime constraints
6
+
7
+ | Symptom | Cause | Fix |
8
+ |---------|-------|-----|
9
+ | Websockets disconnect every few requests | No session affinity on Cloud Run | Add `--session-affinity` to `gcloud run deploy` |
10
+ | Socket connections drop after ~5 minutes | Default Cloud Run request timeout (300s) | Raise `--timeout` (for example `3600`) |
11
+ | Log: `FeatureFlag.watch() failed — live updates require MongoDB as a replica set` | `MONGO_URI` points at standalone `mongod` | Use Atlas or a replica set — change streams required |
12
+ | First request after idle is slow; sockets reconnect | Cloud Run scaled to zero | Set `--min-instances=1` for user-facing services |
13
+ | Browser: `Access to fetch at '...' from origin '...' has been blocked by CORS policy` | `corsOrigin` / Better Auth `trustedOrigins` missing web CDN origin | Add CDN URL to backend CORS and auth config |
14
+
15
+ ## Deploy-time failures
16
+
17
+ | Symptom | Cause | Fix |
18
+ |---------|-------|-----|
19
+ | `denied: Permission "artifactregistry.repositories.uploadArtifacts" denied` | Deploy identity lacks Artifact Registry write | Grant `roles/artifactregistry.writer` to the deploy service account |
20
+ | `Permission denied on secret ...` or `Secret Manager access denied` at cold start | Runtime SA lacks `secretmanager.secretAccessor` | Grant role on each secret or project |
21
+ | Cloud Run revision unhealthy; logs show connection refused on health check | Container not listening on `$PORT` | Terreno reads `process.env.PORT` (`api/src/terrenoApp.ts`); set `ENV PORT=3000` in Dockerfile and `--port=3000` on deploy |
22
+ | Web app loads but API calls go to `localhost` | `EXPO_PUBLIC_API_URL` not set at build time | Rebuild with `EXPO_PUBLIC_API_URL=$SERVICE_URL bun run export` |
23
+ | CDN serves old UI after deploy | CDN cache not invalidated | `gcloud compute url-maps invalidate-cdn-cache SITE-url-map --path "/*" --async` |
24
+
25
+ ## Health check
26
+
27
+ | Symptom | Cause | Fix |
28
+ |---------|-------|-----|
29
+ | `curl $SERVICE_URL/health` returns `503` with `"database": "disconnected"` | MongoDB unreachable from Cloud Run | Check `MONGO_URI`, VPC/Atlas IP allowlist, and credentials |
@@ -0,0 +1,145 @@
1
+ ---
2
+ name: generate-release-announcements
3
+ description: >-
4
+ Generate, validate, and optionally upload a multi-announcement Markdown release
5
+ pack for @terreno/announcements. Use when asked to announce a product release,
6
+ turn release notes or a Linear Release into in-app announcements, create an
7
+ announcement pack, or upload release announcements as drafts or live.
8
+ targets:
9
+ - '*'
10
+ ---
11
+
12
+ # Generate release announcements
13
+
14
+ Create a source-controlled `terreno.announcement-pack/v1` directory from release notes. Human reference: `docs/reference/announcement-release-packs.md` in Terreno or the published Terreno announcement release-pack reference.
15
+
16
+ ## 1. Collect the release
17
+
18
+ Determine:
19
+
20
+ - product identifier
21
+ - user-facing version
22
+ - client build number when one exists
23
+ - channel (`production` unless the user names another)
24
+ - release notes source: provided text/file, GitHub release/tag, changelog range, or Linear Release
25
+
26
+ For a Linear URL, use the available Linear connector to read the release and completed issues. If Linear is unavailable, stop only that source lookup and ask for the exported release notes; do not invent issue contents.
27
+
28
+ The collection step is complete when every feature, fix, breaking change, and operator action in the source is accounted for or explicitly excluded.
29
+
30
+ ## 2. Choose the surfaces
31
+
32
+ Generate one or more focused announcements rather than copying raw issue lists:
33
+
34
+ 1. Always create a `feed` changelog containing the complete user-relevant release.
35
+ 2. Create a `modal` only for required action, breaking workflow, or high-impact staff rollout.
36
+ 3. Create a `banner` for a short, non-blocking user-facing highlight.
37
+ 4. Split staff and patient/user copy when their actions or vocabulary differ.
38
+
39
+ Default to `dismiss-only`. Use `required` only when the reader must take an action or confirm operational information.
40
+
41
+ The surface step is complete when each item has one audience, one purpose, and a title containing the release version.
42
+
43
+ ## 3. Write the pack
44
+
45
+ Create:
46
+
47
+ ```text
48
+ announcements/releases/<version>/
49
+ pack.yaml
50
+ <one-or-more-slugs>.md
51
+ ```
52
+
53
+ Write `pack.yaml`:
54
+
55
+ ```yaml
56
+ schema: terreno.announcement-pack/v1
57
+ release:
58
+ product: <product>
59
+ version: "<version>"
60
+ buildNumber: <positive integer; omit when unknown>
61
+ channel: production
62
+ defaults:
63
+ platforms: [ios, android, web]
64
+ audienceType: all
65
+ displayMode: feed
66
+ acknowledgementPolicy: dismiss-only
67
+ priority: 0
68
+ announcements:
69
+ - changelog.md
70
+ ```
71
+
72
+ Each listed file uses YAML frontmatter plus Markdown:
73
+
74
+ ```markdown
75
+ ---
76
+ slug: changelog
77
+ title: "Version <version>"
78
+ displayMode: feed
79
+ audienceType: all
80
+ acknowledgementPolicy: dismiss-only
81
+ ---
82
+
83
+ ## What changed in <version>
84
+
85
+ <reader-focused release copy>
86
+ ```
87
+
88
+ Frontmatter may override `platforms`, `priority`, `minBuildNumber`, `publishAt`, `expiresAt`, `audience`, and `primaryAction` (`label` plus absolute `url`). The release build number becomes `minBuildNumber` unless an item overrides it.
89
+
90
+ Do not write the server-managed numeric announcement `version`; that is a content revision, not the product release version.
91
+
92
+ The writing step is complete when every manifest file exists and every body is useful without reading another announcement.
93
+
94
+ ## 4. Validate
95
+
96
+ Fail before upload when any check fails:
97
+
98
+ - manifest `schema` is exactly `terreno.announcement-pack/v1`
99
+ - product and release version are non-empty
100
+ - build number, when present, is a positive integer
101
+ - every listed Markdown file exists exactly once
102
+ - every item has a unique lowercase hyphenated `slug`, a title, and a non-empty body
103
+ - enum values match the announcement reference
104
+ - platforms are non-empty
105
+ - timestamps are ISO 8601 and primary-action URLs are absolute
106
+ - required modals state the required action clearly
107
+
108
+ Print the product, release version, build number, channel, item count, and a table of slug/surface/audience/policy. The validation step is complete only when all checks pass.
109
+
110
+ ## 5. Optionally upload
111
+
112
+ Do not upload unless the user asked for API posting. Convert manifest defaults and each file's frontmatter/body into the JSON request documented by `POST /announcements/import-release`.
113
+
114
+ Use:
115
+
116
+ - API base URL from the user or app configuration
117
+ - token from `ANNOUNCEMENTS_UPLOAD_TOKEN` unless the user names another environment variable
118
+ - `Authorization: Bearer <token>`
119
+ - `"publish": false` by default
120
+
121
+ Write request JSON to a temporary file outside the repository, then:
122
+
123
+ ```bash
124
+ curl --fail-with-body \
125
+ -H "Authorization: Bearer $ANNOUNCEMENTS_UPLOAD_TOKEN" \
126
+ -H "Content-Type: application/json" \
127
+ --data @/tmp/terreno-announcement-release.json \
128
+ "$API_URL/announcements/import-release"
129
+ ```
130
+
131
+ Never print the token or commit the temporary JSON.
132
+
133
+ Set `"publish": true` only when the user explicitly says `live`, `publish`, or equivalent. Before a live upload, show the validated item table and require confirmation unless the original unattended task explicitly authorized live publication. A normal request to “upload” means drafts.
134
+
135
+ The upload step is complete when the API reports every item as created, updated, or unchanged. Report returned IDs and statuses. Delete the temporary JSON.
136
+
137
+ ## Completion
138
+
139
+ Return:
140
+
141
+ - pack directory
142
+ - product version and build number
143
+ - generated item count and surfaces
144
+ - validation result
145
+ - upload result (`not requested`, `draft`, or `live`)
@@ -0,0 +1,156 @@
1
+ ---
2
+ name: generate-sdk
3
+ description: >-
4
+ Regenerate the example-frontend RTK Query SDK (store/openApiSdk.ts) from the
5
+ example-backend's OpenAPI spec. Handles dependency install, backend startup,
6
+ SDK codegen, and cleanup from a fresh checkout. Use whenever the backend's API
7
+ surface changes — new modelRouter routes, custom routes registered via
8
+ createOpenApiBuilder, new/modified Mongoose models or field descriptions in
9
+ example-backend, changes to
10
+ permissions/queryFields/populatePaths/responseHandler, edits to @terreno/api
11
+ routing or OpenAPI generation, or any time a route is added/removed/renamed.
12
+ Also use when the user asks to "regenerate the SDK", "update openApiSdk.ts",
13
+ "run bun run sdk", or reports stale/missing generated hooks. Lifecycle
14
+ composition: Pick after API-shape changes; Roast to verify generated-client
15
+ compatibility.
16
+ targets:
17
+ - '*'
18
+ ---
19
+ # Generate Frontend SDK
20
+
21
+ Regenerates `example-frontend/store/openApiSdk.ts` from the backend OpenAPI spec via RTK Query codegen.
22
+
23
+ **Scope:** This skill covers **non-synced** routes only (auth, profile, AI,
24
+ feature flags, custom RPC). Synced collections use `@terreno/syncdb` hooks
25
+ (`useQuery`, `useMutate`) — they are **not** in the generated SDK. Built-in
26
+ admin RPC uses admin-frontend's host-bound fetch client; do not add admin
27
+ `injectEndpoints`. See [syncdb reference](../../../../docs/reference/syncdb.md)
28
+ and [migrate-rtk-to-syncdb.md](../../../../docs/how-to/migrate-rtk-to-syncdb.md).
29
+
30
+ ### Legacy RTK data path
31
+
32
+ During the `@terreno/rtk` support window, some screens may still use generated `useGetXQuery` / `usePostXMutation` for collections. Do not add new collection hooks — migrate to syncdb instead.
33
+
34
+ The `example-frontend` consumes the `example-backend` API via auto-generated RTK Query hooks in `store/openApiSdk.ts`. The codegen pulls from the live backend at `http://localhost:4000/openapi.json`, so the backend must be running while `bun run sdk` executes.
35
+
36
+ **Never edit `example-frontend/store/openApiSdk.ts` by hand — it is fully overwritten by this skill.**
37
+
38
+ ## Documentation
39
+
40
+ Read `docs/reference/syncdb.md` and the backend/OpenAPI reference before regenerating. After a public API change, run `update-docs` in the same slice so reference and how-to pages match the new hooks. Generated SDK files are not hand-edited docs.
41
+
42
+ ## When to run this skill
43
+
44
+ Run it whenever the backend's OpenAPI surface changes. Common triggers:
45
+
46
+ - New or removed `modelRouter` registration
47
+ - Edits to `permissions`, `queryFields`, `populatePaths`, `defaultQueryParams`, `sort`, `responseHandler`, or `openApiOverwrite` on an existing `modelRouter`
48
+ - New custom routes built with `createOpenApiBuilder` (path params, query params, request body, response schema changes)
49
+ - Mongoose schema changes that affect the public shape: added/removed fields, type changes, required/enum/default changes, `description` updates, ref changes
50
+ - Changes to auth routes (e.g. `addAuthRoutes`, `addMeRoutes`, GitHub OAuth, Better Auth)
51
+ - Edits to `@terreno/api` files that produce the OpenAPI spec (`openApi.ts`, `openApiBuilder.ts`, `api.ts`, `populate.ts`)
52
+ - User asks: "regenerate SDK", "update the SDK", "run bun run sdk", "openApiSdk.ts is out of date"
53
+
54
+ If the change is purely backend-internal (logging, refactor that doesn't alter routes/models, test-only edits), the SDK does **not** need regeneration — skip this skill.
55
+
56
+ ## Prerequisites
57
+
58
+ - Bun is on PATH (run `bun` directly; do not modify PATH)
59
+ - MongoDB reachable at `mongodb://localhost:27017` (the example backend connects on boot)
60
+ - Ports 4000 (backend) must be free
61
+
62
+ If MongoDB is not running, tell the user and stop — do not try to install or start it automatically.
63
+
64
+ ## Procedure
65
+
66
+ Run the steps in order. Stop and report on the first failure.
67
+
68
+ ### 1. Verify the worktree is bootstrapped
69
+
70
+ Detect whether dependencies are installed. The repo uses Bun workspaces, so a single root `node_modules` is sufficient.
71
+
72
+ ```bash
73
+ test -d node_modules && test -d example-backend/node_modules && test -d example-frontend/node_modules && echo "installed" || echo "missing"
74
+ ```
75
+
76
+ If `missing`, run the full bootstrap from the repo root. This installs deps **and** compiles every workspace package (needed because `example-frontend` imports built artifacts from `@terreno/rtk` and other workspaces):
77
+
78
+ ```bash
79
+ bun run bootstrap
80
+ ```
81
+
82
+ Bootstrap can take a few minutes — run it in the background and wait for it to finish before continuing.
83
+
84
+ ### 2. Ensure backend env vars are set
85
+
86
+ The backend reads `example-backend/.env`. If that file is missing, copy the example:
87
+
88
+ ```bash
89
+ test -f example-backend/.env || cp example-backend/.env.example example-backend/.env
90
+ ```
91
+
92
+ The default `.env.example` values are fine for local SDK generation. Do not invent secrets or overwrite an existing `.env`.
93
+
94
+ ### 3. Start the example backend on port 4000
95
+
96
+ The dev script watches files, which is fine for a one-shot codegen run. Start it in the background from the repo root:
97
+
98
+ ```bash
99
+ bun run backend:dev
100
+ ```
101
+
102
+ Use the Bash tool's `run_in_background: true` so the process keeps running while you continue.
103
+
104
+ ### 4. Wait for the OpenAPI spec to be served
105
+
106
+ Poll `http://localhost:4000/openapi.json` until it returns HTTP 200. The backend has to connect to MongoDB and register all routes before the spec is available, which can take 5–20 seconds on a cold start. Do not skip this wait — running `bun run sdk` before the spec is ready produces an empty/broken `openApiSdk.ts`.
107
+
108
+ ```bash
109
+ until curl -sf -o /dev/null -w "%{http_code}" http://localhost:4000/openapi.json | grep -q 200; do sleep 2; done
110
+ ```
111
+
112
+ If the backend logs an error (Mongo connection refused, port already in use, compile error), stop and surface the error to the user. Do not retry blindly.
113
+
114
+ ### 5. Generate the SDK
115
+
116
+ Run the codegen from `example-frontend`. The script also runs Biome formatting on the output and strips an empty re-export line, so no follow-up formatting is needed.
117
+
118
+ ```bash
119
+ cd example-frontend && bun run sdk
120
+ ```
121
+
122
+ If the script fails with a missing module error (e.g. `@rtk-query/codegen-openapi`), dependencies are stale — re-run `bun run bootstrap` from the repo root and try again.
123
+
124
+ ### 6. Stop the backend
125
+
126
+ Kill the background backend process you started in step 3. Use the PID from the background shell, or:
127
+
128
+ ```bash
129
+ pkill -f "example-backend.*src/index.ts" || true
130
+ ```
131
+
132
+ ### 7. Verify and report
133
+
134
+ Confirm `example-frontend/store/openApiSdk.ts` exists and was modified. Show the user a short summary:
135
+
136
+ - Which routes/models triggered the regeneration
137
+ - A `git diff --stat example-frontend/store/openApiSdk.ts` to show the size of the change
138
+ - Any warnings or errors emitted by the backend or codegen
139
+
140
+ Do **not** commit `openApiSdk.ts` automatically — leave that to the user's normal commit flow.
141
+
142
+ ## Common failure modes
143
+
144
+ | Symptom | Cause | Fix |
145
+ |---|---|---|
146
+ | `ECONNREFUSED 127.0.0.1:27017` | MongoDB not running | Ask the user to start Mongo; do not auto-start |
147
+ | `EADDRINUSE :::4000` | Stale backend process | `pkill -f "example-backend.*src/index.ts"` then retry |
148
+ | `Cannot find module '@rtk-query/codegen-openapi'` | Missing/stale deps | Re-run `bun run bootstrap` |
149
+ | `openApiSdk.ts` has no endpoints | Backend wasn't ready when codegen ran | Step 4 wait was skipped — start over with a proper poll |
150
+ | Codegen errors on `$ref` | Backend OpenAPI spec is malformed | Inspect `/openapi.json` manually; check recent changes to `openApi.ts` or model schemas |
151
+
152
+ ## Notes
153
+
154
+ - The codegen config lives at `example-frontend/openapi-config.ts`. The schema URL respects `OPENAPI_URL` if set, otherwise defaults to `http://localhost:4000/openapi.json`.
155
+ - The script entry point is `example-frontend/scripts/generate-sdk.ts` — it shells out to the `@rtk-query/codegen-openapi` CLI, then post-processes the output with Biome.
156
+ - Custom endpoints and cache tag types are layered on top in `example-frontend/store/sdk.ts` via `injectEndpoints`/`enhanceEndpoints`; that file is hand-maintained and is **not** overwritten by codegen.