@octanejs/mcp-server 0.2.8 → 0.2.10

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
@@ -159,11 +159,14 @@ optionally writes the generated Vitest skeleton to an output file.
159
159
 
160
160
  Runs benchmark suites through the unified runner (`node benchmarks/bench.mjs`):
161
161
  one manifest suite by name (`js-framework`, `todomvc`, `weather-app`,
162
- `weather-app-lighthouse`, `chat-stream`, `dbmon`, `news`, `react-hosted-islands`,
163
- `ssr-throughput`, `streaming-ssr`,
164
- `codegen-size`, `bundle-size`, `three-renderer`, `three-bundle-size`, …) or every
165
- suite with `all`; `quick` selects the reduced-iteration smoke pass. The suite
166
- list mirrors the runner manifest and `node benchmarks/bench.mjs --list`.
162
+ `hydration-interactivity`, `hydration-stress`, `lifecycle-memory`,
163
+ `controlled-form`, `external-store-fanout`, `external-store-integrations`,
164
+ `scheduler-responsiveness`, `suspense-recovery`, `event-delegation`,
165
+ `application-composition`, `scaling-curves`, `streaming-ssr`,
166
+ `streaming-backpressure`, `compiler-throughput`, `codegen-size`,
167
+ `bundle-size`, `three-renderer`, `three-bundle-size`, …) or every suite with
168
+ `all`; `quick` selects the reduced-iteration smoke pass. The suite list mirrors
169
+ the runner manifest and `node benchmarks/bench.mjs --list`.
167
170
 
168
171
  ### `octane_issue_context`
169
172
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@octanejs/mcp-server",
3
- "version": "0.2.8",
3
+ "version": "0.2.10",
4
4
  "type": "module",
5
5
  "engines": {
6
6
  "node": ">=22"
@@ -32,6 +32,11 @@ of bridging by hand:
32
32
  | `@base-ui-components/react` | `@octanejs/base-ui` |
33
33
  | `@dnd-kit/react` | `@octanejs/dnd-kit` |
34
34
  | `sonner` | `@octanejs/sonner` |
35
+ | `streamdown` | `@octanejs/streamdown` |
36
+ | `@streamdown/code` | `@octanejs/streamdown/code` |
37
+ | `@streamdown/math` | `@octanejs/streamdown/math` |
38
+ | `@streamdown/mermaid` | `@octanejs/streamdown/mermaid` |
39
+ | `@streamdown/cjk` | `@octanejs/streamdown/cjk` |
35
40
  | `recharts` | `@octanejs/recharts` |
36
41
  | `@react-three/fiber` | `@octanejs/three` |
37
42
  | `@visx/*` | `@octanejs/visx` |
@@ -71,7 +76,19 @@ So a bridge never means "run the React package unchanged". It means:
71
76
  `*-core` dependency, or a pure internal module). Identify the React surface:
72
77
  hooks, components, providers, portals, refs.
73
78
 
74
- 2. **Map the React APIs.** Same-name and same-semantics in Octane: `useState`,
79
+ 2. **Bridge from the pinned upstream source, not from memory.** Fix the exact
80
+ upstream version you are bridging and copy that release's React binding source
81
+ into your repository next to your port, for example
82
+ `src/vendor/<package>@<version>/`, keeping the upstream LICENSE and leaving
83
+ the copy unmodified. Put each Octane module beside the upstream module it
84
+ replaces, and work through them one by one. A bridge written from the README
85
+ or from type declarations covers the demo path and silently drops the rest of
86
+ the API. Anything you cannot reach (React internals, class components,
87
+ synthetic-event timing) goes in a short divergence note next to the port, with
88
+ what to do instead. On an upgrade, re-copy at the new version: the diff
89
+ against the old copy is your work list.
90
+
91
+ 3. **Map the React APIs.** Same-name and same-semantics in Octane: `useState`,
75
92
  `useReducer`, `useEffect`, `useLayoutEffect`, `useInsertionEffect`, `useMemo`,
76
93
  `useCallback`, `useRef`, `useContext`, `useId`, `useImperativeHandle`,
77
94
  `useSyncExternalStore` (full React 19 shape, including `getServerSnapshot`),
@@ -83,7 +100,7 @@ So a bridge never means "run the React package unchanged". It means:
83
100
  server rendering imports from `octane/server`, including the streaming
84
101
  `renderToPipeableStream`/`renderToReadableStream`.
