@pyreon/mcp 0.14.0 → 0.15.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pyreon/mcp",
3
- "version": "0.14.0",
3
+ "version": "0.15.0",
4
4
  "description": "MCP server for Pyreon — AI-powered framework assistance",
5
5
  "homepage": "https://github.com/pyreon/pyreon/tree/main/packages/mcp#readme",
6
6
  "bugs": {
@@ -17,6 +17,7 @@
17
17
  },
18
18
  "files": [
19
19
  "lib",
20
+ "!lib/**/*.map",
20
21
  "src",
21
22
  "README.md",
22
23
  "LICENSE"
@@ -46,7 +47,7 @@
46
47
  },
47
48
  "dependencies": {
48
49
  "@modelcontextprotocol/sdk": "^1.29.0",
49
- "@pyreon/compiler": "^0.14.0",
50
+ "@pyreon/compiler": "^0.15.0",
50
51
  "zod": "^4.3.6"
51
52
  },
52
53
  "devDependencies": {
@@ -271,10 +271,10 @@ const getMode = useContext(ModeCtx) // reactive: returns () => T`,
271
271
  example: `<Show when={isLoggedIn()} fallback={<LoginForm />}>
272
272
  <Dashboard />
273
273
  </Show>`,
274
- notes: 'Reactive conditional rendering. Mounts children when `when` is truthy, unmounts and shows `fallback` when falsy. More efficient than ternary for signal-driven conditions because it avoids re-evaluating the entire branch expression on every signal change — `Show` only transitions between mounted/unmounted when the boolean flips. See also: Switch, Match, For.',
274
+ notes: `Reactive conditional rendering. Mounts children when \`when\` is truthy, unmounts and shows \`fallback\` when falsy. More efficient than ternary for signal-driven conditions because it avoids re-evaluating the entire branch expression on every signal change — \`Show\` only transitions between mounted/unmounted when the boolean flips. \`when\` accepts BOTH a value (\`when={true}\`, \`when={signal()}\`) and an accessor (\`when={() => signal()}\`) — the framework normalizes via \`typeof === "function"\`. The accessor form is required for true reactivity (the framework re-evaluates it on signal change); a bare \`when={signal}\` reference works because the compiler's signal auto-call rewrites it to \`when={signal()}\`. See also: Switch, Match, For.`,
275
275
  mistakes: `- \`{cond() ? <A /> : <B />}\` — works but less efficient than \`<Show>\` for signal-driven conditions
276
276
  - \`<Show when={items().length}>\` — works (truthy check), but be explicit: \`<Show when={items().length > 0}>\`
277
- - \`<Show when={user}>\` without calling the signal must call: \`<Show when={user()}>\``,
277
+ - \`<Show when={signal}>\` (bare reference) — relies on the compiler's signal auto-call to rewrite to \`when={signal()}\`. Works defensively but use \`when={() => signal()}\` for explicit accessor semantics across the entire reactive lifecycle.`,
278
278
  },
279
279
 
280
280
  'core/Switch': {
@@ -299,7 +299,7 @@ const getMode = useContext(ModeCtx) // reactive: returns () => T`,
299
299
  <Match when={tab() === "home"}><Home /></Match>
300
300
  <Match when={tab() === "settings"}><Settings /></Match>
301
301
  </Switch>`,
302
- notes: 'A branch inside a `<Switch>`. Renders its children when `when` is truthy and it is the first truthy `<Match>` in the parent `<Switch>`. Must be a direct child of `<Switch>`. See also: Switch, Show.',
302
+ notes: 'A branch inside a `<Switch>`. Renders its children when `when` is truthy and it is the first truthy `<Match>` in the parent `<Switch>`. Must be a direct child of `<Switch>`. `when` accepts both a value and an accessor (same normalization as `<Show>`). See also: Switch, Show.',
303
303
  },
304
304
 
305
305
  'core/For': {
@@ -445,6 +445,35 @@ return <input ref={inputRef} />`,
445
445
  notes: 'Execute a function reading signals WITHOUT subscribing to them. Alias for `runUntracked` from `@pyreon/reactivity`. Use inside effects when you need a one-shot snapshot of a signal value without the effect re-running when that signal changes. See also: @pyreon/reactivity.',
446
446
  },
447
447
 
