@openora/mcp 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/README.md +27 -14
- package/dist/.tsbuildinfo +1 -1
- package/dist/dep-graph-cli.d.ts +3 -0
- package/dist/dep-graph-cli.d.ts.map +1 -0
- package/dist/dep-graph-cli.js +11 -0
- package/dist/dep-graph-cli.js.map +1 -0
- package/dist/dep-graph.d.ts +33 -0
- package/dist/dep-graph.d.ts.map +1 -0
- package/dist/dep-graph.js +131 -0
- package/dist/dep-graph.js.map +1 -0
- package/dist/intent.d.ts +3 -0
- package/dist/intent.d.ts.map +1 -0
- package/dist/intent.js +18 -0
- package/dist/intent.js.map +1 -0
- package/dist/main.js +159 -63
- package/dist/main.js.map +1 -1
- package/docs/catalog.json +4395 -279
- package/docs/dep-graph.json +1 -0
- package/package.json +14 -11
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process';
|
|
2
|
+
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
3
|
+
import { join, posix } from 'node:path';
|
|
4
|
+
const WORKSPACE_ROOTS = ['apps', 'packages'];
|
|
5
|
+
const CONFIG = '.dependency-cruiser.cjs';
|
|
6
|
+
function cruise(cwd, targets) {
|
|
7
|
+
const out = execFileSync('pnpm', ['exec', 'depcruise', ...targets, '--config', CONFIG, '--output-type', 'json'], { cwd, maxBuffer: 512 * 1024 * 1024 });
|
|
8
|
+
return JSON.parse(out.toString()).modules;
|
|
9
|
+
}
|
|
10
|
+
// A consumer repo keeps one config per workspace; the platform repo keeps one at the root.
|
|
11
|
+
function cruiseRuns(root) {
|
|
12
|
+
const workspaces = WORKSPACE_ROOTS.filter((r) => existsSync(join(root, r))).flatMap((r) => readdirSync(join(root, r))
|
|
13
|
+
.map((name) => `${r}/${name}`)
|
|
14
|
+
.filter((dir) => existsSync(join(root, dir, CONFIG))));
|
|
15
|
+
if (workspaces.length > 0) {
|
|
16
|
+
return workspaces.map((dir) => ({
|
|
17
|
+
dir,
|
|
18
|
+
targets: [existsSync(join(root, dir, 'src')) ? 'src' : '.'],
|
|
19
|
+
}));
|
|
20
|
+
}
|
|
21
|
+
if (!existsSync(join(root, CONFIG))) {
|
|
22
|
+
throw new Error(`No ${CONFIG} at ${root} or in any of ${WORKSPACE_ROOTS.join('/')}/*`);
|
|
23
|
+
}
|
|
24
|
+
return [{ dir: '.', targets: WORKSPACE_ROOTS.filter((r) => existsSync(join(root, r))) }];
|
|
25
|
+
}
|
|
26
|
+
// `exports` point at dist; the graph speaks source, so map each entry back to its src file.
|
|
27
|
+
function publishedEntryPoints(root) {
|
|
28
|
+
const entries = {};
|
|
29
|
+
const packagesDir = join(root, 'packages');
|
|
30
|
+
if (!existsSync(packagesDir)) {
|
|
31
|
+
return entries;
|
|
32
|
+
}
|
|
33
|
+
for (const name of readdirSync(packagesDir)) {
|
|
34
|
+
const manifestPath = join(packagesDir, name, 'package.json');
|
|
35
|
+
if (!existsSync(manifestPath)) {
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
|
|
39
|
+
if (!manifest.name?.startsWith('@openora/') || manifest.private) {
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
for (const [key, target] of Object.entries(manifest.exports ?? {})) {
|
|
43
|
+
const importPath = typeof target === 'string' ? target : target?.import;
|
|
44
|
+
const dist = importPath?.match(/^\.\/dist\/(.+)\.js$/)?.[1];
|
|
45
|
+
if (!dist) {
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
const source = ['ts', 'tsx']
|
|
49
|
+
.map((ext) => `packages/${name}/src/${dist}.${ext}`)
|
|
50
|
+
.find((file) => existsSync(join(root, file)));
|
|
51
|
+
if (source) {
|
|
52
|
+
entries[source] = manifest.name + key.slice(1);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return entries;
|
|
57
|
+
}
|
|
58
|
+
export function buildDepGraph(root) {
|
|
59
|
+
const dependents = {};
|
|
60
|
+
const platform = {};
|
|
61
|
+
for (const { dir, targets } of cruiseRuns(root)) {
|
|
62
|
+
const toRepoPath = (source) => posix.normalize(`${dir}/${source}`);
|
|
63
|
+
// Linked or installed @openora/* resolve outside the repo; the graph covers the repo's own files.
|
|
64
|
+
const isInternal = (d, path) => !d.coreModule &&
|
|
65
|
+
!d.couldNotResolve &&
|
|
66
|
+
!path.includes('node_modules/') &&
|
|
67
|
+
!toRepoPath(path).startsWith('../');
|
|
68
|
+
for (const module of cruise(join(root, dir), targets)) {
|
|
69
|
+
if (!isInternal(module, module.source)) {
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
const file = toRepoPath(module.source);
|
|
73
|
+
for (const dependency of module.dependencies) {
|
|
74
|
+
if (isInternal(dependency, dependency.resolved)) {
|
|
75
|
+
(dependents[toRepoPath(dependency.resolved)] ??= new Set()).add(file);
|
|
76
|
+
}
|
|
77
|
+
else if (dependency.module.startsWith('@openora/')) {
|
|
78
|
+
(platform[file] ??= new Set()).add(dependency.module);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
const sorted = (map) => Object.fromEntries(Object.entries(map)
|
|
84
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
85
|
+
.map(([file, values]) => [file, [...values].sort()]));
|
|
86
|
+
return {
|
|
87
|
+
generatedAt: new Date().toISOString(),
|
|
88
|
+
commit: process.env['CI_COMMIT_SHA'] ?? process.env['GITHUB_SHA'] ?? null,
|
|
89
|
+
dependents: sorted(dependents),
|
|
90
|
+
platform: sorted(platform),
|
|
91
|
+
entryPoints: publishedEntryPoints(root),
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
/** Transitive importers of `file`, up to `maxDepth` hops, nearest first. */
|
|
95
|
+
export function importersOf(graph, file, maxDepth) {
|
|
96
|
+
const depth = new Map([[file, 0]]);
|
|
97
|
+
const queue = [file];
|
|
98
|
+
for (let current = queue.shift(); current !== undefined; current = queue.shift()) {
|
|
99
|
+
const level = depth.get(current) ?? 0;
|
|
100
|
+
if (level >= maxDepth) {
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
for (const importer of graph.dependents[current] ?? []) {
|
|
104
|
+
if (!depth.has(importer)) {
|
|
105
|
+
depth.set(importer, level + 1);
|
|
106
|
+
queue.push(importer);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
depth.delete(file);
|
|
111
|
+
return [...depth.keys()];
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* What a change to `file` can break. With a consumer graph, a platform file is followed through
|
|
115
|
+
* the public entry points it reaches into the consumer files that import them - the cross-repo
|
|
116
|
+
* half that no single-repo graph shows.
|
|
117
|
+
*/
|
|
118
|
+
export function impactOf(graph, file, maxDepth, consumer) {
|
|
119
|
+
const importers = importersOf(graph, file, maxDepth);
|
|
120
|
+
// Nearest first: a change reaches its own entry point before the barrels that re-export it.
|
|
121
|
+
const specifiers = [...new Set([file, ...importers].flatMap((f) => graph.entryPoints[f] ?? []))];
|
|
122
|
+
const rank = (specs) => Math.min(...specs.map((s) => specifiers.indexOf(s)).filter((i) => i >= 0));
|
|
123
|
+
const consumerFiles = consumer
|
|
124
|
+
? Object.entries(consumer.platform)
|
|
125
|
+
.filter(([, specs]) => specs.some((s) => specifiers.includes(s)))
|
|
126
|
+
.sort(([a, x], [b, y]) => rank(x) - rank(y) || a.localeCompare(b))
|
|
127
|
+
.map(([f]) => f)
|
|
128
|
+
: [];
|
|
129
|
+
return { file, importers, specifiers, consumerFiles };
|
|
130
|
+
}
|
|
131
|
+
//# sourceMappingURL=dep-graph.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dep-graph.js","sourceRoot":"","sources":["../src/dep-graph.ts"],"names":[],"mappings":"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"}
|
package/dist/intent.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"intent.d.ts","sourceRoot":"","sources":["../src/intent.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,GAAG,QAAQ,CAAC;AAEhF,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,CAoBtD"}
|
package/dist/intent.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export function classifyIntent(ask) {
|
|
2
|
+
const a = ` ${ask.toLowerCase()} `;
|
|
3
|
+
const has = (re) => re.test(a);
|
|
4
|
+
if (has(/\b(payment|psp|stripe|adyen|kyc|onfido|sms|email|notification|vendor|adapter|gateway)\b/)) {
|
|
5
|
+
return 'adapter';
|
|
6
|
+
}
|
|
7
|
+
if (has(/\b(page|screen|dashboard|view|frontend|admin panel|backoffice page|player page)\b/)) {
|
|
8
|
+
return 'ui-page';
|
|
9
|
+
}
|
|
10
|
+
if (has(/\b(endpoint|route|procedure|api method|rpc)\b/)) {
|
|
11
|
+
return 'route';
|
|
12
|
+
}
|
|
13
|
+
if (has(/\b(feature|module|tournament|leaderboard|jackpot|loyalty|bonus|cashback|mission|reward)\b/)) {
|
|
14
|
+
return 'feature';
|
|
15
|
+
}
|
|
16
|
+
return 'unsure';
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=intent.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"intent.js","sourceRoot":"","sources":["../src/intent.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,cAAc,CAAC,GAAW;IACxC,MAAM,CAAC,GAAG,IAAI,GAAG,CAAC,WAAW,EAAE,GAAG,CAAC;IACnC,MAAM,GAAG,GAAG,CAAC,EAAU,EAAE,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACvC,IACE,GAAG,CAAC,yFAAyF,CAAC,EAC9F,CAAC;QACD,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,IAAI,GAAG,CAAC,mFAAmF,CAAC,EAAE,CAAC;QAC7F,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,IAAI,GAAG,CAAC,+CAA+C,CAAC,EAAE,CAAC;QACzD,OAAO,OAAO,CAAC;IACjB,CAAC;IACD,IACE,GAAG,CAAC,2FAA2F,CAAC,EAChG,CAAC;QACD,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC"}
|
package/dist/main.js
CHANGED
|
@@ -5,7 +5,36 @@ import { existsSync, readFileSync } from 'node:fs';
|
|
|
5
5
|
import { execSync } from 'node:child_process';
|
|
6
6
|
import { dirname, join, parse } from 'node:path';
|
|
7
7
|
import { fileURLToPath } from 'node:url';
|
|
8
|
+
import { buildDepGraph, impactOf } from './dep-graph.js';
|
|
9
|
+
import { classifyIntent } from './intent.js';
|
|
8
10
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
11
|
+
const ownPackage = JSON.parse(readFileSync(join(here, '..', 'package.json'), 'utf8'));
|
|
12
|
+
const formatIam = (iam) => iam ? `${iam.resource}:${iam.action}` : '(non-literal)';
|
|
13
|
+
function formatAgentTool(t) {
|
|
14
|
+
return [
|
|
15
|
+
`- ${t.id} [${t.class ?? '?'}, v${t.schemaVersion ?? '?'}, iam ${formatIam(t.iam)}, module ${t.module}]`,
|
|
16
|
+
...(t.title || t.description
|
|
17
|
+
? [` ${[t.title, t.description].filter(Boolean).join(' - ')}`]
|
|
18
|
+
: []),
|
|
19
|
+
];
|
|
20
|
+
}
|
|
21
|
+
function formatAgentAction(a) {
|
|
22
|
+
const reversibility = a.reversible === null ? 'reversible ?' : a.reversible ? 'reversible' : 'irreversible';
|
|
23
|
+
const benefit = a.benefitsPlayer === null
|
|
24
|
+
? ', benefits player ?'
|
|
25
|
+
: a.benefitsPlayer
|
|
26
|
+
? ', benefits player'
|
|
27
|
+
: a.benefitsPlayerWhen
|
|
28
|
+
? `, benefits player when ${a.benefitsPlayerWhen.field} is ${a.benefitsPlayerWhen.values.join(' or ')}`
|
|
29
|
+
: '';
|
|
30
|
+
const distinct = a.distinctBy ? `, distinct by ${a.distinctBy}` : '';
|
|
31
|
+
return [
|
|
32
|
+
`- ${a.id} [${reversibility}${benefit}${distinct}, v${a.schemaVersion ?? '?'}, iam ${formatIam(a.iam)}, module ${a.module}]`,
|
|
33
|
+
...(a.title || a.description
|
|
34
|
+
? [` ${[a.title, a.description].filter(Boolean).join(' - ')}`]
|
|
35
|
+
: []),
|
|
36
|
+
];
|
|
37
|
+
}
|
|
9
38
|
const NOT_FOUND_MESSAGE = 'catalog.json not found - run `pnpm regen` in the platform repo, or set OSS_CATALOG to its path.';
|
|
10
39
|
function catalogCandidates() {
|
|
11
40
|
const candidates = [];
|
|
@@ -54,16 +83,16 @@ function withCatalog(fn) {
|
|
|
54
83
|
}
|
|
55
84
|
const server = new McpServer({
|
|
56
85
|
name: 'oss',
|
|
57
|
-
version:
|
|
86
|
+
version: ownPackage.version,
|
|
58
87
|
});
|
|
59
88
|
server.registerTool('catalog-overview', {
|
|
60
|
-
description: 'START HERE. A concise "what can I extend" summary of the OSS igaming platform you are consuming: counts of modules/events/
|
|
89
|
+
description: 'START HERE. A concise "what can I extend" summary of the OSS igaming platform you are consuming: counts of modules/events/schemas, the adapter swap-seam table (category/interface/token/status), and the igaming-config fields. Read this first to orient before drilling into the other tools.',
|
|
61
90
|
inputSchema: {},
|
|
62
91
|
}, withCatalog((c) => {
|
|
63
92
|
const lines = ['=== OSS igaming platform catalog ==='];
|
|
64
93
|
lines.push(`modules: ${c.modules.length} adapters: ${c.adapters.length} events: ${c.events.length} ` +
|
|
65
|
-
`
|
|
66
|
-
`
|
|
94
|
+
`schemas: ${c.schemas.length} agent tools: ${c.agentTools?.length ?? 0} ` +
|
|
95
|
+
`action types: ${c.agentActions?.length ?? 0}`);
|
|
67
96
|
lines.push('\n--- Adapter seams (implement an interface, bind to the token) ---');
|
|
68
97
|
for (const a of c.adapters) {
|
|
69
98
|
lines.push(`- ${a.category}: ${a.interface} -> ${a.token} [${a.status}]`);
|
|
@@ -72,7 +101,7 @@ server.registerTool('catalog-overview', {
|
|
|
72
101
|
for (const f of c.config.fields) {
|
|
73
102
|
lines.push(`- ${f.key}${f.note ? `: ${f.note}` : ''}`);
|
|
74
103
|
}
|
|
75
|
-
lines.push('\nNext: list-adapters | list-routes | list-events | list-
|
|
104
|
+
lines.push('\nNext: list-adapters | list-routes | list-events | list-agent-tools | describe-module <name> | schema-get <name> | get-config-schema');
|
|
76
105
|
return lines.join('\n');
|
|
77
106
|
}));
|
|
78
107
|
server.registerTool('list-adapters', {
|
|
@@ -97,7 +126,7 @@ server.registerTool('list-adapters', {
|
|
|
97
126
|
return lines.join('\n');
|
|
98
127
|
}));
|
|
99
128
|
server.registerTool('list-routes', {
|
|
100
|
-
description: 'List oRPC route namespaces exposed by the platform modules. Pass `module` to scope to a single module.
|
|
129
|
+
description: 'List oRPC route namespaces exposed by the platform modules. Pass `module` to scope to a single module.',
|
|
101
130
|
inputSchema: {
|
|
102
131
|
module: z.string().optional().describe('Module id to filter by (e.g. "wallet")'),
|
|
103
132
|
},
|
|
@@ -124,9 +153,6 @@ server.registerTool('list-routes', {
|
|
|
124
153
|
lines.push(`${m.id}:\n${m.routes.map((r) => ` ${r}`).join('\n')}`);
|
|
125
154
|
}
|
|
126
155
|
}
|
|
127
|
-
if (!mod && c.httpRoutes.length > 0) {
|
|
128
|
-
lines.push(`httpRoutes:\n${c.httpRoutes.map((r) => ` ${r}`).join('\n')}`);
|
|
129
|
-
}
|
|
130
156
|
const text = lines.join('\n\n') || 'No routes defined in the catalog yet.';
|
|
131
157
|
return { content: [{ type: 'text', text }] };
|
|
132
158
|
});
|
|
@@ -139,19 +165,25 @@ server.registerTool('list-events', {
|
|
|
139
165
|
}
|
|
140
166
|
return `=== Domain events (${c.events.length}) ===\n${c.events.map((e) => `- ${e}`).join('\n')}`;
|
|
141
167
|
}));
|
|
142
|
-
server.registerTool('list-
|
|
143
|
-
description: 'List the
|
|
144
|
-
inputSchema: {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
lines.push(`- ${s.name}${s.description ? ` # ${s.description}` : ''}`);
|
|
168
|
+
server.registerTool('list-agent-tools', {
|
|
169
|
+
description: 'List the agent surface: MCP tools (read calls a model may make during a run) and action types (state changes a model may only propose; an idempotent executor runs them after approval). Each entry shows its IAM resource:action, schema version and owning module. Modules register them in plugin.ts with ctx.mcp.tool(defineMcpTool({...}), factory) and ctx.actions.register(defineActionType({...}), factory); consumers reach them only through the MCP_KERNEL token, which validates input, checks IAM, applies the output allow-list and audits every call. Pass `module` to scope to one module.',
|
|
170
|
+
inputSchema: {
|
|
171
|
+
module: z.string().optional().describe('Module id to filter by (e.g. "wallet")'),
|
|
172
|
+
},
|
|
173
|
+
}, async ({ module: mod }) => {
|
|
174
|
+
const loaded = loadCatalog();
|
|
175
|
+
if (!loaded) {
|
|
176
|
+
return { content: [{ type: 'text', text: NOT_FOUND_MESSAGE }] };
|
|
152
177
|
}
|
|
153
|
-
|
|
154
|
-
|
|
178
|
+
const c = loaded.catalog;
|
|
179
|
+
const tools = (c.agentTools ?? []).filter((t) => !mod || t.module === mod);
|
|
180
|
+
const actions = (c.agentActions ?? []).filter((a) => !mod || a.module === mod);
|
|
181
|
+
const lines = [`=== Agent tools (${tools.length}) ===`];
|
|
182
|
+
lines.push(...(tools.length > 0 ? tools.flatMap(formatAgentTool) : ['(none)']));
|
|
183
|
+
lines.push(`\n=== Agent action types (${actions.length}) ===`);
|
|
184
|
+
lines.push(...(actions.length > 0 ? actions.flatMap(formatAgentAction) : ['(none)']));
|
|
185
|
+
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
|
186
|
+
});
|
|
155
187
|
server.registerTool('describe-module', {
|
|
156
188
|
description: "Describe one platform module from the catalog: its group (player/backoffice/platform), Drizzle tables, and oRPC routes. Use this to understand a module's surface before extending or integrating with it.",
|
|
157
189
|
inputSchema: { name: z.string().describe('Module id (e.g. "wallet", "gaming")') },
|
|
@@ -176,6 +208,14 @@ server.registerTool('describe-module', {
|
|
|
176
208
|
const lines = [`=== Module: ${m.id} (${m.group}) ===`];
|
|
177
209
|
lines.push(`\n--- Tables ---\n${m.tables.length > 0 ? m.tables.map((t) => `- ${t}`).join('\n') : '(none)'}`);
|
|
178
210
|
lines.push(`\n--- Routes ---\n${m.routes.length > 0 ? m.routes.map((r) => `- ${r}`).join('\n') : '(none)'}`);
|
|
211
|
+
const tools = (c.agentTools ?? []).filter((t) => t.module === m.id);
|
|
212
|
+
const actions = (c.agentActions ?? []).filter((a) => a.module === m.id);
|
|
213
|
+
if (tools.length > 0) {
|
|
214
|
+
lines.push(`\n--- Agent tools ---\n${tools.flatMap(formatAgentTool).join('\n')}`);
|
|
215
|
+
}
|
|
216
|
+
if (actions.length > 0) {
|
|
217
|
+
lines.push(`\n--- Agent action types ---\n${actions.flatMap(formatAgentAction).join('\n')}`);
|
|
218
|
+
}
|
|
179
219
|
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
|
180
220
|
});
|
|
181
221
|
server.registerTool('schema-get', {
|
|
@@ -220,23 +260,6 @@ server.registerTool('get-config-schema', {
|
|
|
220
260
|
}
|
|
221
261
|
return lines.join('\n');
|
|
222
262
|
}));
|
|
223
|
-
function classifyIntent(ask) {
|
|
224
|
-
const a = ` ${ask.toLowerCase()} `;
|
|
225
|
-
const has = (re) => re.test(a);
|
|
226
|
-
if (has(/\b(payment|psp|stripe|adyen|kyc|onfido|sms|email|notification|vendor|adapter|gateway)\b/)) {
|
|
227
|
-
return 'adapter';
|
|
228
|
-
}
|
|
229
|
-
if (has(/\b(page|screen|dashboard|view|frontend|admin panel|backoffice page|player page)\b/)) {
|
|
230
|
-
return 'ui-page';
|
|
231
|
-
}
|
|
232
|
-
if (has(/\b(endpoint|route|procedure|api method|rpc)\b/)) {
|
|
233
|
-
return 'route';
|
|
234
|
-
}
|
|
235
|
-
if (has(/\b(feature|module|tournament|leaderboard|jackpot|loyalty|bonus|cashback|mission|reward)\b/)) {
|
|
236
|
-
return 'feature';
|
|
237
|
-
}
|
|
238
|
-
return 'unsure';
|
|
239
|
-
}
|
|
240
263
|
function buildConsumerPlaybook(kind, ctx) {
|
|
241
264
|
const moduleList = ctx.modules.length
|
|
242
265
|
? ctx.modules.map((m) => `- ${m}`).join('\n')
|
|
@@ -245,7 +268,7 @@ function buildConsumerPlaybook(kind, ctx) {
|
|
|
245
268
|
case 'feature':
|
|
246
269
|
return [
|
|
247
270
|
'## Where it goes',
|
|
248
|
-
'New behavior in a consumer repo -> an overlay plugin (`pnpm gen plugin`). It can add routes, subscribe to events, rebind adapters
|
|
271
|
+
'New behavior in a consumer repo -> an overlay plugin (`pnpm gen plugin`). It can add routes, subscribe to events, and rebind adapters without touching OSS core.',
|
|
249
272
|
'If this is a new business domain that should be in the OSS platform itself, open an issue on the OSS repo instead.',
|
|
250
273
|
'',
|
|
251
274
|
'## Existing platform modules you can hook into',
|
|
@@ -253,15 +276,15 @@ function buildConsumerPlaybook(kind, ctx) {
|
|
|
253
276
|
'',
|
|
254
277
|
'## Playbook',
|
|
255
278
|
'1. Spawn the `expert` agent (Task tool) to turn this ask into requirements + acceptance criteria (player journey, jurisdiction rules, edge cases). Skip only for a trivial change.',
|
|
256
|
-
'2. Spawn the `builder` agent to implement: `pnpm gen plugin`, then in `register(ctx)` add routes (`ctx.routers.add`)
|
|
279
|
+
'2. Spawn the `builder` agent to implement: `pnpm gen plugin`, then in `register(ctx)` add routes (`ctx.routers.add`) and subscribe to events (`ctx.events.on`).',
|
|
257
280
|
'3. Register the plugin in `extensions.config.ts` at the repo root.',
|
|
258
|
-
'4. Spawn the `
|
|
259
|
-
'5. Run `pnpm
|
|
281
|
+
'4. Spawn the `tester` agent to write/run a Playwright E2E test for the acceptance criteria.',
|
|
282
|
+
'5. Run `pnpm check:types && pnpm check:lint`.',
|
|
260
283
|
'',
|
|
261
284
|
'## Agents',
|
|
262
285
|
'- `expert` - requirements + AC (advisory, no code)',
|
|
263
286
|
'- `builder` - implements the overlay',
|
|
264
|
-
'- `
|
|
287
|
+
'- `tester` - E2E test + bug triage (OSS-core vs overlay)',
|
|
265
288
|
].join('\n');
|
|
266
289
|
case 'adapter':
|
|
267
290
|
return [
|
|
@@ -277,7 +300,7 @@ function buildConsumerPlaybook(kind, ctx) {
|
|
|
277
300
|
'1. If the adapter is compliance-sensitive (KYC/AML, PSP, geo), spawn the `expert` agent first to confirm jurisdiction requirements.',
|
|
278
301
|
'2. Spawn the `builder` agent to implement: `pnpm gen adapter` (prompts for name + token), implement the interface, bind it in the generated plugin.',
|
|
279
302
|
'3. Ensure the plugin is listed AFTER the module that owns the default binding in `extensions.config.ts` (last registration wins).',
|
|
280
|
-
'4. Run `pnpm
|
|
303
|
+
'4. Run `pnpm check:types && pnpm check:lint`.',
|
|
281
304
|
'',
|
|
282
305
|
'## Agents',
|
|
283
306
|
'- `expert` - jurisdiction/compliance requirements (advisory)',
|
|
@@ -286,21 +309,16 @@ function buildConsumerPlaybook(kind, ctx) {
|
|
|
286
309
|
case 'ui-page':
|
|
287
310
|
return [
|
|
288
311
|
'## Where it goes',
|
|
289
|
-
'The platform is headless - pages live in your own frontend repo and consume the api via `@openora/core/react`.
|
|
290
|
-
'',
|
|
291
|
-
'## Named UI slots you can fill (via defineUIPlugin)',
|
|
292
|
-
ctx.slots.length
|
|
293
|
-
? ctx.slots.map((s) => `- ${s}`).join('\n')
|
|
294
|
-
: '- (run list-slots for details)',
|
|
312
|
+
'The platform is headless - pages live in your own frontend repo and consume the api via `@openora/core/react`.',
|
|
295
313
|
'',
|
|
296
314
|
'## Playbook',
|
|
297
|
-
'1. Spawn the `builder` agent to implement the page in your frontend repo
|
|
298
|
-
'2. Spawn the `
|
|
299
|
-
'3. Run `pnpm
|
|
315
|
+
'1. Spawn the `builder` agent to implement the page in your frontend repo.',
|
|
316
|
+
'2. Spawn the `tester` agent to verify the page renders and behaves in a browser.',
|
|
317
|
+
'3. Run `pnpm check:types`.',
|
|
300
318
|
'',
|
|
301
319
|
'## Agents',
|
|
302
320
|
'- `builder` - mounts/extends the page',
|
|
303
|
-
'- `
|
|
321
|
+
'- `tester` - browser verification',
|
|
304
322
|
].join('\n');
|
|
305
323
|
case 'route':
|
|
306
324
|
return [
|
|
@@ -312,7 +330,7 @@ function buildConsumerPlaybook(kind, ctx) {
|
|
|
312
330
|
'',
|
|
313
331
|
'## Playbook',
|
|
314
332
|
'1. Spawn the `builder` agent to implement: `pnpm gen plugin` if no overlay exists, then add the oRPC route in `register(ctx)`. Admin routes must assert AdminGuard first.',
|
|
315
|
-
'2. Run `pnpm
|
|
333
|
+
'2. Run `pnpm check:types && pnpm check:lint`.',
|
|
316
334
|
'',
|
|
317
335
|
'## Agents',
|
|
318
336
|
'- `builder` - implements the route',
|
|
@@ -329,7 +347,7 @@ function buildConsumerPlaybook(kind, ctx) {
|
|
|
329
347
|
'## Agents available in this repo',
|
|
330
348
|
'- `expert` - domain/product requirements (advisory)',
|
|
331
349
|
'- `builder` - fullstack implementation',
|
|
332
|
-
'- `
|
|
350
|
+
'- `tester` - E2E testing',
|
|
333
351
|
].join('\n');
|
|
334
352
|
}
|
|
335
353
|
}
|
|
@@ -365,7 +383,7 @@ const REQUIREMENTS_INTERVIEW = [
|
|
|
365
383
|
'Then summarize the requirements back to the user as a short structured list and get an explicit yes before any building starts.',
|
|
366
384
|
].join('\n');
|
|
367
385
|
server.registerTool('enhance-intent', {
|
|
368
|
-
description: 'Turn a fuzzy "I want to build X" ask into a grounded, consumer-context brief. Uses the platform catalog (live module
|
|
386
|
+
description: 'Turn a fuzzy "I want to build X" ask into a grounded, consumer-context brief. Uses the platform catalog (live module and adapter list) to return a classified intent, a requirements checklist to collect from the user, a step-by-step playbook using the consumer pnpm gen commands, and an acceptance-criteria stub.',
|
|
369
387
|
inputSchema: {
|
|
370
388
|
ask: z
|
|
371
389
|
.string()
|
|
@@ -382,7 +400,6 @@ server.registerTool('enhance-intent', {
|
|
|
382
400
|
const ctx = {
|
|
383
401
|
modules: catalog?.modules.map((m) => `${m.group}/${m.id}`) ?? [],
|
|
384
402
|
tokens: catalog?.adapters.map((a) => a.token) ?? [],
|
|
385
|
-
slots: catalog?.uiSlots.map((s) => s.name) ?? [],
|
|
386
403
|
};
|
|
387
404
|
const detected = kind && kind !== 'unsure' ? '' : ' (auto-detected)';
|
|
388
405
|
const text = [
|
|
@@ -403,7 +420,7 @@ server.registerTool('enhance-intent', {
|
|
|
403
420
|
'## Acceptance criteria (derive from the requirements above)',
|
|
404
421
|
'- [ ] Happy path works end to end',
|
|
405
422
|
'- [ ] Edge cases handled',
|
|
406
|
-
'- [ ] `pnpm
|
|
423
|
+
'- [ ] `pnpm check:types && pnpm check:lint` is green',
|
|
407
424
|
].join('\n');
|
|
408
425
|
return { content: [{ type: 'text', text }] };
|
|
409
426
|
});
|
|
@@ -422,14 +439,13 @@ server.registerTool('start', {
|
|
|
422
439
|
const tokens = catalog?.adapters.map((a) => a.token) ?? [];
|
|
423
440
|
const role = [
|
|
424
441
|
'## Your role',
|
|
425
|
-
'You are a requirements interviewer and orchestrator, NOT the implementer. Your one human-facing job is to collect thorough requirements from the user. Everything after that - scaffolding, code, tests - you delegate to the agents (`expert`, `builder`, `
|
|
442
|
+
'You are a requirements interviewer and orchestrator, NOT the implementer. Your one human-facing job is to collect thorough requirements from the user. Everything after that - scaffolding, code, tests - you delegate to the agents (`expert`, `builder`, `tester`) via the Task tool. Keep the user out of the loop after requirements are confirmed, except to resolve genuine decisions only they can make.',
|
|
426
443
|
].join('\n');
|
|
427
444
|
if (ask) {
|
|
428
445
|
const resolved = classifyIntent(ask);
|
|
429
446
|
const playbook = buildConsumerPlaybook(resolved, {
|
|
430
447
|
modules,
|
|
431
448
|
tokens,
|
|
432
|
-
slots: catalog?.uiSlots.map((s) => s.name) ?? [],
|
|
433
449
|
});
|
|
434
450
|
return {
|
|
435
451
|
content: [
|
|
@@ -480,15 +496,95 @@ server.registerTool('start', {
|
|
|
480
496
|
'Call `enhance-intent` (ask = the confirmed requirements summary, kind = step 1). Then follow its playbook, which delegates to the agents:',
|
|
481
497
|
'- `expert` formalizes requirements + acceptance criteria (and may surface gaps - if so, ask the user those, then continue).',
|
|
482
498
|
'- `builder` implements (`pnpm gen ...`, code).',
|
|
483
|
-
'- `
|
|
499
|
+
'- `tester` writes/runs the E2E test.',
|
|
484
500
|
'You orchestrate; you do not write feature code yourself.',
|
|
485
501
|
'',
|
|
486
|
-
`Platform modules: ${modules.join(', ') || '(catalog not loaded
|
|
502
|
+
`Platform modules: ${modules.join(', ') || '(catalog not loaded)'}`,
|
|
487
503
|
].join('\n'),
|
|
488
504
|
},
|
|
489
505
|
],
|
|
490
506
|
};
|
|
491
507
|
});
|
|
508
|
+
let builtGraph = null;
|
|
509
|
+
// The repo's own graph: a prebuilt dep-graph.json at the root wins (CI writes one), else built
|
|
510
|
+
// once per server process - a full cruise takes seconds, not milliseconds.
|
|
511
|
+
function localGraph(rebuild) {
|
|
512
|
+
const prebuilt = join(process.cwd(), 'dep-graph.json');
|
|
513
|
+
if (!rebuild && existsSync(prebuilt)) {
|
|
514
|
+
return JSON.parse(readFileSync(prebuilt, 'utf8'));
|
|
515
|
+
}
|
|
516
|
+
if (rebuild || !builtGraph) {
|
|
517
|
+
builtGraph = buildDepGraph(process.cwd());
|
|
518
|
+
}
|
|
519
|
+
return builtGraph;
|
|
520
|
+
}
|
|
521
|
+
// Shipped with the package (built by the platform's publish job), so a consumer repo can follow
|
|
522
|
+
// a platform file into its own code without a platform checkout.
|
|
523
|
+
function platformGraph() {
|
|
524
|
+
const shipped = join(here, '..', 'docs', 'dep-graph.json');
|
|
525
|
+
return existsSync(shipped) ? JSON.parse(readFileSync(shipped, 'utf8')) : null;
|
|
526
|
+
}
|
|
527
|
+
server.registerTool('impact', {
|
|
528
|
+
description: 'Blast radius of a change: for each repo-relative file, the files that import it (transitively), and for a platform file (packages/core/src/...) the public @openora/* entry points it reaches plus the consumer files importing them. Use before editing a shared file, or to decide what to re-test after a change.',
|
|
529
|
+
inputSchema: {
|
|
530
|
+
files: z
|
|
531
|
+
.array(z.string())
|
|
532
|
+
.min(1)
|
|
533
|
+
.describe('Repo-relative paths, e.g. "apps/web/src/lib/x.ts"'),
|
|
534
|
+
depth: z
|
|
535
|
+
.number()
|
|
536
|
+
.int()
|
|
537
|
+
.min(1)
|
|
538
|
+
.max(10)
|
|
539
|
+
.optional()
|
|
540
|
+
.describe('Import hops to follow (default 4)'),
|
|
541
|
+
rebuild: z
|
|
542
|
+
.boolean()
|
|
543
|
+
.optional()
|
|
544
|
+
.describe('Re-cruise the repo instead of using a cached graph'),
|
|
545
|
+
},
|
|
546
|
+
}, async ({ files, depth = 4, rebuild = false }) => {
|
|
547
|
+
let local;
|
|
548
|
+
try {
|
|
549
|
+
local = localGraph(rebuild);
|
|
550
|
+
}
|
|
551
|
+
catch (error) {
|
|
552
|
+
return {
|
|
553
|
+
content: [{ type: 'text', text: `Could not build the graph: ${String(error)}` }],
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
const platform = platformGraph();
|
|
557
|
+
const list = (items, max = 40) => items.length === 0
|
|
558
|
+
? ' (none)'
|
|
559
|
+
: items
|
|
560
|
+
.slice(0, max)
|
|
561
|
+
.map((i) => ` - ${i}`)
|
|
562
|
+
.join('\n') + (items.length > max ? `\n (+${items.length - max} more)` : '');
|
|
563
|
+
const sections = files.map((file) => {
|
|
564
|
+
const known = (g) => file in g.dependents || file in g.entryPoints || file in g.platform;
|
|
565
|
+
const onPlatform = platform && !known(local) && known(platform);
|
|
566
|
+
const impact = onPlatform
|
|
567
|
+
? impactOf(platform, file, depth, local)
|
|
568
|
+
: impactOf(local, file, depth);
|
|
569
|
+
const lines = [
|
|
570
|
+
`=== ${file} ===`,
|
|
571
|
+
`Importers (${impact.importers.length}):`,
|
|
572
|
+
list(impact.importers),
|
|
573
|
+
];
|
|
574
|
+
if (impact.specifiers.length > 0) {
|
|
575
|
+
lines.push(`Public entry points reached:`, list(impact.specifiers));
|
|
576
|
+
}
|
|
577
|
+
if (onPlatform) {
|
|
578
|
+
lines.push(`Files in this repo importing them:`, list(impact.consumerFiles));
|
|
579
|
+
}
|
|
580
|
+
const imports = local.platform[file];
|
|
581
|
+
if (imports?.length) {
|
|
582
|
+
lines.push(`Platform imports of this file:`, list(imports));
|
|
583
|
+
}
|
|
584
|
+
return lines.join('\n');
|
|
585
|
+
});
|
|
586
|
+
return { content: [{ type: 'text', text: sections.join('\n\n') }] };
|
|
587
|
+
});
|
|
492
588
|
server.registerTool('dev:infra', {
|
|
493
589
|
description: 'Start / stop / status local dev infrastructure via docker compose (postgres on :5432). Call before pnpm db:migrate or pnpm dev when the database is not reachable.',
|
|
494
590
|
inputSchema: {
|