@octanejs/mcp-server 0.2.3 → 0.2.6
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 +53 -12
- package/package.json +3 -2
- package/skills/bridge-react-package.md +35 -17
- package/skills/build-octane-software.md +69 -0
- package/skills/migrate-react-component.md +24 -6
- package/skills/react-divergences.md +66 -10
- package/skills/setup-ssr.md +48 -17
- package/src/bridge.js +209 -7
- package/src/bridge.test.js +125 -34
- package/src/index.js +190 -21
- package/src/index.test.js +127 -0
package/src/index.js
CHANGED
|
@@ -18,6 +18,7 @@ const PACKAGE_ROOT = resolve(dirname(__filename), '..');
|
|
|
18
18
|
// workflows: triage, PRs, core changes).
|
|
19
19
|
export const BUNDLED_SKILLS = {
|
|
20
20
|
'bridge-react-package': 'skills/bridge-react-package.md',
|
|
21
|
+
'build-octane-software': 'skills/build-octane-software.md',
|
|
21
22
|
'migrate-react-component': 'skills/migrate-react-component.md',
|
|
22
23
|
'react-divergences': 'skills/react-divergences.md',
|
|
23
24
|
'setup-ssr': 'skills/setup-ssr.md',
|
|
@@ -33,16 +34,50 @@ export const REPO_SKILLS = {
|
|
|
33
34
|
triage: '.ai/skills/triage.md',
|
|
34
35
|
};
|
|
35
36
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
'
|
|
41
|
-
'
|
|
42
|
-
|
|
37
|
+
// Suite names from the unified runner manifest (`SUITES` in
|
|
38
|
+
// benchmarks/bench.mjs; `node benchmarks/bench.mjs --list` prints the same
|
|
39
|
+
// set). index.test.js keeps this list in sync with the runner.
|
|
40
|
+
export const BENCHMARK_SUITES = [
|
|
41
|
+
'js-framework',
|
|
42
|
+
'js-framework-reorder',
|
|
43
|
+
'todomvc',
|
|
44
|
+
'weather-app',
|
|
45
|
+
'weather-app-lighthouse',
|
|
46
|
+
'chat-stream',
|
|
47
|
+
'dbmon',
|
|
48
|
+
'recursive-context',
|
|
49
|
+
'signal-favoring',
|
|
50
|
+
'news',
|
|
51
|
+
'effectful-list',
|
|
52
|
+
'memo-wall',
|
|
53
|
+
'portal-swarm',
|
|
54
|
+
'react-hosted-islands',
|
|
55
|
+
'ssr-throughput',
|
|
56
|
+
'streaming-ssr',
|
|
57
|
+
'ssr-http',
|
|
58
|
+
'ssr-workerd',
|
|
59
|
+
'tanstack-start',
|
|
60
|
+
'dbmon-deopt',
|
|
61
|
+
'js-framework-deopt',
|
|
62
|
+
'async-waterfall',
|
|
63
|
+
'async-composition',
|
|
64
|
+
'lynx-list',
|
|
65
|
+
'codegen-size',
|
|
66
|
+
'bundle-size',
|
|
67
|
+
'three-renderer',
|
|
68
|
+
'three-bundle-size',
|
|
69
|
+
];
|
|
43
70
|
|
|
44
71
|
const DEFAULT_TIMEOUT_MS = 120_000;
|
|
45
72
|
|
|
73
|
+
export function instructionsFor(repoMode) {
|
|
74
|
+
const common =
|
|
75
|
+
'Before creating or materially changing Octane software, call octane_engineering_plan and load the build-octane-software skill. Treat its correctness, performance evidence, and adversarial self-review gates as required. Load the task-specific migration, binding, divergence, or SSR skill in addition when relevant. Do not claim a performance improvement without comparable measurements.';
|
|
76
|
+
return repoMode
|
|
77
|
+
? `${common} For Octane framework-fundamental work, also load octane-core-extend and performance-audit, establish a relevant baseline before editing, and use octane_validate_plan for the final changed paths.`
|
|
78
|
+
: common;
|
|
79
|
+
}
|
|
80
|
+
|
|
46
81
|
export function text(content) {
|
|
47
82
|
return { content: [{ type: 'text', text: content }] };
|
|
48
83
|
}
|
|
@@ -63,12 +98,15 @@ export function areaForPath(path) {
|
|
|
63
98
|
if (path.startsWith('packages/rspack-plugin-octane/')) return 'rspack-plugin';
|
|
64
99
|
if (path.startsWith('packages/rsbuild-plugin-octane/')) return 'rsbuild-plugin';
|
|
65
100
|
if (path.startsWith('packages/vite-plugin-octane/')) return 'vite-plugin';
|
|
101
|
+
if (/^packages\/adapter-[^/]+\//.test(path)) return 'deploy-adapter';
|
|
102
|
+
if (path.startsWith('packages/octane-evals/')) return 'evals';
|
|
66
103
|
if (path.startsWith('packages/octane-mcp-server/')) return 'mcp-server';
|
|
67
104
|
const packageMatch = path.match(/^packages\/([^/]+)\//);
|
|
68
105
|
if (packageMatch && KNOWN_BINDING_PACKAGE_DIRS.has(packageMatch[1])) {
|
|
69
106
|
return 'ecosystem-binding';
|
|
70
107
|
}
|
|
71
108
|
if (path.startsWith('benchmarks/')) return 'benchmark';
|
|
109
|
+
if (path.startsWith('website/')) return 'website';
|
|
72
110
|
if (path.startsWith('.rulesync/')) return 'rulesync-source';
|
|
73
111
|
if (path.startsWith('.ai/') || path.startsWith('.codex/') || path.startsWith('.claude/')) {
|
|
74
112
|
return 'agent-instructions';
|
|
@@ -105,15 +143,54 @@ export function validationFor(paths, taskKind) {
|
|
|
105
143
|
'./node_modules/.bin/vitest run packages/octane-mcp-server --project octane-mcp-server',
|
|
106
144
|
);
|
|
107
145
|
}
|
|
146
|
+
if (areas.has('evals')) {
|
|
147
|
+
commands.add(
|
|
148
|
+
'./node_modules/.bin/vitest run packages/octane-evals/tests --project octane-evals',
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
if (areas.has('website')) {
|
|
152
|
+
commands.add('./node_modules/.bin/vitest run website/tests --project website');
|
|
153
|
+
}
|
|
154
|
+
if (areas.has('metaframework-core')) {
|
|
155
|
+
commands.add('./node_modules/.bin/vitest run packages/app-core/tests --project app-core');
|
|
156
|
+
}
|
|
157
|
+
if (areas.has('rspack-plugin')) {
|
|
158
|
+
commands.add(
|
|
159
|
+
'./node_modules/.bin/vitest run packages/rspack-plugin-octane/tests --project rspack-plugin',
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
if (areas.has('rsbuild-plugin')) {
|
|
163
|
+
commands.add(
|
|
164
|
+
'./node_modules/.bin/vitest run packages/rsbuild-plugin-octane/tests --project rsbuild-plugin',
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
if (areas.has('vite-plugin')) {
|
|
168
|
+
commands.add(
|
|
169
|
+
'./node_modules/.bin/vitest run packages/vite-plugin-octane/tests --project vite-plugin',
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
if (areas.has('deploy-adapter')) {
|
|
173
|
+
for (const path of paths) {
|
|
174
|
+
const match = path.match(/^packages\/(adapter-[^/]+)\//);
|
|
175
|
+
if (match) {
|
|
176
|
+
commands.add(
|
|
177
|
+
`./node_modules/.bin/vitest run packages/${match[1]}/tests --project ${match[1]}`,
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
108
182
|
if (
|
|
109
183
|
areas.has('metaframework-core') ||
|
|
110
184
|
areas.has('rspack-plugin') ||
|
|
111
185
|
areas.has('rsbuild-plugin') ||
|
|
112
|
-
areas.has('vite-plugin')
|
|
186
|
+
areas.has('vite-plugin') ||
|
|
187
|
+
areas.has('deploy-adapter')
|
|
113
188
|
) {
|
|
114
189
|
commands.add('pnpm typecheck');
|
|
115
190
|
}
|
|
116
|
-
if (areas.has('benchmark') || taskKind === 'performance'
|
|
191
|
+
if (areas.has('benchmark') || taskKind === 'performance' || taskKind === 'core') {
|
|
192
|
+
commands.add('node benchmarks/bench.mjs --quick --ratios');
|
|
193
|
+
}
|
|
117
194
|
if (taskKind === 'api' || taskKind === 'core' || taskKind === 'package')
|
|
118
195
|
commands.add('pnpm typecheck');
|
|
119
196
|
commands.add('pnpm format:check');
|
|
@@ -121,6 +198,74 @@ export function validationFor(paths, taskKind) {
|
|
|
121
198
|
return [...commands];
|
|
122
199
|
}
|
|
123
200
|
|
|
201
|
+
export function engineeringPlanFor(input, repoMode = false) {
|
|
202
|
+
const paths = input.paths ?? [];
|
|
203
|
+
const scope = input.scope;
|
|
204
|
+
const changeKind = input.changeKind ?? 'feature';
|
|
205
|
+
const performanceSensitive =
|
|
206
|
+
scope === 'framework-core' ||
|
|
207
|
+
changeKind === 'performance' ||
|
|
208
|
+
input.performanceSensitive === true;
|
|
209
|
+
const correctnessGate =
|
|
210
|
+
changeKind === 'bug'
|
|
211
|
+
? 'Reproduce the bug through a realistic public boundary and verify that the test has a credible pre-fix failure.'
|
|
212
|
+
: 'Protect the new or changed behavior through a realistic public boundary with an assertion that would fail if the contract were absent.';
|
|
213
|
+
const plan = {
|
|
214
|
+
scope,
|
|
215
|
+
changeKind,
|
|
216
|
+
performanceSensitive,
|
|
217
|
+
areas: paths.map((path) => ({ path, area: areaForPath(path) })),
|
|
218
|
+
requiredSkills: ['build-octane-software'],
|
|
219
|
+
gates: {
|
|
220
|
+
contract: [
|
|
221
|
+
'State the consumer-observable behavior, invariants, failure states, and supported execution modes.',
|
|
222
|
+
'Inspect current source, callers, tests, configuration, and documented Octane divergences before editing.',
|
|
223
|
+
],
|
|
224
|
+
correctness: [
|
|
225
|
+
correctnessGate,
|
|
226
|
+
'Exercise applicable empty, large, repeated, nested, error, abort, cleanup, production, SSR, and hydration cases.',
|
|
227
|
+
],
|
|
228
|
+
performance: performanceSensitive
|
|
229
|
+
? [
|
|
230
|
+
'Identify hot paths and record a relevant baseline before editing.',
|
|
231
|
+
'Compare baseline and candidate with the same environment, warmup, iterations, and semantic controls.',
|
|
232
|
+
'Inspect allocations, retained memory, DOM work, compiler and generated-code cost, SSR/hydration work, and bundle size as applicable.',
|
|
233
|
+
'Do not claim improvement when the delta is within noise or no trustworthy measurement exists.',
|
|
234
|
+
]
|
|
235
|
+
: [
|
|
236
|
+
'Check that the change does not add unnecessary reactive work, retained state, dependencies, or common-path cost.',
|
|
237
|
+
'Measure the important user journey when the change can materially affect it.',
|
|
238
|
+
],
|
|
239
|
+
selfReview: [
|
|
240
|
+
'Read the complete diff adversarially and try to falsify the solution with boundary and lifecycle cases.',
|
|
241
|
+
'Trace new state and allocations through invalidation, cleanup, errors, and aborts.',
|
|
242
|
+
'Compare with a simpler design and remove complexity that does not justify its permanent cost.',
|
|
243
|
+
'Resolve findings, rerun affected checks, and repeat the review on the final diff.',
|
|
244
|
+
],
|
|
245
|
+
handoff: [
|
|
246
|
+
'Report the protected contract, validation commands and results, and applicable baseline/candidate measurements.',
|
|
247
|
+
'Report improvements made during self-review, untested modes, inconclusive evidence, and residual risk.',
|
|
248
|
+
],
|
|
249
|
+
},
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
if (scope === 'framework-core' && repoMode) {
|
|
253
|
+
plan.requiredSkills.push('octane-core-extend', 'performance-audit');
|
|
254
|
+
}
|
|
255
|
+
if (scope === 'framework-core' && !repoMode) {
|
|
256
|
+
plan.blockingConditions = [
|
|
257
|
+
'Framework-core work requires the MCP server to run against an Octane monorepo checkout. Set OCTANE_REPO_ROOT, reconnect, and request this plan again so maintainer skills and repository validation are available.',
|
|
258
|
+
];
|
|
259
|
+
}
|
|
260
|
+
if (repoMode) {
|
|
261
|
+
const taskKind =
|
|
262
|
+
scope === 'framework-core' ? 'core' : performanceSensitive ? 'performance' : changeKind;
|
|
263
|
+
plan.validationCommands = validationFor(paths, taskKind);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return plan;
|
|
267
|
+
}
|
|
268
|
+
|
|
124
269
|
export function runCommand(command, args, options = {}) {
|
|
125
270
|
const cwd = options.cwd ?? process.cwd();
|
|
126
271
|
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
@@ -182,15 +327,14 @@ export async function scaffoldReactPort(repoRoot, input) {
|
|
|
182
327
|
}
|
|
183
328
|
|
|
184
329
|
export async function runBenchmark(repoRoot, input) {
|
|
185
|
-
const args =
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
const result = await runCommand('pnpm', args, {
|
|
330
|
+
const args = ['benchmarks/bench.mjs'];
|
|
331
|
+
if (input.benchmark && input.benchmark !== 'all') args.push(input.benchmark);
|
|
332
|
+
if (input.quick) args.push('--quick');
|
|
333
|
+
const result = await runCommand(process.execPath, args, {
|
|
190
334
|
cwd: repoRoot,
|
|
191
|
-
timeoutMs: input.timeoutMs ??
|
|
335
|
+
timeoutMs: input.timeoutMs ?? 600_000,
|
|
192
336
|
});
|
|
193
|
-
return { command: [
|
|
337
|
+
return { command: [process.execPath, ...args], benchmark: input.benchmark, ...result };
|
|
194
338
|
}
|
|
195
339
|
|
|
196
340
|
export async function issueContext(repoRoot, input) {
|
|
@@ -246,7 +390,7 @@ function registerUserTools(server, repoRoot, repoMode) {
|
|
|
246
390
|
{
|
|
247
391
|
title: 'Octane skill',
|
|
248
392
|
description:
|
|
249
|
-
'Return an Octane agent skill by name.
|
|
393
|
+
'Return an Octane agent skill by name. Load build-octane-software before creating or materially changing Octane code; other bundled skills cover React package bridges, component migration, intentional divergences, and SSR setup.' +
|
|
250
394
|
(repoMode ? ' Repo skills cover octane maintainer workflows.' : ''),
|
|
251
395
|
inputSchema: {
|
|
252
396
|
name: z.enum(Object.keys(skills)),
|
|
@@ -259,12 +403,33 @@ function registerUserTools(server, repoRoot, repoMode) {
|
|
|
259
403
|
},
|
|
260
404
|
);
|
|
261
405
|
|
|
406
|
+
server.registerTool(
|
|
407
|
+
'octane_engineering_plan',
|
|
408
|
+
{
|
|
409
|
+
title: 'Plan high-quality Octane engineering work',
|
|
410
|
+
description:
|
|
411
|
+
'Return required correctness, performance-evidence, adversarial self-review, and handoff gates before creating or materially changing Octane software. Framework-core scope always requires baseline/candidate performance evidence and the maintainer core/performance skills.',
|
|
412
|
+
inputSchema: {
|
|
413
|
+
scope: z.enum(['application', 'library', 'framework-core']),
|
|
414
|
+
changeKind: z
|
|
415
|
+
.enum(['bug', 'feature', 'performance', 'refactor', 'api', 'docs', 'test', 'unknown'])
|
|
416
|
+
.default('feature'),
|
|
417
|
+
paths: z.array(z.string()).default([]).describe('Repository-relative changed paths.'),
|
|
418
|
+
performanceSensitive: z
|
|
419
|
+
.boolean()
|
|
420
|
+
.optional()
|
|
421
|
+
.describe('Force performance evidence for application or library work.'),
|
|
422
|
+
},
|
|
423
|
+
},
|
|
424
|
+
async (input) => text(JSON.stringify(engineeringPlanFor(input, repoMode), null, 2)),
|
|
425
|
+
);
|
|
426
|
+
|
|
262
427
|
server.registerTool(
|
|
263
428
|
'octane_bridge_react_package',
|
|
264
429
|
{
|
|
265
430
|
title: 'Bridge a React package to Octane',
|
|
266
431
|
description:
|
|
267
|
-
'Scan a React package (from node_modules by name, or any source directory by path) for React API usage and return an Octane compatibility report: which APIs map 1:1, which need rewrites (forwardRef,
|
|
432
|
+
'Scan a React package (from node_modules by name, or any source directory by path) for React API usage and return an Octane compatibility report: which APIs map 1:1, which need rewrites (forwardRef, class components, React-style text-host onChange, react-dom/server imports), whether a framework-agnostic core can be reused verbatim, whether an official @octanejs binding already exists, and a step-by-step bridge plan. Follow up with the bridge-react-package skill for the full workflow.',
|
|
268
433
|
inputSchema: {
|
|
269
434
|
package: z
|
|
270
435
|
.string()
|
|
@@ -389,9 +554,10 @@ function registerRepoTools(server, repoRoot) {
|
|
|
389
554
|
{
|
|
390
555
|
title: 'Run Octane benchmark',
|
|
391
556
|
description:
|
|
392
|
-
'Run
|
|
557
|
+
'Run benchmark suites through the unified runner (node benchmarks/bench.mjs): one manifest suite by name, or every suite with "all". Set quick for the reduced-iteration smoke pass.',
|
|
393
558
|
inputSchema: {
|
|
394
|
-
benchmark: z.enum(['all', ...
|
|
559
|
+
benchmark: z.enum(['all', ...BENCHMARK_SUITES]).default('all'),
|
|
560
|
+
quick: z.boolean().default(false),
|
|
395
561
|
timeoutMs: z.number().int().positive().optional(),
|
|
396
562
|
},
|
|
397
563
|
},
|
|
@@ -418,7 +584,10 @@ function registerRepoTools(server, repoRoot) {
|
|
|
418
584
|
export function createServer(options = {}) {
|
|
419
585
|
const repoRoot = resolve(options.repoRoot || process.env.OCTANE_REPO_ROOT || process.cwd());
|
|
420
586
|
const repoMode = isOctaneRepo(repoRoot);
|
|
421
|
-
const server = new McpServer(
|
|
587
|
+
const server = new McpServer(
|
|
588
|
+
{ name: 'octane', version: '0.2.0' },
|
|
589
|
+
{ instructions: instructionsFor(repoMode) },
|
|
590
|
+
);
|
|
422
591
|
registerUserTools(server, repoRoot, repoMode);
|
|
423
592
|
if (repoMode) registerRepoTools(server, repoRoot);
|
|
424
593
|
return server;
|
package/src/index.test.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
3
|
+
import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js';
|
|
2
4
|
import { existsSync } from 'node:fs';
|
|
3
5
|
import { mkdir, mkdtemp, readFile, writeFile } from 'node:fs/promises';
|
|
4
6
|
import { tmpdir } from 'node:os';
|
|
@@ -6,8 +8,12 @@ import { dirname, join, resolve } from 'node:path';
|
|
|
6
8
|
import { fileURLToPath } from 'node:url';
|
|
7
9
|
import {
|
|
8
10
|
areaForPath,
|
|
11
|
+
BENCHMARK_SUITES,
|
|
9
12
|
BUNDLED_SKILLS,
|
|
13
|
+
createServer,
|
|
14
|
+
engineeringPlanFor,
|
|
10
15
|
isOctaneRepo,
|
|
16
|
+
runCommand,
|
|
11
17
|
scaffoldReactPort,
|
|
12
18
|
validationFor,
|
|
13
19
|
} from './index.js';
|
|
@@ -23,10 +29,54 @@ describe('@octanejs/mcp-server helpers', () => {
|
|
|
23
29
|
expect(areaForPath('packages/zustand/src/index.ts')).toBe('ecosystem-binding');
|
|
24
30
|
expect(areaForPath('packages/radix/src/index.ts')).toBe('ecosystem-binding');
|
|
25
31
|
expect(areaForPath('packages/octane-mcp-server/src/index.js')).toBe('mcp-server');
|
|
32
|
+
expect(areaForPath('packages/adapter-vercel/src/index.ts')).toBe('deploy-adapter');
|
|
33
|
+
expect(areaForPath('packages/adapter-cloudflare/src/index.js')).toBe('deploy-adapter');
|
|
34
|
+
expect(areaForPath('packages/octane-evals/tools/run.mjs')).toBe('evals');
|
|
35
|
+
expect(areaForPath('website/src/pages/index.tsrx')).toBe('website');
|
|
26
36
|
expect(areaForPath('benchmarks/news/run.mjs')).toBe('benchmark');
|
|
27
37
|
expect(areaForPath('.rulesync/rules/project.md')).toBe('rulesync-source');
|
|
28
38
|
});
|
|
29
39
|
|
|
40
|
+
it('recommends the adapter, evals, and website test projects', () => {
|
|
41
|
+
const commands = validationFor(
|
|
42
|
+
[
|
|
43
|
+
'packages/adapter-vercel/src/index.ts',
|
|
44
|
+
'packages/adapter-cloudflare/src/index.js',
|
|
45
|
+
'packages/octane-evals/tools/run.mjs',
|
|
46
|
+
'website/src/pages/index.tsrx',
|
|
47
|
+
],
|
|
48
|
+
'feature',
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
expect(commands).toContain(
|
|
52
|
+
'./node_modules/.bin/vitest run packages/adapter-vercel/tests --project adapter-vercel',
|
|
53
|
+
);
|
|
54
|
+
expect(commands).toContain(
|
|
55
|
+
'./node_modules/.bin/vitest run packages/adapter-cloudflare/tests --project adapter-cloudflare',
|
|
56
|
+
);
|
|
57
|
+
expect(commands).toContain(
|
|
58
|
+
'./node_modules/.bin/vitest run packages/octane-evals/tests --project octane-evals',
|
|
59
|
+
);
|
|
60
|
+
expect(commands).toContain('./node_modules/.bin/vitest run website/tests --project website');
|
|
61
|
+
expect(commands).toContain('pnpm typecheck');
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('keeps the benchmark suite list in sync with the unified runner manifest', async () => {
|
|
65
|
+
// BENCHMARK_SUITES is hand-maintained in index.js; the runner manifest in
|
|
66
|
+
// benchmarks/bench.mjs is the source of truth. --list prints one suite
|
|
67
|
+
// name per line, in manifest order.
|
|
68
|
+
const repoRoot = resolve(PACKAGE_ROOT, '../..');
|
|
69
|
+
const result = await runCommand(process.execPath, ['benchmarks/bench.mjs', '--list'], {
|
|
70
|
+
cwd: repoRoot,
|
|
71
|
+
});
|
|
72
|
+
expect(result.code).toBe(0);
|
|
73
|
+
const suites = result.stdout
|
|
74
|
+
.split('\n')
|
|
75
|
+
.map((line) => line.trim())
|
|
76
|
+
.filter((line) => line && line !== 'Available suites:');
|
|
77
|
+
expect(suites).toEqual(BENCHMARK_SUITES);
|
|
78
|
+
});
|
|
79
|
+
|
|
30
80
|
it('classifies every maintained binding and recommends its test project', () => {
|
|
31
81
|
const paths = [...KNOWN_BINDING_PACKAGE_DIRS].map(
|
|
32
82
|
(directory) => `packages/${directory}/src/index.ts`,
|
|
@@ -63,9 +113,86 @@ describe('@octanejs/mcp-server helpers', () => {
|
|
|
63
113
|
'./node_modules/.bin/vitest run packages/radix/tests --project radix',
|
|
64
114
|
);
|
|
65
115
|
expect(commands).toContain('pnpm typecheck');
|
|
116
|
+
expect(commands).toContain('node benchmarks/bench.mjs --quick --ratios');
|
|
66
117
|
expect(commands).toContain('pnpm format:check');
|
|
67
118
|
});
|
|
68
119
|
|
|
120
|
+
it('requires performance evidence and adversarial review for framework fundamentals', () => {
|
|
121
|
+
const plan = engineeringPlanFor(
|
|
122
|
+
{
|
|
123
|
+
scope: 'framework-core',
|
|
124
|
+
changeKind: 'refactor',
|
|
125
|
+
paths: ['packages/octane/src/runtime.ts'],
|
|
126
|
+
},
|
|
127
|
+
true,
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
expect(plan.performanceSensitive).toBe(true);
|
|
131
|
+
expect(plan.requiredSkills).toEqual([
|
|
132
|
+
'build-octane-software',
|
|
133
|
+
'octane-core-extend',
|
|
134
|
+
'performance-audit',
|
|
135
|
+
]);
|
|
136
|
+
expect(plan.gates.performance).toContain(
|
|
137
|
+
'Identify hot paths and record a relevant baseline before editing.',
|
|
138
|
+
);
|
|
139
|
+
expect(plan.gates.selfReview).toContain(
|
|
140
|
+
'Resolve findings, rerun affected checks, and repeat the review on the final diff.',
|
|
141
|
+
);
|
|
142
|
+
expect(plan.validationCommands).toContain('node benchmarks/bench.mjs --quick --ratios');
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('blocks framework-core plans when maintainer tools are unavailable', () => {
|
|
146
|
+
const plan = engineeringPlanFor({ scope: 'framework-core', changeKind: 'bug' });
|
|
147
|
+
|
|
148
|
+
expect(plan.requiredSkills).toEqual(['build-octane-software']);
|
|
149
|
+
expect(plan.blockingConditions).toContain(
|
|
150
|
+
'Framework-core work requires the MCP server to run against an Octane monorepo checkout. Set OCTANE_REPO_ROOT, reconnect, and request this plan again so maintainer skills and repository validation are available.',
|
|
151
|
+
);
|
|
152
|
+
expect(plan.gates.correctness).toContain(
|
|
153
|
+
'Reproduce the bug through a realistic public boundary and verify that the test has a credible pre-fix failure.',
|
|
154
|
+
);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('keeps performance gates and validation commands aligned', () => {
|
|
158
|
+
const performancePlan = engineeringPlanFor(
|
|
159
|
+
{ scope: 'application', changeKind: 'performance' },
|
|
160
|
+
true,
|
|
161
|
+
);
|
|
162
|
+
const flaggedPlan = engineeringPlanFor(
|
|
163
|
+
{ scope: 'library', changeKind: 'feature', performanceSensitive: true },
|
|
164
|
+
true,
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
for (const plan of [performancePlan, flaggedPlan]) {
|
|
168
|
+
expect(plan.performanceSensitive).toBe(true);
|
|
169
|
+
expect(plan.gates.performance).toContain(
|
|
170
|
+
'Identify hot paths and record a relevant baseline before editing.',
|
|
171
|
+
);
|
|
172
|
+
expect(plan.validationCommands).toContain('node benchmarks/bench.mjs --quick --ratios');
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it('advertises the engineering gates during MCP initialization', async () => {
|
|
177
|
+
const server = createServer({ repoRoot: resolve(PACKAGE_ROOT, '../..') });
|
|
178
|
+
const client = new Client({ name: 'octane-mcp-test', version: '1.0.0' });
|
|
179
|
+
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
|
|
180
|
+
|
|
181
|
+
try {
|
|
182
|
+
await Promise.all([server.connect(serverTransport), client.connect(clientTransport)]);
|
|
183
|
+
expect(client.getInstructions()).toContain(
|
|
184
|
+
'Before creating or materially changing Octane software',
|
|
185
|
+
);
|
|
186
|
+
expect(client.getInstructions()).toContain('establish a relevant baseline before editing');
|
|
187
|
+
|
|
188
|
+
const tools = await client.listTools();
|
|
189
|
+
expect(tools.tools.map((tool) => tool.name)).toContain('octane_engineering_plan');
|
|
190
|
+
} finally {
|
|
191
|
+
await client.close();
|
|
192
|
+
await server.close();
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
|
|
69
196
|
it('detects the octane monorepo for repo-mode tools', async () => {
|
|
70
197
|
expect(isOctaneRepo(resolve(PACKAGE_ROOT, '../..'))).toBe(true);
|
|
71
198
|
const elsewhere = await mkdtemp(join(tmpdir(), 'octane-mcp-test-'));
|