|
| 1 | +# AGENTS.md — `dev/seed/` |
| 2 | + |
| 3 | +Contract for `pnpm dev:seed` runner/topic modules. Read before adding, modifying, or invoking seeds. |
| 4 | + |
| 5 | +## Layout |
| 6 | + |
| 7 | +``` |
| 8 | +dev/seed/ |
| 9 | + index.ts Runner: args, listing, result output. |
| 10 | + lib/ |
| 11 | + preflight.ts Import FIRST; mutates process.env from argv. |
| 12 | + db.ts Lazy drizzle client. |
| 13 | + stripe.ts Lazy Stripe test-mode client/customer helpers. |
| 14 | + <scope>/<topic>.ts Topic module. Scope = folder; topic = filename. |
| 15 | +``` |
| 16 | + |
| 17 | +Runner globs `<scope>/*.ts`. Any top-level dir except `lib/` is a scope. Scope folders must contain only topic files; shared code goes in `lib/`. |
| 18 | + |
| 19 | +## Invocation |
| 20 | + |
| 21 | +```sh |
| 22 | +pnpm dev:seed <scope>:<topic> [args...] # canonical |
| 23 | +pnpm dev:seed <scope> <topic> [args...] # accepted |
| 24 | +pnpm dev:seed # list topics + usage |
| 25 | +``` |
| 26 | + |
| 27 | +Global flag: |
| 28 | + |
| 29 | +- `--json`: suppress topic stdout; print exactly one `JSON.stringify(result)` line. Use `pnpm -s` for clean pipes: |
| 30 | + ```sh |
| 31 | + USER_ID=$(pnpm -s dev:seed app:create-user "Foo" foo@example.com --json | jq -r .userId) |
| 32 | + ``` |
| 33 | + Implementation: `preflight.ts` sets `DOTENV_CONFIG_QUIET=true` before other imports; runner suppresses `console.log/info/warn` during `run()`. Do not bypass with `process.stdout.write`. |
| 34 | + |
| 35 | +## Topic contract |
| 36 | + |
| 37 | +Topic files MUST: |
| 38 | + |
| 39 | +- Export `run(...args: string[]): Promise<SeedResult | void> | SeedResult | void`. |
| 40 | + - `type SeedResult = Record<string, string | number | boolean | null>`: flat JSON primitives only; no nested objects; stringify Dates. |
| 41 | + - Return every id/email/handle/balance needed by follow-up commands; the runner formats all output from this object. |
| 42 | +- Support `--help`/`-h` via local `printUsage()` and early return. |
| 43 | +- Reset only their own data at start for idempotent reruns. Delete by stable ids/emails, stable sandbox prefixes, or `dev-seed:` category prefixes. |
| 44 | +- Avoid module-level side effects (DB writes/network); no-args listing imports modules. |
| 45 | + |
| 46 | +Topic files SHOULD: |
| 47 | + |
| 48 | +- Export `const usage = '<arg> <arg> [options]'`; omit if no args. |
| 49 | +- Print short context before returning (`This fixture represents:`, `Note:`, `Suggested next step:`). Runner appends the Result block. |
| 50 | + |
| 51 | +Topic files MUST NOT: |
| 52 | + |
| 53 | +- Write stdout in `--json` beyond the runner's single JSON line; `console.*` is suppressed during `run()`, but `process.stdout.write` is not. |
| 54 | +- Print completion blocks or `key: value` lines duplicating `SeedResult`. |
| 55 | +- Use `as` casts or `!` assertions; prefer `satisfies typeof <table>.$inferInsert`. |
| 56 | +- Import from `apps/web/src/...` (server-only/Next runtime); replicate minimal logic in `lib/`. |
| 57 | +- Return nested objects or non-primitives in `SeedResult`; flatten with prefixed keys (`eligibleUserId`, `eligibleInstanceId`, ...). |
| 58 | + |
| 59 | +## Helpers |
| 60 | + |
| 61 | +- `getSeedDb()`: lazy singleton drizzle client, safe from any topic; pool cap 1. |
| 62 | +- `closeSeedDb()`: runner calls in `finally`; topics should not. |
| 63 | +- `createSeedStripeCustomer({ email, name, kiloUserId })`: creates real Stripe test-mode customer with `metadata: { kiloUserId, source: 'dev-seed' }`. |
| 64 | +- `deleteSeedStripeCustomer(id)`: rollback helper; swallows "no such customer". |
| 65 | +- `lib/stripe.ts` rejects missing/non-`sk_test_...` `STRIPE_SECRET_KEY`. |
| 66 | +- For seeded users used by Stripe-touching app code (`/profile`, billing pages, KiloClaw subscriptions), create a real Stripe customer. Never use `cus_seed_...`: it causes `StripeInvalidRequestError: No such customer` 400s. Order matches `createUserOnSignIn`: create Stripe customer, insert DB row, delete Stripe customer in `catch` on insert failure. |
| 67 | + |
| 68 | +## Direct user inserts |
| 69 | + |
| 70 | +Bare `kilocode_users` inserts leave users trapped in onboarding. To reach product surfaces, set: |
| 71 | + |
| 72 | +- `has_validation_stytch: true` — bypasses `/account-verification` (`!== null`). |
| 73 | +- `customer_source: 'dev-seed'` — bypasses `/customer-source-survey` (`!== null`; `''` means skipped). |
| 74 | + |
| 75 | +Canonical example: `app/create-user.ts`. Gate code: `apps/web/src/lib/stytch.ts` (`getStytchStatus`) and `apps/web/src/lib/survey-redirect.ts` (`maybeInterceptWithSurvey`). When adding a gate, update this section and `app/create-user.ts` together. |
| 76 | + |
| 77 | +## New-topic checklist |
| 78 | + |
| 79 | +1. Add `<scope>/<topic>.ts` (new top-level dirs become scopes). |
| 80 | +2. Export `run()` and usually `usage`. |
| 81 | +3. Use `getSeedDb()`; use `createSeedStripeCustomer` for app users needing Stripe. |
| 82 | +4. Reset only own fixtures idempotently. |
| 83 | +5. Return flat `SeedResult` with every follow-up id. |
| 84 | +6. Verify both modes: |
| 85 | + ```sh |
| 86 | + pnpm dev:seed <scope>:<topic> <args> # human-readable Result block |
| 87 | + pnpm -s dev:seed <scope>:<topic> <args> --json # single JSON line |
| 88 | + ``` |
| 89 | +7. Run `pnpm format` and `pnpm lint`. Skip `pnpm typecheck`: `dev/seed/` runs via `tsx` and is outside tsconfig; runtime is the type check. |
0 commit comments