@openora/create 0.4.1-canary.33 → 0.4.1-canary.331
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.
- package/dist/.tsbuildinfo +1 -1
- package/dist/generated/versions.d.ts +11 -0
- package/dist/generated/versions.d.ts.map +1 -0
- package/dist/generated/versions.js +11 -0
- package/dist/generated/versions.js.map +1 -0
- package/dist/index.js +48 -93
- package/dist/index.js.map +1 -1
- package/dist/render.d.ts +15 -0
- package/dist/render.d.ts.map +1 -0
- package/dist/render.js +125 -0
- package/dist/render.js.map +1 -0
- package/package.json +8 -6
- package/template/README.md.tpl +12 -7
- package/template/__dot__claude/settings.json.tpl +22 -12
- package/template/__dot__env.example +10 -0
- package/template/__dot__gitignore +6 -1
- package/template/__dot__rulesync/commands/adr.md +32 -0
- package/template/__dot__rulesync/commands/check.md +11 -10
- package/template/__dot__rulesync/commands/doctor.md.tpl +17 -0
- package/template/__dot__rulesync/commands/scaffold-module.md +3 -2
- package/template/__dot__rulesync/commands/scaffold-plugin.md +19 -3
- package/template/__dot__rulesync/commands/scaffold-route.md +1 -0
- package/template/__dot__rulesync/commands/start.md +2 -2
- package/template/__dot__rulesync/hooks/_shared.mjs +26 -13
- package/template/__dot__rulesync/hooks/guard-core.mjs.tpl +76 -28
- package/template/__dot__rulesync/hooks/guard-generated.mjs +16 -14
- package/template/__dot__rulesync/hooks/guard-mr.mjs +81 -0
- package/template/__dot__rulesync/hooks/guard-subagent.mjs +22 -6
- package/template/__dot__rulesync/hooks/post-edit.mjs.tpl +116 -0
- package/template/__dot__rulesync/hooks.json +27 -4
- package/template/__dot__rulesync/rules/conventions.md +114 -0
- package/template/__dot__rulesync/rules/db-conventions.md +6 -72
- package/template/__dot__rulesync/rules/e2e-conventions.md +52 -0
- package/template/__dot__rulesync/rules/frontend-conventions.md.tpl +35 -0
- package/template/__dot__rulesync/rules/oss-boundaries.md.tpl +78 -0
- package/template/__dot__rulesync/rules/overview.md +18 -73
- package/template/__dot__rulesync/skills/create-plugin/{SKILL.md → SKILL.md.tpl} +13 -24
- package/template/__dot__rulesync/skills/create-pr/SKILL.md.tpl +38 -0
- package/template/__dot__rulesync/skills/create-task/{SKILL.md → SKILL.md.tpl} +14 -22
- package/template/__dot__rulesync/skills/create-ui-module/SKILL.md.tpl +60 -0
- package/template/__dot__rulesync/skills/enhance-prompt/{SKILL.md → SKILL.md.tpl} +4 -4
- package/template/__dot__rulesync/skills/handoff/SKILL.md +95 -0
- package/template/__dot__rulesync/skills/review/SKILL.md.tpl +277 -0
- package/template/__dot__rulesync/skills/ship/SKILL.md.tpl +157 -0
- package/template/__dot__rulesync/skills/ship/handoff.md.tpl +48 -0
- package/template/__dot__rulesync/skills/ship/watch-pr.mjs +172 -0
- package/template/__dot__rulesync/skills/ui-verify/SKILL.md.tpl +120 -0
- package/template/__dot__rulesync/skills/ui-verify/layout-check.check.sh +58 -0
- package/template/__dot__rulesync/skills/ui-verify/layout-check.js +163 -0
- package/template/__dot__rulesync/subagents/builder.md.tpl +115 -0
- package/template/__dot__rulesync/subagents/compliance-reviewer.md +78 -0
- package/template/__dot__rulesync/subagents/debugger.md.tpl +86 -0
- package/template/__dot__rulesync/subagents/deployer.md.tpl +54 -0
- package/template/__dot__rulesync/subagents/expert.md +28 -34
- package/template/__dot__rulesync/subagents/planner.md.tpl +39 -0
- package/template/__dot__rulesync/subagents/quality-reviewer.md.tpl +112 -0
- package/template/__dot__rulesync/subagents/security-reviewer.md.tpl +85 -0
- package/template/__dot__rulesync/subagents/tester.md.tpl +102 -0
- package/template/__dot__rulesync/sync.json.tpl +26 -0
- package/template/apps/api/package.json.tpl +3 -4
- package/template/apps/api/src/extensions.config.ts.tpl +12 -0
- package/template/apps/api/src/main.ts.tpl +1 -4
- package/template/apps/api/src/seed.ts.tpl +8 -1
- package/template/docker-compose.yml +13 -0
- package/template/docs/agents/cross-repo.md.tpl +37 -0
- package/template/docs/agents/forge.md.tpl +54 -0
- package/template/docs/agents/issue-tracker.md.tpl +42 -0
- package/template/docs/standards/comments.md +16 -0
- package/template/docs/standards/database.md +77 -0
- package/template/docs/standards/enforcement.md +12 -0
- package/template/docs/standards/errors.md +26 -0
- package/template/docs/standards/frontend.md +60 -0
- package/template/docs/standards/functions.md +38 -0
- package/template/docs/standards/git-delivery.md +20 -0
- package/template/docs/standards/module-structure.md +37 -0
- package/template/docs/standards/testing.md +88 -0
- package/template/docs/standards/types.md +31 -0
- package/template/package.json.tpl +19 -13
- package/template/rulesync.jsonc +4 -4
- package/template/tools/oss-worktree.mjs.tpl +105 -0
- package/template/tools/review-precheck.mjs +531 -0
- package/template/tools/sync-agents.mjs +257 -0
- package/template/turbo/generators/config.ts +1 -1
- package/template/turbo.json +14 -2
- package/dist/generated/core-version.d.ts +0 -2
- package/dist/generated/core-version.d.ts.map +0 -1
- package/dist/generated/core-version.js +0 -2
- package/dist/generated/core-version.js.map +0 -1
- package/template/__dot__rulesync/hooks/post-edit.mjs +0 -57
- package/template/__dot__rulesync/skills/add-feature/SKILL.md +0 -113
- package/template/__dot__rulesync/skills/add-feature/handoff.md +0 -58
- package/template/__dot__rulesync/skills/code-review/SKILL.md +0 -111
- package/template/__dot__rulesync/skills/create-pr/SKILL.md +0 -55
- package/template/__dot__rulesync/subagents/builder.md +0 -93
- package/template/__dot__rulesync/subagents/debugger.md +0 -83
- package/template/__dot__rulesync/subagents/deployer.md +0 -66
- package/template/__dot__rulesync/subagents/qa.md +0 -88
- package/template/apps/api/src/migrate.ts.tpl +0 -46
package/dist/render.js
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { basename, dirname, join, relative, sep } from 'node:path';
|
|
3
|
+
const MCP_COMMAND = 'node';
|
|
4
|
+
const MCP_ARGS = ['node_modules/@openora/mcp/dist/main.js'];
|
|
5
|
+
const SKIPPED_BASENAMES = new Set();
|
|
6
|
+
const USAGE = 'Usage: npm create @openora <target-dir> [--name <name>] [--no-telemetry]';
|
|
7
|
+
function readFlag(args, index) {
|
|
8
|
+
const arg = args[index] ?? '';
|
|
9
|
+
const eq = arg.indexOf('=');
|
|
10
|
+
if (eq !== -1) {
|
|
11
|
+
return { value: arg.slice(eq + 1), next: index };
|
|
12
|
+
}
|
|
13
|
+
return { value: args[index + 1], next: index + 1 };
|
|
14
|
+
}
|
|
15
|
+
export function parseArgs(argv) {
|
|
16
|
+
const args = argv.slice(2);
|
|
17
|
+
let target;
|
|
18
|
+
let name;
|
|
19
|
+
let telemetry = true;
|
|
20
|
+
for (let i = 0; i < args.length; i++) {
|
|
21
|
+
const arg = args[i];
|
|
22
|
+
if (arg === undefined) {
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
if (arg === '--name' || arg.startsWith('--name=')) {
|
|
26
|
+
const flag = readFlag(args, i);
|
|
27
|
+
name = flag.value;
|
|
28
|
+
i = flag.next;
|
|
29
|
+
}
|
|
30
|
+
else if (arg === '--no-telemetry') {
|
|
31
|
+
telemetry = false;
|
|
32
|
+
}
|
|
33
|
+
else if (!arg.startsWith('-') && !target) {
|
|
34
|
+
target = arg;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (!target) {
|
|
38
|
+
throw new Error(`missing target directory.\n ${USAGE}`);
|
|
39
|
+
}
|
|
40
|
+
return name === undefined ? { target, telemetry } : { target, name, telemetry };
|
|
41
|
+
}
|
|
42
|
+
function isSet(flag) {
|
|
43
|
+
return flag !== undefined && ['1', 'true', 'yes'].includes(flag.trim().toLowerCase());
|
|
44
|
+
}
|
|
45
|
+
// Mirrors `isTelemetryEnabled` in core's server/runtime/telemetry.ts; change both together.
|
|
46
|
+
export function isTelemetryEnabled(telemetry, env) {
|
|
47
|
+
return (telemetry &&
|
|
48
|
+
!isSet(env['OPENORA_TELEMETRY_DISABLED']) &&
|
|
49
|
+
!isSet(env['DO_NOT_TRACK']) &&
|
|
50
|
+
env['CI'] === undefined &&
|
|
51
|
+
env['NODE_ENV'] !== 'test');
|
|
52
|
+
}
|
|
53
|
+
export function sanitizeName(raw) {
|
|
54
|
+
return (raw
|
|
55
|
+
.toLowerCase()
|
|
56
|
+
.replace(/[^a-z0-9-_]/g, '-')
|
|
57
|
+
.replace(/^-+|-+$/g, '') || 'igaming-app');
|
|
58
|
+
}
|
|
59
|
+
export function posix(path) {
|
|
60
|
+
return path.split(sep).join('/');
|
|
61
|
+
}
|
|
62
|
+
export function substitute(content, vars) {
|
|
63
|
+
return content.replace(/\{\{(\w+)\}\}/g, (match, key) => {
|
|
64
|
+
const value = vars[key];
|
|
65
|
+
return value === undefined ? match : value;
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
function walk(dir) {
|
|
69
|
+
const out = [];
|
|
70
|
+
for (const entry of readdirSync(dir)) {
|
|
71
|
+
const full = join(dir, entry);
|
|
72
|
+
if (statSync(full).isDirectory()) {
|
|
73
|
+
out.push(...walk(full));
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
out.push(full);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return out;
|
|
80
|
+
}
|
|
81
|
+
export function undotSegment(segment) {
|
|
82
|
+
return segment.startsWith('__dot__') ? `.${segment.slice('__dot__'.length)}` : segment;
|
|
83
|
+
}
|
|
84
|
+
export function outputPath(templateRel) {
|
|
85
|
+
const mapped = templateRel.split(sep).map(undotSegment).join(sep);
|
|
86
|
+
return mapped.endsWith('.tpl') ? mapped.slice(0, -'.tpl'.length) : mapped;
|
|
87
|
+
}
|
|
88
|
+
export function emitTree(srcRoot, vars, targetDir) {
|
|
89
|
+
for (const file of walk(srcRoot)) {
|
|
90
|
+
if (SKIPPED_BASENAMES.has(basename(file))) {
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
const outRel = outputPath(relative(srcRoot, file));
|
|
94
|
+
const raw = readFileSync(file, 'utf8');
|
|
95
|
+
const content = file.endsWith('.tpl') ? substitute(raw, vars) : raw;
|
|
96
|
+
const outPath = join(targetDir, outRel);
|
|
97
|
+
mkdirSync(dirname(outPath), { recursive: true });
|
|
98
|
+
writeFileSync(outPath, content);
|
|
99
|
+
console.log(` + ${posix(outRel)}`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
// Operator coordinates the agent files reference. A fresh scaffold gets placeholders;
|
|
103
|
+
// the consumer fills them in once in .rulesync/sync.json and `pnpm sync:agents`
|
|
104
|
+
// re-renders every template-owned file from them.
|
|
105
|
+
export function buildVars(name, versions) {
|
|
106
|
+
return {
|
|
107
|
+
name,
|
|
108
|
+
...versions,
|
|
109
|
+
mcpCommand: MCP_COMMAND,
|
|
110
|
+
mcpArgsJson: JSON.stringify(MCP_ARGS),
|
|
111
|
+
scope: `@${name}`,
|
|
112
|
+
trackerKey: 'KEY',
|
|
113
|
+
jiraSite: '<your-site>.atlassian.net',
|
|
114
|
+
jiraCloudId: '<your-jira-cloud-id>',
|
|
115
|
+
wikiSpace: '<your-wiki-space>',
|
|
116
|
+
teamChannel: '#<your-team-channel>',
|
|
117
|
+
gitRemotePath: '<group>/<repo>',
|
|
118
|
+
ossDir: '../openora',
|
|
119
|
+
// Only meaningful once the consumer links a local platform checkout; until then the
|
|
120
|
+
// guard-core hook simply never matches.
|
|
121
|
+
ossFromRoot: '../openora',
|
|
122
|
+
mrTarget: 'dev',
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=render.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render.js","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openora/create",
|
|
3
|
-
"version": "0.4.1-canary.
|
|
3
|
+
"version": "0.4.1-canary.331",
|
|
4
4
|
"description": "npm initializer that scaffolds a headless Openora consumer app from the published @openora/* packages - no OSS checkout required",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai-native",
|
|
@@ -28,16 +28,18 @@
|
|
|
28
28
|
"access": "public"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
|
-
"@types/node": "
|
|
31
|
+
"@types/node": "26.6.5",
|
|
32
32
|
"typescript": "6.0.3",
|
|
33
|
-
"
|
|
33
|
+
"vitest": "4.1.12",
|
|
34
|
+
"@openora/core": "0.4.1-canary.331"
|
|
34
35
|
},
|
|
35
36
|
"engines": {
|
|
36
37
|
"node": ">=26"
|
|
37
38
|
},
|
|
38
39
|
"scripts": {
|
|
39
|
-
"build": "node scripts/copy-template.mjs && node scripts/generate-
|
|
40
|
-
"
|
|
41
|
-
"clean": "rm -rf dist template src/generated"
|
|
40
|
+
"build": "node scripts/copy-template.mjs && node scripts/generate-versions.mjs && tsc",
|
|
41
|
+
"check:types": "node scripts/generate-versions.mjs && tsc --noEmit",
|
|
42
|
+
"clean": "rm -rf dist template src/generated",
|
|
43
|
+
"test:unit": "vitest run"
|
|
42
44
|
}
|
|
43
45
|
}
|
package/template/README.md.tpl
CHANGED
|
@@ -13,7 +13,8 @@ in its own repo and talk to this api over HTTP via `@openora/react`.
|
|
|
13
13
|
```
|
|
14
14
|
apps/
|
|
15
15
|
api/ # Hono + oRPC API (:3001) - thin createApp entry + your extensions.config.ts
|
|
16
|
-
.
|
|
16
|
+
.rulesync/ # source of truth for agent rules, subagents, commands, skills
|
|
17
|
+
.claude/agents/ # generated AI agents
|
|
17
18
|
turbo/generators/ # turbo gen: plugin, adapter
|
|
18
19
|
```
|
|
19
20
|
|
|
@@ -27,18 +28,20 @@ newest release; pin an exact version if you need reproducible installs.
|
|
|
27
28
|
|
|
28
29
|
```bash
|
|
29
30
|
pnpm install # pulls @openora/* from npm
|
|
30
|
-
pnpm setup:mcp # trust the MCP server + install the /start onboarding flow
|
|
31
|
-
pnpm regen # regenerate OpenAPI + catalog + Drizzle client (after schema changes)
|
|
32
31
|
cp .env.example .env # set DATABASE_URL + AUTH_SECRET
|
|
33
32
|
pnpm db:migrate # apply the OSS schema to your database
|
|
34
33
|
pnpm dev # api :3001
|
|
35
34
|
```
|
|
36
35
|
|
|
37
|
-
|
|
38
|
-
you want to build and scaffolds it for you.
|
|
36
|
+
The `oss` MCP server is declared in `.mcp.json`. Approve it in your editor, restart, then run
|
|
37
|
+
**`/start`** in Claude Code - it asks what you want to build and scaffolds it for you.
|
|
39
38
|
|
|
40
39
|
To pull the newest platform release, re-run `pnpm install` (the pin tracks the `latest` stable tag).
|
|
41
40
|
|
|
41
|
+
The API sends anonymous usage telemetry (a random instance id, the version, the Node.js runtime and
|
|
42
|
+
two active-player counts). Set `OPENORA_TELEMETRY_DISABLED=1` in `.env` to turn it off; every field
|
|
43
|
+
is listed at https://openora.ai/documentation/telemetry.
|
|
44
|
+
|
|
42
45
|
## Extending
|
|
43
46
|
|
|
44
47
|
| Need | Command |
|
|
@@ -52,10 +55,12 @@ a DI token wins).
|
|
|
52
55
|
|
|
53
56
|
## AI agents
|
|
54
57
|
|
|
55
|
-
`.claude/agents/` ships
|
|
58
|
+
`.claude/agents/` ships agents scoped to this repo, generated from `.rulesync/subagents/`:
|
|
56
59
|
|
|
60
|
+
- `planner` - plan a ticket: the blocking questions, or a plan with risk-driven test cases
|
|
57
61
|
- `builder` - configure extensions, swap adapters, write overlays, customize UI
|
|
58
62
|
- `expert` - turn product asks into requirements + acceptance criteria
|
|
59
|
-
- `
|
|
63
|
+
- `tester` - attack a built change against the running stack, return a verdict table, add the tests, triage bugs; once tests and review pass, record a video per AC on the final commit
|
|
64
|
+
- `debugger`, `deployer`, `quality-reviewer`, `security-reviewer`, `compliance-reviewer` - root-cause, packaging, review
|
|
60
65
|
|
|
61
66
|
The `oss` MCP server (`.mcp.json`) gives them read-only inspection of the platform surface.
|
|
@@ -1,49 +1,59 @@
|
|
|
1
1
|
{
|
|
2
2
|
"enableAllProjectMcpServers": false,
|
|
3
3
|
"enabledMcpjsonServers": ["oss"],
|
|
4
|
+
"env": {
|
|
5
|
+
"CLAUDE_CODE_ADDITIONAL_DIRECTORIES_CLAUDE_MD": "1"
|
|
6
|
+
},
|
|
4
7
|
"permissions": {
|
|
5
|
-
"deny": [
|
|
6
|
-
|
|
7
|
-
"Write(./node_modules/**)"
|
|
8
|
-
]
|
|
8
|
+
"deny": ["Edit(./node_modules/**)"],
|
|
9
|
+
"additionalDirectories": ["{{ossFromRoot}}"]
|
|
9
10
|
},
|
|
10
11
|
"hooks": {
|
|
11
12
|
"PreToolUse": [
|
|
12
13
|
{
|
|
13
|
-
"matcher": "Bash|Edit|Write",
|
|
14
|
+
"matcher": "Bash|Edit|Write|MultiEdit|NotebookEdit",
|
|
15
|
+
"hooks": [
|
|
16
|
+
{
|
|
17
|
+
"type": "command",
|
|
18
|
+
"command": "node \"${CLAUDE_PROJECT_DIR:-.}/.rulesync/hooks/guard-core.mjs\""
|
|
19
|
+
}
|
|
20
|
+
]
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"matcher": "Bash|Edit|Write|MultiEdit|NotebookEdit",
|
|
14
24
|
"hooks": [
|
|
15
25
|
{
|
|
16
26
|
"type": "command",
|
|
17
|
-
"command": "node
|
|
27
|
+
"command": "node \"${CLAUDE_PROJECT_DIR:-.}/.rulesync/hooks/guard-generated.mjs\""
|
|
18
28
|
}
|
|
19
29
|
]
|
|
20
30
|
},
|
|
21
31
|
{
|
|
22
|
-
"matcher": "
|
|
32
|
+
"matcher": "Bash",
|
|
23
33
|
"hooks": [
|
|
24
34
|
{
|
|
25
35
|
"type": "command",
|
|
26
|
-
"command": "node
|
|
36
|
+
"command": "node \"${CLAUDE_PROJECT_DIR:-.}/.rulesync/hooks/guard-mr.mjs\""
|
|
27
37
|
}
|
|
28
38
|
]
|
|
29
39
|
},
|
|
30
40
|
{
|
|
31
|
-
"matcher": "Task",
|
|
41
|
+
"matcher": "Task|Agent",
|
|
32
42
|
"hooks": [
|
|
33
43
|
{
|
|
34
44
|
"type": "command",
|
|
35
|
-
"command": "node
|
|
45
|
+
"command": "node \"${CLAUDE_PROJECT_DIR:-.}/.rulesync/hooks/guard-subagent.mjs\""
|
|
36
46
|
}
|
|
37
47
|
]
|
|
38
48
|
}
|
|
39
49
|
],
|
|
40
50
|
"PostToolUse": [
|
|
41
51
|
{
|
|
42
|
-
"matcher": "Edit|Write",
|
|
52
|
+
"matcher": "Edit|Write|MultiEdit|NotebookEdit",
|
|
43
53
|
"hooks": [
|
|
44
54
|
{
|
|
45
55
|
"type": "command",
|
|
46
|
-
"command": "node
|
|
56
|
+
"command": "node \"${CLAUDE_PROJECT_DIR:-.}/.rulesync/hooks/post-edit.mjs\""
|
|
47
57
|
}
|
|
48
58
|
]
|
|
49
59
|
}
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
# Database (Postgres). In dev link-mode this is typically the same local DB the
|
|
2
2
|
# OSS checkout uses, so migrations run via `pnpm db:migrate` line up.
|
|
3
3
|
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/oss_igaming
|
|
4
|
+
# Optional pool tuning: connections per process (default 10) and how long a query waits for
|
|
5
|
+
# a free one before failing (default 5000 ms).
|
|
6
|
+
# DATABASE_POOL_MAX=10
|
|
7
|
+
# DATABASE_POOL_ACQUIRE_TIMEOUT_MS=5000
|
|
8
|
+
# Optional read replicas, comma-separated. Only reads that opt into `drizzle.replica`
|
|
9
|
+
# use them; everything else stays on DATABASE_URL. Each replica gets its own pool.
|
|
10
|
+
# DATABASE_REPLICA_URLS=
|
|
4
11
|
|
|
5
12
|
# Auth secret (min 32 chars). Generate one: openssl rand -base64 32
|
|
6
13
|
AUTH_SECRET=change-me-in-production-min-32-chars
|
|
@@ -10,6 +17,9 @@ AUTH_SECRET=change-me-in-production-min-32-chars
|
|
|
10
17
|
# up -d` starts this alongside Postgres.
|
|
11
18
|
REDIS_URL=redis://localhost:6379
|
|
12
19
|
|
|
20
|
+
# Anonymous usage telemetry is on by default: https://openora.ai/documentation/telemetry
|
|
21
|
+
# OPENORA_TELEMETRY_DISABLED=1
|
|
22
|
+
|
|
13
23
|
# Ports
|
|
14
24
|
PORT=3001
|
|
15
25
|
CORS_ORIGINS=http://localhost:3000,http://localhost:3002
|
|
@@ -17,7 +17,7 @@ coverage
|
|
|
17
17
|
.DS_Store
|
|
18
18
|
|
|
19
19
|
# Generated by rulesync from .rulesync/ (single source of truth).
|
|
20
|
-
# Regenerated on `pnpm install` (prepare) or `pnpm
|
|
20
|
+
# Regenerated on `pnpm install` (prepare) or `pnpm gen:agents`. Do not commit.
|
|
21
21
|
/AGENTS.md
|
|
22
22
|
/CLAUDE.md
|
|
23
23
|
/.mcp.json
|
|
@@ -25,7 +25,12 @@ coverage
|
|
|
25
25
|
.codex/
|
|
26
26
|
.claude/agents/
|
|
27
27
|
.claude/commands/
|
|
28
|
+
.claude/worktrees/
|
|
29
|
+
.claude/reviews/
|
|
28
30
|
.github/copilot-instructions.md
|
|
29
31
|
.github/agents/
|
|
30
32
|
.github/prompts/
|
|
31
33
|
.github/hooks/
|
|
34
|
+
|
|
35
|
+
# Agent files rendered from the @openora/create consumer template on install.
|
|
36
|
+
# The block below is rewritten by tools/sync-agents.mjs - do not edit by hand.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
---
|
|
2
|
+
targets:
|
|
3
|
+
- '*'
|
|
4
|
+
description: 'Record an architectural decision in docs/adr/. Args: <title>. Numbers sequentially, follows the ADR-0001 format (Context / Decision / Consequences).'
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
Create `docs/adr/NNNN-<kebab-slug>.md` where `NNNN` is the next number after the highest existing one (`ls docs/adr/`).
|
|
8
|
+
|
|
9
|
+
Structure (match `0001-modular-architecture.md`):
|
|
10
|
+
|
|
11
|
+
```markdown
|
|
12
|
+
# ADR NNNN: <Title>
|
|
13
|
+
|
|
14
|
+
## Context
|
|
15
|
+
|
|
16
|
+
<the problem/forces, 2-5 lines - why a decision was needed>
|
|
17
|
+
|
|
18
|
+
## Decision
|
|
19
|
+
|
|
20
|
+
<what was decided, imperative and concrete; bullets for the specifics>
|
|
21
|
+
|
|
22
|
+
## Consequences
|
|
23
|
+
|
|
24
|
+
<what becomes easier/harder; follow-ups it creates>
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Rules:
|
|
28
|
+
|
|
29
|
+
- Fill it from the current conversation/diff - state the decision actually made, not options considered. If the decision isn't settled, say so and ask instead of writing a speculative ADR.
|
|
30
|
+
- One ADR = one decision. Reference related ADRs by number.
|
|
31
|
+
- If the decision changes agent-facing conventions, also update the matching `.rulesync/rules/*` file and run `pnpm gen:agents`.
|
|
32
|
+
- Report the file path when done. Don't commit unless asked.
|
|
@@ -1,21 +1,22 @@
|
|
|
1
1
|
---
|
|
2
2
|
targets:
|
|
3
3
|
- '*'
|
|
4
|
-
description: Run the local gate
|
|
4
|
+
description: Run the fast local gate - typecheck + lint + unit tests. Use `pnpm verify` for the full pre-MR gate (adds format check, boundaries, build).
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Run in order, stop at the first failure:
|
|
8
8
|
|
|
9
|
-
1. `pnpm
|
|
10
|
-
2. `pnpm lint`
|
|
9
|
+
1. `pnpm check:types`
|
|
10
|
+
2. `pnpm check:lint`
|
|
11
|
+
3. `pnpm test:unit`
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
The generated agent files (AGENTS.md, CLAUDE.md, .codex/config.toml, Copilot mirrors) are gitignored and regenerated from `.rulesync/` on `pnpm install` - there is nothing to drift-check. If you changed agent instructions, edit `.rulesync/` and run `pnpm sync:agents`.
|
|
13
|
+
`pnpm verify` is the pre-MR gate: these three plus whatever else the repo wires into that script (format, boundaries, build). It also records the verified commit, which the `guard-mr` hook checks before an MR/PR is opened.
|
|
15
14
|
|
|
16
15
|
After running:
|
|
17
16
|
|
|
18
|
-
- All green: report ready
|
|
19
|
-
-
|
|
17
|
+
- All green: report ready; list changed files (`git diff --name-only`).
|
|
18
|
+
- A step fails: show the exact location, propose a fix. Never report ready while any step is red.
|
|
19
|
+
|
|
20
|
+
Agent files (`AGENTS.md`, `CLAUDE.md`, mirrors) are gitignored and generated from `.rulesync/` on install - nothing to drift-check. If you changed agent instructions, edit `.rulesync/` and run `pnpm gen:agents`.
|
|
20
21
|
|
|
21
|
-
Do not touch `@openora/*` core or `node_modules` - this repo extends the platform from the outside only (overlay plugins, adapter rebindings, UI plugins, config).
|
|
22
|
+
Do not touch `@openora/*` core or `node_modules` - this repo extends the platform from the outside only (overlay plugins, adapter rebindings, UI plugins, config).
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
---
|
|
2
|
+
targets:
|
|
3
|
+
- '*'
|
|
4
|
+
description: Diagnose the local dev environment - linked {{ossDir}}, node, postgres, ports, caches. Read-only; reports findings + the fix command for each.
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
Run these checks and report a pass/fail line per item with the fix command for failures. Read-only - never apply fixes without being asked.
|
|
8
|
+
|
|
9
|
+
1. **Node version**: `node -v` matches the major in `.nvmrc` (read the file, don't assume a number). Fix: `nvm use`.
|
|
10
|
+
2. **OSS checkout linked** (only in repos that link a local platform checkout - a stock scaffold installs `@openora/*` from npm and skips this and the next check): `{{ossDir}}` exists and `node --input-type=module -e "import {createRequire} from 'node:module'; console.log(createRequire(process.cwd()+'/').resolve('@openora/core/react'))"` resolves. Fix: clone the OSS repo side by side at `{{ossDir}}`, run the repo's link script if it ships one (`pnpm link:oss`), then `pnpm install`.
|
|
11
|
+
3. **OSS built**: the resolved path above points into a `dist/` that exists and is newer than `{{ossDir}}/packages/*/src`. Fix: rebuild the linked checkout with the repo's build script if it ships one (`pnpm build:oss`), otherwise build it from `{{ossDir}}`.
|
|
12
|
+
4. **Database**: postgres reachable on :5432 (`nc -z localhost 5432`). Fix: `dev:infra` MCP tool (server `oss`) or the compose stack.
|
|
13
|
+
5. **Services**: port 3001 (api), plus 3000 (web) and 3002 (backoffice) in repos that have those apps - report which are up. Fix: `pnpm dev`.
|
|
14
|
+
6. **Stale bundler cache**: if this repo has a Next app and a build error persists after a fix, `rm -rf apps/*/.next` and rebuild (Turbopack caches across the link boundary).
|
|
15
|
+
7. **Generated agent files**: `pnpm gen:agents` runs clean (regenerates from `.rulesync/`).
|
|
16
|
+
|
|
17
|
+
If everything passes but a build still fails, hand off to the `debugger` agent with the verbatim error - it owns the deeper resolution table (turbopack.root, symlinked tsconfig, externalDir).
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
---
|
|
2
|
-
|
|
2
|
+
targets: ['*']
|
|
3
|
+
description: 'Generate a self-contained local add-on package (schema, service, router, plugin.ts) and register it. Args: <name>. Consumers rarely need this - prefer an overlay plugin.'
|
|
3
4
|
---
|
|
4
5
|
|
|
5
6
|
> A consumer extends the platform with **overlay plugins** (`scaffold-plugin`) almost always. Reach
|
|
@@ -11,7 +12,7 @@ ships a buildable add-on - a `list` route wired contract -> router -> service ov
|
|
|
11
12
|
so `/check` is green immediately. Then:
|
|
12
13
|
|
|
13
14
|
1. `<name>/src/schema/index.ts` - Drizzle tables (`pgTable`, snake_case, `timestamp({ withTimezone:
|
|
14
|
-
true })` per `
|
|
15
|
+
true })` per `docs/standards/database.md`).
|
|
15
16
|
2. `<name>/src/schemas/index.ts` - Zod input/output schemas; types via `z.infer`.
|
|
16
17
|
3. `<name>/src/service/<name>.service.ts` - business logic; inject `DRIZZLE` + `EVENT_BUS`, no inline
|
|
17
18
|
fetch/SQL.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
---
|
|
2
|
-
|
|
2
|
+
targets: ['*']
|
|
3
|
+
description: 'Generate an overlay extension under apps/api/src/extensions/<name>/. Adds routes, providers, event handlers, agent tools, or agent action types without touching @openora/* core. Args: <name>.'
|
|
3
4
|
---
|
|
4
5
|
|
|
5
6
|
Run `pnpm gen plugin $ARGUMENTS` in the repo root. For a vendor swap use
|
|
@@ -12,13 +13,28 @@ After the generator finishes, open `apps/api/src/extensions/<name>/plugin.ts` an
|
|
|
12
13
|
compile + runtime).
|
|
13
14
|
- `ctx.routers.add(namespace, (c) => router)` - mount oRPC routes (Zod input/output, no inline schemas).
|
|
14
15
|
- `ctx.events.on(event, handler)` - subscribe to the typed `EventBus`.
|
|
15
|
-
- `ctx.
|
|
16
|
+
- `ctx.triggers.listen((trigger, payload, envelope) => ...)` - hear the domain events the operator
|
|
17
|
+
allow-listed as agent triggers (`agents.triggers.topics`). Delivery is at-least-once: dedupe on
|
|
18
|
+
`envelope.eventId`, and drop what is older than you care about by `envelope.occurredAt`, since a
|
|
19
|
+
topic listed again can replay its backlog. The payload is raw and may hold personal data, so
|
|
20
|
+
never hand it to a model. Enqueue a job and return: a slow listener delays the topic's other
|
|
21
|
+
handlers.
|
|
22
|
+
- `ctx.mcp.tool(defineMcpTool({...}), (c) => async (input, run) => output)` - register an agent tool,
|
|
23
|
+
a read a model makes during a run.
|
|
24
|
+
- `ctx.actions.register(defineActionType({...}), (c) => ({ precondition, execute }))` - register an
|
|
25
|
+
agent action type, a change a model may only propose. `execute` performs nothing on a replay
|
|
26
|
+
while its effect still holds, and answers `already_applied`.
|
|
27
|
+
|
|
28
|
+
Both are served only through the `MCP_KERNEL` token, which checks IAM, validates input, applies the
|
|
29
|
+
output allow-list and audits every tool call and action execution; a shape it cannot serve fails
|
|
30
|
+
boot with the fix. The one-argument `ctx.mcp.tool(definition)` still registers but the kernel does
|
|
31
|
+
not serve it.
|
|
16
32
|
|
|
17
33
|
Then:
|
|
18
34
|
|
|
19
35
|
1. Register the plugin in `apps/api/src/extensions.config.ts`. Last registration of a DI token wins -
|
|
20
36
|
list an adapter swap AFTER the module that owns the default binding.
|
|
21
|
-
2. Tables go in the overlay's own `src/schema/index.ts` (follow `
|
|
37
|
+
2. Tables go in the overlay's own `src/schema/index.ts` (follow `docs/standards/database.md`); run `pnpm db:migrate`.
|
|
22
38
|
3. Audit every state-changing action (emit an event the `audit` add-on consumes, or `AUDIT_WRITER.record`).
|
|
23
39
|
4. `/check` to confirm wiring compiles and boundary lint passes.
|
|
24
40
|
|
|
@@ -6,6 +6,6 @@ description: First-run onboarding for this consumer igaming repo. Interview the
|
|
|
6
6
|
|
|
7
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
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 `
|
|
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 `tester` agents (via the Task tool). You gather requirements and orchestrate; you do not write feature code yourself, and you never modify `@openora/*` core.
|
|
10
10
|
|
|
11
|
-
If the `start` tool is not available, the `oss` MCP server is not connected - tell the user to
|
|
11
|
+
If the `start` tool is not available, the `oss` MCP server is not connected - tell the user to approve the `oss` server declared in `.mcp.json` (or run the repo's MCP setup script if it ships one) and restart the editor.
|
|
@@ -2,7 +2,10 @@
|
|
|
2
2
|
//
|
|
3
3
|
// One script serves Claude Code, Copilot CLI, Codex CLI, and Gemini CLI. Each
|
|
4
4
|
// passes the tool call as JSON on stdin but with a slightly different shape:
|
|
5
|
-
// Claude
|
|
5
|
+
// Claude : { tool_name, tool_input: { command?, file_path? } }
|
|
6
|
+
// Codex : same, except an edit is { tool_name: "apply_patch", tool_input: { command: "<patch>" } },
|
|
7
|
+
// the paths only in its "*** Add File: <path>" style headers; an Edit|Write
|
|
8
|
+
// matcher matches apply_patch
|
|
6
9
|
// Copilot : { toolName, toolArgs: "<json-string>" | object }
|
|
7
10
|
// Gemini : { toolName/tool_name, ... } (best-effort)
|
|
8
11
|
//
|
|
@@ -13,6 +16,7 @@
|
|
|
13
16
|
|
|
14
17
|
import { readFileSync } from 'node:fs';
|
|
15
18
|
|
|
19
|
+
/** Read the hook payload from stdin; {} on any error. */
|
|
16
20
|
export function readPayload() {
|
|
17
21
|
try {
|
|
18
22
|
return JSON.parse(readFileSync(0, 'utf8') || '{}');
|
|
@@ -21,6 +25,7 @@ export function readPayload() {
|
|
|
21
25
|
}
|
|
22
26
|
}
|
|
23
27
|
|
|
28
|
+
/** Copilot's toolArgs may be a JSON string; normalize to an object. */
|
|
24
29
|
function toolArgsObject(payload) {
|
|
25
30
|
const a = payload.toolArgs;
|
|
26
31
|
if (typeof a === 'string') {
|
|
@@ -33,27 +38,35 @@ function toolArgsObject(payload) {
|
|
|
33
38
|
return a && typeof a === 'object' ? a : {};
|
|
34
39
|
}
|
|
35
40
|
|
|
36
|
-
|
|
41
|
+
const PATCH_PATH = /^\*\*\* (?:Add File|Update File|Delete File|Move to): (.+)$/gm;
|
|
42
|
+
|
|
43
|
+
function rawCommand(payload) {
|
|
37
44
|
const ti = payload.tool_input ?? {};
|
|
38
45
|
const ca = toolArgsObject(payload);
|
|
39
46
|
return String(ti.command ?? ca.command ?? '');
|
|
40
47
|
}
|
|
41
48
|
|
|
42
|
-
|
|
49
|
+
// Only the tool name tells a patch from a shell command: a Bash command can carry patch text too.
|
|
50
|
+
const isPatch = (payload) => (payload.tool_name ?? payload.toolName) === 'apply_patch';
|
|
51
|
+
|
|
52
|
+
/** The shell command string for this tool call, across tool shapes ('' if none or a patch). */
|
|
53
|
+
export function extractCommand(payload) {
|
|
54
|
+
return isPatch(payload) ? '' : rawCommand(payload);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Every file an edit/write tool call touches, across tool shapes ([] if none). */
|
|
58
|
+
export function extractFilePaths(payload) {
|
|
59
|
+
if (isPatch(payload)) {
|
|
60
|
+
return [...rawCommand(payload).matchAll(PATCH_PATH)].map((match) => match[1].trim());
|
|
61
|
+
}
|
|
43
62
|
const ti = payload.tool_input ?? {};
|
|
44
63
|
const ca = toolArgsObject(payload);
|
|
45
|
-
|
|
46
|
-
ti.file_path ??
|
|
47
|
-
|
|
48
|
-
ca.path ??
|
|
49
|
-
ca.filePath ??
|
|
50
|
-
ca.file ??
|
|
51
|
-
ca.targetFile ??
|
|
52
|
-
ca.target_file ??
|
|
53
|
-
'',
|
|
54
|
-
);
|
|
64
|
+
const filePath =
|
|
65
|
+
ti.file_path ?? ti.path ?? ca.path ?? ca.filePath ?? ca.file ?? ca.targetFile ?? ca.target_file;
|
|
66
|
+
return filePath ? [String(filePath)] : [];
|
|
55
67
|
}
|
|
56
68
|
|
|
69
|
+
/** Deny the tool call (works on Claude, Copilot, Codex, Gemini). */
|
|
57
70
|
export function deny(message) {
|
|
58
71
|
process.stderr.write(message.endsWith('\n') ? message : message + '\n');
|
|
59
72
|
process.exit(2);
|