@octanejs/mcp-server 0.2.2 → 0.2.5
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 +57 -18
- package/package.json +6 -2
- package/skills/bridge-react-package.md +40 -18
- 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 +44 -17
- package/src/bridge.js +232 -11
- package/src/bridge.test.js +130 -1
- package/src/index.js +197 -27
- package/src/index.test.js +137 -0
package/src/index.js
CHANGED
|
@@ -7,7 +7,7 @@ import { readFile } from 'node:fs/promises';
|
|
|
7
7
|
import { dirname, join, resolve } from 'node:path';
|
|
8
8
|
import { fileURLToPath } from 'node:url';
|
|
9
9
|
import { z } from 'zod';
|
|
10
|
-
import { bridgeReport, KNOWN_BINDINGS } from './bridge.js';
|
|
10
|
+
import { bridgeReport, KNOWN_BINDINGS, KNOWN_BINDING_PACKAGE_DIRS } from './bridge.js';
|
|
11
11
|
|
|
12
12
|
const __filename = fileURLToPath(import.meta.url);
|
|
13
13
|
const PACKAGE_ROOT = resolve(dirname(__filename), '..');
|
|
@@ -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,46 @@ 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
|
+
'dbmon-deopt',
|
|
58
|
+
'js-framework-deopt',
|
|
59
|
+
'async-waterfall',
|
|
60
|
+
'async-composition',
|
|
61
|
+
'codegen-size',
|
|
62
|
+
'bundle-size',
|
|
63
|
+
'three-renderer',
|
|
64
|
+
'three-bundle-size',
|
|
65
|
+
];
|
|
43
66
|
|
|
44
67
|
const DEFAULT_TIMEOUT_MS = 120_000;
|
|
45
68
|
|
|
69
|
+
export function instructionsFor(repoMode) {
|
|
70
|
+
const common =
|
|
71
|
+
'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.';
|
|
72
|
+
return repoMode
|
|
73
|
+
? `${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.`
|
|
74
|
+
: common;
|
|
75
|
+
}
|
|
76
|
+
|
|
46
77
|
export function text(content) {
|
|
47
78
|
return { content: [{ type: 'text', text: content }] };
|
|
48
79
|
}
|
|
@@ -59,12 +90,19 @@ export function areaForPath(path) {
|
|
|
59
90
|
return 'core-runtime';
|
|
60
91
|
}
|
|
61
92
|
if (path.startsWith('packages/octane/tests/')) return 'core-tests';
|
|
93
|
+
if (path.startsWith('packages/app-core/')) return 'metaframework-core';
|
|
94
|
+
if (path.startsWith('packages/rspack-plugin-octane/')) return 'rspack-plugin';
|
|
95
|
+
if (path.startsWith('packages/rsbuild-plugin-octane/')) return 'rsbuild-plugin';
|
|
62
96
|
if (path.startsWith('packages/vite-plugin-octane/')) return 'vite-plugin';
|
|
97
|
+
if (path.startsWith('packages/adapter-vercel/')) return 'deploy-adapter';
|
|
98
|
+
if (path.startsWith('packages/octane-evals/')) return 'evals';
|
|
63
99
|
if (path.startsWith('packages/octane-mcp-server/')) return 'mcp-server';
|
|
64
|
-
|
|
100
|
+
const packageMatch = path.match(/^packages\/([^/]+)\//);
|
|
101
|
+
if (packageMatch && KNOWN_BINDING_PACKAGE_DIRS.has(packageMatch[1])) {
|
|
65
102
|
return 'ecosystem-binding';
|
|
66
103
|
}
|
|
67
104
|
if (path.startsWith('benchmarks/')) return 'benchmark';
|
|
105
|
+
if (path.startsWith('website/')) return 'website';
|
|
68
106
|
if (path.startsWith('.rulesync/')) return 'rulesync-source';
|
|
69
107
|
if (path.startsWith('.ai/') || path.startsWith('.codex/') || path.startsWith('.claude/')) {
|
|
70
108
|
return 'agent-instructions';
|
|
@@ -88,13 +126,12 @@ export function validationFor(paths, taskKind) {
|
|
|
88
126
|
}
|
|
89
127
|
if (areas.has('ecosystem-binding')) {
|
|
90
128
|
for (const path of paths) {
|
|
91
|
-
const match = path.match(
|
|
92
|
-
|
|
93
|
-
);
|
|
94
|
-
if (match)
|
|
129
|
+
const match = path.match(/^packages\/([^/]+)\//);
|
|
130
|
+
if (match && KNOWN_BINDING_PACKAGE_DIRS.has(match[1])) {
|
|
95
131
|
commands.add(
|
|
96
132
|
`./node_modules/.bin/vitest run packages/${match[1]}/tests --project ${match[1]}`,
|
|
97
133
|
);
|
|
134
|
+
}
|
|
98
135
|
}
|
|
99
136
|
}
|
|
100
137
|
if (areas.has('mcp-server')) {
|
|
@@ -102,8 +139,49 @@ export function validationFor(paths, taskKind) {
|
|
|
102
139
|
'./node_modules/.bin/vitest run packages/octane-mcp-server --project octane-mcp-server',
|
|
103
140
|
);
|
|
104
141
|
}
|
|
105
|
-
if (areas.has('
|
|
106
|
-
|
|
142
|
+
if (areas.has('evals')) {
|
|
143
|
+
commands.add(
|
|
144
|
+
'./node_modules/.bin/vitest run packages/octane-evals/tests --project octane-evals',
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
if (areas.has('website')) {
|
|
148
|
+
commands.add('./node_modules/.bin/vitest run website/tests --project website');
|
|
149
|
+
}
|
|
150
|
+
if (areas.has('metaframework-core')) {
|
|
151
|
+
commands.add('./node_modules/.bin/vitest run packages/app-core/tests --project app-core');
|
|
152
|
+
}
|
|
153
|
+
if (areas.has('rspack-plugin')) {
|
|
154
|
+
commands.add(
|
|
155
|
+
'./node_modules/.bin/vitest run packages/rspack-plugin-octane/tests --project rspack-plugin',
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
if (areas.has('rsbuild-plugin')) {
|
|
159
|
+
commands.add(
|
|
160
|
+
'./node_modules/.bin/vitest run packages/rsbuild-plugin-octane/tests --project rsbuild-plugin',
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
if (areas.has('vite-plugin')) {
|
|
164
|
+
commands.add(
|
|
165
|
+
'./node_modules/.bin/vitest run packages/vite-plugin-octane/tests --project vite-plugin',
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
if (areas.has('deploy-adapter')) {
|
|
169
|
+
commands.add(
|
|
170
|
+
'./node_modules/.bin/vitest run packages/adapter-vercel/tests --project adapter-vercel',
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
if (
|
|
174
|
+
areas.has('metaframework-core') ||
|
|
175
|
+
areas.has('rspack-plugin') ||
|
|
176
|
+
areas.has('rsbuild-plugin') ||
|
|
177
|
+
areas.has('vite-plugin') ||
|
|
178
|
+
areas.has('deploy-adapter')
|
|
179
|
+
) {
|
|
180
|
+
commands.add('pnpm typecheck');
|
|
181
|
+
}
|
|
182
|
+
if (areas.has('benchmark') || taskKind === 'performance' || taskKind === 'core') {
|
|
183
|
+
commands.add('node benchmarks/bench.mjs --quick --ratios');
|
|
184
|
+
}
|
|
107
185
|
if (taskKind === 'api' || taskKind === 'core' || taskKind === 'package')
|
|
108
186
|
commands.add('pnpm typecheck');
|
|
109
187
|
commands.add('pnpm format:check');
|
|
@@ -111,6 +189,74 @@ export function validationFor(paths, taskKind) {
|
|
|
111
189
|
return [...commands];
|
|
112
190
|
}
|
|
113
191
|
|
|
192
|
+
export function engineeringPlanFor(input, repoMode = false) {
|
|
193
|
+
const paths = input.paths ?? [];
|
|
194
|
+
const scope = input.scope;
|
|
195
|
+
const changeKind = input.changeKind ?? 'feature';
|
|
196
|
+
const performanceSensitive =
|
|
197
|
+
scope === 'framework-core' ||
|
|
198
|
+
changeKind === 'performance' ||
|
|
199
|
+
input.performanceSensitive === true;
|
|
200
|
+
const correctnessGate =
|
|
201
|
+
changeKind === 'bug'
|
|
202
|
+
? 'Reproduce the bug through a realistic public boundary and verify that the test has a credible pre-fix failure.'
|
|
203
|
+
: 'Protect the new or changed behavior through a realistic public boundary with an assertion that would fail if the contract were absent.';
|
|
204
|
+
const plan = {
|
|
205
|
+
scope,
|
|
206
|
+
changeKind,
|
|
207
|
+
performanceSensitive,
|
|
208
|
+
areas: paths.map((path) => ({ path, area: areaForPath(path) })),
|
|
209
|
+
requiredSkills: ['build-octane-software'],
|
|
210
|
+
gates: {
|
|
211
|
+
contract: [
|
|
212
|
+
'State the consumer-observable behavior, invariants, failure states, and supported execution modes.',
|
|
213
|
+
'Inspect current source, callers, tests, configuration, and documented Octane divergences before editing.',
|
|
214
|
+
],
|
|
215
|
+
correctness: [
|
|
216
|
+
correctnessGate,
|
|
217
|
+
'Exercise applicable empty, large, repeated, nested, error, abort, cleanup, production, SSR, and hydration cases.',
|
|
218
|
+
],
|
|
219
|
+
performance: performanceSensitive
|
|
220
|
+
? [
|
|
221
|
+
'Identify hot paths and record a relevant baseline before editing.',
|
|
222
|
+
'Compare baseline and candidate with the same environment, warmup, iterations, and semantic controls.',
|
|
223
|
+
'Inspect allocations, retained memory, DOM work, compiler and generated-code cost, SSR/hydration work, and bundle size as applicable.',
|
|
224
|
+
'Do not claim improvement when the delta is within noise or no trustworthy measurement exists.',
|
|
225
|
+
]
|
|
226
|
+
: [
|
|
227
|
+
'Check that the change does not add unnecessary reactive work, retained state, dependencies, or common-path cost.',
|
|
228
|
+
'Measure the important user journey when the change can materially affect it.',
|
|
229
|
+
],
|
|
230
|
+
selfReview: [
|
|
231
|
+
'Read the complete diff adversarially and try to falsify the solution with boundary and lifecycle cases.',
|
|
232
|
+
'Trace new state and allocations through invalidation, cleanup, errors, and aborts.',
|
|
233
|
+
'Compare with a simpler design and remove complexity that does not justify its permanent cost.',
|
|
234
|
+
'Resolve findings, rerun affected checks, and repeat the review on the final diff.',
|
|
235
|
+
],
|
|
236
|
+
handoff: [
|
|
237
|
+
'Report the protected contract, validation commands and results, and applicable baseline/candidate measurements.',
|
|
238
|
+
'Report improvements made during self-review, untested modes, inconclusive evidence, and residual risk.',
|
|
239
|
+
],
|
|
240
|
+
},
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
if (scope === 'framework-core' && repoMode) {
|
|
244
|
+
plan.requiredSkills.push('octane-core-extend', 'performance-audit');
|
|
245
|
+
}
|
|
246
|
+
if (scope === 'framework-core' && !repoMode) {
|
|
247
|
+
plan.blockingConditions = [
|
|
248
|
+
'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.',
|
|
249
|
+
];
|
|
250
|
+
}
|
|
251
|
+
if (repoMode) {
|
|
252
|
+
const taskKind =
|
|
253
|
+
scope === 'framework-core' ? 'core' : performanceSensitive ? 'performance' : changeKind;
|
|
254
|
+
plan.validationCommands = validationFor(paths, taskKind);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return plan;
|
|
258
|
+
}
|
|
259
|
+
|
|
114
260
|
export function runCommand(command, args, options = {}) {
|
|
115
261
|
const cwd = options.cwd ?? process.cwd();
|
|
116
262
|
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
@@ -172,15 +318,14 @@ export async function scaffoldReactPort(repoRoot, input) {
|
|
|
172
318
|
}
|
|
173
319
|
|
|
174
320
|
export async function runBenchmark(repoRoot, input) {
|
|
175
|
-
const args =
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
const result = await runCommand('pnpm', args, {
|
|
321
|
+
const args = ['benchmarks/bench.mjs'];
|
|
322
|
+
if (input.benchmark && input.benchmark !== 'all') args.push(input.benchmark);
|
|
323
|
+
if (input.quick) args.push('--quick');
|
|
324
|
+
const result = await runCommand(process.execPath, args, {
|
|
180
325
|
cwd: repoRoot,
|
|
181
|
-
timeoutMs: input.timeoutMs ??
|
|
326
|
+
timeoutMs: input.timeoutMs ?? 600_000,
|
|
182
327
|
});
|
|
183
|
-
return { command: [
|
|
328
|
+
return { command: [process.execPath, ...args], benchmark: input.benchmark, ...result };
|
|
184
329
|
}
|
|
185
330
|
|
|
186
331
|
export async function issueContext(repoRoot, input) {
|
|
@@ -236,7 +381,7 @@ function registerUserTools(server, repoRoot, repoMode) {
|
|
|
236
381
|
{
|
|
237
382
|
title: 'Octane skill',
|
|
238
383
|
description:
|
|
239
|
-
'Return an Octane agent skill by name.
|
|
384
|
+
'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.' +
|
|
240
385
|
(repoMode ? ' Repo skills cover octane maintainer workflows.' : ''),
|
|
241
386
|
inputSchema: {
|
|
242
387
|
name: z.enum(Object.keys(skills)),
|
|
@@ -249,12 +394,33 @@ function registerUserTools(server, repoRoot, repoMode) {
|
|
|
249
394
|
},
|
|
250
395
|
);
|
|
251
396
|
|
|
397
|
+
server.registerTool(
|
|
398
|
+
'octane_engineering_plan',
|
|
399
|
+
{
|
|
400
|
+
title: 'Plan high-quality Octane engineering work',
|
|
401
|
+
description:
|
|
402
|
+
'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.',
|
|
403
|
+
inputSchema: {
|
|
404
|
+
scope: z.enum(['application', 'library', 'framework-core']),
|
|
405
|
+
changeKind: z
|
|
406
|
+
.enum(['bug', 'feature', 'performance', 'refactor', 'api', 'docs', 'test', 'unknown'])
|
|
407
|
+
.default('feature'),
|
|
408
|
+
paths: z.array(z.string()).default([]).describe('Repository-relative changed paths.'),
|
|
409
|
+
performanceSensitive: z
|
|
410
|
+
.boolean()
|
|
411
|
+
.optional()
|
|
412
|
+
.describe('Force performance evidence for application or library work.'),
|
|
413
|
+
},
|
|
414
|
+
},
|
|
415
|
+
async (input) => text(JSON.stringify(engineeringPlanFor(input, repoMode), null, 2)),
|
|
416
|
+
);
|
|
417
|
+
|
|
252
418
|
server.registerTool(
|
|
253
419
|
'octane_bridge_react_package',
|
|
254
420
|
{
|
|
255
421
|
title: 'Bridge a React package to Octane',
|
|
256
422
|
description:
|
|
257
|
-
'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,
|
|
423
|
+
'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.',
|
|
258
424
|
inputSchema: {
|
|
259
425
|
package: z
|
|
260
426
|
.string()
|
|
@@ -379,9 +545,10 @@ function registerRepoTools(server, repoRoot) {
|
|
|
379
545
|
{
|
|
380
546
|
title: 'Run Octane benchmark',
|
|
381
547
|
description:
|
|
382
|
-
'Run
|
|
548
|
+
'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.',
|
|
383
549
|
inputSchema: {
|
|
384
|
-
benchmark: z.enum(['all', ...
|
|
550
|
+
benchmark: z.enum(['all', ...BENCHMARK_SUITES]).default('all'),
|
|
551
|
+
quick: z.boolean().default(false),
|
|
385
552
|
timeoutMs: z.number().int().positive().optional(),
|
|
386
553
|
},
|
|
387
554
|
},
|
|
@@ -408,7 +575,10 @@ function registerRepoTools(server, repoRoot) {
|
|
|
408
575
|
export function createServer(options = {}) {
|
|
409
576
|
const repoRoot = resolve(options.repoRoot || process.env.OCTANE_REPO_ROOT || process.cwd());
|
|
410
577
|
const repoMode = isOctaneRepo(repoRoot);
|
|
411
|
-
const server = new McpServer(
|
|
578
|
+
const server = new McpServer(
|
|
579
|
+
{ name: 'octane', version: '0.2.0' },
|
|
580
|
+
{ instructions: instructionsFor(repoMode) },
|
|
581
|
+
);
|
|
412
582
|
registerUserTools(server, repoRoot, repoMode);
|
|
413
583
|
if (repoMode) registerRepoTools(server, repoRoot);
|
|
414
584
|
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,11 +8,16 @@ 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';
|
|
20
|
+
import { KNOWN_BINDING_PACKAGE_DIRS } from './bridge.js';
|
|
14
21
|
|
|
15
22
|
const PACKAGE_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
16
23
|
|
|
@@ -22,10 +29,63 @@ describe('@octanejs/mcp-server helpers', () => {
|
|
|
22
29
|
expect(areaForPath('packages/zustand/src/index.ts')).toBe('ecosystem-binding');
|
|
23
30
|
expect(areaForPath('packages/radix/src/index.ts')).toBe('ecosystem-binding');
|
|
24
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/octane-evals/tools/run.mjs')).toBe('evals');
|
|
34
|
+
expect(areaForPath('website/src/pages/index.tsrx')).toBe('website');
|
|
25
35
|
expect(areaForPath('benchmarks/news/run.mjs')).toBe('benchmark');
|
|
26
36
|
expect(areaForPath('.rulesync/rules/project.md')).toBe('rulesync-source');
|
|
27
37
|
});
|
|
28
38
|
|
|
39
|
+
it('recommends the adapter, evals, and website test projects', () => {
|
|
40
|
+
const commands = validationFor(
|
|
41
|
+
[
|
|
42
|
+
'packages/adapter-vercel/src/index.ts',
|
|
43
|
+
'packages/octane-evals/tools/run.mjs',
|
|
44
|
+
'website/src/pages/index.tsrx',
|
|
45
|
+
],
|
|
46
|
+
'feature',
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
expect(commands).toContain(
|
|
50
|
+
'./node_modules/.bin/vitest run packages/adapter-vercel/tests --project adapter-vercel',
|
|
51
|
+
);
|
|
52
|
+
expect(commands).toContain(
|
|
53
|
+
'./node_modules/.bin/vitest run packages/octane-evals/tests --project octane-evals',
|
|
54
|
+
);
|
|
55
|
+
expect(commands).toContain('./node_modules/.bin/vitest run website/tests --project website');
|
|
56
|
+
expect(commands).toContain('pnpm typecheck');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('keeps the benchmark suite list in sync with the unified runner manifest', async () => {
|
|
60
|
+
// BENCHMARK_SUITES is hand-maintained in index.js; the runner manifest in
|
|
61
|
+
// benchmarks/bench.mjs is the source of truth. --list prints one suite
|
|
62
|
+
// name per line, in manifest order.
|
|
63
|
+
const repoRoot = resolve(PACKAGE_ROOT, '../..');
|
|
64
|
+
const result = await runCommand(process.execPath, ['benchmarks/bench.mjs', '--list'], {
|
|
65
|
+
cwd: repoRoot,
|
|
66
|
+
});
|
|
67
|
+
expect(result.code).toBe(0);
|
|
68
|
+
const suites = result.stdout
|
|
69
|
+
.split('\n')
|
|
70
|
+
.map((line) => line.trim())
|
|
71
|
+
.filter((line) => line && line !== 'Available suites:');
|
|
72
|
+
expect(suites).toEqual(BENCHMARK_SUITES);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('classifies every maintained binding and recommends its test project', () => {
|
|
76
|
+
const paths = [...KNOWN_BINDING_PACKAGE_DIRS].map(
|
|
77
|
+
(directory) => `packages/${directory}/src/index.ts`,
|
|
78
|
+
);
|
|
79
|
+
const commands = validationFor(paths, 'binding');
|
|
80
|
+
|
|
81
|
+
for (const directory of KNOWN_BINDING_PACKAGE_DIRS) {
|
|
82
|
+
expect(areaForPath(`packages/${directory}/src/index.ts`)).toBe('ecosystem-binding');
|
|
83
|
+
expect(commands).toContain(
|
|
84
|
+
`./node_modules/.bin/vitest run packages/${directory}/tests --project ${directory}`,
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
|
|
29
89
|
it('recommends validation commands from changed paths', () => {
|
|
30
90
|
const commands = validationFor(
|
|
31
91
|
[
|
|
@@ -48,9 +108,86 @@ describe('@octanejs/mcp-server helpers', () => {
|
|
|
48
108
|
'./node_modules/.bin/vitest run packages/radix/tests --project radix',
|
|
49
109
|
);
|
|
50
110
|
expect(commands).toContain('pnpm typecheck');
|
|
111
|
+
expect(commands).toContain('node benchmarks/bench.mjs --quick --ratios');
|
|
51
112
|
expect(commands).toContain('pnpm format:check');
|
|
52
113
|
});
|
|
53
114
|
|
|
115
|
+
it('requires performance evidence and adversarial review for framework fundamentals', () => {
|
|
116
|
+
const plan = engineeringPlanFor(
|
|
117
|
+
{
|
|
118
|
+
scope: 'framework-core',
|
|
119
|
+
changeKind: 'refactor',
|
|
120
|
+
paths: ['packages/octane/src/runtime.ts'],
|
|
121
|
+
},
|
|
122
|
+
true,
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
expect(plan.performanceSensitive).toBe(true);
|
|
126
|
+
expect(plan.requiredSkills).toEqual([
|
|
127
|
+
'build-octane-software',
|
|
128
|
+
'octane-core-extend',
|
|
129
|
+
'performance-audit',
|
|
130
|
+
]);
|
|
131
|
+
expect(plan.gates.performance).toContain(
|
|
132
|
+
'Identify hot paths and record a relevant baseline before editing.',
|
|
133
|
+
);
|
|
134
|
+
expect(plan.gates.selfReview).toContain(
|
|
135
|
+
'Resolve findings, rerun affected checks, and repeat the review on the final diff.',
|
|
136
|
+
);
|
|
137
|
+
expect(plan.validationCommands).toContain('node benchmarks/bench.mjs --quick --ratios');
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('blocks framework-core plans when maintainer tools are unavailable', () => {
|
|
141
|
+
const plan = engineeringPlanFor({ scope: 'framework-core', changeKind: 'bug' });
|
|
142
|
+
|
|
143
|
+
expect(plan.requiredSkills).toEqual(['build-octane-software']);
|
|
144
|
+
expect(plan.blockingConditions).toContain(
|
|
145
|
+
'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.',
|
|
146
|
+
);
|
|
147
|
+
expect(plan.gates.correctness).toContain(
|
|
148
|
+
'Reproduce the bug through a realistic public boundary and verify that the test has a credible pre-fix failure.',
|
|
149
|
+
);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('keeps performance gates and validation commands aligned', () => {
|
|
153
|
+
const performancePlan = engineeringPlanFor(
|
|
154
|
+
{ scope: 'application', changeKind: 'performance' },
|
|
155
|
+
true,
|
|
156
|
+
);
|
|
157
|
+
const flaggedPlan = engineeringPlanFor(
|
|
158
|
+
{ scope: 'library', changeKind: 'feature', performanceSensitive: true },
|
|
159
|
+
true,
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
for (const plan of [performancePlan, flaggedPlan]) {
|
|
163
|
+
expect(plan.performanceSensitive).toBe(true);
|
|
164
|
+
expect(plan.gates.performance).toContain(
|
|
165
|
+
'Identify hot paths and record a relevant baseline before editing.',
|
|
166
|
+
);
|
|
167
|
+
expect(plan.validationCommands).toContain('node benchmarks/bench.mjs --quick --ratios');
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('advertises the engineering gates during MCP initialization', async () => {
|
|
172
|
+
const server = createServer({ repoRoot: resolve(PACKAGE_ROOT, '../..') });
|
|
173
|
+
const client = new Client({ name: 'octane-mcp-test', version: '1.0.0' });
|
|
174
|
+
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
|
|
175
|
+
|
|
176
|
+
try {
|
|
177
|
+
await Promise.all([server.connect(serverTransport), client.connect(clientTransport)]);
|
|
178
|
+
expect(client.getInstructions()).toContain(
|
|
179
|
+
'Before creating or materially changing Octane software',
|
|
180
|
+
);
|
|
181
|
+
expect(client.getInstructions()).toContain('establish a relevant baseline before editing');
|
|
182
|
+
|
|
183
|
+
const tools = await client.listTools();
|
|
184
|
+
expect(tools.tools.map((tool) => tool.name)).toContain('octane_engineering_plan');
|
|
185
|
+
} finally {
|
|
186
|
+
await client.close();
|
|
187
|
+
await server.close();
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
|
|
54
191
|
it('detects the octane monorepo for repo-mode tools', async () => {
|
|
55
192
|
expect(isOctaneRepo(resolve(PACKAGE_ROOT, '../..'))).toBe(true);
|
|
56
193
|
const elsewhere = await mkdtemp(join(tmpdir(), 'octane-mcp-test-'));
|