448
+ 'core/nativeCompat': {
449
+ signature: '<T>(fn: T) => T',
450
+ example: `// In a framework package:
451
+ export const RouterView = nativeCompat(function RouterView(props) {
452
+ provide(RouterContext, ...)
453
+ return <div>{children}</div>
454
+ })`,
455
+ notes: `Mark a Pyreon framework component as "self-managing" so compat layers (\`@pyreon/{react,preact,vue,solid}-compat\`) skip their wrapping and route the component through Pyreon's mount path. Use on every \`@pyreon/*\` JSX component whose setup body uses \`provide()\` / lifecycle hooks / signal subscriptions — wrapping breaks those by running the body inside the compat layer's render context instead of Pyreon's. Idempotent; non-function inputs pass through unchanged. The marker is a registry symbol (\`Symbol.for("pyreon:native-compat")\`), so framework and compat sides share it without an import dependency between them. See also: isNativeCompat, NATIVE_COMPAT_MARKER.`,
456
+ mistakes: `- Forgetting to mark a new framework JSX export — under compat mode, the component's \`provide()\` / \`onMount()\` calls fail with "called outside component setup" warnings and the rendered DOM silently breaks.
457
+ - Marking user-app components — only \`@pyreon/*\` framework components that already manage their own reactivity should be marked. User components in compat mode are SUPPOSED to be wrapped (that's how they get re-render-on-state-change semantics).`,
458
+ },
459
+
460
+ 'core/isNativeCompat': {
461
+ signature: '(fn: unknown) => boolean',
462
+ example: `// In a compat layer's jsx-runtime:
463
+ if (isNativeCompat(type)) return h(type, props)
464
+ return wrapCompatComponent(type)(props)`,
465
+ notes: 'Compat-layer-side: read whether a function has been marked as a Pyreon native framework component via `nativeCompat()`. Compat `jsx()` calls this to decide whether to skip the React/Vue/Solid/Preact-style wrapping. Always returns `false` for non-function inputs. See also: nativeCompat, NATIVE_COMPAT_MARKER.',
466
+ },
467
+
468
+ 'core/NATIVE_COMPAT_MARKER': {
469
+ signature: 'symbol',
470
+ example: `import { NATIVE_COMPAT_MARKER } from '@pyreon/core'
471
+
472
+ // Equivalent to nativeCompat(MyComponent):
473
+ ;(MyComponent as Record<symbol, boolean>)[NATIVE_COMPAT_MARKER] = true`,
474
+ notes: 'The well-known registry symbol (`Symbol.for("pyreon:native-compat")`) used to mark a component as a Pyreon native framework component. Most callers should use `nativeCompat()` / `isNativeCompat()` instead of touching the symbol directly; exported for advanced cases (e.g., a compat layer that wants to inspect the property without going through the helper). See also: nativeCompat, isNativeCompat.',
475
+ },
476
+
448
477
  'core/ExtractProps': {
449
478
  signature: 'type ExtractProps<T> = T extends ComponentFn<infer P> ? P : T',
450
479
  example: `const Greet: ComponentFn<{ name: string }> = ({ name }) => <h1>{name}</h1>
@@ -607,6 +636,58 @@ const User = () => {
607
636
  notes: `Access the data returned by the current route's \`loader\` function. The loader runs before the route component mounts; its return value is cached and available synchronously via this hook. Generic over the loader return type. See also: useMiddlewareData, useRoute.`,
608
637
  },
609
638
 
