@openora/create 0.1.0 → 0.4.1-canary.101

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 (52) hide show
  1. package/README.md +74 -0
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/generated/core-version.d.ts +1 -1
  4. package/dist/generated/core-version.d.ts.map +1 -1
  5. package/dist/generated/core-version.js +1 -1
  6. package/dist/generated/core-version.js.map +1 -1
  7. package/package.json +3 -3
  8. package/template/README.md.tpl +1 -1
  9. package/template/__dot__env.example +3 -1
  10. package/template/__dot__gitignore +1 -1
  11. package/template/__dot__rulesync/commands/adr.md +32 -0
  12. package/template/__dot__rulesync/commands/check.md +3 -3
  13. package/template/__dot__rulesync/commands/doctor.md +16 -0
  14. package/template/__dot__rulesync/commands/scaffold-module.md +2 -2
  15. package/template/__dot__rulesync/commands/scaffold-plugin.md +1 -1
  16. package/template/__dot__rulesync/commands/scaffold-route.md +1 -1
  17. package/template/__dot__rulesync/hooks/guard-generated.mjs +3 -3
  18. package/template/__dot__rulesync/rules/conventions.md +90 -0
  19. package/template/__dot__rulesync/rules/e2e-conventions.md +48 -0
  20. package/template/__dot__rulesync/rules/oss-boundaries.md +31 -0
  21. package/template/__dot__rulesync/rules/overview.md +9 -2
  22. package/template/__dot__rulesync/skills/add-feature/SKILL.md +3 -3
  23. package/template/__dot__rulesync/skills/create-plugin/SKILL.md +6 -6
  24. package/template/__dot__rulesync/skills/create-pr/SKILL.md +1 -1
  25. package/template/__dot__rulesync/skills/create-ui-module/SKILL.md +58 -0
  26. package/template/__dot__rulesync/skills/handoff/SKILL.md +94 -0
  27. package/template/__dot__rulesync/skills/review/SKILL.md +111 -0
  28. package/template/__dot__rulesync/subagents/builder.md +3 -3
  29. package/template/__dot__rulesync/subagents/debugger.md +1 -2
  30. package/template/__dot__rulesync/subagents/qa.md +1 -1
  31. package/template/__dot__rulesync/subagents/quality-reviewer.md +67 -0
  32. package/template/__dot__rulesync/subagents/security-reviewer.md +54 -0
  33. package/template/apps/api/package.json.tpl +1 -2
  34. package/template/apps/api/src/main.ts.tpl +1 -3
  35. package/template/apps/api/src/seed.ts.tpl +5 -1
  36. package/template/docker-compose.yml +11 -0
  37. package/template/docs/standards/comments.md +16 -0
  38. package/template/{__dot__rulesync/rules/db-conventions.md → docs/standards/database.md} +15 -21
  39. package/template/docs/standards/enforcement.md +12 -0
  40. package/template/docs/standards/errors.md +26 -0
  41. package/template/docs/standards/frontend.md +60 -0
  42. package/template/docs/standards/functions.md +38 -0
  43. package/template/docs/standards/git-delivery.md +16 -0
  44. package/template/docs/standards/module-structure.md +37 -0
  45. package/template/docs/standards/testing.md +48 -0
  46. package/template/docs/standards/types.md +31 -0
  47. package/template/package.json.tpl +4 -4
  48. package/template/rulesync.jsonc +1 -1
  49. package/template/turbo/generators/config.ts +1 -1
  50. package/template/turbo.json +14 -2
  51. package/template/__dot__rulesync/skills/code-review/SKILL.md +0 -111
  52. package/template/apps/api/src/migrate.ts.tpl +0 -46