85
102
 
86
- 3. **Handle the gaps:**
103
+ 4. **Handle the gaps:**
87
104
  - `forwardRef`: does not exist. Accept `ref` as a normal prop (React 19
88
105
  style) and drop the wrapper.
89
106
  - Class components: rewrite as function components. Error boundary classes
@@ -98,7 +115,7 @@ So a bridge never means "run the React package unchanged". It means:
98
115
  - StrictMode double-invoke: does not exist; delete test expectations that
99
116
  count double renders.
100
117
 
101
- 4. **Custom hooks in plain `.ts` files.** Octane's compiler auto-slots hook
118
+ 5. **Custom hooks in plain `.ts` files.** Octane's compiler auto-slots hook
102
119
  calls in files it compiles. A binding published as plain `.ts` that calls
103
120
  hooks internally must forward the caller's slot: accept a trailing `slot`
104
121
  argument and derive stable child slots per call site. The convention used by
@@ -122,13 +139,23 @@ So a bridge never means "run the React package unchanged". It means:
122
139
  auto-slotting pass. The simpler alternative: keep the binding in compiled
123
140
  files so slots are injected for you.
124
141
 
125
- 5. **Re-author shipped components in `.tsrx`.** `props.children` works, refs are
142
+ 6. **Re-author shipped components in `.tsrx`.** `props.children` works, refs are
126
143
  props, lists use `@for (const x of xs; key x.id) { }`, conditionals use
127
144
  `@if`, dynamic text holes use `{expr as string}` unless the expression is
128
145
  provably a string.
129
146
 
130
- 6. **Validate.** Drive real DOM events against the bridged binding and, where
131
- possible, run the same fixture against the React original and compare
147
+ 7. **Run the package's own tests.** If the pinned release ships a suite, that is
148
+ the parity oracle: it encodes what its maintainers care about, and it covers
149
+ cases a suite written against your own bridge will not think to check. Run the
150
+ framework-neutral suites unmodified against the core you reused. Port the
151
+ React-binding ones case by case: fixtures re-authored in `.tsrx`,
152
+ `@octanejs/testing-library` in place of `@testing-library/react`, upstream case
153
+ names kept. Write down which upstream test files you ran, ported, or left out
154
+ and why. Do not soften an upstream assertion to get it green; find out whether
155
+ it is a bridge bug or a documented Octane divergence first.
156
+
157
+ 8. **Validate the rest.** Drive real DOM events against the bridged binding and,
158
+ where possible, run the same fixture against the React original and compare
132
159
  rendered HTML after each step. Also test what HTML comparison cannot see:
133
160
  render counts, subscription add/remove, effect ordering, ref lifecycle.
134
161
 
@@ -34,6 +34,10 @@ details but do not replace these gates.
34
34
  hydration with production-compiled output and preserve abort/error behavior.
35
35
  - Treat bundle size and dependency cost as performance. Check for an official
36
36
  binding before adding a compatibility layer or a second framework runtime.
37
+ - When you do port a React package yourself, work from a pinned copy of that
38
+ release's source kept beside your port, cover its exports rather than the demo
39
+ path, and write down what parity could not reach. The `bridge-react-package`
40
+ skill has the procedure.
37
41
 
38
42
  ## Validate behavior and performance
39
43
 
@@ -38,7 +38,7 @@ locals, early returns) stays above it.
38
38
  | `items.map(x => <li key={x.id}>...` | `@for (const x of items; key x.id) { <li>... }` with optional `@empty { }` |
39
39
  | `cond ? <A/> : <B/>` in JSX | `@if (cond) { <A/> } @else { <B/> }` |
40
40
  | `{cond && <A/>}` | `@if (cond) { <A/> }` |
41
- | switch on a value | `@switch (v) { @case (a) { } @default { } }` |
41
+ | switch on a value | `@switch (v) { @case a: { } @default: { } }` |
42
42
  | `<Suspense fallback={...}>` | `<Suspense>` or `@try { } @pending { }` |
43
43
  | Error boundary class | `<ErrorBoundary>` or `@try { } @catch (e) { }` |
44
44
  | `forwardRef((props, ref) => ...)` | plain function; `ref` arrives as a prop |
package/src/bridge.js CHANGED
@@ -6,13 +6,20 @@ import { join, resolve } from 'node:path';
6
6
  // expected union from the workspace manifests, so publishing a new binding
