@openora/create 0.4.1-canary.31 → 0.4.1-canary.310

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 (91) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/generated/core-version.d.ts +1 -1
  3. package/dist/generated/core-version.d.ts.map +1 -1
  4. package/dist/generated/core-version.js +1 -1
  5. package/dist/generated/core-version.js.map +1 -1
  6. package/dist/index.js +47 -92
  7. package/dist/index.js.map +1 -1
  8. package/dist/render.d.ts +15 -0
  9. package/dist/render.d.ts.map +1 -0
  10. package/dist/render.js +125 -0
  11. package/dist/render.js.map +1 -0
  12. package/package.json +7 -5
  13. package/template/README.md.tpl +12 -7
  14. package/template/__dot__claude/settings.json.tpl +22 -12
  15. package/template/__dot__env.example +10 -0
  16. package/template/__dot__gitignore +6 -1
  17. package/template/__dot__rulesync/commands/adr.md +32 -0
  18. package/template/__dot__rulesync/commands/check.md +11 -10
  19. package/template/__dot__rulesync/commands/doctor.md.tpl +17 -0
  20. package/template/__dot__rulesync/commands/scaffold-module.md +3 -2
  21. package/template/__dot__rulesync/commands/scaffold-plugin.md +19 -3
  22. package/template/__dot__rulesync/commands/scaffold-route.md +1 -0
  23. package/template/__dot__rulesync/commands/start.md +2 -2
  24. package/template/__dot__rulesync/hooks/_shared.mjs +5 -0
  25. package/template/__dot__rulesync/hooks/guard-core.mjs.tpl +75 -26
  26. package/template/__dot__rulesync/hooks/guard-generated.mjs +12 -5
  27. package/template/__dot__rulesync/hooks/guard-mr.mjs +81 -0
  28. package/template/__dot__rulesync/hooks/guard-subagent.mjs +22 -6
  29. package/template/__dot__rulesync/hooks/post-edit.mjs.tpl +106 -0
  30. package/template/__dot__rulesync/hooks.json +27 -4
  31. package/template/__dot__rulesync/rules/conventions.md +114 -0
  32. package/template/__dot__rulesync/rules/db-conventions.md +6 -72
  33. package/template/__dot__rulesync/rules/e2e-conventions.md +52 -0
  34. package/template/__dot__rulesync/rules/frontend-conventions.md.tpl +35 -0
  35. package/template/__dot__rulesync/rules/oss-boundaries.md.tpl +78 -0
  36. package/template/__dot__rulesync/rules/overview.md +18 -73
  37. package/template/__dot__rulesync/skills/create-plugin/{SKILL.md → SKILL.md.tpl} +13 -24
  38. package/template/__dot__rulesync/skills/create-pr/SKILL.md.tpl +38 -0
  39. package/template/__dot__rulesync/skills/create-task/{SKILL.md → SKILL.md.tpl} +14 -22
  40. package/template/__dot__rulesync/skills/create-ui-module/SKILL.md.tpl +60 -0
  41. package/template/__dot__rulesync/skills/enhance-prompt/{SKILL.md → SKILL.md.tpl} +4 -4
  42. package/template/__dot__rulesync/skills/handoff/SKILL.md +95 -0
  43. package/template/__dot__rulesync/skills/review/SKILL.md.tpl +277 -0
  44. package/template/__dot__rulesync/skills/ship/SKILL.md.tpl +149 -0
  45. package/template/__dot__rulesync/skills/ship/handoff.md.tpl +48 -0
  46. package/template/__dot__rulesync/skills/ship/watch-pr.mjs +168 -0
  47. package/template/__dot__rulesync/subagents/builder.md.tpl +115 -0
  48. package/template/__dot__rulesync/subagents/compliance-reviewer.md +78 -0
  49. package/template/__dot__rulesync/subagents/debugger.md.tpl +86 -0
  50. package/template/__dot__rulesync/subagents/deployer.md.tpl +54 -0
  51. package/template/__dot__rulesync/subagents/expert.md +28 -34
  52. package/template/__dot__rulesync/subagents/planner.md.tpl +39 -0
  53. package/template/__dot__rulesync/subagents/quality-reviewer.md.tpl +112 -0
  54. package/template/__dot__rulesync/subagents/security-reviewer.md.tpl +85 -0
  55. package/template/__dot__rulesync/subagents/tester.md.tpl +88 -0
  56. package/template/__dot__rulesync/sync.json.tpl +26 -0
  57. package/template/apps/api/package.json.tpl +1 -2
  58. package/template/apps/api/src/extensions.config.ts.tpl +12 -0
  59. package/template/apps/api/src/main.ts.tpl +1 -4
  60. package/template/apps/api/src/seed.ts.tpl +8 -1
  61. package/template/docker-compose.yml +13 -0
  62. package/template/docs/agents/cross-repo.md.tpl +37 -0
  63. package/template/docs/agents/forge.md.tpl +54 -0
  64. package/template/docs/agents/issue-tracker.md.tpl +42 -0
  65. package/template/docs/standards/comments.md +16 -0
  66. package/template/docs/standards/database.md +77 -0
  67. package/template/docs/standards/enforcement.md +12 -0
  68. package/template/docs/standards/errors.md +26 -0
  69. package/template/docs/standards/frontend.md +60 -0
  70. package/template/docs/standards/functions.md +38 -0
  71. package/template/docs/standards/git-delivery.md +20 -0
  72. package/template/docs/standards/module-structure.md +37 -0
  73. package/template/docs/standards/testing.md +72 -0
  74. package/template/docs/standards/types.md +31 -0
  75. package/template/package.json.tpl +11 -5
  76. package/template/rulesync.jsonc +1 -1
  77. package/template/tools/oss-worktree.mjs.tpl +105 -0
  78. package/template/tools/review-precheck.mjs +531 -0
  79. package/template/tools/sync-agents.mjs +257 -0
  80. package/template/turbo/generators/config.ts +1 -1
  81. package/template/turbo.json +14 -2
  82. package/template/__dot__rulesync/hooks/post-edit.mjs +0 -57
  83. package/template/__dot__rulesync/skills/add-feature/SKILL.md +0 -113
  84. package/template/__dot__rulesync/skills/add-feature/handoff.md +0 -58
  85. package/template/__dot__rulesync/skills/code-review/SKILL.md +0 -111
  86. package/template/__dot__rulesync/skills/create-pr/SKILL.md +0 -55
  87. package/template/__dot__rulesync/subagents/builder.md +0 -93
  88. package/template/__dot__rulesync/subagents/debugger.md +0 -83
  89. package/template/__dot__rulesync/subagents/deployer.md +0 -66
  90. package/template/__dot__rulesync/subagents/qa.md +0 -88
  91. package/template/apps/api/src/migrate.ts.tpl +0 -46