@@ -0,0 +1,67 @@
1
+ ---
2
+ targets:
3
+ - '*'
4
+ name: quality-reviewer
5
+ description: >-
6
+ Code-quality review of changed files: OSS-core boundaries, conventions,
7
+ frontend rules, performance, duplication, and simplification in a single
8
+ pass. Findings only, no edits.
9
+ claudecode:
10
+ model: sonnet
11
+ ---
12
+
13
+ You are a senior code-quality reviewer for this consumer igaming repo (built on `@openora/*` OSS core). One pass over the changed files, several lenses. You are NOT the implementer - findings only, no changes.
14
+
15
+ ## Grounding
16
+
17
+ - Read `.claude/rules/conventions.md` IN FULL, and `docs/standards/frontend.md` IN FULL when the diff touches a UI app or the shared UI package (skip if this repo deleted that file as headless) - enforce all of it, the lenses below are high-signal reminders, not the boundary of the review.
18
+ - For import/extension questions, `.claude/rules/oss-boundaries.md`; for overlay tables, `docs/standards/database.md`.
19
+ - Where no repo rule covers a problem, judge by established industry practice (algorithmic complexity, DB query patterns, transaction scope, React render behavior, error-handling hygiene) and name the principle in the finding.
20
+ - Library API in doubt (Next, React, Drizzle, Zod, `@openora/*`)? Check current docs via context7/web search - never claim from memory.
21
+
22
+ ## Scope
23
+
24
+ The orchestrator passes you the base ref and changed-file list - do not re-scope the diff. Read only the changed files plus the immediate callees a finding depends on. If no file list was passed: `git diff origin/dev...HEAD --name-only`.
25
+
26
+ ## Lenses
27
+
28
+ ### OSS boundaries & extension
29
+
30
+ - [ ] No edits to `@openora/*` core or `node_modules`; extension only via `extensions.config.ts`, overlay plugins, adapters.
31
+ - [ ] No deep imports into core internals - package/subpath entries only.
32
+ - [ ] Overlay talks to platform data via the typed client, events, or read-only `/schema` - never another module's internals.
33
+
34
+ ### Conventions
35
+
36
+ - [ ] Types inferred from schemas/contracts (`z.infer`, `$inferSelect`) - no hand-written duplicates; derive with `.pick/.omit/.extend`.
37
+ - [ ] Single source of truth for enums (values + schema + type triple); `timestamptz` for datetimes; named-object params over 3+ positionals.
38
+ - [ ] Zero-value comments flagged; missing WHY comments on genuinely surprising code flagged.
39
+
40
+ ### Frontend (`apps/web`, `apps/backoffice`, `packages/ui`)
41
+
42
+ - [ ] Module isolation per the Modular-architecture rules; no cross-module reach-ins.
43
+ - [ ] React Compiler assumptions hold (Rules of React); server state via the query lib, not raw `useEffect(fetch)`.
44
+ - [ ] daisyUI/styling conventions followed; no one-off design systems.
45
+
46
+ ### Performance
47
+
48
+ - [ ] No N+1 queries or `await` in a loop that could batch; lists paginate.
49
+ - [ ] No repeated hot-path work computable once; no unbounded reads filtered in JS.
50
+
51
+ ### Duplication & simplification
52
+
53
+ - [ ] No copy-pasted logic a helper a few files over provides - name the existing helper.
54
+ - [ ] No speculative abstraction (interface-with-one-impl, config for a constant); nested ifs flattenable with early returns; dead code introduced by the change.
55
+
56
+ ## Do NOT flag (false-positive guard)
57
+
58
+ - Anything lint/CI (`/check`, oxlint) already enforces.
59
+ - Style taste with no rule behind it (import order, naming preference, blank lines).
60
+ - Theoretical performance issues on cold/admin paths with no evidence they matter.
61
+ - Pre-existing code outside the diff, unless the change actively makes it worse.
62
+ - Missing features or scope expansion - review the change, not the roadmap.
63
+ - Speculative hardening or "might need later" abstractions.
64
+
65
+ ## Output
66
+
67
+ Max 10 findings, highest impact first. Each: `[WARN]`/`[INFO]` `file:line - finding - evidence - rule cited - fix`. Use `[BLOCK]` only for a core edit or boundary break. No prose around the list. End with **PASS** / **CHANGES REQUESTED** + one line on the most impactful finding.
@@ -0,0 +1,54 @@
1
+ ---
2
+ targets:
3
+ - '*'
4
+ name: security-reviewer
5
+ description: >-
6
+ Security review of changed overlay/frontend files for authz, secret/PII,
7
+ money-path, and input-validation risks in a real-money igaming consumer repo.
8
+ Findings only, no edits.
9
+ claudecode:
10
+ model: opus
11
+ ---
12
+
13
+ You are a security reviewer for a real-money igaming consumer repo built on `@openora/*`. Core money/auth logic lives upstream in the platform; you review what the OVERLAY adds: custom routes, adapter swaps, config, and the frontend. Findings only, no changes.
14
+
15
+ ## Grounding
16
+
17
+ If the orchestrator passed a base ref + changed-file list, use them - do not re-scope the diff. Otherwise: `git diff origin/dev...HEAD --name-only`. Read each changed file plus the immediate callees a finding depends on. Prioritize overlay plugins/routes, adapter implementations (KYC, PSP, notifications), auth/session touchpoints, and anything reading env/secrets.
18
+
19
+ ## Checklist
20
+
21
+ ### Authorization
22
+
23
+ - [ ] Overlay admin/backoffice routes enforce the platform guard - never a re-implemented role check.
24
+ - [ ] No client-supplied user id trusted for ownership decisions; caller resolved server-side.
25
+ - [ ] Frontend hides UI by role but the API is the enforcement point - flag authz that exists only client-side.
26
+
27
+ ### Money paths
28
+
29
+ - [ ] Overlay code never mutates balances directly - money flows through platform commands/ports.
30
+ - [ ] Any overlay money-adjacent mutation is idempotent at the data layer (DB guard, not just a key).
31
+ - [ ] Amounts are integer minor units; no float arithmetic on money.
32
+
33
+ ### Secrets & PII
34
+
35
+ - [ ] Vendor adapter credentials from env/config - never in source, templates, or client bundles.
36
+ - [ ] No PII (email, KYC docs, DOB, payment details) in logs, analytics events, error messages, or client-visible payloads.
37
+ - [ ] Nothing secret leaks into `NEXT_PUBLIC_*` or the browser bundle.
38
+
39
+ ### Input & injection
40
+
41
+ - [ ] All external input Zod-validated at the boundary (no `z.any()`/`z.unknown()` on a security edge).
42
+ - [ ] No raw SQL string interpolation; no inline `fetch` to vendors - adapters only (auditable egress).
43
+ - [ ] Webhooks from PSP/KYC vendors verify signatures before trusting payloads.
44
+
45
+ ## Do NOT flag (false-positive guard)
46
+
47
+ - Attack paths you have not traced through the actual code - state the concrete trigger or don't raise it.
48
+ - Platform-core internals (upstream's responsibility) - flag only how the overlay USES them.
49
+ - Code outside the diff, unless the change makes it newly exploitable.
50
+ - Generic hardening wishlists (rate limits everywhere, CSP) with no tie to the changed surface.
51
+
52
+ ## Output
53
+
54
+ Max 10 findings, most severe first. Each: `[BLOCK]` (exploitable / data leak - file:line, risk, concrete fix) / `[WARN]` (missing defense-in-depth) / `[INFO]` (hardening). End with **PASS** / **CHANGES REQUESTED** + one line on the most severe finding.
@@ -7,9 +7,8 @@
7
7
  "build": "tsc",
8
8
  "dev": "node --import tsx --watch --env-file-if-exists=../../.env src/main.ts",
9
9
  "start": "node --import tsx --env-file-if-exists=../../.env src/main.ts",
10
- "db:migrate": "node --import tsx --env-file-if-exists=../../.env src/migrate.ts",
11
10
  "db:seed": "node --import tsx --env-file-if-exists=../../.env src/seed.ts",
12
- "typecheck": "tsc --noEmit"
11
+ "check:types": "tsc --noEmit"
13
12
  },
