@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.
@@ -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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"}
@@ -0,0 +1,3 @@
1
+ export type IntentKind = 'feature' | 'adapter' | 'ui-page' | 'route' | 'unsure';
2
+ export declare function classifyIntent(ask: string): IntentKind;
3
+ //# sourceMappingURL=intent.d.ts.map
@@ -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/slots/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.',
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
- `uiSlots: ${c.uiSlots.length} schemas: ${c.schemas.length} ` +
66
- `httpRoutes: ${c.httpRoutes.length}`);
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-slots | describe-module <name> | schema-get <name> | get-config-schema');
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. Includes top-level httpRoutes when present.',
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-slots', {
143
- description: 'List the named UI slots you can fill from a client-side UI plugin (ctx.<slot>.add(...)) to extend the backoffice without forking: nav items, table columns, dashboard tiles, detail sections. Includes each slot description and subject type.',
144
- inputSchema: {},
145
- }, withCatalog((c) => {
146
- if (c.uiSlots.length === 0) {
147
- return 'No UI slots in the catalog.';
148
- }
149
- const lines = ['=== UI slots ==='];
150
- for (const s of c.uiSlots) {
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
- return lines.join('\n');
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, and fill UI slots without touching OSS core.',
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`), subscribe to events (`ctx.events.on`), fill slots (`ctx.slots.fill`).',
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 `qa` agent to write/run a Playwright E2E test for the acceptance criteria.',
259
- '5. Run `pnpm typecheck && pnpm lint`.',
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
- '- `qa` - E2E test + bug triage (OSS-core vs overlay)',
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 typecheck && pnpm lint`.',
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`. Fill named UI slots from a client-side UI plugin.',
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, or extend an existing surface via `defineUIPlugin` into a slot above.',
298
- '2. Spawn the `qa` agent to verify the page renders and behaves in a browser.',
299
- '3. Run `pnpm typecheck`.',
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
- '- `qa` - browser verification',
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 typecheck && pnpm lint`.',
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
- '- `qa` - E2E testing',
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/adapter/slot 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.',
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 typecheck && pnpm lint` is green',
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`, `qa`) via the Task tool. Keep the user out of the loop after requirements are confirmed, except to resolve genuine decisions only they can make.',
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
- '- `qa` writes/runs the E2E test.',
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 - run pnpm build:oss first)'}`,
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: {