@octanejs/mcp-server 0.2.16 → 0.2.18
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 +3 -3
- package/package.json +1 -1
- package/skills/bridge-react-package.md +1 -0
- package/src/bridge.js +8 -5
- package/src/bridge.test.js +22 -3
- package/src/index.js +7 -1
- package/src/index.test.js +18 -2
package/README.md
CHANGED
|
@@ -121,7 +121,7 @@ Returns a skill by name. Bundled skills (shipped with this package):
|
|
|
121
121
|
- `setup-ssr`: server rendering and hydration setup.
|
|
122
122
|
|
|
123
123
|
When running inside the octane monorepo, the skills from `.rulesync/skills` are
|
|
124
|
-
also available: `authoring-tsrx`, `react-library-port`, `bug-hunter`,
|
|
124
|
+
also available: `authoring-tsrx`, `octane-react-library-port`, `bug-hunter`,
|
|
125
125
|
`create-a-pr`, `handle-issue`, `octane-core-extend`, `triage`,
|
|
126
126
|
`performance-audit`. A test compares this map against the directory in both
|
|
127
127
|
directions, so a new skill cannot stay unreachable here. This tool reads the RuleSync source, and
|
|
@@ -162,8 +162,8 @@ one manifest suite by name (`js-framework`, `todomvc`, `weather-app`,
|
|
|
162
162
|
`hydration-interactivity`, `hydration-stress`, `lifecycle-memory`,
|
|
163
163
|
`controlled-form`, `external-store-fanout`, `external-store-integrations`,
|
|
164
164
|
`scheduler-responsiveness`, `suspense-recovery`, `event-delegation`,
|
|
165
|
-
`application-composition`, `scaling-curves`, `streaming-ssr`,
|
|
166
|
-
`streaming-backpressure`, `compiler-throughput`, `codegen-size`,
|
|
165
|
+
`application-composition`, `scaling-curves`, `activity`, `streaming-ssr`,
|
|
166
|
+
`streaming-backpressure`, `compiler-throughput`, `codegen-size`, `hook-memo`,
|
|
167
167
|
`bundle-size`, `bundle-reachability`, `three-renderer`, `three-bundle-size`, …)
|
|
168
168
|
or every suite with `all`; `quick` selects the reduced-iteration smoke pass. The
|
|
169
169
|
suite list mirrors the runner manifest and `node benchmarks/bench.mjs --list`.
|
package/package.json
CHANGED
|
@@ -45,6 +45,7 @@ of bridging by hand:
|
|
|
45
45
|
| `@reduxjs/toolkit` | `@octanejs/redux-toolkit` |
|
|
46
46
|
| `@testing-library/react` | `@octanejs/testing-library` |
|
|
47
47
|
| `@mdx-js/react` | `@octanejs/mdx` |
|
|
48
|
+
| `ink` | `@octanejs/ink` |
|
|
48
49
|
|
|
49
50
|
The `octane_bindings` tool returns the same map machine-readably. For anything
|
|
50
51
|
else, run the `octane_bridge_react_package` tool to get a scan of the
|
package/src/bridge.js
CHANGED
|
@@ -18,6 +18,7 @@ export const KNOWN_BINDINGS = {
|
|
|
18
18
|
'@apollo/client': '@octanejs/apollo-client',
|
|
19
19
|
'@solana/react': '@octanejs/solana-react',
|
|
20
20
|
'@tanstack/ai-react': '@octanejs/tanstack-ai',
|
|
21
|
+
'@tanstack/react-db': '@octanejs/tanstack-db',
|
|
21
22
|
'@tanstack/react-devtools': '@octanejs/tanstack-devtools',
|
|
22
23
|
'@tanstack/react-form': '@octanejs/tanstack-form',
|
|
23
24
|
'@tanstack/react-query': '@octanejs/tanstack-query',
|
|
@@ -145,6 +146,7 @@ export const KNOWN_BINDINGS = {
|
|
|
145
146
|
'@testing-library/react': '@octanejs/testing-library',
|
|
146
147
|
'react-i18next': '@octanejs/i18next',
|
|
147
148
|
'@inertiajs/react': '@octanejs/inertia',
|
|
149
|
+
ink: '@octanejs/ink',
|
|
148
150
|
'@mdx-js/react': '@octanejs/mdx',
|
|
149
151
|
'dexie-react-hooks': '@octanejs/dexie',
|
|
150
152
|
'@livestore/react': '@octanejs/livestore',
|
|
@@ -171,6 +173,7 @@ export const KNOWN_BINDING_PACKAGE_DIRS = new Set(
|
|
|
171
173
|
export const KNOWN_VANILLA_CORES = {
|
|
172
174
|
'@apollo/client': '@apollo/client',
|
|
173
175
|
'@solana/react': '@solana/kit',
|
|
176
|
+
'@tanstack/react-db': '@tanstack/db',
|
|
174
177
|
'@tanstack/react-query': '@tanstack/query-core',
|
|
175
178
|
wagmi: '@wagmi/core',
|
|
176
179
|
'@tanstack/react-table': '@tanstack/table-core',
|
|
@@ -261,11 +264,11 @@ export const REACT_API_MAP = {
|
|
|
261
264
|
note: "Supported. Accepts React's { default } module shape and additionally a bare component from the loader; wrapping Suspense or ViewTransition in lazy() is valid (nested lazy wrappers are not).",
|
|
262
265
|
},
|
|
263
266
|
Component: {
|
|
264
|
-
status: '
|
|
267
|
+
status: 'rewrite',
|
|
265
268
|
note: 'No class components. Rewrite as a function component.',
|
|
266
269
|
},
|
|
267
270
|
PureComponent: {
|
|
268
|
-
status: '
|
|
271
|
+
status: 'rewrite',
|
|
269
272
|
note: 'No class components. Rewrite as a function component with memo.',
|
|
270
273
|
},
|
|
271
274
|
StrictMode: {
|
|
@@ -274,7 +277,7 @@ export const REACT_API_MAP = {
|
|
|
274
277
|
},
|
|
275
278
|
Profiler: { status: 'unsupported', note: 'Not present.' },
|
|
276
279
|
SuspenseList: { status: 'unsupported', note: 'Not present.' },
|
|
277
|
-
findDOMNode: { status: '
|
|
280
|
+
findDOMNode: { status: 'rewrite', note: 'Removed in React 19 too. Use refs.' },
|
|
278
281
|
renderToString: {
|
|
279
282
|
status: 'rewrite',
|
|
280
283
|
note: 'Use renderToString() from octane/server (sync) or prerender() from octane/static (async, awaits Suspense); both return { html, css }.',
|
|
@@ -493,8 +496,8 @@ function apiRows(totals) {
|
|
|
493
496
|
}
|
|
494
497
|
|
|
495
498
|
function verdictFor(rows, classComponents) {
|
|
496
|
-
if (
|
|
497
|
-
if (rows.some((row) => row.status === 'rewrite' || row.status === 'partial')) {
|
|
499
|
+
if (rows.some((row) => row.status === 'unsupported')) return 'needs-rework';
|
|
500
|
+
if (classComponents || rows.some((row) => row.status === 'rewrite' || row.status === 'partial')) {
|
|
498
501
|
return 'bridgeable-with-rewrites';
|
|
499
502
|
}
|
|
500
503
|
return 'bridgeable';
|
package/src/bridge.test.js
CHANGED
|
@@ -166,7 +166,7 @@ describe('bridgeReport', () => {
|
|
|
166
166
|
expect(report.plan.join('\n')).toContain('octane/server');
|
|
167
167
|
});
|
|
168
168
|
|
|
169
|
-
it('reports class components as
|
|
169
|
+
it('reports class components as bridgeable with mandatory rewrites', async () => {
|
|
170
170
|
const root = await mkdtemp(join(tmpdir(), 'octane-bridge-'));
|
|
171
171
|
await writeFakePackage(root, 'classy', {
|
|
172
172
|
'index.js': `
|
|
@@ -176,6 +176,20 @@ describe('bridgeReport', () => {
|
|
|
176
176
|
});
|
|
177
177
|
const report = await bridgeReport({ packageName: 'classy', projectRoot: root });
|
|
178
178
|
expect(report.classComponents).toBe(true);
|
|
179
|
+
expect(report.apis.find((row) => row.name === 'Component').status).toBe('rewrite');
|
|
180
|
+
expect(report.verdict).toBe('bridgeable-with-rewrites');
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it('reserves needs-rework for a public React API with no Octane rewrite', async () => {
|
|
184
|
+
const root = await mkdtemp(join(tmpdir(), 'octane-bridge-'));
|
|
185
|
+
await writeFakePackage(root, 'profiler-lib', {
|
|
186
|
+
'index.js': `
|
|
187
|
+
import { Profiler } from 'react';
|
|
188
|
+
export { Profiler };
|
|
189
|
+
`,
|
|
190
|
+
});
|
|
191
|
+
const report = await bridgeReport({ packageName: 'profiler-lib', projectRoot: root });
|
|
192
|
+
expect(report.apis.find((row) => row.name === 'Profiler').status).toBe('unsupported');
|
|
179
193
|
expect(report.verdict).toBe('needs-rework');
|
|
180
194
|
});
|
|
181
195
|
|
|
@@ -234,13 +248,13 @@ describe('bridgeReportFromSource', () => {
|
|
|
234
248
|
expect(report.plan.join('\n')).toContain('forwardRef');
|
|
235
249
|
});
|
|
236
250
|
|
|
237
|
-
it('reports class components as
|
|
251
|
+
it('reports class components as bridgeable with mandatory rewrites', () => {
|
|
238
252
|
const report = bridgeReportFromSource(`
|
|
239
253
|
import React from 'react';
|
|
240
254
|
export class Panel extends React.Component { render() { return null; } }
|
|
241
255
|
`);
|
|
242
256
|
expect(report.classComponents).toBe(true);
|
|
243
|
-
expect(report.verdict).toBe('
|
|
257
|
+
expect(report.verdict).toBe('bridgeable-with-rewrites');
|
|
244
258
|
expect(report.plan.join('\n')).toContain('function component');
|
|
245
259
|
});
|
|
246
260
|
|
|
@@ -288,6 +302,11 @@ describe('KNOWN_BINDINGS', () => {
|
|
|
288
302
|
expect(KNOWN_BINDINGS['react-window']).toBe('@octanejs/window');
|
|
289
303
|
});
|
|
290
304
|
|
|
305
|
+
it('maps TanStack React DB to the Octane binding and framework-neutral core', () => {
|
|
306
|
+
expect(KNOWN_BINDINGS['@tanstack/react-db']).toBe('@octanejs/tanstack-db');
|
|
307
|
+
expect(KNOWN_VANILLA_CORES['@tanstack/react-db']).toBe('@tanstack/db');
|
|
308
|
+
});
|
|
309
|
+
|
|
291
310
|
it('maps Streamdown and every official plugin package to the consolidated binding', () => {
|
|
292
311
|
const upstreamPackages = [
|
|
293
312
|
'streamdown',
|
package/src/index.js
CHANGED
|
@@ -32,7 +32,9 @@ export const REPO_SKILLS = {
|
|
|
32
32
|
'handle-issue': '.rulesync/skills/handle-issue/SKILL.md',
|
|
33
33
|
'octane-core-extend': '.rulesync/skills/octane-core-extend/SKILL.md',
|
|
34
34
|
'performance-audit': '.rulesync/skills/performance-audit/SKILL.md',
|
|
35
|
+
// Temporary compatibility alias for callers using the pre-rename skill name.
|
|
35
36
|
'react-library-port': '.rulesync/skills/react-library-port/SKILL.md',
|
|
37
|
+
'octane-react-library-port': '.rulesync/skills/octane-react-library-port/SKILL.md',
|
|
36
38
|
triage: '.rulesync/skills/triage/SKILL.md',
|
|
37
39
|
};
|
|
38
40
|
|
|
@@ -65,6 +67,8 @@ export const BENCHMARK_SUITES = [
|
|
|
65
67
|
'application-composition',
|
|
66
68
|
'scaling-curves',
|
|
67
69
|
'store-selector-fanout',
|
|
70
|
+
'hook-store-composition',
|
|
71
|
+
'activity',
|
|
68
72
|
'effectful-list',
|
|
69
73
|
'list-clear',
|
|
70
74
|
'memo-wall',
|
|
@@ -82,11 +86,13 @@ export const BENCHMARK_SUITES = [
|
|
|
82
86
|
'async-composition',
|
|
83
87
|
'lynx-list',
|
|
84
88
|
'universal-leaf-update',
|
|
89
|
+
'universal-external-store',
|
|
85
90
|
'lynx-render',
|
|
86
91
|
'lynx-table',
|
|
87
92
|
'lynx-table-web',
|
|
88
93
|
'lynx-bundle-size',
|
|
89
94
|
'codegen-size',
|
|
95
|
+
'hook-memo',
|
|
90
96
|
'compiler-throughput',
|
|
91
97
|
'bundle-size',
|
|
92
98
|
'bundle-reachability',
|
|
@@ -292,7 +298,7 @@ export function engineeringPlanFor(input, repoMode = false) {
|
|
|
292
298
|
'Record what parity cannot reach as a divergence in UPSTREAM.md and status.json, with the reason, what the consumer should do instead, and a behavioral test pinning the Octane behavior.',
|
|
293
299
|
"Run the pinned release's own suite as the parity oracle: its framework-neutral tests unmodified against the reused core, its React-binding tests ported case by case with the upstream case names and citations. Record every upstream test file as run as-is, ported, or out of scope with the reason, and never weaken an upstream assertion to make it pass.",
|
|
294
300
|
];
|
|
295
|
-
if (repoMode) plan.requiredSkills.push('react-library-port');
|
|
301
|
+
if (repoMode) plan.requiredSkills.push('octane-react-library-port');
|
|
296
302
|
}
|
|
297
303
|
if (scope === 'framework-core' && !repoMode) {
|
|
298
304
|
plan.blockingConditions = [
|
package/src/index.test.js
CHANGED
|
@@ -77,6 +77,21 @@ describe('@octanejs/mcp-server helpers', () => {
|
|
|
77
77
|
.map((line) => line.trim())
|
|
78
78
|
.filter((line) => line && line !== 'Available suites:');
|
|
79
79
|
expect(suites).toEqual(BENCHMARK_SUITES);
|
|
80
|
+
|
|
81
|
+
// The public MCP schema must accept every runner suite, not just keep
|
|
82
|
+
// an exported helper list in sync.
|
|
83
|
+
const server = createServer({ repoRoot });
|
|
84
|
+
const client = new Client({ name: 'octane-mcp-test', version: '1.0.0' });
|
|
85
|
+
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
|
|
86
|
+
try {
|
|
87
|
+
await Promise.all([server.connect(serverTransport), client.connect(clientTransport)]);
|
|
88
|
+
const tools = await client.listTools();
|
|
89
|
+
const benchmark = tools.tools.find((tool) => tool.name === 'octane_benchmark');
|
|
90
|
+
expect(benchmark?.inputSchema.properties?.benchmark?.enum).toEqual(['all', ...suites]);
|
|
91
|
+
} finally {
|
|
92
|
+
await client.close();
|
|
93
|
+
await server.close();
|
|
94
|
+
}
|
|
80
95
|
});
|
|
81
96
|
|
|
82
97
|
it('classifies every maintained binding and recommends its test project', () => {
|
|
@@ -150,7 +165,7 @@ describe('@octanejs/mcp-server helpers', () => {
|
|
|
150
165
|
true,
|
|
151
166
|
);
|
|
152
167
|
|
|
153
|
-
expect(plan.requiredSkills).toContain('react-library-port');
|
|
168
|
+
expect(plan.requiredSkills).toContain('octane-react-library-port');
|
|
154
169
|
expect(plan.gates.parity.join('\n')).toContain('packages/<name>/UPSTREAM.md');
|
|
155
170
|
expect(plan.gates.parity.join('\n')).toContain('divergence');
|
|
156
171
|
expect(plan.gates.parity.join('\n')).toContain("pinned release's own suite");
|
|
@@ -160,7 +175,7 @@ describe('@octanejs/mcp-server helpers', () => {
|
|
|
160
175
|
true,
|
|
161
176
|
);
|
|
162
177
|
expect(applicationPlan.gates.parity).toBeUndefined();
|
|
163
|
-
expect(applicationPlan.requiredSkills).not.toContain('react-library-port');
|
|
178
|
+
expect(applicationPlan.requiredSkills).not.toContain('octane-react-library-port');
|
|
164
179
|
});
|
|
165
180
|
|
|
166
181
|
it('blocks framework-core plans when maintainer tools are unavailable', () => {
|
|
@@ -268,6 +283,7 @@ describe('@octanejs/mcp-server helpers', () => {
|
|
|
268
283
|
.map((entry) => entry.name)
|
|
269
284
|
.sort();
|
|
270
285
|
|
|
286
|
+
expect(REPO_SKILLS['react-library-port']).toBe('.rulesync/skills/react-library-port/SKILL.md');
|
|
271
287
|
expect(Object.keys(REPO_SKILLS).sort()).toEqual(onDisk);
|
|
272
288
|
|
|
273
289
|
for (const file of Object.values(REPO_SKILLS)) {
|