@@ -0,0 +1,257 @@
1
+ #!/usr/bin/env node
2
+ // Render the template-owned agent files from `@openora/create`'s consumer template.
3
+ //
4
+ // Source of truth: node_modules/@openora/create/template at the pinned @openora/* version
5
+ // (with `pnpm link:oss`, ../openora/packages/create-openora's own tools/templates/consumer).
6
+ // Runs on `pnpm install` via `prepare`, so these files are generated artifacts: they are
7
+ // listed in the managed .gitignore block below and never committed. Only this repo's own
8
+ // rules and skills - and the `consumerOwned` overrides in .rulesync/sync.json - are tracked.
9
+ //
10
+ // To change a synced file, edit openora's tools/templates/consumer and take the next bump.
11
+ import {
12
+ existsSync,
13
+ mkdirSync,
14
+ readdirSync,
15
+ readFileSync,
16
+ realpathSync,
17
+ rmdirSync,
18
+ statSync,
19
+ unlinkSync,
20
+ writeFileSync,
21
+ } from 'node:fs';
22
+ import { execFileSync } from 'node:child_process';
23
+ import { createRequire } from 'node:module';
24
+ import { dirname, join, relative, sep } from 'node:path';
25
+
26
+ const ROOT = process.cwd();
27
+ const SYNCED_ROOTS = [
28
+ '.rulesync/',
29
+ 'docs/standards/',
30
+ 'docs/agents/',
31
+ 'tools/sync-agents.mjs',
32
+ 'tools/oss-worktree.mjs',
33
+ 'tools/review-precheck.mjs',
34
+ ];
35
+ // This script bootstraps `prepare`, so it must stay tracked: ignoring it would leave a
36
+ // fresh clone with no way to run the install that would have rendered it.
37
+ // Tracked on purpose: `prepare` runs the script, and the Claude hooks in .claude/settings.json
38
+ // import _shared.mjs, so both must exist in a fresh clone before the first install.
39
+ const NEVER_IGNORED = new Set(['tools/sync-agents.mjs', '.rulesync/hooks/_shared.mjs']);
40
+ const SKIPPED_BASENAMES = new Set();
41
+ // Dropped before the manifest below existed. The first install after the bump runs the previous,
42
+ // tracked copy of this script, which overwrites itself and the .gitignore block but deletes
43
+ // nothing, so the next run has no record of these.
44
+ const RETIRED = [
45
+ '.rulesync/skills/add-feature/SKILL.md',
46
+ '.rulesync/skills/add-feature/handoff.md',
47
+ '.rulesync/subagents/qa.md',
48
+ ];
49
+
50
+ const die = (message) => {
51
+ console.error(`sync:agents - ${message}`);
52
+ process.exit(1);
53
+ };
54
+
55
+ const config = (() => {
56
+ const path = join(ROOT, '.rulesync/sync.json');
57
+ if (!existsSync(path)) {
58
+ die(`${path} is missing - it holds this repo's template variables and consumerOwned list.`);
59
+ }
60
+ try {
61
+ return JSON.parse(readFileSync(path, 'utf8'));
62
+ } catch (err) {
63
+ return die(`${path} is not valid JSON: ${err.message}`);
64
+ }
65
+ })();
66
+ const vars = config.vars ?? {};
67
+ const consumerOwned = new Set(config.consumerOwned ?? []);
68
+
69
+ // The published package ships `template/`; a linked source checkout (pnpm link:oss, CI's
70
+ // unbuilt clone) only has the source at tools/templates/consumer - read that directly.
71
+ const templateRoot = (() => {
72
+ let pkgDir;
73
+ try {
74
+ pkgDir = dirname(createRequire(join(ROOT, '/')).resolve('@openora/create/package.json'));
75
+ } catch {
76
+ return die('@openora/create is not installed - add it as a devDependency and reinstall.');
77
+ }
78
+ const candidates = [
79
+ join(pkgDir, 'template'),
80
+ join(pkgDir, '..', '..', 'tools', 'templates', 'consumer'),
81
+ ];
82
+ const found = candidates.find((dir) => existsSync(dir));
83
+ if (!found) {
84
+ return die(`consumer template not found at ${candidates.join(' or ')}`);
85
+ }
86
+ return found;
87
+ })();
88
+
89
+ const walk = (dir) =>
90
+ readdirSync(dir).flatMap((entry) => {
91
+ const full = join(dir, entry);
92
+ return statSync(full).isDirectory() ? walk(full) : [full];
93
+ });
94
+
95
+ const undot = (segment) => (segment.startsWith('__dot__') ? `.${segment.slice(7)}` : segment);
96
+
97
+ const gitTracked = (paths) => {
98
+ try {
99
+ return execFileSync('git', ['ls-files', '-z', '--', ...paths], {
100
+ cwd: ROOT,
101
+ encoding: 'utf8',
102
+ stdio: ['ignore', 'pipe', 'ignore'],
103
+ })
104
+ .split('\0')
105
+ .filter(Boolean);
106
+ } catch {
107
+ return [];
108
+ }
109
+ };
110
+
111
+ const substitute = (content) =>
112
+ content.replace(/\{\{(\w+)\}\}/g, (match, key) => vars[key] ?? match);
113
+
114
+ const render = (file) => {
115
+ const rel = relative(templateRoot, file).split(sep).map(undot).join('/');
116
+ const isTpl = rel.endsWith('.tpl');
117
+ const raw = readFileSync(file, 'utf8');
118
+ return { path: isTpl ? rel.slice(0, -4) : rel, content: isTpl ? substitute(raw) : raw };
119
+ };
120
+
121
+ const rendered = walk(templateRoot)
122
+ .filter((file) => !SKIPPED_BASENAMES.has(file.split(sep).at(-1)))
123
+ .map(render)
124
+ .filter(({ path }) => SYNCED_ROOTS.some((root) => path.startsWith(root)))
125
+ .filter(({ path }) => !consumerOwned.has(path));
126
+
127
+ // A new template variable must not brick `pnpm install` (which is also how the bump MR that
128
+ // would add it gets built) - leave the placeholder in place and say what to add.
129
+ const unresolved = rendered.filter(({ content }) => /\{\{\w+\}\}/.test(content));
130
+ if (unresolved.length > 0) {
131
+ console.warn(
132
+ 'sync:agents - unresolved template variables, add them to .rulesync/sync.json vars:',
133
+ );
134
+ for (const { path, content } of unresolved) {
135
+ console.warn(` ${path}: ${[...new Set(content.match(/\{\{\w+\}\}/g))].join(' ')}`);
136
+ }
137
+ }
138
+
139
+ // Tracking a path is how this repo says "mine": .gitignore does not apply to it, so writing
140
+ // would replace the committed content and leave a permanently dirty file. Skip those and
141
+ // name them - opting one into being generated is a deliberate `git rm --cached`.
142
+ const trackedPaths = new Set(gitTracked(rendered.map(({ path }) => path)));
143
+ const owned = rendered.filter(({ path }) => trackedPaths.has(path) && !NEVER_IGNORED.has(path));
144
+
145
+ for (const { path, content } of rendered) {
146
+ if (trackedPaths.has(path) && !NEVER_IGNORED.has(path)) {
147
+ continue;
148
+ }
149
+ const target = join(ROOT, path);
150
+ mkdirSync(dirname(target), { recursive: true });
151
+ if (!existsSync(target) || readFileSync(target, 'utf8') !== content) {
152
+ writeFileSync(target, content);
153
+ }
154
+ }
155
+
156
+ // Git must ignore exactly what this script writes, so a template that gains or loses a
157
+ // file cannot leave an untracked stray behind. The block is rewritten in place; anything
158
+ // outside the markers is left alone.
159
+ const BEGIN = '# BEGIN synced-agents (generated by tools/sync-agents.mjs - do not edit)';
160
+ const END = '# END synced-agents';
161
+ const gitignore = join(ROOT, '.gitignore');
162
+ // A tracked path is never ignored by git anyway - listing it would only mislead.
163
+ const ignored = rendered.filter(({ path }) => !NEVER_IGNORED.has(path) && !trackedPaths.has(path));
164
+ const block = [BEGIN, ...ignored.map(({ path }) => `/${path}`).sort(), END].join('\n');
165
+ const current = existsSync(gitignore) ? readFileSync(gitignore, 'utf8') : '';
166
+ const start = current.indexOf(BEGIN);
167
+ // Search for END after BEGIN: a stray END earlier in the file (hand-edit, bad merge) would
168
+ // otherwise splice at a negative offset and duplicate the whole block.
169
+ const end = start === -1 ? -1 : current.indexOf(END, start + BEGIN.length);
170
+ if (start !== -1 && end === -1) {
171
+ die(`${gitignore} has a ${BEGIN} marker with no matching ${END} - repair or delete the block.`);
172
+ }
173
+ // A file the template dropped or renamed is still on disk from the last render, untracked and
174
+ // no longer ignored: delete what fell out of it. The old block lists what was rendered then,
175
+ // but after a branch switch the committed block already names the new set, so the manifest
176
+ // (outside git, per checkout) remembers what this checkout actually has on disk.
177
+ const manifest = join(ROOT, 'node_modules/.cache/sync-agents.json');
178
+ const lastRendered = (() => {
179
+ try {
180
+ const paths = JSON.parse(readFileSync(manifest, 'utf8'));
181
+ return Array.isArray(paths) ? paths.filter((path) => typeof path === 'string') : [];
182
+ } catch {
183
+ return [];
184
+ }
185
+ })();
186
+ const lastBlock =
187
+ start === -1
188
+ ? []
189
+ : current
190
+ .slice(start + BEGIN.length, end)
191
+ .split('\n')
192
+ .filter((line) => line.startsWith('/'))
193
+ .map((line) => line.slice(1));
194
+ // The block and the manifest are plain text anyone can edit: a symlinked directory on the way
195
+ // would point the delete outside the checkout, so every parent must resolve to itself.
196
+ const realRoot = realpathSync(ROOT);
197
+ const parentIsReal = (path) => {
198
+ try {
199
+ return realpathSync(dirname(join(ROOT, path))) === join(realRoot, dirname(path));
200
+ } catch {
201
+ return false;
202
+ }
203
+ };
204
+ const stale = [...new Set([...lastBlock, ...lastRendered, ...RETIRED])]
205
+ .filter((path) => SYNCED_ROOTS.some((root) => path.startsWith(root)) && !path.includes('..'))
206
+ .filter((path) => !ignored.some((file) => file.path === path) && !consumerOwned.has(path))
207
+ .filter(parentIsReal);
208
+ // Never one this repo has since started tracking. (No paths: ls-files would list them all.)
209
+ const staleTracked = new Set(stale.length > 0 ? gitTracked(stale) : []);
210
+ for (const path of stale.filter((path) => !staleTracked.has(path))) {
211
+ const target = join(ROOT, path);
212
+ if (existsSync(target)) {
213
+ unlinkSync(target);
214
+ for (
215
+ let dir = dirname(target);
216
+ dir !== ROOT && readdirSync(dir).length === 0;
217
+ dir = dirname(dir)
218
+ ) {
219
+ rmdirSync(dir);
220
+ }
221
+ }
222
+ }
223
+ const updated =
224
+ start === -1
225
+ ? `${current.trimEnd()}\n\n${block}\n`.trimStart()
226
+ : current.slice(0, start) + block + current.slice(end + END.length);
227
+ if (updated !== current) {
228
+ writeFileSync(gitignore, updated);
229
+ }
230
+ mkdirSync(dirname(manifest), { recursive: true });
231
+ writeFileSync(manifest, JSON.stringify(ignored.map(({ path }) => path)));
232
+
233
+ console.log(`sync:agents - ${rendered.length - owned.length} template-owned file(s) rendered.`);
234
+ if (owned.length > 0) {
235
+ console.warn(
236
+ ` ${owned.length} kept as-is because this repo tracks them: ${owned.map(({ path }) => path).join(', ')}\n` +
237
+ ` To let the template own one, untrack it: git rm --cached <path>`,
238
+ );
239
+ }
240
+
241
+ // Both files are tracked by every consumer, so a template bump cannot wire the verify gate in.
242
+ const missingWiring = [
243
+ ['.claude/settings.json', 'guard-mr.mjs', 'a PreToolUse "Bash" hook running guard-mr.mjs'],
244
+ [
245
+ 'package.json',
246
+ 'guard-mr.mjs --stamp',
247
+ 'end "verify" with `&& node .rulesync/hooks/guard-mr.mjs --stamp`',
248
+ ],
249
+ ].filter(([path, marker]) => {
250
+ const target = join(ROOT, path);
251
+ return existsSync(target) && !readFileSync(target, 'utf8').includes(marker);
252
+ });
253
+ for (const [path, , fix] of missingWiring) {
254
+ console.warn(
255
+ `sync:agents - ${path} is missing the verify gate: ${fix} (see the template's copy).`,
256
+ );
257
+ }
@@ -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
  }
