@openora/create 0.1.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 (56) hide show
  1. package/LICENSE +661 -0
  2. package/dist/.tsbuildinfo +1 -0
  3. package/dist/generated/core-version.d.ts +2 -0
  4. package/dist/generated/core-version.d.ts.map +1 -0
  5. package/dist/generated/core-version.js +2 -0
  6. package/dist/generated/core-version.js.map +1 -0
  7. package/dist/index.d.ts +3 -0
  8. package/dist/index.d.ts.map +1 -0
  9. package/dist/index.js +136 -0
  10. package/dist/index.js.map +1 -0
  11. package/package.json +43 -0
  12. package/template/README.md.tpl +61 -0
  13. package/template/__dot__claude/settings.json.tpl +52 -0
  14. package/template/__dot__env.example +21 -0
  15. package/template/__dot__gitignore +31 -0
  16. package/template/__dot__mcp.json +9 -0
  17. package/template/__dot__nvmrc +1 -0
  18. package/template/__dot__rulesync/commands/check.md +21 -0
  19. package/template/__dot__rulesync/commands/scaffold-module.md +21 -0
  20. package/template/__dot__rulesync/commands/scaffold-plugin.md +25 -0
  21. package/template/__dot__rulesync/commands/scaffold-route.md +23 -0
  22. package/template/__dot__rulesync/commands/start.md +11 -0
  23. package/template/__dot__rulesync/hooks/_shared.mjs +60 -0
  24. package/template/__dot__rulesync/hooks/guard-core.mjs.tpl +44 -0
  25. package/template/__dot__rulesync/hooks/guard-generated.mjs +34 -0
  26. package/template/__dot__rulesync/hooks/guard-subagent.mjs +54 -0
  27. package/template/__dot__rulesync/hooks/post-edit.mjs +57 -0
  28. package/template/__dot__rulesync/hooks.json +11 -0
  29. package/template/__dot__rulesync/mcp.json.tpl +10 -0
  30. package/template/__dot__rulesync/rules/db-conventions.md +83 -0
  31. package/template/__dot__rulesync/rules/overview.md +92 -0
  32. package/template/__dot__rulesync/skills/add-feature/SKILL.md +113 -0
  33. package/template/__dot__rulesync/skills/add-feature/handoff.md +58 -0
  34. package/template/__dot__rulesync/skills/code-review/SKILL.md +111 -0
  35. package/template/__dot__rulesync/skills/create-plugin/SKILL.md +72 -0
  36. package/template/__dot__rulesync/skills/create-pr/SKILL.md +55 -0
  37. package/template/__dot__rulesync/skills/create-task/SKILL.md +77 -0
  38. package/template/__dot__rulesync/skills/enhance-prompt/SKILL.md +53 -0
  39. package/template/__dot__rulesync/subagents/builder.md +93 -0
  40. package/template/__dot__rulesync/subagents/debugger.md +83 -0
  41. package/template/__dot__rulesync/subagents/deployer.md +66 -0
  42. package/template/__dot__rulesync/subagents/expert.md +53 -0
  43. package/template/__dot__rulesync/subagents/qa.md +88 -0
  44. package/template/apps/api/package.json.tpl +21 -0
  45. package/template/apps/api/src/extensions/__dot__gitkeep +0 -0
  46. package/template/apps/api/src/extensions.config.ts.tpl +22 -0
  47. package/template/apps/api/src/main.ts.tpl +84 -0
  48. package/template/apps/api/src/migrate.ts.tpl +46 -0
  49. package/template/apps/api/src/seed.ts.tpl +39 -0
  50. package/template/apps/api/tsconfig.json +8 -0
  51. package/template/docker-compose.yml +19 -0
  52. package/template/package.json.tpl +32 -0
  53. package/template/pnpm-workspace.yaml.tpl +8 -0
  54. package/template/rulesync.jsonc +26 -0
  55. package/template/turbo/generators/config.ts +2 -0
  56. package/template/turbo.json +17 -0
