@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,255 @@
1
+ // Shared config, logging, and process helpers for the store-assets scripts.
2
+ //
3
+ // Every repo-specific value (apps, ports, test accounts, brand, banner copy) lives in
4
+ // storeAssets.config.json at the repo root, or the path in STORE_ASSETS_CONFIG.
5
+
6
+ import {existsSync} from "node:fs";
7
+ import {join, resolve} from "node:path";
8
+
9
+ export interface AccountConfig {
10
+ email: string;
11
+ password: string;
12
+ }
13
+
14
+ export interface BannerConfig {
15
+ headline: string;
16
+ body: string;
17
+ capsules?: string[];
18
+ // Two names from storeScreenshots/android, front one first.
19
+ screenshots: string[];
20
+ }
21
+
22
+ export interface AppConfig {
23
+ // Directory containing the Expo app (app.json, eas.json, maestro/).
24
+ appDir: string;
25
+ // Port the app's `expo start` uses; the screenshot run serves a production bundle on it.
26
+ metroPort: number;
27
+ // Device keys to capture: iphone | ipad | android.
28
+ devices: string[];
29
+ account: AccountConfig;
30
+ // Simulator to boot for `devclient:*` when none is running (e.g. "iPad (A16)").
31
+ preferredIosSimulator?: string;
32
+ banner?: BannerConfig;
33
+ }
34
+
35
+ export interface BannerDecoration {
36
+ // Image path relative to the repo root.
37
+ src: string;
38
+ // CSS declarations positioning the image, e.g. "width:72px;right:24px;top:20px".
39
+ style: string;
40
+ }
41
+
42
+ export interface BrandConfig {
43
+ // Feature graphic / icon surface and the color drawn on it.
44
+ surface: string;
45
+ onSurface: string;
46
+ capsuleBackground: string;
47
+ capsuleText: string;
48
+ border: string;
49
+ headingFont: string;
50
+ bodyFont: string;
51
+ // Google Fonts css2 query, e.g. "family=Merriweather:wght@700;900&family=DM+Sans:wght@400;700".
52
+ googleFontsQuery?: string;
53
+ // Vector logo (wordmark lockup) for the banner, recolored via logoRecolor.
54
+ logoSvg: string;
55
+ logoRecolor?: Record<string, string>;
56
+ logoHeight?: number;
57
+ // Mark used for the store icon. Either a mark-only SVG, or a logo SVG plus the number of
58
+ // leading <path> elements that form the mark and their viewBox.
59
+ iconSvg: string;
60
+ iconPathCount?: number;
61
+ iconViewBox?: string;
62
+ // Colors in the icon SVG to replace with onSurface (e.g. ["black"]).
63
+ iconRecolor?: string[];
64
+ bannerDecorations?: BannerDecoration[];
65
+ }
66
+
67
+ export interface ApiProbe {
68
+ method: "GET" | "POST";
69
+ path: string;
70
+ body?: string;
71
+ }
72
+
73
+ export interface ApiConfig {
74
+ port: number;
75
+ // A route only this backend serves, so another project's server on the same port is
76
+ // detected. Any status other than 404 or a connection error counts as "ours".
77
+ probe: ApiProbe;
78
+ // Shown when the backend is down.
79
+ startCommand: string;
80
+ }
81
+
82
+ export interface DevClientProfiles {
83
+ ios: string;
84
+ android: string;
85
+ }
86
+
87
+ export interface StoreAssetsConfig {
88
+ api: ApiConfig;
89
+ apps: Record<string, AppConfig>;
90
+ brand?: BrandConfig;
91
+ devClientProfiles?: DevClientProfiles;
92
+ homeScreenTestId?: string;
93
+ }
94
+
95
+ export interface ExpoInfo {
96
+ androidPackage: string;
97
+ bundleId: string;
98
+ owner: string;
99
+ slug: string;
100
+ }
101
+
102
+ export const REPO_ROOT = resolve(process.env.STORE_ASSETS_ROOT ?? process.cwd());
103
+
104
+ // `devclient:ensure` exit codes the screenshot run reacts to.
105
+ export const EXIT_BUILD_PENDING = 3;
106
+ export const EXIT_BUILD_NEEDED = 4;
107
+
108
+ // Identifiers interpolated into adb shell strings; reject anything a shell could interpret.
109
+ const SAFE_IDENTIFIER = /^[A-Za-z0-9._-]+$/;
110
+
111
+ export const say = ({tag, message}: {tag: string; message: string}): void => {
112
+ console.info(`\x1b[1;36m[${tag}]\x1b[0m ${message}`);
113
+ };
114
+
115
+ export const warn = ({tag, message}: {tag: string; message: string}): void => {
116
+ console.warn(`\x1b[1;33m[${tag}]\x1b[0m ${message}`);
117
+ };
118
+
119
+ export const fail = ({tag, message}: {tag: string; message: string}): never => {
120
+ console.error(`\x1b[1;31m[${tag}]\x1b[0m ${message}`);
121
+ process.exit(1);
122
+ };
123
+
124
+ export const loadConfig = async ({tag}: {tag: string}): Promise<StoreAssetsConfig> => {
125
+ const path = resolve(REPO_ROOT, process.env.STORE_ASSETS_CONFIG ?? "storeAssets.config.json");
126
+ if (!existsSync(path)) {
127
+ return fail({
128
+ message: `Missing ${path}. See the store-assets skill for the config format.`,
129
+ tag,
130
+ });
131
+ }
132
+ const config = (await Bun.file(path).json()) as Partial<StoreAssetsConfig>;
133
+ if (!config.api?.port || !config.api.probe?.path) {
134
+ return fail({message: `${path} needs api.port and api.probe.path`, tag});
135
+ }
136
+ if (!config.apps || Object.keys(config.apps).length === 0) {
137
+ return fail({message: `${path} needs at least one entry in apps`, tag});
138
+ }
139
+ for (const [key, app] of Object.entries(config.apps)) {
140
+ if (!app.appDir || !app.metroPort || !app.devices?.length || !app.account?.email) {
141
+ return fail({message: `apps.${key} needs appDir, metroPort, devices, and account`, tag});
142
+ }
143
+ }
144
+ return config as StoreAssetsConfig;
145
+ };
146
+
147
+ export const getApp = ({
148
+ config,
149
+ appKey,
150
+ tag,
151
+ }: {
152
+ config: StoreAssetsConfig;
153
+ appKey?: string;
154
+ tag: string;
155
+ }): AppConfig => {
156
+ const app = appKey ? config.apps[appKey] : undefined;
157
+ if (!app) {
158
+ return fail({
159
+ message: `Unknown app "${appKey ?? ""}". Use one of: ${Object.keys(config.apps).join(", ")}`,
160
+ tag,
161
+ });
162
+ }
163
+ return app;
164
+ };
165
+
166
+ export const getApiPort = ({config}: {config: StoreAssetsConfig}): number =>
167
+ Number(process.env.STORE_ASSETS_API_PORT ?? config.api.port);
168
+
169
+ export const getExpoInfo = async ({app, tag}: {app: AppConfig; tag: string}): Promise<ExpoInfo> => {
170
+ const path = join(REPO_ROOT, app.appDir, "app.json");
171
+ if (!existsSync(path)) {
172
+ return fail({message: `Missing ${path}`, tag});
173
+ }
174
+ const {expo} = await Bun.file(path).json();
175
+ const info = {
176
+ androidPackage: expo?.android?.package,
177
+ bundleId: expo?.ios?.bundleIdentifier,
178
+ owner: expo?.owner,
179
+ slug: expo?.slug,
180
+ };
181
+ const keys: Record<keyof ExpoInfo, string> = {
182
+ androidPackage: "expo.android.package",
183
+ bundleId: "expo.ios.bundleIdentifier",
184
+ owner: "expo.owner",
185
+ slug: "expo.slug",
186
+ };
187
+ for (const [field, key] of Object.entries(keys) as [keyof ExpoInfo, string][]) {
188
+ if (!info[field] || !SAFE_IDENTIFIER.test(info[field])) {
189
+ return fail({message: `${path} needs a valid ${key} (letters, digits, ".", "_", "-")`, tag});
190
+ }
191
+ }
192
+ return info as ExpoInfo;
193
+ };
194
+
195
+ export const run = async ({
196
+ cmd,
197
+ cwd = REPO_ROOT,
198
+ env,
199
+ isQuiet = false,
200
+ }: {
201
+ cmd: string[];
202
+ cwd?: string;
203
+ env?: Record<string, string>;
204
+ isQuiet?: boolean;
205
+ }): Promise<{exitCode: number; stdout: string}> => {
206
+ const proc = Bun.spawn(cmd, {
207
+ cwd,
208
+ env: {...process.env, ...env},
209
+ stderr: isQuiet ? "pipe" : "inherit",
210
+ stdin: "ignore",
211
+ stdout: isQuiet ? "pipe" : "inherit",
212
+ });
213
+ // Drain both pipes together so a child that fills stderr first can't deadlock.
214
+ const [stdout, stderr] = isQuiet
215
+ ? await Promise.all([
216
+ new Response(proc.stdout as ReadableStream).text(),
217
+ new Response(proc.stderr as ReadableStream).text(),
218
+ ])
219
+ : ["", ""];
220
+ const exitCode = await proc.exited;
221
+ if (exitCode !== 0 && stderr) {
222
+ console.error(stderr);
223
+ }
224
+ return {exitCode, stdout};
225
+ };
226
+
227
+ export const runOrFail = async (
228
+ args: Parameters<typeof run>[0] & {tag: string}
229
+ ): Promise<string> => {
230
+ const {exitCode, stdout} = await run(args);
231
+ if (exitCode !== 0) {
232
+ fail({message: `Command failed (${exitCode}): ${args.cmd.join(" ")}`, tag: args.tag});
233
+ }
234
+ return stdout;
235
+ };
236
+
237
+ export const isMetroRunning = async ({port}: {port: number}): Promise<boolean> => {
238
+ return fetch(`http://localhost:${port}/status`)
239
+ .then(async (response) => (await response.text()).includes("packager-status:running"))
240
+ .catch(() => false);
241
+ };
242
+
243
+ export const getAndroidHome = ({tag}: {tag: string}): string => {
244
+ const candidates = [
245
+ process.env.ANDROID_HOME,
246
+ process.env.ANDROID_SDK_ROOT,
247
+ join(process.env.HOME ?? "", "Library", "Android", "sdk"),
248
+ "/opt/homebrew/share/android-commandlinetools",
249
+ ];
250
+ const androidHome = candidates.find((path) => path && existsSync(join(path, "emulator")));
251
+ if (!androidHome) {
252
+ return fail({message: "Android SDK emulator not found. Set ANDROID_HOME.", tag});
253
+ }
254
+ return androidHome;
255
+ };