@@ -1,57 +0,0 @@
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
- }
@@ -1,113 +0,0 @@
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.
@@ -1,58 +0,0 @@
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.
@@ -1,111 +0,0 @@
1
- ---
2
- name: code-review
3
- description: Multi-agent code review of the working branch against this repo's conventions, OSS-core boundaries, security, and operator-domain fit. Fans out a configurable number of parallel reviewers, each grounded in the rule docs, then synthesizes one verdict. Use on "review this", "code review", "/code-review", optionally "--agents N", "--base <ref>", "--fix", a GitLab MR number, or paths.
4
- ---
5
-
6
- # code-review
7
-
8
- Orchestrate a parallel, convention-grounded code review of the current change set in this downstream
9
- igaming operator repo. You are the orchestrator: scope the diff, fan out N reviewers across review
10
- dimensions, dedup their findings, and report ONE verdict. Report-only by default - never edit unless
11
- `--fix` is passed.
12
-
13
- Copy this checklist and tick it off as you go:
14
-
15
- ```
16
- code-review:
17
- - [ ] 1. Parse args (--agents / --base / MR# / paths / --fix)
18
- - [ ] 2. Scope the diff; if empty, ask
19
- - [ ] 3. Pick applicable dimensions (only those with touched files)
20
- - [ ] 4. Spawn reviewers in ONE message (parallel)
21
- - [ ] 5. Dedup + apply the evidence/confidence gate
22
- - [ ] 6. Report one verdict (+ apply fixes only if --fix)
23
- ```
24
-
25
- ## 1. Parse `$ARGUMENTS`
26
-
27
- | Token | Meaning | Default |
28
- | ------------------- | --------------------------------------------------------------- | ------------------------------- |
29
- | `--agents N` | how many parallel reviewers to spawn (1-5) | one per applicable dimension |
30
- | `--base <ref>` | branch to diff against | `dev` (this repo's integration) |
31
- | `<number>` (e.g. 8) | a GitLab MR number - review that MR's diff (`glab mr diff <n>`) | - |
32
- | paths | restrict review to these files/dirs | whole diff |
33
- | `--fix` | apply fixes for `[BLOCK]`/`[WARN]` findings after the review | off (report only) |
34
-
35
- ## 2. Scope the diff (do this first)
36
-
37
- - Branch: `git diff <base>...HEAD --name-only` (default base `dev`). If empty, fall back to
38
- unstaged/staged via `git status -s`; if still empty, ask what to review.
39
- - MR number given: `glab mr diff <n>` for the patch + `glab mr view <n>` for intent.
40
- - Group changed files by app/package so reviewers and any file-split share the same map.
41
-
42
- ## 3. Ground every reviewer (mandatory)
43
-
44
- Each spawned reviewer MUST read the actual changed code AND the rule docs that own its dimension
45
- before judging - do not infer behavior from a diff hunk. If a finding depends on what a called
46
- function does, open it; if you can't cite it, fetch it. These docs are the single source of truth -
47
- cite them in findings:
48
-
49
- - `.claude/rules/overview.md` - what this repo is (a downstream operator on `@openora/*` consumed as linked packages), the HARD RULE that OSS core is read-only, how you work here, and the enforced import/module boundaries (extend only from the outside).
50
- - `.claude/rules/db-conventions.md` - SQL / Drizzle rules for tables an overlay owns.
51
- - Any other `.claude/rules/*.md` the consumer has added (e.g. `conventions`, `oss-boundaries`, `frontend`) - cite whichever own the touched files.
52
-
53
- ## 4. Review dimensions
54
-
55
- Each dimension maps to a rule doc and, where one fits, a pre-scoped subagent. Spawn only the
56
- dimensions whose files actually changed.
57
-
58
- | # | Dimension | Covers | Use subagent |
59
- | --- | -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | ------------------ |
60
- | 1 | OSS boundaries & extension | never edits `@openora/*` core; extends only via `extensions.config.ts` / overlay plugins / vendor adapters; no deep `dist/` imports | general (overview) |
61
- | 2 | Conventions & quality | naming, functional/immutable, no `any`/`interface`/default-export, types inferred not hand-written, comments WHY, tests | general (overview) |
62
- | 3 | Frontend | React/styling rules - only if a frontend app or shared UI package was added and changed | general (frontend) |
63
- | 4 | Security & secrets | authz on overlay routes, vendor-adapter creds from env (KYC/PSP/notify), no secret/PII leaks, Zod-validated input | general (overview) |
64
- | 5 | Operator/domain fit | does the change make igaming sense for this operator; launch-blockers (only if business logic changed) | `expert` |
65
-
66
- ## 5. Allocate reviewers to `--agents N`
67
-
68
- - `N` unset: one reviewer per applicable dimension (skip dimensions with no touched files).
69
- - `N` >= applicable dimensions: extra agents split the largest dimension by file group (state the split; never silently drop files).
70
- - `N` < applicable dimensions: merge adjacent dimensions into `N` buckets, preferring to keep 1 (OSS boundaries) and 4 (security) standalone.
71
-
72
- Spawn all reviewers in a SINGLE message (parallel `Task` calls). Use `general-purpose` for general
73
- dimensions with an explicit instruction to read the §3 docs first; use `expert` for the
74
- domain-fit dimension. Pass each reviewer: the changed-file list, the base ref, its dimension
75
- checklist, and the report-only constraint.
76
-
77
- ## 6. Evidence & confidence gate (cut false positives)
78
-
79
- Tell every reviewer to apply this before returning, and re-apply it yourself when synthesizing:
80
-
81
- - Every `[BLOCK]` and `[WARN]` MUST cite a concrete `file:line` AND the rule doc it violates. No location or no rule = downgrade to `[INFO]` or drop it.
82
- - Report only high-confidence findings. If unsure whether something is a real defect vs a theoretical nit, downgrade or omit - prefer few actionable findings over flooding.
83
- - Don't invent runtime failures you haven't traced through the code. State the trigger path or don't raise it.
84
- - Don't bikeshed and don't duplicate what tooling already enforces: oxlint and the `/check` gate (`pnpm typecheck`, `pnpm lint`). For a suspected lint/boundary issue, say "confirm with `pnpm lint`" rather than guessing - flag only what those gates miss.
85
- - Each reviewer self-checks before returning: every finding has evidence + a cited rule, uncertain claims downgraded, no style nitpicks lint already catches, no unverified runtime claims.
86
-
87
- ## 7. Synthesize
88
-
89
- Collect all findings, dedup by `file:line`, and merge into one report. Each finding:
90
-
91
- - `[BLOCK]` - must fix before merge (edits OSS core, boundary break, authz/secret/PII risk, broken extension wiring).
92
- - `[WARN]` - should fix (convention violation, missing test, weak input validation).
93
- - `[INFO]` - FYI / hardening.
94
-
95
- Group by dimension, ordered BLOCK -> WARN -> INFO. Each line:
96
- `[SEV] file:line - finding - evidence - rule cited - fix`. BLOCK/WARN without a `file:line` and a
97
- cited rule do not ship - they were already dropped by the gate in §6. Lead with a one-line summary:
98
- counts per severity + verdict. End with **APPROVED** / **CHANGES REQUESTED** and the single most
99
- critical finding.
100
-
101
- If `--fix`: after the report, apply BLOCK + WARN fixes in the working tree (smallest diff that
102
- satisfies the cited rule), then run the `/check` gate (`pnpm typecheck && pnpm lint`)
103
- and report green/red. Leave INFO items untouched. Never commit or push.
104
-
105
- ## Constraints
106
-
107
- - Read-only by default; `--fix` edits the working tree only - no commit, no push.
108
- - NEVER edit `@openora/*` core or `node_modules` - this repo extends the platform from the outside only.
109
- - Reviewers report findings; they do not edit. Only the orchestrator edits, and only under `--fix`.
110
- - Always cite the rule doc a finding rests on - no opinions ungrounded in the conventions.
111
- - Cap at 5 parallel reviewers.