639
+ 'router/redirect': {
640
+ signature: 'redirect(url: string, status?: 301 | 302 | 303 | 307 | 308): never',
641
+ example: `// src/routes/app/_layout.tsx
642
+ import { redirect, type LoaderContext } from "@pyreon/router"
643
+
644
+ export async function loader(ctx: LoaderContext) {
645
+ // SSR: read from request headers; CSR: read from document.cookie
646
+ const cookie = ctx.request?.headers.get("cookie")
647
+ ?? (typeof document !== "undefined" ? document.cookie : "")
648
+ const sid = /(?:^|;\s*)sid=([^;]+)/.exec(cookie)?.[1]
649
+ if (!sid) redirect("/login")
650
+ const session = await getSession(sid)
651
+ if (!session) redirect("/login")
652
+ return { session }
653
+ }`,
654
+ notes: `Throw inside a route loader to redirect the navigation BEFORE the layout renders. On SSR (initial nav), the thrown error is converted by \`@pyreon/server\`'s handler into a real HTTP \`302\`/\`307\` \`Location:\` response — no layout HTML leaves the server. On CSR (subsequent nav), the redirect propagates through the navigate flow and triggers \`router.replace()\` before any matched route's component mounts. Replaces the fragile \`onMount + router.push()\` workaround for auth-gates under nested-layout dev SSR + hydration. Default status is \`307\` (Temporary Redirect, method-preserving). See also: notFound, useLoaderData, isRedirectError.`,
655
+ mistakes: `- Calling \`redirect()\` outside a loader (in a component body, an event handler, etc.) — the helper expects to be caught by the loader-runner. For imperative redirects from event handlers, use \`router.replace(target)\` instead.
656
+ - Forgetting to make \`LoaderContext.request\` access optional. It's populated only on SSR; CSR loaders see \`request: undefined\`. Read both: \`ctx.request?.headers.get('cookie') ?? document.cookie\`.
657
+ - Using \`redirect()\` for control-flow that should be a \`<Match>\` / \`<Show>\` conditional — the helper is for redirecting the URL, not for branching the rendered output.
658
+ - Returning \`redirect()\` instead of throwing it. The helper has return type \`never\` and throws — \`return redirect(...)\` is misleading and may suppress the throw under TS strict-null checks.
659
+ - Picking the wrong status. Default \`307\` preserves the request method (POST stays POST after redirect). Use \`302\`/\`303\` to force GET on the target. Use \`301\`/\`308\` for PERMANENT moves (browsers cache them aggressively).
660
+ - Assuming \`redirect()\` cancels every loader in a sibling chain. The first loader to throw wins; later loaders in the same \`Promise.allSettled\` batch may have already started executing before the redirect short-circuits. Treat them as best-effort.`,
661
+ },
662
+
663
+ 'router/isRedirectError': {
664
+ signature: 'isRedirectError(err: unknown): boolean',
665
+ example: `import { ErrorBoundary } from "@pyreon/core"
666
+ import { isRedirectError } from "@pyreon/router"
667
+
668
+ <ErrorBoundary fallback={(err, reset) => {
669
+ if (isRedirectError(err)) throw err // let the framework handle it
670
+ return <ErrorPage error={err} onReset={reset} />
671
+ }}>
672
+ <App />
673
+ </ErrorBoundary>`,
674
+ notes: 'Type guard for errors thrown by `redirect()`. Used internally by the router (CSR) and `@pyreon/server` (SSR) to distinguish redirect-control-flow errors from real failures. Useful in custom error boundaries that should let redirects pass through to the framework instead of catching them. See also: redirect, isNotFoundError, getRedirectInfo.',
675
+ },
676
+
677
+ 'router/getRedirectInfo': {
678
+ signature: 'getRedirectInfo(err: unknown): { url: string; status: 301 | 302 | 303 | 307 | 308 } | null',
679
+ example: `import { getRedirectInfo } from "@pyreon/router"
680
+
681
+ try {
682
+ await prefetchLoaderData(router, path, request)
683
+ } catch (err) {
684
+ const info = getRedirectInfo(err)
685
+ if (info) return new Response(null, { status: info.status, headers: { Location: info.url } })
686
+ throw err
687
+ }`,
688
+ notes: `Extract the redirect URL and status from a thrown RedirectError. Returns \`null\` for non-redirect errors. Used by \`@pyreon/server\`'s SSR handler to convert the thrown error into a 302/307 \`Response\`. See also: redirect, isRedirectError.`,
689
+ },
690
+
610
691
  'router/useSearchParams': {
611
692
  signature: 'useSearchParams<T>(defaults?: T): [get: () => T, set: (updates: Partial<T>) => Promise<void>]',
612
693
  example: `const [search, setSearch] = useSearchParams({ page: "1", sort: "name" })
@@ -2197,7 +2278,7 @@ lint({
2197
2278
  "pyreon/no-window-in-ssr": { exemptPaths: ["src/foundation/"] },
2198
2279
  },
2199
2280
  })`,
2200
- notes: '59 rules across 12 categories. Auto-loads `.pyreonlintrc.json`. Presets: `recommended`, `strict`, `app`, `lib`. Per-rule options via tuple form in config (`["error", { exemptPaths: [...] }]`) or `ruleOptionsOverrides`. Wrong-typed options surface on `result.configDiagnostics`. Uses `oxc-parser` with AST caching. See also: lintFile, getPreset, AstCache.',
2281
+ notes: '62 rules across 12 categories. Auto-loads `.pyreonlintrc.json`. Presets: `recommended`, `strict`, `app`, `lib`. Per-rule options via tuple form in config (`["error", { exemptPaths: [...] }]`) or `ruleOptionsOverrides`. Wrong-typed options surface on `result.configDiagnostics`. Uses `oxc-parser` with AST caching. See also: lintFile, getPreset, AstCache.',
2201
2282
  },
2202
2283
 
2203
2284
  'lint/lintFile': {
@@ -2216,7 +2297,7 @@ const result = lintFile("app.tsx", source, allRules, config, cache, configSink)`
2216
2297
  example: `pyreon-lint --preset strict --quiet # CI mode
2217
2298
  pyreon-lint --fix # auto-fix
2218
2299
  pyreon-lint --watch src/ # watch mode
2219
- pyreon-lint --list # list all 59 rules
2300
+ pyreon-lint --list # list all 62 rules
2220
2301
  pyreon-lint --format json # machine-readable
2221
2302
  pyreon-lint --rule-options 'pyreon/no-window-in-ssr={"exemptPaths":["src/foundation/"]}' src/`,
2222
2303
  notes: `CLI entry. Config: \`.pyreonlintrc.json\` (reference \`schema/pyreonlintrc.schema.json\` for IDE autocomplete) or \`package.json\`'s \`'pyreonlint'\` field. Ignore: \`.pyreonlintignore\` + \`.gitignore\`. Watch: \`fs.watch\` recursive with 100ms debounce. \`--rule-options id='{json}'\` passes per-rule options on a single run. See also: lint.`,