@octanejs/mcp-server 0.2.17 → 0.2.19
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 +2 -2
- package/package.json +1 -1
- package/skills/bridge-react-package.md +2 -5
- package/src/bridge.js +9 -5
- package/src/bridge.test.js +17 -3
- package/src/index.js +4 -1
- package/src/index.test.js +3 -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,7 +162,7 @@ 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`,
|
|
165
|
+
`application-composition`, `scaling-curves`, `activity`, `streaming-ssr`,
|
|
166
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
|
package/package.json
CHANGED
|
@@ -39,6 +39,7 @@ of bridging by hand:
|
|
|
39
39
|
| `@streamdown/mermaid` | `@octanejs/streamdown/mermaid` |
|
|
40
40
|
| `@streamdown/cjk` | `@octanejs/streamdown/cjk` |
|
|
41
41
|
| `recharts` | `@octanejs/recharts` |
|
|
42
|
+
| `react-resizable-panels` | `@octanejs/resizable-panels` |
|
|
42
43
|
| `@react-three/fiber` | `@octanejs/three` |
|
|
43
44
|
| `@visx/*` | `@octanejs/visx` |
|
|
44
45
|
| `react-redux` | `@octanejs/redux` |
|
|
@@ -128,11 +129,7 @@ So a bridge never means "run the React package unchanged". It means:
|
|
|
128
129
|
the official bindings:
|
|
129
130
|
|
|
130
131
|
```ts
|
|
131
|
-
import { useMemo, useRef } from 'octane';
|
|
132
|
-
|
|
133
|
-
export function subSlot(slot: symbol | undefined, tag: string) {
|
|
134
|
-
return slot !== undefined ? Symbol.for((slot.description ?? '') + ':' + tag) : undefined;
|
|
135
|
-
}
|
|
132
|
+
import { subSlot, useMemo, useRef } from 'octane';
|
|
136
133
|
|
|
137
134
|
export function useControllableState(opts, slot?: symbol) {
|
|
138
135
|
const valueRef = useRef(opts.defaultValue, subSlot(slot, 'value'));
|
package/src/bridge.js
CHANGED
|
@@ -27,6 +27,8 @@ export const KNOWN_BINDINGS = {
|
|
|
27
27
|
'@rainbow-me/rainbowkit': '@octanejs/rainbowkit',
|
|
28
28
|
'@tanstack/react-router': '@octanejs/tanstack-router',
|
|
29
29
|
'@tanstack/react-store': '@octanejs/tanstack-store',
|
|
30
|
+
'@xstate/react': '@octanejs/xstate',
|
|
31
|
+
'@xstate/store-react': '@octanejs/xstate-store',
|
|
30
32
|
'@tanstack/react-router-ssr-query': '@octanejs/tanstack-router-ssr-query',
|
|
31
33
|
'@tanstack/react-hotkeys': '@octanejs/tanstack-hotkeys',
|
|
32
34
|
'@tanstack/react-pacer': '@octanejs/tanstack-pacer',
|
|
@@ -62,6 +64,7 @@ export const KNOWN_BINDINGS = {
|
|
|
62
64
|
'react-dropzone': '@octanejs/dropzone',
|
|
63
65
|
sonner: '@octanejs/sonner',
|
|
64
66
|
'react-error-boundary': '@octanejs/react-error-boundary',
|
|
67
|
+
'react-resizable-panels': '@octanejs/resizable-panels',
|
|
65
68
|
'react-transition-group': '@octanejs/transition-group',
|
|
66
69
|
'react-day-picker': '@octanejs/day-picker',
|
|
67
70
|
'input-otp': '@octanejs/input-otp',
|
|
@@ -183,6 +186,7 @@ export const KNOWN_VANILLA_CORES = {
|
|
|
183
186
|
'@dnd-kit/react': '@dnd-kit/dom',
|
|
184
187
|
'embla-carousel-react': 'embla-carousel',
|
|
185
188
|
'@xstate/react': 'xstate',
|
|
189
|
+
'@xstate/store-react': '@xstate/store',
|
|
186
190
|
'react-redux': 'redux',
|
|
187
191
|
'@reduxjs/toolkit': 'redux',
|
|
188
192
|
'react-i18next': 'i18next',
|
|
@@ -264,11 +268,11 @@ export const REACT_API_MAP = {
|
|
|
264
268
|
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).",
|
|
265
269
|
},
|
|
266
270
|
Component: {
|
|
267
|
-
status: '
|
|
271
|
+
status: 'rewrite',
|
|
268
272
|
note: 'No class components. Rewrite as a function component.',
|
|
269
273
|
},
|
|
270
274
|
PureComponent: {
|
|
271
|
-
status: '
|
|
275
|
+
status: 'rewrite',
|
|
272
276
|
note: 'No class components. Rewrite as a function component with memo.',
|
|
273
277
|
},
|
|
274
278
|
StrictMode: {
|
|
@@ -277,7 +281,7 @@ export const REACT_API_MAP = {
|
|
|
277
281
|
},
|
|
278
282
|
Profiler: { status: 'unsupported', note: 'Not present.' },
|
|
279
283
|
SuspenseList: { status: 'unsupported', note: 'Not present.' },
|
|
280
|
-
findDOMNode: { status: '
|
|
284
|
+
findDOMNode: { status: 'rewrite', note: 'Removed in React 19 too. Use refs.' },
|
|
281
285
|
renderToString: {
|
|
282
286
|
status: 'rewrite',
|
|
283
287
|
note: 'Use renderToString() from octane/server (sync) or prerender() from octane/static (async, awaits Suspense); both return { html, css }.',
|
|
@@ -496,8 +500,8 @@ function apiRows(totals) {
|
|
|
496
500
|
}
|
|
497
501
|
|
|
498
502
|
function verdictFor(rows, classComponents) {
|
|
499
|
-
if (
|
|
500
|
-
if (rows.some((row) => row.status === 'rewrite' || row.status === 'partial')) {
|
|
503
|
+
if (rows.some((row) => row.status === 'unsupported')) return 'needs-rework';
|
|
504
|
+
if (classComponents || rows.some((row) => row.status === 'rewrite' || row.status === 'partial')) {
|
|
501
505
|
return 'bridgeable-with-rewrites';
|
|
502
506
|
}
|
|
503
507
|
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
|
|
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
|
|
|
@@ -66,6 +68,7 @@ export const BENCHMARK_SUITES = [
|
|
|
66
68
|
'scaling-curves',
|
|
67
69
|
'store-selector-fanout',
|
|
68
70
|
'hook-store-composition',
|
|
71
|
+
'activity',
|
|
69
72
|
'effectful-list',
|
|
70
73
|
'list-clear',
|
|
71
74
|
'memo-wall',
|
|
@@ -295,7 +298,7 @@ export function engineeringPlanFor(input, repoMode = false) {
|
|
|
295
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.',
|
|
296
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.",
|
|
297
300
|
];
|
|
298
|
-
if (repoMode) plan.requiredSkills.push('react-library-port');
|
|
301
|
+
if (repoMode) plan.requiredSkills.push('octane-react-library-port');
|
|
299
302
|
}
|
|
300
303
|
if (scope === 'framework-core' && !repoMode) {
|
|
301
304
|
plan.blockingConditions = [
|
package/src/index.test.js
CHANGED
|
@@ -165,7 +165,7 @@ describe('@octanejs/mcp-server helpers', () => {
|
|
|
165
165
|
true,
|
|
166
166
|
);
|
|
167
167
|
|
|
168
|
-
expect(plan.requiredSkills).toContain('react-library-port');
|
|
168
|
+
expect(plan.requiredSkills).toContain('octane-react-library-port');
|
|
169
169
|
expect(plan.gates.parity.join('\n')).toContain('packages/<name>/UPSTREAM.md');
|
|
170
170
|
expect(plan.gates.parity.join('\n')).toContain('divergence');
|
|
171
171
|
expect(plan.gates.parity.join('\n')).toContain("pinned release's own suite");
|
|
@@ -175,7 +175,7 @@ describe('@octanejs/mcp-server helpers', () => {
|
|
|
175
175
|
true,
|
|
176
176
|
);
|
|
177
177
|
expect(applicationPlan.gates.parity).toBeUndefined();
|
|
178
|
-
expect(applicationPlan.requiredSkills).not.toContain('react-library-port');
|
|
178
|
+
expect(applicationPlan.requiredSkills).not.toContain('octane-react-library-port');
|
|
179
179
|
});
|
|
180
180
|
|
|
181
181
|
it('blocks framework-core plans when maintainer tools are unavailable', () => {
|
|
@@ -283,6 +283,7 @@ describe('@octanejs/mcp-server helpers', () => {
|
|
|
283
283
|
.map((entry) => entry.name)
|
|
284
284
|
.sort();
|
|
285
285
|
|
|
286
|
+
expect(REPO_SKILLS['react-library-port']).toBe('.rulesync/skills/react-library-port/SKILL.md');
|
|
286
287
|
expect(Object.keys(REPO_SKILLS).sort()).toEqual(onDisk);
|
|
287
288
|
|
|
288
289
|
for (const file of Object.values(REPO_SKILLS)) {
|