@terreno/agent-plugin 0.0.0-stage → 58.0.0-beta.2

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 +2213 -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 +86 -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 +74 -0
  42. package/skills/building-admin-interfaces/references/screens-and-routes.md +50 -0
  43. package/skills/building-terreno-apps/SKILL.md +294 -0
  44. package/skills/building-terreno-apps/agents/openai.yaml +4 -0
  45. package/skills/building-terreno-apps/references/navigation.md +108 -0
  46. package/skills/building-terreno-apps/references/route-structure.md +104 -0
  47. package/skills/building-terreno-apps/references/screen-patterns.md +174 -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 +165 -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 +112 -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 +288 -0
  78. package/skills/terreno-data-fetching/agents/openai.yaml +4 -0
  79. package/skills/terreno-data-fetching/references/auth-and-tokens.md +89 -0
  80. package/skills/terreno-data-fetching/references/realtime.md +50 -0
  81. package/skills/terreno-data-fetching/references/sdk-customization.md +55 -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 +87 -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 +93 -0
  97. package/skills/upgrading-terreno/references/ordering.md +40 -0
  98. package/skills/verify-ui-changes/SKILL.md +605 -0
@@ -0,0 +1,174 @@
1
+ # Screen Patterns — Terreno Apps
2
+
3
+ ## List screen (synced collection)
4
+
5
+ Synced collections read from the local store: no loading spinner after bootstrap, and
6
+ writes apply immediately.
7
+
8
+ ```tsx
9
+ import {generateMutationId} from "@terreno/syncdb";
10
+ import {useMutate, useQuery} from "@terreno/syncdb/react";
11
+ import {Box, Button, Card, Heading, Page, Text} from "@terreno/ui";
12
+ import {useCallback} from "react";
13
+
14
+ const TodoListScreen: React.FC = () => {
15
+ const todos = useQuery<Todo>("todos", {sort: "-created"});
16
+ const {create} = useMutate("todos");
17
+
18
+ const handleCreate = useCallback((): void => {
19
+ create({data: {_id: generateMutationId(), title: "New todo", completed: false}});
20
+ }, [create]);
21
+
22
+ return (
23
+ <Page title="Todos" scroll>
24
+ <Box gap={3} padding={4}>
25
+ <Box direction="row" justifyContent="between" alignItems="center">
26
+ <Heading size="lg">Todos</Heading>
27
+ <Button text="Add" onClick={handleCreate} iconName="plus" />
28
+ </Box>
29
+ {todos.length === 0 ? (
30
+ <Text color="secondaryLight">No todos yet</Text>
31
+ ) : (
32
+ todos.map((todo) => (
33
+ <Card key={todo._id} padding={3}>
34
+ <Text>{todo.title}</Text>
35
+ </Card>
36
+ ))
37
+ )}
38
+ </Box>
39
+ </Page>
40
+ );
41
+ };
42
+ ```
43
+
44
+ Gate the empty state on the stream's bootstrap (see `docs/how-to/use-syncdb-correctly.md`).
45
+
46
+ ## Profile screen
47
+
48
+ Use `TapToEdit` for per-field profile edits. Save each field with the generated `usePatchAuthMeMutation` — do not collect every field behind one Save button.
49
+
50
+ ```tsx
51
+ import {Box, Card, Page, TapToEdit} from "@terreno/ui";
52
+ import {useCallback, useEffect, useState} from "react";
53
+ import {useGetAuthMeQuery, usePatchAuthMeMutation} from "@/store/apiSdk";
54
+
55
+ const ProfileScreen: React.FC = () => {
56
+ const {data: profile} = useGetAuthMeQuery();
57
+ const [updateProfile] = usePatchAuthMeMutation();
58
+ const [name, setName] = useState<string>("");
59
+
60
+ // Seed only this field from the server so saving another TapToEdit does not wipe the draft.
61
+ useEffect(() => {
62
+ if (!profile) {
63
+ return;
64
+ }
65
+ setName(profile.name || "");
66
+ }, [profile?.name]);
67
+
68
+ const handleSaveName = useCallback(
69
+ async (value: string): Promise<void> => {
70
+ await updateProfile({name: value}).unwrap();
71
+ },
72
+ [updateProfile]
73
+ );
74
+
75
+ return (
76
+ <Page title="Profile" scroll>
77
+ <Box gap={4} padding={4}>
78
+ <Card>
79
+ <TapToEdit
80
+ onSave={handleSaveName}
81
+ setValue={setName}
82
+ title="Name"
83
+ type="text"
84
+ value={name}
85
+ />
86
+ </Card>
87
+ </Box>
88
+ </Page>
89
+ );
90
+ };
91
+ ```
92
+
93
+ ## Form screen
94
+
95
+ ```tsx
96
+ import {generateMutationId} from "@terreno/syncdb";
97
+ import {useMutate} from "@terreno/syncdb/react";
98
+ import {Box, Button, Page, TextField} from "@terreno/ui";
99
+ import {useCallback, useState} from "react";
100
+ import {useRouter} from "expo-router";
101
+
102
+ const CreateTodoScreen: React.FC = () => {
103
+ const router = useRouter();
104
+ const [title, setTitle] = useState<string>("");
105
+ const [titleError, setTitleError] = useState<string | undefined>();
106
+ const {create} = useMutate("todos");
107
+
108
+ const handleSubmit = useCallback((): void => {
109
+ if (!title.trim()) {
110
+ setTitleError("Title is required");
111
+ return;
112
+ }
113
+ create({data: {_id: generateMutationId(), title: title.trim(), completed: false}});
114
+ router.back();
115
+ }, [create, router, title]);
116
+
117
+ return (
118
+ <Page title="New Todo" scroll>
119
+ <Box gap={4} padding={4}>
120
+ <TextField
121
+ title="Title"
122
+ value={title}
123
+ onChange={setTitle}
124
+ errorText={titleError}
125
+ />
126
+ <Button text="Save" onClick={handleSubmit} fullWidth />
127
+ </Box>
128
+ </Page>
129
+ );
130
+ };
131
+ ```
132
+
133
+ Server rejections arrive later in `useConflicts()` / `useSyncStatus().failedCount`, not
134
+ from `create`.
135
+
136
+ ## Detail screen with params
137
+
138
+ ```tsx
139
+ import {useEntity} from "@terreno/syncdb/react";
140
+ import {Page, Text} from "@terreno/ui";
141
+ import {useLocalSearchParams} from "expo-router";
142
+
143
+ const TodoDetailScreen: React.FC = () => {
144
+ const {id} = useLocalSearchParams<{id: string}>();
145
+ const {data, deleted} = useEntity<Todo>("todos", id ?? "");
146
+
147
+ if (!data || deleted) {
148
+ return <Page title="Todo"><Text color="error">Not found</Text></Page>;
149
+ }
150
+
151
+ return (
152
+ <Page title={data.title} scroll>
153
+ <Text>{data.title}</Text>
154
+ </Page>
155
+ );
156
+ };
157
+ ```
158
+
159
+ ## Admin table screen
160
+
161
+ Prefer `@terreno/admin-frontend` over hand-rolled tables. Use `AdminScreenRouter` so
162
+ custom screens and models share `[model]/index`. Full nav and custom-screen rules:
163
+ skill `building-admin-interfaces`.
164
+
165
+ ```tsx
166
+ import {AdminScreenRouter} from "@terreno/admin-frontend";
167
+ import {useLocalSearchParams} from "expo-router";
168
+
169
+ // Rendered inside the AdminProvider in app/admin/_layout.tsx.
170
+ const AdminModelScreen: React.FC = () => {
171
+ const {model} = useLocalSearchParams<{model: string}>();
172
+ return <AdminScreenRouter apiBase="/admin" name={model ?? ""} routeBase="/admin" />;
173
+ };
174
+ ```
@@ -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,165 @@
1
+ ---
2
+ name: generate-sdk
3
+ description: >-
4
+ Regenerate the example-frontend SDKs from the example-backend's OpenAPI spec
5
+ with terreno-syncdb-codegen: store/apiSdk.ts (typed request hooks for every
6
+ non-synced route, `bun run sdk`) and store/syncDbSdk.ts (synced collection
7
+ hooks, `bun run sync-sdk`). Handles dependency install, backend startup,
8
+ codegen, and cleanup from a fresh checkout. Use whenever the backend's API
9
+ surface changes — new modelRouter routes or actions, custom routes registered
10
+ via createOpenApiBuilder, new/modified Mongoose models or field descriptions
11
+ in example-backend, sync config changes, changes to
12
+ permissions/queryFields/populatePaths/responseHandler, edits to @terreno/api
13
+ routing or OpenAPI generation, or any time a route is added/removed/renamed.
14
+ Also use when the user asks to "regenerate the SDK", "update apiSdk.ts" or
15
+ "syncDbSdk.ts", "run bun run sdk" or "bun run sync-sdk", or reports
16
+ stale/missing generated hooks. Lifecycle composition: Pick after API-shape
17
+ changes; Roast to verify generated-client compatibility.
18
+ targets:
19
+ - '*'
20
+ ---
21
+ # Generate Frontend SDK
22
+
23
+ Regenerates the two generated files in `example-frontend` from the live backend spec at
24
+ `http://localhost:4000/openapi.json` (or `OPENAPI_URL`). Both come from
25
+ `terreno-syncdb-codegen` in `@terreno/syncdb`.
26
+
27
+ | Command | Output | Covers |
28
+ |---------|--------|--------|
29
+ | `bun run sdk` (runs `api-sdk`) | `store/apiSdk.ts` | Every operation without `x-terreno-sync`: `/auth/me`, modelRouter actions, AI, consents, announcements, version check |
30
+ | `bun run sync-sdk` | `store/syncDbSdk.ts` | `SYNC_COLLECTIONS` + collection hooks for the collections in the script's `--collections` list |
31
+
32
+ **Never edit `store/apiSdk.ts` or `store/syncDbSdk.ts` by hand — codegen overwrites them.**
33
+ Codegen options live in `example-frontend/syncdb-codegen.json` (`excludePaths` drops
34
+ routes such as the SSE streams; `overrides` sets per-collection options). Built-in admin
35
+ uses admin-frontend's own fetch client and is not generated here.
36
+
37
+ ## Documentation
38
+
39
+ Read `docs/reference/syncdb.md#codegen` and `docs/how-to/generate-the-client-sdk.md`
40
+ before regenerating. After a public API change, run `update-docs` in the same slice.
41
+
42
+ ## When to run this skill
43
+
44
+ Run `sdk` when the non-synced OpenAPI surface changes; run `sync-sdk` when a synced
45
+ collection's schema or `sync` config changes. Run both when unsure. Triggers:
46
+
47
+ - New or removed `modelRouter` registration, `collectionActions`, or `instanceActions`
48
+ - Edits to `permissions`, `queryFields`, `populatePaths`, `defaultQueryParams`, `sort`, `responseHandler`, `sync`, or `openApiOverwrite`
49
+ - New custom routes built with `createOpenApiBuilder`
50
+ - Mongoose schema changes that affect the public shape (fields, types, required/enum/default, `description`, refs)
51
+ - Auth route changes (`addAuthRoutes`, `addMeRoutes`, GitHub OAuth, Better Auth)
52
+ - Edits to `@terreno/api` OpenAPI generation (`openApi.ts`, `openApiBuilder.ts`, `api.ts`, `populate.ts`) or to `syncdb/src/codegen/`
53
+ - A new synced collection: also add it to the `--collections` list in the `sync-sdk` script
54
+
55
+ Skip it for backend-internal changes (logging, refactors that keep routes and models, tests).
56
+
57
+ ## Prerequisites
58
+
59
+ - Bun on PATH (run `bun` directly; do not modify PATH)
60
+ - A MongoDB **replica set** at `MONGO_URI` (the backend's change streams need one)
61
+ - Port 4000 free
62
+
63
+ ## Procedure
64
+
65
+ Run the steps in order. Stop and report on the first failure.
66
+
67
+ ### 1. Verify the worktree is bootstrapped
68
+
69
+ ```bash
70
+ test -d node_modules && test -d example-backend/node_modules && test -d example-frontend/node_modules && echo "installed" || echo "missing"
71
+ ```
72
+
73
+ If `missing`, run `bun run bootstrap` from the repo root in the background and wait for it.
74
+ It installs deps and compiles every workspace package.
75
+
76
+ ### 2. Start MongoDB if it is not running
77
+
78
+ `mongosh --quiet --eval 'rs.status().ok'` (or a `node` MongoClient ping) succeeds when a
79
+ replica set is up. Otherwise start one from the `mongodb-memory-server` binary cache and
80
+ initiate it once, as in `/workspace/CLAUDE.md` ("Example full stack"):
81
+
82
+ ```bash
83
+ mkdir -p .devdata/mongo
84
+ "$(ls ~/.cache/mongodb-binaries/mongod-* | head -1)" \
85
+ --replSet rs0 --port 27017 --bind_ip 127.0.0.1 --dbpath .devdata/mongo
86
+ ```
87
+
88
+ Run `mongod` in the background. Initiate the replica set with `node` (not `bun`) from
89
+ `example-backend/`. If no `mongod` binary exists, tell the user and stop.
90
+
91
+ ### 3. Ensure backend env vars are set
92
+
93
+ ```bash
94
+ test -f example-backend/.env || cp example-backend/.env.example example-backend/.env
95
+ ```
96
+
97
+ The example values (`MONGO_URI=mongodb://127.0.0.1:27017/terreno-example?replicaSet=rs0`,
98
+ token and session secrets) are fine for codegen. Never overwrite an existing `.env`.
99
+
100
+ ### 4. Start the example backend on port 4000
101
+
102
+ ```bash
103
+ bun run backend:dev
104
+ ```
105
+
106
+ Use `run_in_background: true`.
107
+
108
+ ### 5. Wait for the OpenAPI spec
109
+
110
+ Poll until the spec returns 200 (5–20 s on a cold start). Codegen against a backend that is
111
+ not ready fails or emits an empty SDK.
112
+
113
+ ```bash
114
+ until curl -sf -o /dev/null http://localhost:4000/openapi.json; do sleep 2; done
115
+ ```
116
+
117
+ If the backend logs an error (Mongo connection refused, port in use, compile error), stop
118
+ and report it.
119
+
120
+ ### 6. Generate
121
+
122
+ ```bash
123
+ cd example-frontend && bun run sdk && bun run sync-sdk
124
+ ```
125
+
126
+ Both scripts format their output with Biome. A non-zero exit names the problem: two
127
+ operations sharing a hook name (give one an `operationId`), a missing collection path, or a
128
+ list response without `data.items`.
129
+
130
+ ### 7. Stop the backend
131
+
132
+ Kill the background backend you started (and `mongod`, if you started it):
133
+
134
+ ```bash
135
+ pkill -f "example-backend.*src/index.ts" || true
136
+ ```
137
+
138
+ ### 8. Verify and report
139
+
140
+ ```bash
141
+ git diff --stat example-frontend/store/apiSdk.ts example-frontend/store/syncDbSdk.ts
142
+ cd example-frontend && bunx tsc --noEmit
143
+ ```
144
+
145
+ Report which backend changes triggered the run, the diff stat, and any codegen or compile
146
+ errors. Do not commit the generated files automatically.
147
+
148
+ ## Common failure modes
149
+
150
+ | Symptom | Cause | Fix |
151
+ |---|---|---|
152
+ | `ECONNREFUSED 127.0.0.1:27017` | MongoDB not running | Step 2 |
153
+ | Backend boots but change streams fail | Mongo is not a replica set | Start `mongod --replSet rs0` and initiate it |
154
+ | `EADDRINUSE :::4000` | Stale backend process | `pkill -f "example-backend.*src/index.ts"`, then retry |
155
+ | `fetch failed` / empty output | Backend not ready when codegen ran | Redo step 5 |
156
+ | Duplicate hook name error | Two operations map to one name | Add an `operationId` to one route |
157
+ | Screen imports a hook that no longer exists | Route renamed or excluded | Update the import; check `excludePaths` in `syncdb-codegen.json` |
158
+
159
+ ## Notes
160
+
161
+ - Script definitions: `example-frontend/package.json` (`api-sdk`, `sdk`, `sync-sdk`). They run
162
+ `syncdb/src/codegen/cli.ts` directly; consumer apps call the `terreno-syncdb-codegen` bin.
163
+ - Hook names match the old RTK codegen rule (`GET /auth/me` → `useGetAuthMeQuery`), so
164
+ migrated screens keep their names. Apps upgrading from `@terreno/rtk` follow
165
+ `docs/how-to/migrate-rtk-to-syncdb.md`.