@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 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@octanejs/mcp-server",
3
- "version": "0.2.17",
3
+ "version": "0.2.19",
4
4
  "type": "module",
5
5
  "engines": {
6
6
  "node": ">=22.22.2"
@@ -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: 'unsupported',
271
+ status: 'rewrite',
268
272
  note: 'No class components. Rewrite as a function component.',
269
273
  },
270
274
  PureComponent: {
271
- status: 'unsupported',
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: 'unsupported', note: 'Removed in React 19 too. Use refs.' },
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 (classComponents || rows.some((row) => row.status === 'unsupported')) return 'needs-rework';
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';
@@ -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 needs-rework', async () => {
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 needs-rework', () => {
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('needs-rework');
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)) {