@openora/mcp 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.
- 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 +149 -62
- package/dist/main.js.map +1 -1
- package/docs/catalog.json +4257 -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,27 @@ 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 formatIam = (iam) => iam ? `${iam.resource}:${iam.action}` : '(non-literal)';
|
|
12
|
+
function formatAgentTool(t) {
|
|
13
|
+
return [
|
|
14
|
+
`- ${t.id} [${t.class ?? '?'}, v${t.schemaVersion ?? '?'}, iam ${formatIam(t.iam)}, module ${t.module}]`,
|
|
15
|
+
...(t.title || t.description
|
|
16
|
+
? [` ${[t.title, t.description].filter(Boolean).join(' - ')}`]
|
|
17
|
+
: []),
|
|
18
|
+
];
|
|
19
|
+
}
|
|
20
|
+
function formatAgentAction(a) {
|
|
21
|
+
const reversibility = a.reversible === null ? 'reversible ?' : a.reversible ? 'reversible' : 'irreversible';
|
|
22
|
+
return [
|
|
23
|
+
`- ${a.id} [${reversibility}, v${a.schemaVersion ?? '?'}, iam ${formatIam(a.iam)}, module ${a.module}]`,
|
|
24
|
+
...(a.title || a.description
|
|
25
|
+
? [` ${[a.title, a.description].filter(Boolean).join(' - ')}`]
|
|
26
|
+
: []),
|
|
27
|
+
];
|
|
28
|
+
}
|
|
9
29
|
const NOT_FOUND_MESSAGE = 'catalog.json not found - run `pnpm regen` in the platform repo, or set OSS_CATALOG to its path.';
|
|
10
30
|
function catalogCandidates() {
|
|
11
31
|
const candidates = [];
|
|
@@ -57,13 +77,13 @@ const server = new McpServer({
|
|
|
57
77
|
version: '0.1.0',
|
|
58
78
|
});
|
|
59
79
|
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/
|
|
80
|
+
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
81
|
inputSchema: {},
|
|
62
82
|
}, withCatalog((c) => {
|
|
63
83
|
const lines = ['=== OSS igaming platform catalog ==='];
|
|
64
84
|
lines.push(`modules: ${c.modules.length} adapters: ${c.adapters.length} events: ${c.events.length} ` +
|
|
65
|
-
`
|
|
66
|
-
`
|
|
85
|
+
`schemas: ${c.schemas.length} agent tools: ${c.agentTools?.length ?? 0} ` +
|
|
86
|
+
`action types: ${c.agentActions?.length ?? 0}`);
|
|
67
87
|
lines.push('\n--- Adapter seams (implement an interface, bind to the token) ---');
|
|
68
88
|
for (const a of c.adapters) {
|
|
69
89
|
lines.push(`- ${a.category}: ${a.interface} -> ${a.token} [${a.status}]`);
|
|
@@ -72,7 +92,7 @@ server.registerTool('catalog-overview', {
|
|
|
72
92
|
for (const f of c.config.fields) {
|
|
73
93
|
lines.push(`- ${f.key}${f.note ? `: ${f.note}` : ''}`);
|
|
74
94
|
}
|
|
75
|
-
lines.push('\nNext: list-adapters | list-routes | list-events | list-
|
|
95
|
+
lines.push('\nNext: list-adapters | list-routes | list-events | list-agent-tools | describe-module <name> | schema-get <name> | get-config-schema');
|
|
76
96
|
return lines.join('\n');
|
|
77
97
|
}));
|
|
78
98
|
server.registerTool('list-adapters', {
|
|
@@ -97,7 +117,7 @@ server.registerTool('list-adapters', {
|
|
|
97
117
|
return lines.join('\n');
|
|
98
118
|
}));
|
|
99
119
|
server.registerTool('list-routes', {
|
|
100
|
-
description: 'List oRPC route namespaces exposed by the platform modules. Pass `module` to scope to a single module.
|
|
120
|
+
description: 'List oRPC route namespaces exposed by the platform modules. Pass `module` to scope to a single module.',
|
|
101
121
|
inputSchema: {
|
|
102
122
|
module: z.string().optional().describe('Module id to filter by (e.g. "wallet")'),
|
|
103
123
|
},
|
|
@@ -124,9 +144,6 @@ server.registerTool('list-routes', {
|
|
|
124
144
|
lines.push(`${m.id}:\n${m.routes.map((r) => ` ${r}`).join('\n')}`);
|
|
125
145
|
}
|
|
126
146
|
}
|
|
127
|
-
if (!mod && c.httpRoutes.length > 0) {
|
|
128
|
-
lines.push(`httpRoutes:\n${c.httpRoutes.map((r) => ` ${r}`).join('\n')}`);
|
|
129
|
-
}
|
|
130
147
|
const text = lines.join('\n\n') || 'No routes defined in the catalog yet.';
|
|
131
148
|
return { content: [{ type: 'text', text }] };
|
|
132
149
|
});
|
|
@@ -139,19 +156,25 @@ server.registerTool('list-events', {
|
|
|
139
156
|
}
|
|
140
157
|
return `=== Domain events (${c.events.length}) ===\n${c.events.map((e) => `- ${e}`).join('\n')}`;
|
|
141
158
|
}));
|
|
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}` : ''}`);
|
|
159
|
+
server.registerTool('list-agent-tools', {
|
|
160
|
+
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.',
|
|
161
|
+
inputSchema: {
|
|
162
|
+
module: z.string().optional().describe('Module id to filter by (e.g. "wallet")'),
|
|
163
|
+
},
|
|
164
|
+
}, async ({ module: mod }) => {
|
|
165
|
+
const loaded = loadCatalog();
|
|
166
|
+
if (!loaded) {
|
|
167
|
+
return { content: [{ type: 'text', text: NOT_FOUND_MESSAGE }] };
|
|
152
168
|
}
|
|
153
|
-
|
|
154
|
-
|
|
169
|
+
const c = loaded.catalog;
|
|
170
|
+
const tools = (c.agentTools ?? []).filter((t) => !mod || t.module === mod);
|
|
171
|
+
const actions = (c.agentActions ?? []).filter((a) => !mod || a.module === mod);
|
|
172
|
+
const lines = [`=== Agent tools (${tools.length}) ===`];
|
|
173
|
+
lines.push(...(tools.length > 0 ? tools.flatMap(formatAgentTool) : ['(none)']));
|
|
174
|
+
lines.push(`\n=== Agent action types (${actions.length}) ===`);
|
|
175
|
+
lines.push(...(actions.length > 0 ? actions.flatMap(formatAgentAction) : ['(none)']));
|
|
176
|
+
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
|
177
|
+
});
|
|
155
178
|
server.registerTool('describe-module', {
|
|
156
179
|
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
180
|
inputSchema: { name: z.string().describe('Module id (e.g. "wallet", "gaming")') },
|
|
@@ -176,6 +199,14 @@ server.registerTool('describe-module', {
|
|
|
176
199
|
const lines = [`=== Module: ${m.id} (${m.group}) ===`];
|
|
177
200
|
lines.push(`\n--- Tables ---\n${m.tables.length > 0 ? m.tables.map((t) => `- ${t}`).join('\n') : '(none)'}`);
|
|
178
201
|
lines.push(`\n--- Routes ---\n${m.routes.length > 0 ? m.routes.map((r) => `- ${r}`).join('\n') : '(none)'}`);
|
|
202
|
+
const tools = (c.agentTools ?? []).filter((t) => t.module === m.id);
|
|
203
|
+
const actions = (c.agentActions ?? []).filter((a) => a.module === m.id);
|
|
204
|
+
if (tools.length > 0) {
|
|
205
|
+
lines.push(`\n--- Agent tools ---\n${tools.flatMap(formatAgentTool).join('\n')}`);
|
|
206
|
+
}
|
|
207
|
+
if (actions.length > 0) {
|
|
208
|
+
lines.push(`\n--- Agent action types ---\n${actions.flatMap(formatAgentAction).join('\n')}`);
|
|
209
|
+
}
|
|
179
210
|
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
|
180
211
|
});
|
|
181
212
|
server.registerTool('schema-get', {
|
|
@@ -220,23 +251,6 @@ server.registerTool('get-config-schema', {
|
|
|
220
251
|
}
|
|
221
252
|
return lines.join('\n');
|
|
222
253
|
}));
|
|
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
254
|
function buildConsumerPlaybook(kind, ctx) {
|
|
241
255
|
const moduleList = ctx.modules.length
|
|
242
256
|
? ctx.modules.map((m) => `- ${m}`).join('\n')
|
|
@@ -245,7 +259,7 @@ function buildConsumerPlaybook(kind, ctx) {
|
|
|
245
259
|
case 'feature':
|
|
246
260
|
return [
|
|
247
261
|
'## 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
|
|
262
|
+
'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
263
|
'If this is a new business domain that should be in the OSS platform itself, open an issue on the OSS repo instead.',
|
|
250
264
|
'',
|
|
251
265
|
'## Existing platform modules you can hook into',
|
|
@@ -253,15 +267,15 @@ function buildConsumerPlaybook(kind, ctx) {
|
|
|
253
267
|
'',
|
|
254
268
|
'## Playbook',
|
|
255
269
|
'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`)
|
|
270
|
+
'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
271
|
'3. Register the plugin in `extensions.config.ts` at the repo root.',
|
|
258
|
-
'4. Spawn the `
|
|
259
|
-
'5. Run `pnpm
|
|
272
|
+
'4. Spawn the `tester` agent to write/run a Playwright E2E test for the acceptance criteria.',
|
|
273
|
+
'5. Run `pnpm check:types && pnpm check:lint`.',
|
|
260
274
|
'',
|
|
261
275
|
'## Agents',
|
|
262
276
|
'- `expert` - requirements + AC (advisory, no code)',
|
|
263
277
|
'- `builder` - implements the overlay',
|
|
264
|
-
'- `
|
|
278
|
+
'- `tester` - E2E test + bug triage (OSS-core vs overlay)',
|
|
265
279
|
].join('\n');
|
|
266
280
|
case 'adapter':
|
|
267
281
|
return [
|
|
@@ -277,7 +291,7 @@ function buildConsumerPlaybook(kind, ctx) {
|
|
|
277
291
|
'1. If the adapter is compliance-sensitive (KYC/AML, PSP, geo), spawn the `expert` agent first to confirm jurisdiction requirements.',
|
|
278
292
|
'2. Spawn the `builder` agent to implement: `pnpm gen adapter` (prompts for name + token), implement the interface, bind it in the generated plugin.',
|
|
279
293
|
'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
|
|
294
|
+
'4. Run `pnpm check:types && pnpm check:lint`.',
|
|
281
295
|
'',
|
|
282
296
|
'## Agents',
|
|
283
297
|
'- `expert` - jurisdiction/compliance requirements (advisory)',
|
|
@@ -286,21 +300,16 @@ function buildConsumerPlaybook(kind, ctx) {
|
|
|
286
300
|
case 'ui-page':
|
|
287
301
|
return [
|
|
288
302
|
'## 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)',
|
|
303
|
+
'The platform is headless - pages live in your own frontend repo and consume the api via `@openora/core/react`.',
|
|
295
304
|
'',
|
|
296
305
|
'## Playbook',
|
|
297
|
-
'1. Spawn the `builder` agent to implement the page in your frontend repo
|
|
298
|
-
'2. Spawn the `
|
|
299
|
-
'3. Run `pnpm
|
|
306
|
+
'1. Spawn the `builder` agent to implement the page in your frontend repo.',
|
|
307
|
+
'2. Spawn the `tester` agent to verify the page renders and behaves in a browser.',
|
|
308
|
+
'3. Run `pnpm check:types`.',
|
|
300
309
|
'',
|
|
301
310
|
'## Agents',
|
|
302
311
|
'- `builder` - mounts/extends the page',
|
|
303
|
-
'- `
|
|
312
|
+
'- `tester` - browser verification',
|
|
304
313
|
].join('\n');
|
|
305
314
|
case 'route':
|
|
306
315
|
return [
|
|
@@ -312,7 +321,7 @@ function buildConsumerPlaybook(kind, ctx) {
|
|
|
312
321
|
'',
|
|
313
322
|
'## Playbook',
|
|
314
323
|
'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
|
|
324
|
+
'2. Run `pnpm check:types && pnpm check:lint`.',
|
|
316
325
|
'',
|
|
317
326
|
'## Agents',
|
|
318
327
|
'- `builder` - implements the route',
|
|
@@ -329,7 +338,7 @@ function buildConsumerPlaybook(kind, ctx) {
|
|
|
329
338
|
'## Agents available in this repo',
|
|
330
339
|
'- `expert` - domain/product requirements (advisory)',
|
|
331
340
|
'- `builder` - fullstack implementation',
|
|
332
|
-
'- `
|
|
341
|
+
'- `tester` - E2E testing',
|
|
333
342
|
].join('\n');
|
|
334
343
|
}
|
|
335
344
|
}
|
|
@@ -365,7 +374,7 @@ const REQUIREMENTS_INTERVIEW = [
|
|
|
365
374
|
'Then summarize the requirements back to the user as a short structured list and get an explicit yes before any building starts.',
|
|
366
375
|
].join('\n');
|
|
367
376
|
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
|
|
377
|
+
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
378
|
inputSchema: {
|
|
370
379
|
ask: z
|
|
371
380
|
.string()
|
|
@@ -382,7 +391,6 @@ server.registerTool('enhance-intent', {
|
|
|
382
391
|
const ctx = {
|
|
383
392
|
modules: catalog?.modules.map((m) => `${m.group}/${m.id}`) ?? [],
|
|
384
393
|
tokens: catalog?.adapters.map((a) => a.token) ?? [],
|
|
385
|
-
slots: catalog?.uiSlots.map((s) => s.name) ?? [],
|
|
386
394
|
};
|
|
387
395
|
const detected = kind && kind !== 'unsure' ? '' : ' (auto-detected)';
|
|
388
396
|
const text = [
|
|
@@ -403,7 +411,7 @@ server.registerTool('enhance-intent', {
|
|
|
403
411
|
'## Acceptance criteria (derive from the requirements above)',
|
|
404
412
|
'- [ ] Happy path works end to end',
|
|
405
413
|
'- [ ] Edge cases handled',
|
|
406
|
-
'- [ ] `pnpm
|
|
414
|
+
'- [ ] `pnpm check:types && pnpm check:lint` is green',
|
|
407
415
|
].join('\n');
|
|
408
416
|
return { content: [{ type: 'text', text }] };
|
|
409
417
|
});
|
|
@@ -422,14 +430,13 @@ server.registerTool('start', {
|
|
|
422
430
|
const tokens = catalog?.adapters.map((a) => a.token) ?? [];
|
|
423
431
|
const role = [
|
|
424
432
|
'## 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`, `
|
|
433
|
+
'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
434
|
].join('\n');
|
|
427
435
|
if (ask) {
|
|
428
436
|
const resolved = classifyIntent(ask);
|
|
429
437
|
const playbook = buildConsumerPlaybook(resolved, {
|
|
430
438
|
modules,
|
|
431
439
|
tokens,
|
|
432
|
-
slots: catalog?.uiSlots.map((s) => s.name) ?? [],
|
|
433
440
|
});
|
|
434
441
|
return {
|
|
435
442
|
content: [
|
|
@@ -480,15 +487,95 @@ server.registerTool('start', {
|
|
|
480
487
|
'Call `enhance-intent` (ask = the confirmed requirements summary, kind = step 1). Then follow its playbook, which delegates to the agents:',
|
|
481
488
|
'- `expert` formalizes requirements + acceptance criteria (and may surface gaps - if so, ask the user those, then continue).',
|
|
482
489
|
'- `builder` implements (`pnpm gen ...`, code).',
|
|
483
|
-
'- `
|
|
490
|
+
'- `tester` writes/runs the E2E test.',
|
|
484
491
|
'You orchestrate; you do not write feature code yourself.',
|
|
485
492
|
'',
|
|
486
|
-
`Platform modules: ${modules.join(', ') || '(catalog not loaded
|
|
493
|
+
`Platform modules: ${modules.join(', ') || '(catalog not loaded)'}`,
|
|
487
494
|
].join('\n'),
|
|
488
495
|
},
|
|
489
496
|
],
|
|
490
497
|
};
|
|
491
498
|
});
|
|
499
|
+
let builtGraph = null;
|
|
500
|
+
// The repo's own graph: a prebuilt dep-graph.json at the root wins (CI writes one), else built
|
|
501
|
+
// once per server process - a full cruise takes seconds, not milliseconds.
|
|
502
|
+
function localGraph(rebuild) {
|
|
503
|
+
const prebuilt = join(process.cwd(), 'dep-graph.json');
|
|
504
|
+
if (!rebuild && existsSync(prebuilt)) {
|
|
505
|
+
return JSON.parse(readFileSync(prebuilt, 'utf8'));
|
|
506
|
+
}
|
|
507
|
+
if (rebuild || !builtGraph) {
|
|
508
|
+
builtGraph = buildDepGraph(process.cwd());
|
|
509
|
+
}
|
|
510
|
+
return builtGraph;
|
|
511
|
+
}
|
|
512
|
+
// Shipped with the package (built by the platform's publish job), so a consumer repo can follow
|
|
513
|
+
// a platform file into its own code without a platform checkout.
|
|
514
|
+
function platformGraph() {
|
|
515
|
+
const shipped = join(here, '..', 'docs', 'dep-graph.json');
|
|
516
|
+
return existsSync(shipped) ? JSON.parse(readFileSync(shipped, 'utf8')) : null;
|
|
517
|
+
}
|
|
518
|
+
server.registerTool('impact', {
|
|
519
|
+
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.',
|
|
520
|
+
inputSchema: {
|
|
521
|
+
files: z
|
|
522
|
+
.array(z.string())
|
|
523
|
+
.min(1)
|
|
524
|
+
.describe('Repo-relative paths, e.g. "apps/web/src/lib/x.ts"'),
|
|
525
|
+
depth: z
|
|
526
|
+
.number()
|
|
527
|
+
.int()
|
|
528
|
+
.min(1)
|
|
529
|
+
.max(10)
|
|
530
|
+
.optional()
|
|
531
|
+
.describe('Import hops to follow (default 4)'),
|
|
532
|
+
rebuild: z
|
|
533
|
+
.boolean()
|
|
534
|
+
.optional()
|
|
535
|
+
.describe('Re-cruise the repo instead of using a cached graph'),
|
|
536
|
+
},
|
|
537
|
+
}, async ({ files, depth = 4, rebuild = false }) => {
|
|
538
|
+
let local;
|
|
539
|
+
try {
|
|
540
|
+
local = localGraph(rebuild);
|
|
541
|
+
}
|
|
542
|
+
catch (error) {
|
|
543
|
+
return {
|
|
544
|
+
content: [{ type: 'text', text: `Could not build the graph: ${String(error)}` }],
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
const platform = platformGraph();
|
|
548
|
+
const list = (items, max = 40) => items.length === 0
|
|
549
|
+
? ' (none)'
|
|
550
|
+
: items
|
|
551
|
+
.slice(0, max)
|
|
552
|
+
.map((i) => ` - ${i}`)
|
|
553
|
+
.join('\n') + (items.length > max ? `\n (+${items.length - max} more)` : '');
|
|
554
|
+
const sections = files.map((file) => {
|
|
555
|
+
const known = (g) => file in g.dependents || file in g.entryPoints || file in g.platform;
|
|
556
|
+
const onPlatform = platform && !known(local) && known(platform);
|
|
557
|
+
const impact = onPlatform
|
|
558
|
+
? impactOf(platform, file, depth, local)
|
|
559
|
+
: impactOf(local, file, depth);
|
|
560
|
+
const lines = [
|
|
561
|
+
`=== ${file} ===`,
|
|
562
|
+
`Importers (${impact.importers.length}):`,
|
|
563
|
+
list(impact.importers),
|
|
564
|
+
];
|
|
565
|
+
if (impact.specifiers.length > 0) {
|
|
566
|
+
lines.push(`Public entry points reached:`, list(impact.specifiers));
|
|
567
|
+
}
|
|
568
|
+
if (onPlatform) {
|
|
569
|
+
lines.push(`Files in this repo importing them:`, list(impact.consumerFiles));
|
|
570
|
+
}
|
|
571
|
+
const imports = local.platform[file];
|
|
572
|
+
if (imports?.length) {
|
|
573
|
+
lines.push(`Platform imports of this file:`, list(imports));
|
|
574
|
+
}
|
|
575
|
+
return lines.join('\n');
|
|
576
|
+
});
|
|
577
|
+
return { content: [{ type: 'text', text: sections.join('\n\n') }] };
|
|
578
|
+
});
|
|
492
579
|
server.registerTool('dev:infra', {
|
|
493
580
|
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
581
|
inputSchema: {
|