7
7
  // without registering it in either catalog fails the mcp-server tests.
8
8
  export const KNOWN_BINDINGS = {
9
+ 'usehooks-ts': '@octanejs/usehooks-ts',
9
10
  zustand: '@octanejs/zustand',
11
+ valtio: '@octanejs/valtio',
10
12
  jotai: '@octanejs/jotai',
13
+ '@mantine/hooks': '@octanejs/mantine-hooks',
14
+ 'mobx-react-lite': '@octanejs/mobx',
15
+ 'mobx-react': '@octanejs/mobx',
11
16
  '@apollo/client': '@octanejs/apollo-client',
12
17
  '@tanstack/ai-react': '@octanejs/tanstack-ai',
13
18
  '@tanstack/react-devtools': '@octanejs/tanstack-devtools',
14
19
  '@tanstack/react-form': '@octanejs/tanstack-form',
15
20
  '@tanstack/react-query': '@octanejs/tanstack-query',
21
+ wagmi: '@octanejs/wagmi',
22
+ '@rainbow-me/rainbowkit': '@octanejs/rainbowkit',
16
23
  '@tanstack/react-router': '@octanejs/tanstack-router',
17
24
  '@tanstack/react-store': '@octanejs/tanstack-store',
18
25
  '@tanstack/react-router-ssr-query': '@octanejs/tanstack-router-ssr-query',
@@ -27,9 +34,11 @@ export const KNOWN_BINDINGS = {
27
34
  'react-router': '@octanejs/remix-router',
28
35
  'react-router-dom': '@octanejs/remix-router',
29
36
  nuqs: '@octanejs/nuqs',
37
+ cmdk: '@octanejs/cmdk',
30
38
  '@lexical/react': '@octanejs/lexical',
31
39
  '@tiptap/react': '@octanejs/tiptap',
32
40
  'lucide-react': '@octanejs/lucide',
41
+ '@phosphor-icons/react': '@octanejs/phosphor-icons',
33
42
  '@floating-ui/react': '@octanejs/floating-ui',
34
43
  'react-aria': '@octanejs/aria',
35
44
  'react-aria-components': '@octanejs/aria',
@@ -39,6 +48,14 @@ export const KNOWN_BINDINGS = {
39
48
  '@base-ui-components/react': '@octanejs/base-ui',
40
49
  '@dnd-kit/react': '@octanejs/dnd-kit',
41
50
  sonner: '@octanejs/sonner',
51
+ 'react-error-boundary': '@octanejs/react-error-boundary',
52
+ streamdown: '@octanejs/streamdown',
53
+ // The official plugins are consolidated as subpaths of the same package.
54
+ // The bundled bridge skill documents each exact import rewrite.
55
+ '@streamdown/code': '@octanejs/streamdown',
56
+ '@streamdown/math': '@octanejs/streamdown',
57
+ '@streamdown/mermaid': '@octanejs/streamdown',
58
+ '@streamdown/cjk': '@octanejs/streamdown',
42
59
  shadcn: '@octanejs/shadcn',
43
60
  recharts: '@octanejs/recharts',
44
61
  '@react-three/fiber': '@octanejs/three',
@@ -93,6 +110,8 @@ export const KNOWN_BINDINGS = {
93
110
  '@visx/zoom': '@octanejs/visx',
94
111
  'react-redux': '@octanejs/redux',
95
112
  '@reduxjs/toolkit': '@octanejs/redux-toolkit',
113
+ '@react-rxjs/core': '@octanejs/rxjs',
114
+ '@react-rxjs/utils': '@octanejs/rxjs',
96
115
  '@testing-library/react': '@octanejs/testing-library',
97
116
  'react-i18next': '@octanejs/i18next',
98
117
  '@mdx-js/react': '@octanejs/mdx',
@@ -102,7 +121,7 @@ export const KNOWN_BINDINGS = {
102
121
  // Octane-specific ecosystem packages that have no React import to rewrite.
103
122
  // Keep these out of KNOWN_BINDINGS so the React bridge never invents a source
104
123
  // package mapping for native tooling.
105
- export const KNOWN_NATIVE_BINDINGS = new Set(['@octanejs/devtools']);
124
+ export const KNOWN_NATIVE_BINDINGS = new Set(['@octanejs/devtools', '@octanejs/tauri']);
106
125
 
107
126
  // Workspace directory names for the maintained bindings. Keep this derived
108
127
  // from both catalogs so repository path routing cannot drift from the public
@@ -116,6 +135,7 @@ export const KNOWN_BINDING_PACKAGE_DIRS = new Set(
116
135
  export const KNOWN_VANILLA_CORES = {
117
136
  '@apollo/client': '@apollo/client',
118
137
  '@tanstack/react-query': '@tanstack/query-core',
138
+ wagmi: '@wagmi/core',
119
139
  '@tanstack/react-table': '@tanstack/table-core',
120
140
  '@tanstack/react-virtual': '@tanstack/virtual-core',
121
141
  '@tanstack/react-form': '@tanstack/form-core',
@@ -532,6 +552,9 @@ function planFor(report) {
532
552
  `Reuse the framework-agnostic core '${report.vanillaCore}' unchanged; it has no React imports and runs on Octane as-is.`,
533
553
  );
534
554
  }
555
+ steps.push(
556
+ "Pin the upstream version you are bridging and copy that release's React binding source into your repository beside the port, keeping the upstream LICENSE and leaving the copy unmodified, then work through it module by module. A bridge written from the README or the type declarations covers the demo path and drops the rest of the API; the copy is also the diff you review on the next upgrade.",
557
+ );
535
558
  steps.push(
536
559
  'Re-implement the React binding layer (the hooks/components that import react) against Octane hooks of the same names. Most store bindings reduce to useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot).',
537
560
  );
@@ -554,7 +577,10 @@ function planFor(report) {
554
577
  'Re-author any JSX components shipped by the package in .tsrx: compiled React JSX output cannot run on Octane, and hooks called from non-compiled files need compiler slotting (see the bridge-react-package skill for the subSlot pattern).',
555
578
  );
556
579
  steps.push(
557
- 'Validate with tests that drive real DOM events and compare behavior against the React original where possible.',
580
+ "Run the pinned release's own test suite against the bridge where it ships one: framework-neutral suites unmodified against the reused core, React-binding suites ported case by case (fixtures in .tsrx, @octanejs/testing-library for @testing-library/react, upstream case names kept). Note which upstream test files you ran, ported, or left out and why, and triage a failure before touching its assertion.",
581
+ );
582
+ steps.push(
583
+ 'Validate the rest with tests that drive real DOM events and compare behavior against the React original where possible.',
558
584
  );
559
585
  return steps;
560
586
  }
@@ -186,6 +186,18 @@ describe('bridgeReport', () => {
186
186
  expect(report.plan[0]).toContain('@octanejs/zustand');
187
187
  });
188
188
 
189
+ it('tells the caller to bridge from a pinned copy of the upstream source', async () => {
190
+ const root = await mkdtemp(join(tmpdir(), 'octane-bridge-'));
191
+ await writeFakePackage(root, 'widgets', {
192
+ 'index.js': `
193
+ import { useState } from 'react';
194
+ export function useWidget() { return useState(0); }
195
+ `,
196
+ });
197
+ const report = await bridgeReport({ packageName: 'widgets', projectRoot: root });
198
+ expect(report.plan.join('\n')).toContain('Pin the upstream version');
199
+ });
200
+
189
201
  it('errors clearly when the package is not installed', async () => {
190
202
  const root = await mkdtemp(join(tmpdir(), 'octane-bridge-'));
191
203
  const report = await bridgeReport({ packageName: 'missing-lib', projectRoot: root });
@@ -253,6 +265,19 @@ describe('bridgeReportFromSource', () => {
253
265
  });
254
266
 
255
267
  describe('KNOWN_BINDINGS', () => {
268
+ it('maps Streamdown and every official plugin package to the consolidated binding', () => {
269
+ const upstreamPackages = [
270
+ 'streamdown',
271
+ '@streamdown/code',
272
+ '@streamdown/math',
273
+ '@streamdown/mermaid',
274
+ '@streamdown/cjk',
275
+ ];
276
+ expect(upstreamPackages.every((name) => KNOWN_BINDINGS[name] === '@octanejs/streamdown')).toBe(
277
+ true,
278
+ );
279
+ });
280
+
256
281
  it('maps every public Visx entry point to the aggregate Octane port', async () => {
257
282
  const packagesRoot = fileURLToPath(new URL('../..', import.meta.url));
258
283
  const manifest = JSON.parse(await readFile(join(packagesRoot, 'visx', 'package.json'), 'utf8'));
package/src/index.js CHANGED
@@ -46,18 +46,32 @@ export const BENCHMARK_SUITES = [
46
46
  'weather-app',
47
47
  'weather-app-lighthouse',
48
48
  'chat-stream',
49
+ 'streamdown-hosted',
49
50
  'dbmon',
50
51
  'recursive-context',
51
52
  'signal-favoring',
52
53
  'news',
53
54
  'hydration-interactivity',
55
+ 'hydration-stress',
56
+ 'lifecycle-memory',
57
+ 'controlled-form',
58
+ 'external-store-fanout',
59
+ 'external-store-integrations',
60
+ 'scheduler-responsiveness',
61
+ 'suspense-recovery',
62
+ 'event-delegation',
63
+ 'application-composition',
64
+ 'scaling-curves',
65
+ 'store-selector-fanout',
54
66
  'effectful-list',
67
+ 'list-clear',
55
68
  'memo-wall',
56
69
  'portal-swarm',
57
70
  'react-hosted-islands',
58
71
  'ssr-throughput',
59
72
  'streaming-ssr',
60
73
  'ssr-http',
74
+ 'streaming-backpressure',
61
75
  'ssr-workerd',
62
76
  'tanstack-start',
63
77
  'dbmon-deopt',
@@ -65,8 +79,10 @@ export const BENCHMARK_SUITES = [
65
79
  'async-waterfall',
66
80
  'async-composition',
67
81
  'lynx-list',
82
+ 'lynx-render',
68
83
  'lynx-bundle-size',
69
84
  'codegen-size',
85
+ 'compiler-throughput',
70
86
  'bundle-size',
71
87
  'three-renderer',
72
88
  'three-bundle-size',
@@ -261,6 +277,17 @@ export function engineeringPlanFor(input, repoMode = false) {
261
277
  if (scope === 'framework-core' && repoMode) {
262
278
  plan.requiredSkills.push('octane-core-extend', 'performance-audit');
263
279
  }
280
+ // A binding is a port of one pinned upstream release, so its gates are about
281
+ // coverage of that release rather than the runtime cost gates above.
282
+ if (plan.areas.some((entry) => entry.area === 'ecosystem-binding')) {
283
+ plan.gates.parity = [
284
+ 'Port module by module from the pinned upstream release vendored under packages/<name>/upstream/, not from the README, the type declarations, or memory.',
285
+ 'Account for every export of the pinned upstream React entry points in the packages/<name>/UPSTREAM.md crosswalk: ported, reused verbatim from a framework-neutral core, divergence, or not applicable, each with its evidence. An unfinished export is an explicit gap row.',
286
+ '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.',
287
+ "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.",
288
+ ];
289
+ if (repoMode) plan.requiredSkills.push('react-library-port');
290
+ }
264
291
  if (scope === 'framework-core' && !repoMode) {
265
292
  plan.blockingConditions = [
266
293
  '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.',
package/src/index.test.js CHANGED
@@ -143,6 +143,25 @@ describe('@octanejs/mcp-server helpers', () => {
143
143
  expect(plan.validationCommands).toContain('node benchmarks/bench.mjs --quick --ratios');
144
144
  });
145
145
 
146
+ it('requires pinned-upstream coverage for binding work', () => {
147
+ const plan = engineeringPlanFor(
148
+ { scope: 'library', changeKind: 'feature', paths: ['packages/zustand/src/index.ts'] },
149
+ true,
150
+ );
151
+
152
+ expect(plan.requiredSkills).toContain('react-library-port');
153
+ expect(plan.gates.parity.join('\n')).toContain('packages/<name>/UPSTREAM.md');
154
+ expect(plan.gates.parity.join('\n')).toContain('divergence');
155
+ expect(plan.gates.parity.join('\n')).toContain("pinned release's own suite");
156
+
157
+ const applicationPlan = engineeringPlanFor(
158
+ { scope: 'application', changeKind: 'feature', paths: ['src/App.tsrx'] },
159
+ true,
160
+ );
161
+ expect(applicationPlan.gates.parity).toBeUndefined();
162
+ expect(applicationPlan.requiredSkills).not.toContain('react-library-port');
163
+ });
164
+
146
165
  it('blocks framework-core plans when maintainer tools are unavailable', () => {
147
166
  const plan = engineeringPlanFor({ scope: 'framework-core', changeKind: 'bug' });
148
167