@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 +8 -5
- package/package.json +1 -1
- package/skills/bridge-react-package.md +33 -6
- package/skills/build-octane-software.md +4 -0
- package/skills/migrate-react-component.md +1 -1
- package/src/bridge.js +28 -2
- package/src/bridge.test.js +25 -0
- package/src/index.js +27 -0
- package/src/index.test.js +19 -0
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
|
-
`
|
|
163
|
-
`
|
|
164
|
-
`
|
|
165
|
-
|
|
166
|
-
|
|
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
|
@@ -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. **
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
131
|
-
|
|
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
|
|
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
|
-
'
|
|
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
|
}
|
package/src/bridge.test.js
CHANGED
|
@@ -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
|
|