14
13
  "dependencies": {
15
14
  "@openora/core": "{{coreVersion}}"
@@ -67,18 +67,16 @@ process.env['EXTENSIONS_CONFIG'] ??= resolve(
67
67
  );
68
68
 
69
69
  async function bootstrap() {
70
- const { listen, emitOpenApiSpec } = await createApp({
70
+ const { listen } = await createApp({
71
71
  plugins: await loadExtensions(),
72
72
  contract,
73
73
  authSchema: { user, session, account, verification, twoFactor },
74
74
  igaming,
75
75
  port: Number(process.env['PORT'] ?? 3001),
76
76
  cors: { origins: process.env['CORS_ORIGINS']?.split(',') ?? '*' },
77
- openapi: { info: { title: '{{name}} API', version: '0.1.0' } },
78
77
  });
79
78
 
80
79
  await listen();
81
- await emitOpenApiSpec();
82
80
  }
83
81
 
84
82
  void bootstrap();
@@ -10,6 +10,8 @@
10
10
  */
11
11
  import { createDrizzleDb } from '@openora/core/server';
12
12
  import { seedRoles } from '@openora/core/iam/seed';
13
+ import { seedTag } from '@openora/core/pam/tag/seed';
14
+ import { seedAutoWithdrawalConfig, seedBonusRolloverConfig } from '@openora/core/wallet/seed';
13
15
  // import additional module seeders here as you enable them
14
16
 
15
17
  async function main() {
@@ -27,7 +29,9 @@ async function main() {
27
29
  const db = createDrizzleDb(databaseUrl);
28
30
 
29
31
  await seedRoles(db);
30
- // await seedOtherModule(db);
32
+ await seedTag(db);
33
+ await seedAutoWithdrawalConfig(db);
34
+ await seedBonusRolloverConfig(db);
31
35
  console.log('Reference data seeded.');
32
36
  }
33
37
 
@@ -15,5 +15,16 @@ services:
15
15
  timeout: 5s
16
16
  retries: 10
17
17
 
18
+ redis:
19
+ image: redis:7-alpine
20
+ command: ['redis-server', '--maxmemory-policy', 'noeviction']
21
+ ports:
22
+ - '6379:6379'
23
+ healthcheck:
24
+ test: ['CMD', 'redis-cli', 'ping']
25
+ interval: 5s
26
+ timeout: 5s
27
+ retries: 10
28
+
18
29
  volumes:
19
30
  postgres_data:
@@ -0,0 +1,16 @@
1
+ # Comments
2
+
3
+ Read this before writing a comment or JSDoc.
4
+
5
+ - Comment WHY, never WHAT: hidden constraint, invariant, bug workaround (link it), trade-off. If a
6
+ block needs a comment to be understood, rename/extract instead.
7
+ - No section-divider comments (`// ---`, `// ===`).
8
+ - Every JSDoc is multi-line, always - `/**`, the text, and `*/` each on their own line. A
9
+ single-line `/** ... */` is never allowed, even for one sentence. Add one only on an independent
10
+ function/class (not a React component or hook) that is >~15 lines or has non-obvious params. One
11
+ sentence; document the surprising contract, not the name.
12
+ - Never JSDoc a React component or hook - a genuinely surprising note goes on the specific
13
+ prop/param type, or an inline comment at the call site.
14
+ - Deferred work: `// TODO:` with the concrete follow-up, never a bare TODO.
15
+ - Placeholder/sample data and stubs: greppable `// mock:` comment so throwaway code stays
16
+ findable.
@@ -1,22 +1,16 @@
1
- ---
2
- root: false
3
- targets:
4
- - '*'
5
- globs:
6
- - 'apps/api/**'
7
- description: SQL / Drizzle conventions for tables an overlay or local add-on owns.
8
- ---
9
-
10
1
  # Database conventions (SQL / Drizzle)
11
2
 
12
- Applies to every table an overlay or local add-on owns (`apps/api/src/extensions/<name>/src/schema/`).
13
- Tables live in `@openora/*` core for platform domains - never edit those; these rules govern the
14
- tables you add. Boundary/import rules live in `oss-boundaries`; this file is SQL only.
3
+ Read this in full before editing a schema, Drizzle query, migration config, or seed under
4
+ `apps/api/**`. Tables live in `@openora/*` core for platform domains - never edit those; these
5
+ rules govern the tables your overlay adds
6
+ (`apps/api/src/extensions/<name>/src/schema/`). Import/boundary rules live in `oss-boundaries`;
7
+ this file is SQL only.
15
8
 
16
9
  ## Identifiers - snake_case everywhere
17
10
 
18
- Every drizzle instance sets `casing: 'snake_case'`, so the SQL name derives from the camelCase key.
19
- Pass an explicit name only where casing can't derive it: table names, `pgEnum` types, index names.
11
+ Every drizzle instance sets `casing: 'snake_case'`, so the SQL name derives from the camelCase
12
+ key. Pass an explicit name only where casing can't derive it: table names, `pgEnum` types, index
13
+ names.
20
14
 
21
15
  ```ts
22
16
  // good - key derives the column; const camelCase; explicit snake_case only for table + index
@@ -46,8 +40,8 @@ Every datetime column (`createdAt`, `updatedAt`, `expiresAt`, any `*At`) carries
46
40
  ## Keys, references, indexes
47
41
 
48
42
  - UUID primary keys (`uuid().primaryKey().defaultRandom()`).
49
- - **No foreign keys across a module/overlay boundary** - store a plain ID string and resolve via the
50
- oRPC client or a schema subpath. FKs only within the same add-on.
43
+ - **No foreign keys across a module/overlay boundary** - store a plain ID string and resolve via
44
+ the oRPC client or a schema subpath. FKs only within the same add-on.
51
45
  - `NOT NULL` by default; push defaults to the DB (`.notNull().default(...)`), not app code.
52
46
  - Index every column you filter or join on; name it `<table>_<cols>_idx`.
53
47
 
@@ -62,11 +56,11 @@ await db.select().from(wallet).where(inArray(wallet.playerId, ids));
62
56
 
63
57
  - Select only the columns you use; don't `select(*)` wide rows to read one field.
64
58
  - **Build rows by spread + override, never hand-copy field-by-field.** When a row mostly mirrors a
65
- validated input, spread it and set only the server-computed fields - `db.insert(x).values({ ...input,
66
- id, createdAt })` - never re-list `field: input.field` per key (it silently drifts the moment a
67
- column is added). Keep fields explicit only for: an order-sensitive hash/signature payload; a source
68
- carrying columns the row must not receive (spread then omit them); or a null-vs-undefined boundary
69
- that won't coerce (`actorId: input.actorId ?? null`).
59
+ validated input, spread it and set only the server-computed fields - `db.insert(x).values({
60
+ ...input, id, createdAt })` - never re-list `field: input.field` per key (it silently drifts the
61
+ moment a column is added). Keep fields explicit only for: an order-sensitive hash/signature
62
+ payload; a source carrying columns the row must not receive (spread then omit them); or a
63
+ null-vs-undefined boundary that won't coerce (`actorId: input.actorId ?? null`).
70
64
  - **Money / critical paths are transactional and idempotent** - a DB guard inside the transaction,
71
65
  not just an `idempotencyKey` (delivery is at-least-once).
72
66
 
@@ -0,0 +1,12 @@
1
+ # Enforcement
2
+
3
+ Read this before working around a failing gate or adding a lint rule.
4
+
5
+ - `pnpm check:types` + `pnpm check:lint` (oxlint) + `pnpm test:unit` is the fast gate;
6
+ `pnpm check:boundaries` (dependency-cruiser) is the whole-graph boundary/cycle check, also run
7
+ by the pre-commit hook and CI.
8
+ - oxlint extends the platform's shared config (`./node_modules/@openora/core/oxlint/oxlintrc.json`)
9
+ - add local rules on top, never fork it.
10
+ - Don't work around a lint/boundary violation - fix the import.
11
+ - Agent rules are generated from `.rulesync/` via `pnpm gen:agents` - never hand-edit a generated
12
+ file.
@@ -0,0 +1,26 @@
1
+ # Errors
2
+
3
+ Read this before adding an error class, a catch, or a money-handling path.
4
+
5
+ - Fail fast at boundaries with typed errors (`CreateOrderSchema.parse(raw)` throws early).
6
+ - No silent catches - log with context and rethrow.
7
+ - Typed, named error classes via the shared factories (`makeNotFoundError`/`makeOwnershipError`/
8
+ `makeConflictError`); the router's `mapErrors` keys off the exported class.
9
+ - `ORPCError.message` is an English fallback for logs, not player-facing copy - UI copy keys off
10
+ `.code` plus typed `.data` fields.
11
+ - Money/critical paths are transactional AND idempotent: a DB guard inside the transaction, not
12
+ just an `idempotencyKey` (delivery is at-least-once).
13
+
14
+ ```ts
15
+ // bad - swallows the error, no context, no rethrow
16
+ try {
17
+ await chargeWallet(tx, amount);
18
+ } catch {
19
+ return null;
20
+ }
21
+ // good - a DB guard makes the mutation idempotent under at-least-once delivery
22
+ await db.transaction(async (t) => {
23
+ if (await ledgerExists(t, idempotencyKey)) return;
24
+ await insertLedger(t, { idempotencyKey, amount });
25
+ });
26
+ ```
@@ -0,0 +1,60 @@
1
+ # Frontend conventions
2
+
3
+ React/UI rules for this repo's `apps/*` UI apps and shared UI package. Read this before touching
4
+ `apps/web/**`, `apps/backoffice/**`, or `packages/ui/**`. Not applicable if this repo is
5
+ headless/api-only - delete this file and its row in `conventions`'s routing table in that case.
6
+
7
+ ## Component library and styling
8
+
9
+ - React Compiler is ON. Never hand-write `useMemo`/`useCallback`/`React.memo` - a compiler bail is
10
+ a Rules-of-React violation to fix. Exception: your shared UI package is consumed pre-built, so
11
+ hand-write stability there when it's part of a hook's contract (same reason `@openora/core/react`
12
+ does).
13
+ - Pick ONE component library and use its classes customized via utility classes + theme tokens.
14
+ Don't hand-roll what the library provides.
15
+ - Never hand-write component CSS selectors (`.btn-*{}`, `.table th{}`). A per-app `styles.css`
16
+ holds only the theme plus custom properties for raw values with no token; every other style is
17
+ a utility class on the element.
18
+ - App-specific looks live in that app's `styles.css`; shared UI components stay visually neutral.
19
+ - No hardcoded user-facing copy - every label goes through `t()` with a key in the module's
20
+ `locales/`. Pattern: `locales/index.ts` exports `export const ns = registerTranslations('<module>',
21
+ locales)`; components use `useTranslation(ns)`. Non-`en` files mirror `en.json` keys exactly.
22
+ - Theme tokens and CSS variables are declared once in the shared UI package - style with semantic
23
+ tokens (`bg-base-100`, `text-base-content`, `btn-primary`), never inline raw hex. A design color
24
+ with no token: add it to BOTH dark and light theme blocks. A new token without a design source:
25
+ ask the user first.
26
+ - Hoist long class strings into a module-scope `const styles = { ... } as const` keyed by role -
27
+ never inline long strings in JSX; merge with `cn()`.
28
+ - Extract a repeated utility-class recipe into a shared component or constant on the third
29
+ occurrence.
30
+ - Import helpers like `cn()` from the shared UI package barrel - never deep-import.
31
+ - Server state is not client state - key/cache/invalidate via the query lib, never
32
+ `useEffect(fetch)`, never shadowed in ad-hoc caches.
33
+ - No label or text is allowed to overflow its container - never leave text unconstrained by
34
+ default. Pick per element, based on whether the full text is load-bearing: `truncate` + a
35
+ `title` attribute holding the full string when a single line is the right shape and hiding the
36
+ tail is acceptable (a table cell, a narrow badge); `line-clamp-N` + a reserved `min-h-[NlH]` when
37
+ several lines are the right shape or the content differs in length across siblings that must
38
+ stay visually aligned (a KPI tile label, a card title) - the reserved height keeps every sibling
39
+ the same height whether or not its text actually wraps. A tooltip/popover with the full text is
40
+ the fallback only when neither fits the UX.
41
+
42
+ ## Modular architecture (every app)
43
+
44
+ - Feature modules live in `src/modules/<m>/` with the same internal folders: `pages/`,
45
+ `components/`, `hooks/`, `utils/`, `locales/`, and a public barrel `index.ts`.
46
+ - App-level (non-module) code splits the same way: `src/lib/` holds stateful/integration code
47
+ (API clients, SDK wrappers, config); `src/utils/` holds pure stateless helpers (formatters,
48
+ converters).
49
+ - No cross-module imports, ever. Cross-module communication is query invalidation or a domain
50
+ event, never a direct import.
51
+ - Outer composition code (`src/app/`, `src/routes/`, `extensions.config.ts`) imports modules only
52
+ through their barrel; deep-importing internals is forbidden. Files inside a module use relative
53
+ paths to siblings only, never `../../` out of the module.
54
+ - Components in `components/` and `pages/` are presentation-only: props in, JSX out - no
55
+ fetching, no side effects. Business logic, queries, and mutations live in `hooks/`, which accept
56
+ external dependencies (oRPC calls, API clients) as parameters.
57
+ - Next.js client components: `'use client'` on line 1 + `.client.tsx` suffix; server components
58
+ are the default (no marker). All-client apps (Vite/TanStack) use no suffix.
59
+ - One concept per file, exported name matches the kebab-case filename; split hooks and components
60
+ into separate files.
@@ -0,0 +1,38 @@
1
+ # Functions and modules
2
+
3
+ Read this before writing or refactoring a function, service method, or module.
4
+
5
+ - Pure functions with dependencies passed in as arguments; side effects only at the edges
6
+ (services, adapters, plugins, handlers) - never in helpers.
7
+ - Immutability: derive new objects (`{ ...user, roles: [...] }`), don't mutate.
8
+ - Construct objects by spread + override, never field-by-field hand-copy. Keep fields explicit
9
+ only for order-sensitive serialization, when the target must not receive some source fields, or
10
+ when null-vs-undefined matters at a boundary.
11
+ - A `class` is only a thin dependency-holding shell at a composition root; methods delegate to
12
+ pure functions. No inheritance for reuse, no decorators - compose.
13
+ - Short, single-purpose functions - if you'd write `// step 2` inside one, extract it.
14
+ - Guard clauses first, main path last.
15
+ - More than 3 parameters -> a single named-object param.
16
+ - Don't annotate a return type TypeScript can infer. Annotate only when: inference can't
17
+ (recursion), you deliberately widen/narrow, or it's the exported public API of a shared
18
+ `packages/*` module - there the explicit type IS the contract. Argument types always stay
19
+ explicit.
20
+ - Named exports only - no default exports (exceptions: `*.config.*` files, `plugin.ts` whose
21
+ loader reads `mod.default`, and Next.js App Router files; lint-enforced).
22
+
23
+ ```ts
24
+ // bad - the service reaches into a global/container to hide what it depends on
25
+ export class WalletService {
26
+ constructor(private readonly container: Container) {}
27
+ async deposit() {
28
+ const psp = this.container.get(PAYMENT_ADAPTER);
29
+ }
30
+ }
31
+ // good - deps are constructor params of their port type
32
+ export class WalletService {
33
+ constructor(
34
+ private readonly db: DrizzleService,
35
+ private readonly payments: PaymentAdapter,
36
+ ) {}
37
+ }
38
+ ```
@@ -0,0 +1,16 @@
1
+ # Git and delivery
2
+
3
+ Read this before a commit, PR, or branch operation.
4
+
5
+ - Conventional commits, enforced by commitlint (husky + CI): `feat`, `fix`, `refactor`, `chore`,
6
+ `docs`, `test`, `ci`, `perf`. E.g. `feat(wallet): atomic debit command port`.
7
+ - Subject starts lowercase, acronyms included (`feat(pam): kyc status filter`). This applies to
8
+ the PR title too - squash merges derive the commit message from it.
9
+ - One PR = one concern. Stage files explicitly; never `git add -A` when foreign changes are in
10
+ the tree.
11
+ - Green before review: typecheck + lint + unit tests pass; `pnpm verify` is the full gate (adds
12
+ format:check + boundaries + build).
13
+ - Branch off `dev`; never commit directly to a shared branch; never push without an explicit
14
+ per-action "yes push".
15
+ - PR description carries intent: what / why / acceptance criteria / ticket link. No secrets,
16
+ internal hostnames, or PII - it is the public record.
@@ -0,0 +1,37 @@
1
+ # Package and module structure
2
+
3
+ Read this before creating a package, an overlay add-on, or wiring a new module's internal layout.
4
+
5
+ - One package = one concern, named `@<scope>/<kebab>`, with an explicit `exports` map. The
6
+ entrypoint IS the public API; everything else is internal and off-limits to consumers
7
+ (`oss-boundaries`).
8
+ - Overlay/add-on packages mirror the platform module shape: `contract/`, `schema/`, `service/`,
9
+ `router/`, `adapters/`, `__tests__/`, plus `plugin.ts` at the root as the single wiring point.
10
+
11
+ | Layer | File | Holds | Must NOT hold |
12
+ | -------- | --------------------------- | --------------------------------------------------------------- | -------------------------------- |
13
+ | schema | `schema/index.ts` | Drizzle `pgTable`s; row types via `$inferSelect`/`$inferInsert` | logic |
14
+ | contract | `contract/index.ts` | oRPC route contract + req/res Zod schemas | logic, transport wiring |
15
+ | service | `service/<name>.service.ts` | ALL business logic; emits events after DB commit | HTTP/transport knowledge |
16
+ | router | `router/index.ts` | thin oRPC wiring: resolve caller, call service, map errors | business rules |
17
+ | plugin | `plugin.ts` | DI wiring only: `ctx.provide(...)`, route registration | logic |
18
+ | adapters | `adapters/<vendor>/` | concrete impls of adapter ports | being imported by another module |
19
+
20
+ - Each overlay owns its `drizzle.config.ts` and its own migration history - never share one
21
+ migration folder across packages.
22
+ - Pin exact dependency versions in every package; a package never depends on an app.
23
+ - Add a dependency deliberately - std lib or a few lines often beat a tree.
24
+
25
+ ```ts
26
+ // bad - a service reaches into the container and hides what it depends on
27
+ export class WalletService {
28
+ constructor(private readonly container: Container) {}
29
+ }
30
+ // good - deps are constructor params of their port type; plugin.ts does the resolving
31
+ export class WalletService {
32
+ constructor(
33
+ private readonly db: DrizzleService,
34
+ private readonly payments: PaymentAdapter,
35
+ ) {}
36
+ }
37
+ ```
@@ -0,0 +1,48 @@
1
+ # Testing
2
+
3
+ Read this before adding or restructuring a test.
4
+
5
+ ## Pick the tier
6
+
7
+ Pick the OUTERMOST tier that can reach the behaviour - a test earns its keep by running real code,
8
+ not by being cheap.
9
+
10
+ | What you changed | Tier |
11
+ | ------------------------------------------------------------- | --------------------------------------------- |
12
+ | A screen, a form, a player/admin journey | Browser E2E (`apps/e2e/tests/<app>/**`) |
13
+ | An API route, an overlay, a vendor adapter, anything with SQL | API E2E (`apps/e2e/tests/api/**`) |
14
+ | A pure function - parser, resolver, mapper, money calculation | Unit (`src/__tests__/<name>.test.ts`, Vitest) |
15
+
16
+ The API tier drives the real API over HTTP against real Postgres, with each external vendor
17
+ replaced by a stub HTTP server the API is pointed at by env. It is the default tier for
18
+ `apps/api/src/extensions/**`: an overlay is wiring, and wiring is exactly what a unit test skips.
19
+
20
+ ## What is real, what is doubled
21
+
22
+ - **Anything that touches the database is tested against real Postgres.** Never fake a query
23
+ builder: a stubbed chain proves a call order, not a result, so it misses the regressions that
24
+ matter - a unique-index conflict, a wrong `where`, a lost race, a rollback that never happened.
25
+ - **External vendors are stubbed at their HTTP boundary** (a `node:http` stand-in the API's
26
+ base-URL env var points at), never by mocking our own adapter - a mocked adapter skips exactly
27
+ the wiring the test exists to check. Every vendor adapter therefore accepts a base-URL override.
28
+ - **A spy assertion is never the point of a test.** `expect(client.x).toHaveBeenCalledWith(...)`
29
+ only restates the line above it. A spy may stand in for an outbound vendor call, never for the
30
+ thing under test.
31
+
32
+ ```ts
33
+ // bad - a mocked builder chain proves a call order, not a result
34
+ const db = { select: vi.fn().mockReturnValue({ from: vi.fn().mockResolvedValue([row]) }) };
35
+ expect(db.select).toHaveBeenCalled();
36
+ // good - assert the outcome
37
+ expect(await service.get(id)).toEqual(row);
38
+ ```
39
+
40
+ ## How to write them
41
+
42
+ - Test behavior, not implementation - tests must survive a safe refactor (assert outputs, not
43
+ private caches).
44
+ - Cover new logic as part of the same change; always include the authz negatives.
45
+ - Drive a vendor's inbound side the way the vendor does: post the real webhook shape to the real
46
+ route with a signature the stub's key material produces. Never call the adapter directly.
47
+ - Deterministic and isolated: no shared mutable state, no real outbound network, seedable data.
48
+ Own the rows a test creates and clean them up - the API suite shares one database.
@@ -0,0 +1,31 @@
1
+ # Types
2
+
3
+ Read this before adding or changing a schema, type, or enum-like value set.
4
+
5
+ - One source of truth per shape - infer, never hand-write a type that already exists:
6
+ `z.infer<typeof UserSchema>`, `typeof users.$inferSelect`, `Omit<User, 'id'>`.
7
+ - Schema-first at every boundary (HTTP, config, env, messages, events): validate once at the edge,
8
+ trust the type after. oRPC + Zod does this for routes; do the same elsewhere.
9
+ - Never re-infer a schema you imported - the owning contract exports the type once; import it.
10
+ - No `any` outside tests - `unknown` + narrowing.
11
+ - No `as` casts to silence the compiler - fix the root cause (`as const` is fine).
12
+ - Never `!` non-null assertions - narrow explicitly, or restructure so the value is provably
13
+ present (carry it on the object instead of re-looking it up).
14
+ - Under `noUncheckedIndexedAccess`: `.at()`, destructure-with-default
15
+ (`const [first = ''] = parts`), or an explicit guard - never `arr[i]!`.
16
+ - `type` over `interface` (lint-enforced).
17
+ - Type entity ids through their owning type (`playerId: Player['id']`), never a bare `string`.
18
+ - Derive related schemas with `.pick()/.omit()/.partial()/.extend()/.merge()` - never re-type
19
+ fields.
20
+ - Enum-like value sets are a values + schema + type triple declared once on the contract surface:
21
+ `X_STATUSES = [...] as const` -> `XStatusSchema = z.enum(X_STATUSES)` -> inferred `XStatus`.
22
+ Never a TS `enum`, never a second hand-typed copy - import the one from `@openora/*`.
23
+ - Make illegal states unrepresentable - discriminated unions
24
+ (`{ status: 'ok'; data } | { status: 'error'; error }`) over optional-flag soup.
25
+
26
+ ```ts
27
+ // bad - hand-written duplicate of an inferrable type
28
+ type User = { id: string; email: string; roles: string[] };
29
+ // good - infer from the schema that already validates the shape
30
+ type User = z.infer<typeof UserSchema>;
31
+ ```
@@ -6,11 +6,11 @@
6
6
  "scripts": {
7
7
  "dev": "turbo run dev",
8
8
  "build": "turbo run build",
9
- "typecheck": "turbo run typecheck",
10
- "lint": "oxlint .",
11
- "sync:agents": "rulesync generate",
9
+ "check:types": "turbo run check:types",
10
+ "check:lint": "oxlint .",
11
+ "gen:agents": "rulesync generate",
12
12
  "prepare": "rulesync generate",
13
- "db:migrate": "pnpm -F @{{name}}/api db:migrate",
13
+ "db:migrate": "pnpm -F @{{name}}/api exec openora-migrate",
14
14
  "db:seed": "pnpm -F @{{name}}/api db:seed",
15
15
  "gen": "turbo gen"
16
16
  },
@@ -1,6 +1,6 @@
1
1
  // rulesync config - single source of truth for all AI-agent instruction files.
2
2
  // Source lives in .rulesync/ (rules, subagents, commands, mcp.json).
3
- // Regenerate with `pnpm sync:agents`; CI checks with `pnpm sync:agents:check`.
3
+ // Regenerate with `pnpm gen:agents`.
4
4
  {
5
5
  "$schema": "https://github.com/dyoshikawa/rulesync/releases/latest/download/config-schema.json",
6
6
 
@@ -1,2 +1,2 @@
1
1
  // To customize, replace this re-export with your own plop config.
2
- export { default } from '@openora/turbo-generators';
2
+ export { default } from '@openora/core/generators';
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "$schema": "https://turbo.build/schema.json",
3
3
  "ui": "tui",
4
+ "globalDependencies": [".oxlintrc.json"],
4
5
  "tasks": {
5
6
  "build": {
6
7
  "dependsOn": ["^build"],
@@ -10,8 +11,19 @@
10
11
  "cache": false,
11
12
  "persistent": true
12
13
  },
13
- "typecheck": {
14
- "dependsOn": ["^build"]
14
+ "check:types": {
15
+ "dependsOn": ["^build"],
16
+ "outputs": []
17
+ },
18
+ "//#check:lint": {
19
+ "inputs": [
20
+ "**/*.{ts,tsx,mts,cts,js,mjs,cjs,jsx}",
21
+ "!.turbo/**",
22
+ "!**/node_modules/**",
23
+ "!**/dist/**",
24
+ "!**/.next/**"
25
+ ],
26
+ "outputs": []
15
27
  }
16
28
  }
17
29
  }