@@ -0,0 +1,11 @@
1
+ ---
2
+ targets:
3
+ - '*'
4
+ description: First-run onboarding for this consumer igaming repo. Interview the user for requirements, then delegate the build to the scoped agents. Invokes the `start` MCP tool.
5
+ ---
6
+
7
+ Call the `start` MCP tool (server `oss`) with no arguments, then follow the script it returns exactly. If the user already described what they want to build, pass it as the `ask` argument.
8
+
9
+ The returned script will have you: confirm the MCP server is connected, run a thorough requirements interview, call `enhance-intent`, then delegate the implementation to the `expert`, `builder`, and `qa` agents (via the Task tool). You gather requirements and orchestrate; you do not write feature code yourself, and you never modify `@openora/*` core.
10
+
11
+ If the `start` tool is not available, the `oss` MCP server is not connected - tell the user to run `pnpm setup:mcp` and restart the editor.
@@ -0,0 +1,60 @@
1
+ // Shared helpers for the cross-CLI hook guards.
2
+ //
3
+ // One script serves Claude Code, Copilot CLI, Codex CLI, and Gemini CLI. Each
4
+ // passes the tool call as JSON on stdin but with a slightly different shape:
5
+ // Claude / Codex : { tool_name, tool_input: { command?, file_path? } }
6
+ // Copilot : { toolName, toolArgs: "<json-string>" | object }
7
+ // Gemini : { toolName/tool_name, ... } (best-effort)
8
+ //
9
+ // Deny is universal: exit code 2 + a message on stderr. Every other path exits 0
10
+ // (allow). We FAIL OPEN on any parse error or uncertainty - Copilot's preToolUse
11
+ // is fail-closed, so a crashing/slow guard would block real work; we never do
12
+ // that, we only block on a positive match.
13
+
14
+ import { readFileSync } from 'node:fs';
15
+
16
+ export function readPayload() {
17
+ try {
18
+ return JSON.parse(readFileSync(0, 'utf8') || '{}');
19
+ } catch {
20
+ return {};
21
+ }
22
+ }
23
+
24
+ function toolArgsObject(payload) {
25
+ const a = payload.toolArgs;
26
+ if (typeof a === 'string') {
27
+ try {
28
+ return JSON.parse(a);
29
+ } catch {
30
+ return {};
31
+ }
32
+ }
33
+ return a && typeof a === 'object' ? a : {};
34
+ }
35
+
36
+ export function extractCommand(payload) {
37
+ const ti = payload.tool_input ?? {};
38
+ const ca = toolArgsObject(payload);
39
+ return String(ti.command ?? ca.command ?? '');
40
+ }
41
+
42
+ export function extractFilePath(payload) {
43
+ const ti = payload.tool_input ?? {};
44
+ const ca = toolArgsObject(payload);
45
+ return String(
46
+ ti.file_path ??
47
+ ti.path ??
48
+ ca.path ??
49
+ ca.filePath ??
50
+ ca.file ??
51
+ ca.targetFile ??
52
+ ca.target_file ??
53
+ '',
54
+ );
55
+ }
56
+
57
+ export function deny(message) {
58
+ process.stderr.write(message.endsWith('\n') ? message : message + '\n');
59
+ process.exit(2);
60
+ }
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env node
2
+ // PreToolUse guard (Claude / Copilot CLI / Codex CLI / Gemini CLI).
3
+ // Enforces the HARD RULE: never modify OSS core. Blocks shell write-primitives
4
+ // and direct file edits that target the linked OSS checkout or node_modules.
5
+ // Reads/blocks are allowed; only writes into protected paths are denied.
6
+
7
+ import { extractCommand, extractFilePath, readPayload, deny } from './_shared.mjs';
8
+
9
+ const payload = readPayload();
10
+
11
+ // The linked OSS core checkout (baked in at generation time) plus any node_modules.
12
+ const ossPath = String.raw`{{ossFromRoot}}`;
13
+ const escapedOss = ossPath.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
14
+ const PROTECTED = `(?:${escapedOss}|node_modules\\b)`;
15
+
16
+ // 1) Shell command writing into a protected path (sed -i, redirect, tee, rm, ...).
17
+ const command = extractCommand(payload);
18
+ if (command) {
19
+ const writeToCore = [
20
+ [new RegExp(String.raw`\b(?:sed|perl)\b[^|;&]*\s-\w*i\w*\b[^|;&]*(?:${PROTECTED})`), 'in-place edit (sed/perl -i)'],
21
+ [new RegExp(String.raw`(?:>>?|>\|)\s*['"]?[^'"\s|;&]*(?:${PROTECTED})`), 'shell redirection'],
22
+ [new RegExp(String.raw`\btee\b\s+(?:-a\s+)?['"]?[^'"\s|;&]*(?:${PROTECTED})`), 'tee'],
23
+ [new RegExp(String.raw`\b(?:rm|truncate|dd|chmod|chown|unlink|shred|mv)\b[^|;&]*(?:${PROTECTED})`), 'destructive file op'],
24
+ ];
25
+ const hit = writeToCore.find(([re]) => re.test(command));
26
+ if (hit) {
27
+ deny(
28
+ `Blocked: this ${hit[1]} writes into OSS core / node_modules, which is read-only here. ` +
29
+ 'Extend the platform from the OUTSIDE (overlay plugin, adapter rebinding, UI plugin, config). ' +
30
+ 'If it can only be fixed in core, STOP and report it as an upstream OSS issue.',
31
+ );
32
+ }
33
+ }
34
+
35
+ // 2) Direct file edit/write whose target is inside a protected path.
36
+ const filePath = extractFilePath(payload);
37
+ if (filePath && new RegExp(PROTECTED).test(filePath)) {
38
+ deny(
39
+ `Blocked: ${filePath} is inside OSS core / node_modules, which is read-only here. ` +
40
+ 'Extend from the OUTSIDE (overlay, adapter, UI plugin, config), or report an upstream OSS issue.',
41
+ );
42
+ }
43
+
44
+ process.exit(0);
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env node
2
+ // PreToolUse guard (Claude / Copilot CLI / Codex CLI).
3
+ // Blocks edits to files generated by rulesync (`pnpm sync:agents`). The single
4
+ // source of truth is .rulesync/; editing a generated mirror is lost on the next
5
+ // sync. Fail-open: only a positive match denies.
6
+
7
+ import { extractFilePath, readPayload, deny } from './_shared.mjs';
8
+ import { relative, isAbsolute } from 'node:path';
9
+
10
+ const payload = readPayload();
11
+ const filePath = extractFilePath(payload);
12
+ if (!filePath) process.exit(0);
13
+
14
+ const rel = isAbsolute(filePath) ? relative(process.cwd(), filePath) : filePath;
15
+
16
+ const generated = [
17
+ /^AGENTS\.md$/,
18
+ /^CLAUDE\.md$/,
19
+ /^\.github\/copilot-instructions\.md$/,
20
+ /^\.mcp\.json$/,
21
+ /^\.vscode\/mcp\.json$/,
22
+ /^\.codex\/config\.toml$/,
23
+ /^\.claude\/(agents|commands)\//,
24
+ /^\.github\/(agents|prompts)\//,
25
+ ];
26
+
27
+ if (generated.some((re) => re.test(rel))) {
28
+ deny(
29
+ `Blocked: ${rel} is generated by rulesync (\`pnpm sync:agents\`). ` +
30
+ 'Edit the source under .rulesync/ instead (rules/, subagents/, commands/, mcp.json), then run `pnpm sync:agents`.',
31
+ );
32
+ }
33
+
34
+ process.exit(0);
@@ -0,0 +1,54 @@
1
+ #!/usr/bin/env node
2
+ // PreToolUse(Task) guard: keep the orchestrator from spawning a GENERIC subagent
3
+ // (general-purpose / claude) for work a roster agent is purpose-built for. The
4
+ // Agent roster in AGENTS.md is prose the model sometimes skips out of habit; this
5
+ // is the deterministic backstop.
6
+ //
7
+ // Conservative by design: it acts ONLY when (a) the chosen subagent is generic AND
8
+ // (b) the task text contains a HIGH-SIGNAL phrase that maps unambiguously to one
9
+ // roster agent. Generic verbs ("implement", "test", "review") are deliberately not
10
+ // triggers. Fail-open on anything unexpected (a crashing guard must never wedge work).
11
+
12
+ import { readPayload } from './_shared.mjs';
13
+
14
+ const payload = readPayload();
15
+ const ti = payload.tool_input ?? {};
16
+ const sub = String(ti.subagent_type ?? '').toLowerCase();
17
+
18
+ const GENERIC = new Set(['general-purpose', 'claude', '']);
19
+ if (!GENERIC.has(sub)) process.exit(0);
20
+
21
+ const text = `${ti.description ?? ''}\n${ti.prompt ?? ''}`.toLowerCase();
22
+
23
+ // High-signal phrase -> the roster agent that owns it. Keep each pattern SPECIFIC;
24
+ // err toward missing a case over false-blocking a generic task.
25
+ const ROUTES = [
26
+ { agent: 'qa', re: /\b(playwright|e2e test|end-to-end test|e2e coverage)\b/ },
27
+ {
28
+ agent: 'debugger',
29
+ re: /\b(build (failure|error)|turbopack|tsc error|module resolution|runtime error|stack trace)\b/,
30
+ },
31
+ {
32
+ agent: 'expert',
33
+ re: /\b(acceptance criteria|responsible gaming|regulatory requirement|jurisdiction)\b/,
34
+ },
35
+ {
36
+ agent: 'builder',
37
+ re: /\b(overlay plugin|adapter swap|swap (the )?(kyc|psp|payment|notification)|extensions?\.config|mount (a |the )?page)\b/,
38
+ },
39
+ {
40
+ agent: 'deployer',
41
+ re: /\b(dockerfile|containerize|deploy pipeline|deploy to (ecs|kubernetes|fly|railway|render)|ci\/cd deploy|helm chart)\b/,
42
+ },
43
+ ];
44
+
45
+ const hit = ROUTES.find((r) => r.re.test(text));
46
+ if (!hit) process.exit(0);
47
+
48
+ process.stderr.write(
49
+ `Use the \`${hit.agent}\` subagent for this task, not \`${sub || 'general-purpose'}\`. ` +
50
+ `It is pre-scoped for this work (tools + model + brief) - see the Agent roster in AGENTS.md. ` +
51
+ `Re-issue the Task with subagent_type: "${hit.agent}". ` +
52
+ `If it genuinely does not fit ${hit.agent}, rephrase the description to say why.\n`,
53
+ );
54
+ process.exit(2);
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env node
2
+ import { execSync } from 'node:child_process';
3
+ import { readFileSync } from 'node:fs';
4
+ import { join, isAbsolute, relative } from 'node:path';
5
+ import { extractFilePath, readPayload } from './_shared.mjs';
6
+
7
+ const CAP_LINES = 40;
8
+ const CAP_CHARS = 2000;
9
+
10
+ const filePath = extractFilePath(readPayload());
11
+ if (!filePath) process.exit(0);
12
+ if (!/\.(ts|tsx)$/.test(filePath) || /\.d\.ts$/.test(filePath)) process.exit(0);
13
+ if (filePath.includes('/templates/') || filePath.includes('/generated/')) process.exit(0);
14
+
15
+ // Lint-fix (best effort - never block on the linter).
16
+ try {
17
+ execSync(`pnpm exec oxlint --fix "${filePath}"`, { stdio: 'pipe' });
18
+ } catch {
19
+ /* oxlint unavailable or errored - fall through to typecheck */
20
+ }
21
+
22
+ function packageNameFor(fp) {
23
+ const m = fp.match(/(.*?\/(?:apps\/[^/]+|packages\/[^/]+\/[^/]+))\//);
24
+ if (!m) return null;
25
+ try {
26
+ const pkg = JSON.parse(readFileSync(join(m[1], 'package.json'), 'utf8'));
27
+ if (!pkg.name || !pkg.scripts?.typecheck) return null;
28
+ return pkg.name;
29
+ } catch {
30
+ return null;
31
+ }
32
+ }
33
+
34
+ const abs = isAbsolute(filePath) ? filePath : join(process.cwd(), filePath);
35
+ const owner = packageNameFor(abs);
36
+ if (!owner) process.exit(0);
37
+
38
+ function cap(text) {
39
+ const out = text.split('\n').slice(0, CAP_LINES).join('\n').slice(0, CAP_CHARS);
40
+ return out.length < text.length ? `${out}\n... (truncated)` : out;
41
+ }
42
+
43
+ try {
44
+ execSync(`pnpm --filter "${owner}" typecheck`, { stdio: 'pipe' });
45
+ process.exit(0);
46
+ } catch (e) {
47
+ const output = (e.stdout?.toString() ?? '') + (e.stderr?.toString() ?? '');
48
+ const basename =
49
+ (isAbsolute(filePath) ? relative(process.cwd(), filePath) : filePath).split('/').pop() ?? '';
50
+ if (basename && output.includes(basename)) {
51
+ process.stderr.write(
52
+ `Typecheck failed for ${owner} after editing ${basename}:\n${cap(output)}`,
53
+ );
54
+ process.exit(2);
55
+ }
56
+ process.exit(0);
57
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "version": 1,
3
+ "hooks": {
4
+ "preToolUse": [
5
+ { "type": "command", "command": "node .rulesync/hooks/guard-core.mjs" },
6
+ { "type": "command", "command": "node .rulesync/hooks/guard-generated.mjs" },
7
+ { "type": "command", "matcher": "Task", "command": "node .rulesync/hooks/guard-subagent.mjs" }
8
+ ],
9
+ "postToolUse": [{ "type": "command", "command": "node .rulesync/hooks/post-edit.mjs" }]
10
+ }
11
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "$schema": "https://github.com/dyoshikawa/rulesync/releases/latest/download/mcp-schema.json",
3
+ "mcpServers": {
4
+ "oss": {
5
+ "type": "stdio",
6
+ "command": "{{mcpCommand}}",
7
+ "args": {{mcpArgsJson}}
8
+ }
9
+ }
10
+ }
@@ -0,0 +1,83 @@
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
+ # Database conventions (SQL / Drizzle)
11
+
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.
15
+
16
+ ## Identifiers - snake_case everywhere
17
+
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.
20
+
21
+ ```ts
22
+ // good - key derives the column; const camelCase; explicit snake_case only for table + index
23
+ export const playerNote = pgTable(
24
+ 'player_note',
25
+ {
26
+ id: uuid().primaryKey().defaultRandom(),
27
+ playerId: uuid().notNull(), // -> player_id
28
+ createdAt: timestamp({ withTimezone: true }).notNull().defaultNow(),
29
+ },
30
+ (t) => [index('player_note_player_id_idx').on(t.playerId)],
31
+ );
32
+
33
+ // bad - explicit/camelCase column names, PascalCase table
34
+ pgTable('PlayerNote', { player_id: uuid('playerId') });
35
+ ```
36
+
37
+ ## Timestamps - always timestamptz
38
+
39
+ ```ts
40
+ createdAt: timestamp({ withTimezone: true }); // good - Postgres timestamptz, store UTC
41
+ createdAt: timestamp(); // bad - naive, drops the zone
42
+ ```
43
+
44
+ Every datetime column (`createdAt`, `updatedAt`, `expiresAt`, any `*At`) carries the zone.
45
+
46
+ ## Keys, references, indexes
47
+
48
+ - 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.
51
+ - `NOT NULL` by default; push defaults to the DB (`.notNull().default(...)`), not app code.
52
+ - Index every column you filter or join on; name it `<table>_<cols>_idx`.
53
+
54
+ ## Efficient operations
55
+
56
+ ```ts
57
+ // bad - N+1
58
+ for (const id of ids) await db.select().from(wallet).where(eq(wallet.playerId, id));
59
+ // good - one batched query
60
+ await db.select().from(wallet).where(inArray(wallet.playerId, ids));
61
+ ```
62
+
63
+ - Select only the columns you use; don't `select(*)` wide rows to read one field.
64
+ - **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`).
70
+ - **Money / critical paths are transactional and idempotent** - a DB guard inside the transaction,
71
+ not just an `idempotencyKey` (delivery is at-least-once).
72
+
73
+ ```ts
74
+ await db.transaction(async (t) => {
75
+ if (await ledgerExists(t, idempotencyKey)) return; // guard, not just a key
76
+ await insertLedger(t, { idempotencyKey, amountCents });
77
+ });
78
+ ```
79
+
80
+ ## Migrations
81
+
82
+ - Never hand-edit generated migrations. Change the `pgTable`, then `pnpm db:migrate`.
83
+ - One migration per schema change; review the generated SQL before committing.
@@ -0,0 +1,92 @@
1
+ ---
2
+ root: true
3
+ targets:
4
+ - '*'
5
+ globs:
6
+ - '**/*'
7
+ ---
8
+
9
+ # Agent instructions
10
+
11
+ Canonical brief for AI agents (Claude Code, GitHub Copilot, Codex) and humans working on
12
+ this repo. Every per-tool instruction file (`AGENTS.md` - shared, also Codex's brief;
13
+ `CLAUDE.md`; `.github/copilot-instructions.md`) plus Codex's `.codex/config.toml` and the
14
+ subagent and command mirrors are generated by
15
+ [rulesync](https://github.com/dyoshikawa/rulesync) from the single source in `.rulesync/`
16
+ (`rules/`, `subagents/`, `commands/`, `mcp.json`). Edit the source under `.rulesync/`, then
17
+ run `pnpm sync:agents`. Do not hand-edit the generated files.
18
+
19
+ This repo is a downstream igaming operator built on the OSS platform (`@openora/*`).
20
+
21
+ ## HARD RULE: never modify OSS core
22
+
23
+ `@openora/*` is a third-party dependency - treat it like any published npm package. You may READ it for reference, never write to it.
24
+
25
+ - Do NOT edit anything in `node_modules/**` or in the linked OSS checkout. Edit/Write to those paths is denied in `.claude/settings.json`; do not try to work around it with `sed`, shell redirection, or a script.
26
+ - Locally patching a dependency is an anti-pattern: it is lost on reinstall and diverges from the published package every other operator uses.
27
+ - You extend the platform from the OUTSIDE only - overlay plugins, adapter rebindings, UI plugins, config. Never fork or hand-edit core.
28
+ - If something can only be fixed in core, STOP and report it as an upstream bug/feature request for the OSS repo (describe the problem, expected behavior, where you think it lives). Do not patch it here.
29
+
30
+ ## Getting started
31
+
32
+ When the user opens Claude Code or asks to build something, call the `start` MCP tool (server: `oss`) and follow the script it returns.
33
+
34
+ Trigger phrases: "start", "help", "what can I build", "what can I do", "I want to build X", "how do I begin", "getting started".
35
+
36
+ ## Enhance the ask first (pre-step)
37
+
38
+ Before acting on any non-trivial request - and before delegating to an agent - run the `enhance-prompt` skill on the raw ask: restate the intent, gather scoped context (your tracker / roadmap / team chat / local docs), surface the blocking ambiguities, and produce the brief you actually execute. Skip it only when the ask is already precise. Subagents act on the brief they are handed - they do not re-enhance it.
39
+
40
+ ## How you work here: collect requirements, delegate everything else
41
+
42
+ Your one human-facing job is to **gather thorough requirements** from the user - interview them (goal, actors, value flow, rules, lifecycle, compliance, UI, edge cases, success criteria, out of scope) until you could hand a stranger a buildable spec. The `start` / `enhance-intent` tools give you the checklist.
43
+
44
+ Once requirements are confirmed, **delegate the rest to the agents** via the Task tool - you orchestrate, you do not implement feature code yourself:
45
+
46
+ 1. `expert` - formalizes the requirements into acceptance criteria, flags compliance/gaps.
47
+ 2. `builder` - implements (`pnpm gen ...`, code, wiring).
48
+ 3. `qa` - writes/runs the E2E test against the acceptance criteria.
49
+
50
+ Only return to the user to resolve genuine decisions they alone can make.
51
+
52
+ ## What this repo is
53
+
54
+ - `apps/api/` - thin Hono + oRPC API entry, your own `extensions.config.ts`
55
+ - this is a headless api consumer; build your frontend in its own repo and consume the api over HTTP via `@openora/react`
56
+ - `@openora/*` packages are linked from a sibling OSS checkout via `pnpm.overrides`
57
+
58
+ ## How to extend
59
+
60
+ | What you want | Command |
61
+ | -------------------------------------------- | ------------------ |
62
+ | New behavior / routes | `pnpm gen plugin` |
63
+ | Swap a vendor (payment / KYC / notification) | `pnpm gen adapter` |
64
+
65
+ Register new plugins in `apps/api/src/extensions.config.ts`. Adapters must be listed AFTER the module that owns the default binding (last registration of a DI token wins).
66
+
67
+ ## Agents (spawn via the Task tool)
68
+
69
+ Delegate work to these scoped agents - the `start` / `enhance-intent` playbooks tell you when to use each:
70
+
71
+ - `expert` - turns a fuzzy product ask into requirements + acceptance criteria (jurisdiction rules, player journey). Advisory, writes no code. Use BEFORE building anything non-obvious.
72
+ - `builder` - senior fullstack engineer. Implements overlays, swaps adapters, mounts UI pages.
73
+ - `qa` - writes/runs Playwright E2E tests; triages whether a bug is in OSS core (upstream) or your overlay (local fix).
74
+ - `debugger` - root-causes failures, build-time (Next/Turbopack, tsc, module resolution) and runtime (Chrome DevTools: console/network/DOM). Spawn it whenever something errors or behaves wrong; it finds the cause and routes the fix.
75
+
76
+ This repo consumes OSS core as linked packages - never edit `@openora/*` source. If a bug is in core, report it upstream; extend from the outside via plugins.
77
+
78
+ ## MCP tools available (server: `oss`)
79
+
80
+ This server reads the platform CATALOG (not OSS source) - it tells you what exists so you extend without forking. Call these instead of grepping:
81
+
82
+ - `start` - onboarding flow (call when the user asks what to build)
83
+ - `enhance-intent` - turn a fuzzy ask into a grounded spec + playbook
84
+ - `dev:infra` - start/stop/status docker compose (postgres :5432)
85
+ - `catalog-overview` - START HERE: counts + adapter seams + config fields
86
+ - `list-adapters` - vendor swap seams (interface + token + status)
87
+ - `list-routes [module]` - oRPC route namespaces
88
+ - `list-events` - cross-module domain events you can subscribe to
89
+ - `list-slots` - named UI slots you can fill from a UI plugin
90
+ - `describe-module <name>` - one module's group, tables, routes
91
+ - `schema-get <name>` - locate a Zod contract schema's file
92
+ - `get-config-schema` - the igaming-config fields a consumer can set
@@ -0,0 +1,113 @@
1
+ ---
2
+ name: add-feature
3
+
4
+ description: >
5
+ Deliver a feature end-to-end in this consumer repo. Aggregates context (Jira + Confluence + Slack +
6
+ Google Drive + Notion + local docs + past sessions + codebase), produces an approved plan, then drives
7
+ delivery by calling sibling skills - create-plugin (build), code-review (review), create-pr (MR) -
8
+ and create-task for ticket hygiene. Transitions Jira (no comments) and drafts a one-line Slack
9
+ notice. Use on "add feature", "plan <KEY>-XXX", "deliver <KEY>-XXX", or /add-feature [<KEY>-XXX].
10
+ Read-only until the plan is approved; never pushes, transitions Jira, or sends Slack without OK.
11
+ ---
12
+
13
+ # add-feature
14
+
15
+ Feature-delivery orchestrator for this consumer repo: one Jira key in, a delivered MR +
16
+ updated ticket + drafted Slack notice out. You orchestrate and call sibling skills - you do not
17
+ re-implement their work. The platform-core twin is the `/add-feature` skill in the platform OSS repo.
18
+
19
+ ## Coordinates
20
+
21
+ - Jira: the **Atlassian** MCP, cloudId `<your-jira-cloud-id>`,
22
+ project `<your-project-key>` (ticket keys look like `<KEY>-XXX`). Pass `contentFormat` +
23
+ `responseContentFormat: "markdown"`.
24
+ - GitLab: `<your-gitlab-project>`, MR target `dev`, `glab` CLI.
25
+ - Slack: `<your-team-channel>`, draft only.
26
+ - Repo: `apps/api` (Hono entry + extensions) consumes `@openora/*` upstream. This is a headless
27
+ API consumer; build your frontend in its own repo and consume the API over HTTP.
28
+ - **Hard rule:** the linked OSS checkout is read-only (guard-core hook + permission deny). Extend from
29
+ the outside; core changes hand off - see `handoff.md`.
30
+
31
+ ## The contract
32
+
33
+ - **Read-only until the Step 3 plan is approved.** No edits, commits, pushes, Jira writes, or Slack
34
+ sends before sign-off.
35
+ - Reuse sibling skills, don't reinvent: **create-task** (ticket format), **create-plugin** (build an
36
+ overlay), **code-review** (review), **create-pr** (MR). Delegate code to subagents.
37
+
38
+ ## Steps
39
+
40
+ ### 1. Resolve input + enhance the ask
41
+
42
+ `<KEY>-XXX` from `$ARGUMENTS`; if absent, ask. Echo it back. Run the `enhance-prompt` pre-step on the ask before gathering context, so Step 2 pulls only what's relevant and Step 3 plans against a clear brief.
43
+
44
+ ### 2. Gather context in parallel (read-only)
45
+
46
+ Run together; skip any source that returns nothing. Read `handoff.md` only on core-change signals.
47
+
48
+ | Source | How |
49
+ | ------------- | ----------------------------------------------------------------------------------------------- |
50
+ | Jira | the **Atlassian** MCP - read `<KEY>-XXX`: description, AC, comments, parent epic, linked issues |
51
+ | Confluence | the **Atlassian** (Confluence) MCP - search the space + read pages |
52
+ | Slack | the **Slack** MCP - search public/private, read threads, read canvases |
53
+ | Google Drive | the **Google Drive** MCP - PRDs, specs |
54
+ | Notion | `ntn` CLI per `notion-memory` skill - prior decisions, lessons |
55
+ | Local docs | the OSS checkout's `docs` (ADRs, `architecture.md`, `catalog.json`), repo READMEs, `CLAUDE.md` |
56
+ | Past sessions | grep `~/.claude/projects/**` and `~/.claude/plans` for the ticket key |
57
+ | Codebase | `oss` MCP (read-only) + Explore - map touchpoints in `apps/api` |
58
+
59
+ ### 3. Plan + classify (the gate)
60
+
61
+ Synthesize into a plan and present it. Do NOT edit yet.
62
+
63
+ - **Goal** (1-2 lines) + **Acceptance criteria** (observable, testable).
64
+ - **Decisions found** - each with source (who/where/date), so they aren't relitigated.
65
+ - **Open questions** - ask before proceeding if any blocks design.
66
+ - **Implementation breakdown** - tasks mapped to files/packages + the owning subagent. Classify each:
67
+ - **downstream** -> overlay plugin / adapter swap / UI provider / config (build via `create-plugin`).
68
+ - **OSS-core** -> only fixable in `@openora/*`. Flag it; triggers `handoff.md`.
69
+ - **Risks / dependencies** - external services, OSS handoff, data/migrations.
70
+
71
+ Require explicit approval. Treat as plan mode even if the harness isn't.
72
+
73
+ ### 4. Build (delegate)
74
+
75
+ After approval, for **downstream** work: run the **create-plugin** skill for each overlay/adapter/
76
+ page slice - it scaffolds, wires `extensions.config.ts`, and enforces boundaries + audit + db rules.
77
+ The owning subagent (`builder`) also writes unit + integration tests as part of the deliverable.
78
+ `deployer` only if infra changes; `debugger` on demand for build/runtime failures.
79
+
80
+ For **OSS-core** items: read `handoff.md`, write the work-order, STOP that slice, continue the rest.
81
+ When implementation starts, transition Jira to In Progress (Step 7 - confirm first).
82
+
83
+ ### 5. Review + tests
84
+
85
+ - Run **code-review** on the change set; loop `[BLOCK]`/`[WARN]` fixes back through `builder`.
86
+ - Run `/check` (typecheck + lint). Don't proceed on red.
87
+ - Derive an e2e checklist from the AC (happy path, edge cases, authz negatives, error states), then
88
+ `qa`: write/run the E2E specs, drive `chrome-devtools` on failure.
89
+
90
+ ### 6. Open the MR
91
+
92
+ Run **create-pr**: it commits (`feat(<KEY>-XXX): ...`), reports the SHA, asks for "yes push", pushes,
93
+ and `glab mr create`s targeting `dev` with the CODEOWNERS for the changed paths as reviewers. Don't
94
+ bypass its push-consent gate.
95
+
96
+ ### 7. Jira status transition (NOT comments)
97
+
98
+ Use the **Atlassian** MCP - fetch the transitions -> show current status + options -> **confirm** ->
99
+ apply the matching one (In Progress when build starts, In Review when the MR opens). **No MR-link or status comments.**
100
+
101
+ ### 8. Draft Slack notice (one line)
102
+
103
+ Use the **Slack** MCP to draft a message to `<your-team-channel>`: a single line - emoji + PR/task name
104
+ as a link (e.g. `👉 <feature> - MR !NN`). **Draft only**, never direct-send.
105
+
106
+ ## Rules
107
+
108
+ - Read-only until the Step 3 plan is approved.
109
+ - Never push without an explicit per-action "yes push" (inherited from `create-pr`).
110
+ - Never transition Jira without confirming; show status + options first. No Jira comments.
111
+ - Slack is a one-line draft, never direct-send.
112
+ - Never edit the linked OSS checkout; hand off via `handoff.md`. Prefer overlay/plugin/adapter/config.
113
+ - One MR = one concern. Split unrelated work.
@@ -0,0 +1,58 @@
1
+ # OSS core handoff
2
+
3
+ When an `add-feature` work item can only be fixed in `@openora/*` core, this consumer repo cannot
4
+ edit it (the `guard-core.mjs` hook and `permissions.deny` block the linked OSS checkout). Hand off to
5
+ the platform-core twin skill instead of fighting the guard.
6
+
7
+ ## When to hand off
8
+
9
+ - The fix requires changing a contract, schema, service, router, or platform seam in `@openora/*`.
10
+ - An overlay plugin / adapter swap / UI provider override / config change genuinely cannot
11
+ express the behavior.
12
+ - Default bias: try to extend from the outside first. Only hand off when you've confirmed the
13
+ outside-in path doesn't exist.
14
+
15
+ If you're unsure, say so in the plan (Step 3) and let the user decide before writing the order.
16
+
17
+ ## Work-order template
18
+
19
+ Write to `~/.claude/plans/<ticket-key>-oss.md` so both repos and future sessions can read it:
20
+
21
+ ```markdown
22
+ # OSS work order - <ticket-key>: <title>
23
+
24
+ ## Goal
25
+
26
+ <what core must do, 1-2 lines>
27
+
28
+ ## Unblocks
29
+
30
+ consumer feature <ticket-key> - <which downstream slice depends on this>
31
+
32
+ ## Core surface to change
33
+
34
+ - <package / module / contract / adapter token, as specific as possible>
35
+
36
+ ## Contract / schema impact
37
+
38
+ - <new or changed oRPC contracts, Drizzle tables, events; or "none">
39
+
40
+ ## Acceptance
41
+
42
+ - <observable outcomes that prove the core change is correct>
43
+
44
+ ## Dependency
45
+
46
+ consumer MR <url-or-TBD> stays in draft until this merges and a new @openora/core/\* version is consumed.
47
+ ```
48
+
49
+ ## Protocol
50
+
51
+ 1. The consumer repo writes the work-order and **stops** that slice (keep delivering independent
52
+ downstream slices meanwhile).
53
+ 2. The user runs `/add-feature` inside the platform OSS repo (separate cwd/session). It reads
54
+ the work-order, implements, and opens its own MR there.
55
+ 3. The consumer MR stays draft/blocked until the OSS MR merges and the consumer bumps the consumed
56
+ `@openora/*` version. Link the two by the work-order path and the ticket key.
57
+ 4. Note the handoff in the Jira ticket and the consumer MR description so the dependency is
58
+ visible.