@nifrajs/cli 2.3.0 → 2.5.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.
Files changed (47) hide show
  1. package/dist/check.d.ts +1 -1
  2. package/dist/check.d.ts.map +1 -1
  3. package/dist/check.js +28 -2
  4. package/dist/check.js.map +1 -1
  5. package/dist/cli.d.ts.map +1 -1
  6. package/dist/cli.js +17 -4
  7. package/dist/cli.js.map +1 -1
  8. package/dist/doctor.d.ts +27 -0
  9. package/dist/doctor.d.ts.map +1 -1
  10. package/dist/doctor.js +158 -15
  11. package/dist/doctor.js.map +1 -1
  12. package/dist/learn.d.ts +32 -0
  13. package/dist/learn.d.ts.map +1 -0
  14. package/dist/learn.js +127 -0
  15. package/dist/learn.js.map +1 -0
  16. package/dist/mcp-app-tool.d.ts +14 -0
  17. package/dist/mcp-app-tool.d.ts.map +1 -0
  18. package/dist/mcp-app-tool.js +80 -0
  19. package/dist/mcp-app-tool.js.map +1 -0
  20. package/dist/mcp-docs-tools.d.ts +1 -1
  21. package/dist/mcp-docs-tools.d.ts.map +1 -1
  22. package/dist/mcp-docs-tools.js +17 -1
  23. package/dist/mcp-docs-tools.js.map +1 -1
  24. package/dist/mcp-http.d.ts +10 -6
  25. package/dist/mcp-http.d.ts.map +1 -1
  26. package/dist/mcp-http.js +30 -12
  27. package/dist/mcp-http.js.map +1 -1
  28. package/dist/mcp.d.ts +14 -0
  29. package/dist/mcp.d.ts.map +1 -1
  30. package/dist/mcp.js +188 -0
  31. package/dist/mcp.js.map +1 -1
  32. package/dist/upgrade.d.ts +13 -2
  33. package/dist/upgrade.d.ts.map +1 -1
  34. package/dist/upgrade.js +105 -7
  35. package/dist/upgrade.js.map +1 -1
  36. package/docs/llms-full.txt +104 -2
  37. package/docs/types.json +202 -26
  38. package/package.json +9 -9
  39. package/src/check.ts +30 -2
  40. package/src/cli.ts +21 -4
  41. package/src/doctor.ts +186 -3
  42. package/src/learn.ts +152 -0
  43. package/src/mcp-app-tool.ts +86 -0
  44. package/src/mcp-docs-tools.ts +18 -1
  45. package/src/mcp-http.ts +32 -14
  46. package/src/mcp.ts +237 -0
  47. package/src/upgrade.ts +125 -7
package/docs/types.json CHANGED
@@ -128,13 +128,13 @@
128
128
  "name": "AdversarialContractOptions",
129
129
  "kind": "interface",
130
130
  "package": "@nifrajs/testing",
131
- "signature": "export interface AdversarialContractOptions {\n /** Base origin for generated requests. Default `http://nifra.contract`. */\n readonly origin?: string;\n /** Replayable seed used for deterministic witness generation. */\n readonly seed?: number;\n /** Known-good request values keyed by `\"METHOD /path\"`. Required for opaque Standard Schemas. */\n readonly witnesses?: Readonly<Record<string, ContractWitness>>;\n /** Select a subset of reflected routes. */\n readonly include?: (route: ReflectedRoute) => boolean;\n /** Execute every case against these targets. Default: the supplied app, named `in-process`. */\n readonly runtimes?: readonly ContractRuntime[];\n /** Maximum validator-proven mutations per body/query schema. Default 32. */\n readonly maxMutationsPerInput?: number;\n /** Maximum serialized witness size. Default 256 KiB. */\n readonly maxWitnessBytes?: number;\n /** Statuses that prove an invalid input was rejected. Default `[422]`. */\n readonly expectedValidationStatuses?: readonly number[];\n /**\n * Attach auth, tenant, or runtime-specific state. Return a new Request; the input Request is already\n * fresh for this case and runtime.\n */\n readonly prepareRequest?: (request: Request, context: ContractCaseContext) => Request | Promise<Request>;\n /** Override rejection semantics (for apps with a custom validation response). */\n readonly isRejected?: (response: Response, context: ContractCaseContext) => boolean | Promise<boolean>;\n /** Validate a real successful response for every declared response schema. Default true. */\n readonly validateResponses?: boolean;\n /** Fail the report when a contracted target cannot be exercised. Default true. */\n readonly requireCoverage?: boolean;\n /** Greedily minimize an invalid input that unexpectedly reaches the app. Default true. */\n readonly shrinkFailures?: boolean;\n /** Cap shrink probes for one failure. Default 12. */\n readonly maxShrinkAttempts?: number;\n /** Replay only these stable case IDs. Coverage gaps are advisory when this is set. */\n readonly only?: string | readonly string[];\n}"
131
+ "signature": "export interface AdversarialContractOptions {\n /** Base origin for generated requests. Default `http://nifra.contract`. */\n readonly origin?: string;\n /** Replayable seed used for deterministic witness generation. */\n readonly seed?: number;\n /** Known-good request values keyed by `\"METHOD /path\"`. Required for opaque Standard Schemas. */\n readonly witnesses?: Readonly<Record<string, ContractWitness>>;\n /** Select a subset of reflected routes. */\n readonly include?: (route: ReflectedRoute) => boolean;\n /** Execute every case against these targets. Default: the supplied app, named `in-process`. */\n readonly runtimes?: readonly ContractRuntime[];\n /** Maximum validator-proven mutations per body/query schema. Default 32. */\n readonly maxMutationsPerInput?: number;\n /** Maximum serialized witness size. Default 256 KiB. */\n readonly maxWitnessBytes?: number;\n /** Statuses that prove an invalid input was rejected. Default `[422]`. */\n readonly expectedValidationStatuses?: readonly number[];\n /**\n * Attach auth, tenant, or runtime-specific state. Return a new Request; the input Request is already\n * fresh for this case and runtime.\n */\n readonly prepareRequest?: (request: Request, context: ContractCaseContext) => Request | Promise<Request>;\n /** Override rejection semantics (for apps with a custom validation response). */\n readonly isRejected?: (response: Response, context: ContractCaseContext) => boolean | Promise<boolean>;\n /** Validate a real successful response for every declared response schema. Default true. */\n readonly validateResponses?: boolean;\n /** Fail the report when a contracted target cannot be exercised. Default true. */\n readonly requireCoverage?: boolean;\n /** Greedily minimize an invalid input that unexpectedly reaches the app. Default true. */\n readonly shrinkFailures?: boolean;\n /** Cap shrink probes for one failure. Default 12. */\n readonly maxShrinkAttempts?: number;\n /** Replay only these stable case IDs. Coverage gaps are advisory when this is set. */\n readonly only?: string | readonly string[];\n /**\n * Derive an inspectable JSON Schema from a validator that carries none (zod/valibot/arktype expose a\n * Standard Schema validator but no `.jsonSchema`). Called for any body/query schema whose `jsonSchema`\n * is undefined; a non-undefined return turns on witness synthesis AND constraint-driven mutation\n * generation for that route. Fail-safe: throwing or returning undefined leaves the schema opaque\n * (NO_WITNESS, as today). Ready-made for zod: `import { zodJsonSchema } from \"@nifrajs/testing/zod\"`.\n *\n * Defaults to `@nifrajs/mock`'s `autoReflectJsonSchema`, which recognizes zod by its Standard Schema\n * vendor tag and converts via `z.toJSONSchema` when zod is installed - so zod routes get witnesses and\n * constraint-driven mutations with no wiring. Pass a hook to override, or `() => undefined` to force\n * every opaque schema to stay opaque.\n */\n readonly reflectJsonSchema?: (schema: unknown) => JsonSchema | undefined;\n}"
132
132
  },
133
133
  {
134
134
  "name": "AdversarialContractReport",
135
135
  "kind": "interface",
136
136
  "package": "@nifrajs/testing",
137
- "signature": "export interface AdversarialContractReport {\n readonly ok: boolean;\n readonly seed: number;\n readonly routeCount: number;\n readonly targetCount: number;\n readonly runtimeCount: number;\n readonly results: readonly AdversarialContractResult[];\n readonly failures: readonly AdversarialContractResult[];\n readonly gaps: readonly ContractCoverageGap[];\n readonly counts: {\n readonly passed: number;\n readonly failed: number;\n readonly gaps: number;\n };\n}"
137
+ "signature": "export interface AdversarialContractReport {\n readonly ok: boolean;\n readonly seed: number;\n readonly routeCount: number;\n readonly targetCount: number;\n readonly runtimeCount: number;\n readonly results: readonly AdversarialContractResult[];\n readonly failures: readonly AdversarialContractResult[];\n readonly gaps: readonly ContractCoverageGap[];\n /** Non-fatal notes about coverage that is legitimately absent (does not affect `ok`). */\n readonly advisories: readonly string[];\n readonly counts: {\n readonly passed: number;\n readonly failed: number;\n readonly gaps: number;\n };\n}"
138
138
  },
139
139
  {
140
140
  "name": "AdversarialContractResult",
@@ -523,6 +523,13 @@
523
523
  "signature": "export type AuthPlugin<P> = NifraPlugin & {\n /** The verified principal for this request, or `null` (no/invalid token in `optional` mode). */\n principal(request: Request): P | null;\n /** The verified principal, or **throws a `401` `Response`** when absent. */\n requirePrincipal(request: Request): P;\n};",
524
524
  "doc": "/**\n * A token-auth plugin (`bearer` / `apiKey`). Apply it with `app.use(auth)` - it rejects unauthorized\n * requests to **routes defined after it** with `401` (unless `optional`). Read the verified principal\n * inside a handler/loader via {@link AuthPlugin.principal} (nullable) or\n * {@link AuthPlugin.requirePrincipal} (throws `401` when absent). The principal is verified once per\n * request and cached, so reading it is free.\n *\n * `beforeHandle` is **order-scoped**: place `app.use(auth)` before the routes it should guard. Hold the\n * returned instance to read the principal (mirrors `@nifrajs/auth`'s `sessions.get(c)` and\n * `@nifrajs/better-auth`'s `getSession(auth, req)`):\n *\n * ```ts\n * const auth = bearer({ verify: (t) => lookupUser(t) }) // P = User (inferred)\n * const app = server().use(auth).get(\"/me\", (c) => auth.requirePrincipal(c.req))\n * ```\n */"
525
525
  },
526
+ {
527
+ "name": "autoReflectJsonSchema",
528
+ "kind": "function",
529
+ "package": "@nifrajs/mock",
530
+ "signature": "export declare function autoReflectJsonSchema(schema: unknown): JsonSchema | undefined;",
531
+ "doc": "/**\n * Derive a JSON Schema from a zod Standard Schema, or `undefined` for anything else (non-zod vendors,\n * zod not installed, unconvertible schemas). Safe as an always-on default: it can only ever turn an\n * opaque schema inspectable, never change one that already carries JSON Schema metadata.\n */"
532
+ },
526
533
  {
527
534
  "name": "Await",
528
535
  "kind": "function",
@@ -931,6 +938,26 @@
931
938
  "signature": "export interface BuildClientViteOptions extends Omit<BuildClientOptions, \"plugins\"> {\n /** Vite plugins for the app's client transforms (e.g. `react()`, `vue()`). */\n readonly vitePlugins?: readonly unknown[];\n /** Vite project root (default: the parent of `routesDir`). Manifest keys are relative to it. */\n readonly root?: string;\n}",
932
939
  "doc": "/** Options for {@link buildClientVite} - the Bun {@link BuildClientOptions} minus Bun-plugin specifics,\n * plus the Vite plugin list the app injects for its transforms. */"
933
940
  },
941
+ {
942
+ "name": "buildCodeframe",
943
+ "kind": "function",
944
+ "package": "@nifrajs/web",
945
+ "signature": "export declare function buildCodeframe(file: string, line: number, column: number | undefined, read?: SourceReader, radius?: number): Codeframe | undefined;",
946
+ "doc": "/**\n * Build a source codeframe: `radius` lines either side of `line`, each tagged with its 1-based number\n * and whether it is the offending line. Returns undefined if the source can't be read or the line is\n * out of range - a diagnostic without a codeframe is still useful, so this never throws.\n */"
947
+ },
948
+ {
949
+ "name": "buildDiagnostic",
950
+ "kind": "function",
951
+ "package": "@nifrajs/web",
952
+ "signature": "export declare function buildDiagnostic(err: unknown, options?: BuildDiagnosticOptions): Diagnostic;",
953
+ "doc": "/**\n * Resolve any thrown value into a `Diagnostic`: parse the (already source-mapped) stack, locate the top\n * user frame, attach a codeframe, and classify the failure for a cause/fix. The caller is responsible\n * for running Vite's `ssrFixStacktrace` first so the frames point at real source.\n */"
954
+ },
955
+ {
956
+ "name": "BuildDiagnosticOptions",
957
+ "kind": "interface",
958
+ "package": "@nifrajs/web",
959
+ "signature": "export interface BuildDiagnosticOptions {\n readonly request?: {\n readonly method: string;\n readonly url: string;\n };\n /** Project root; frames outside it are treated as non-user. Defaults to `process.cwd()`. */\n readonly root?: string;\n /** Injectable source reader (tests pass a fake; production reads the filesystem). */\n readonly read?: SourceReader;\n}"
960
+ },
934
961
  {
935
962
  "name": "buildManifest",
936
963
  "kind": "function",
@@ -1718,6 +1745,13 @@
1718
1745
  "package": "@nifrajs/core",
1719
1746
  "signature": "export type ClassifiedSchema<S extends object> = S & {\n readonly [CLASSIFICATION]: DataClassification;\n};"
1720
1747
  },
1748
+ {
1749
+ "name": "classify",
1750
+ "kind": "function",
1751
+ "package": "@nifrajs/web",
1752
+ "signature": "export declare function classify(name: string, message: string): {\n code: string;\n cause?: string;\n fix?: string;\n docsAnchor?: string;\n};",
1753
+ "doc": "/** Classify an error name+message against the catalog; falls back to the generic unhandled code. */"
1754
+ },
1721
1755
  {
1722
1756
  "name": "CLAUDE_MD_PATH",
1723
1757
  "kind": "const",
@@ -1823,6 +1857,13 @@
1823
1857
  "package": "@nifrajs/web",
1824
1858
  "signature": "export interface CloudflareRouteRules {\n readonly include: readonly string[];\n readonly exclude: readonly string[];\n /** Public files the budget could not name, so the build can say so instead of capping silently. */\n readonly omitted: readonly string[];\n}"
1825
1859
  },
1860
+ {
1861
+ "name": "Codeframe",
1862
+ "kind": "interface",
1863
+ "package": "@nifrajs/web",
1864
+ "signature": "export interface Codeframe {\n readonly file: string;\n readonly line: number;\n readonly column?: number | undefined;\n readonly lines: ReadonlyArray<{\n readonly number: number;\n readonly text: string;\n readonly caret: boolean;\n }>;\n}",
1865
+ "doc": "/** A source window around the offending line. `caret` marks the exact line the top frame points at. */"
1866
+ },
1826
1867
  {
1827
1868
  "name": "Collection",
1828
1869
  "kind": "interface",
@@ -2316,7 +2357,7 @@
2316
2357
  "name": "CreateMcpServerOptions",
2317
2358
  "kind": "interface",
2318
2359
  "package": "@nifrajs/mcp",
2319
- "signature": "export interface CreateMcpServerOptions {\n readonly name: string;\n readonly version: string;\n readonly tools?: readonly McpTool[];\n /** MCP Apps widgets - their resources are registered and the UI capability is advertised. */\n readonly widgets?: readonly McpWidget[];\n /** Extra (non-widget) resources to expose. */\n readonly resources?: readonly McpResource[];\n readonly prompts?: readonly McpPrompt[];\n /** GET health-page text. */\n readonly health?: string;\n /** Max JSON-RPC body size in bytes (default 1 MB). */\n readonly maxBodyBytes?: number;\n}"
2360
+ "signature": "export interface CreateMcpServerOptions {\n readonly name: string;\n readonly version: string;\n readonly tools?: readonly McpTool[];\n /** MCP Apps widgets - their resources are registered and the UI capability is advertised. */\n readonly widgets?: readonly McpWidget[];\n /** Extra (non-widget) resources to expose. */\n readonly resources?: readonly McpResource[];\n readonly prompts?: readonly McpPrompt[];\n /** GET health-page text. */\n readonly health?: string;\n /** Max JSON-RPC body size in bytes (default 1 MB). */\n readonly maxBodyBytes?: number;\n /** Natural-language guidance for LLMs, surfaced in the modern `server/discover` result (2026-07-28). */\n readonly instructions?: string;\n /** Origin allowlist for the DNS-rebinding guard. Omit to allow any origin; set it to reject other\n * browser origins with 403 (e.g. a hardened, non-public mount). */\n readonly allowedOrigins?: readonly string[];\n}"
2320
2361
  },
2321
2362
  {
2322
2363
  "name": "createMemoryCausalityStore",
@@ -2931,8 +2972,7 @@
2931
2972
  "name": "devtools",
2932
2973
  "kind": "function",
2933
2974
  "package": "@nifrajs/devtools",
2934
- "signature": "export declare function devtools(options?: DevToolsOptions | undefined): import(\"@nifrajs/core/server\").NifraPlugin<import(\"@nifrajs/core/server\").AnyServer, import(\"@nifrajs/core/server\").AnyServer>;",
2935
- "doc": "/**\n * DevTools plugin. Its observation adapter projects the single request span into a\n * `DevToolsEvent`; its middleware only owns the secured SSE transport.\n * When configuring `tracing()` yourself, register it before this plugin so DevTools attaches to\n * that request owner.\n */"
2975
+ "signature": "export declare function devtools(options?: DevToolsOptions | undefined): import(\"@nifrajs/core/server\").NifraPlugin<import(\"@nifrajs/core/server\").AnyServer, import(\"@nifrajs/core/server\").AnyServer>;"
2936
2976
  },
2937
2977
  {
2938
2978
  "name": "DevToolsClientOptions",
@@ -2960,6 +3000,34 @@
2960
3000
  "package": "@nifrajs/devtools",
2961
3001
  "signature": "export interface DevToolsOptions {\n /** Maximum events to retain in the ring buffer (default 200). */\n readonly maxEvents?: number | undefined;\n /** Maximum simultaneous SSE connections (default 5). */\n readonly maxConnections?: number | undefined;\n /** SSE path (default `/_nifra/devtools`). */\n readonly path?: string | undefined;\n /** Explicit enable switch. Defaults to true only when `NODE_ENV === \"development\"`. */\n readonly enabled?: boolean | undefined;\n /** Permit non-loopback URL hosts. Default false. */\n readonly allowRemote?: boolean | undefined;\n /** Additional origins permitted to open the stream. Same-origin requests are always allowed. */\n readonly allowedOrigins?: readonly string[] | undefined;\n /** Optional request authorization for the stream. */\n readonly authorize?: ((request: Request) => boolean | Promise<boolean>) | undefined;\n /**\n * Interval for SSE keep-alive comment frames, in milliseconds (default 15000). Pings stop\n * idle proxies/dev tunnels from dropping the stream and evict dead connections so they no\n * longer count against `maxConnections`.\n */\n readonly pingIntervalMs?: number | undefined;\n}"
2962
3002
  },
3003
+ {
3004
+ "name": "DevToolsQuery",
3005
+ "kind": "interface",
3006
+ "package": "@nifrajs/devtools",
3007
+ "signature": "export interface DevToolsQuery {\n readonly path?: string | undefined;\n readonly limit?: number | undefined;\n}",
3008
+ "doc": "/** Query for a DevTools state snapshot: an optional `path` prefix and a most-recent-`limit`. */"
3009
+ },
3010
+ {
3011
+ "name": "Diagnostic",
3012
+ "kind": "interface",
3013
+ "package": "@nifrajs/web",
3014
+ "signature": "export interface Diagnostic {\n /** Stable, greppable identifier, e.g. `NIFRA_SERVER_ONLY_IN_CLIENT`. `NIFRA_UNHANDLED` when unrecognised. */\n readonly code: string;\n readonly name: string;\n readonly message: string;\n readonly request?: {\n readonly method: string;\n readonly url: string;\n } | undefined;\n readonly frames: readonly DiagnosticFrame[];\n readonly codeframe?: Codeframe | undefined;\n /** Plain-language \"why this happened\", when the failure is recognised. */\n readonly cause?: string | undefined;\n /** Plain-language \"do this\", when the failure is recognised. */\n readonly fix?: string | undefined;\n /** Docs section anchor for the code, e.g. `errors#server-only-in-client`. */\n readonly docsAnchor?: string | undefined;\n}",
3015
+ "doc": "/** The structured failure. Serialisable as-is to JSON for the agent surfaces. */"
3016
+ },
3017
+ {
3018
+ "name": "DIAGNOSTIC_CATALOG",
3019
+ "kind": "const",
3020
+ "package": "@nifrajs/web",
3021
+ "signature": "export declare const DIAGNOSTIC_CATALOG: readonly CatalogEntry[];",
3022
+ "doc": "/**\n * The recognised-failure catalog. Seeded with the highest-signal nifra failures; extend it as new\n * classes of error earn a stable code. Order matters only in that the first match wins.\n */"
3023
+ },
3024
+ {
3025
+ "name": "DiagnosticFrame",
3026
+ "kind": "interface",
3027
+ "package": "@nifrajs/web",
3028
+ "signature": "export interface DiagnosticFrame {\n readonly raw: string;\n readonly file?: string;\n readonly line?: number;\n readonly column?: number;\n}",
3029
+ "doc": "/** One parsed stack frame. `file`/`line`/`column` are present only when the frame could be located. */"
3030
+ },
2963
3031
  {
2964
3032
  "name": "diffManifestRoutes",
2965
3033
  "kind": "function",
@@ -3526,6 +3594,20 @@
3526
3594
  "signature": "export interface Example {\n readonly name: string;\n /** The doc page's title, e.g. \"Nifra - Auth & sessions\". */\n readonly topic: string;\n /** The doc page slug, e.g. \"auth\" - a short, matchable tag. */\n readonly slug: string;\n readonly code: string;\n}",
3527
3595
  "doc": "/**\n * `nifra_example` - verified, copy-pasteable code examples for agents. The corpus is `examples.json`\n * (written into this package by `bun run gen:llms`): the same doc snippets `check:docs` typechecks\n * against the live `@nifrajs/*` API. So every example this returns is GUARANTEED to compile against the\n * installed version - the antidote to an agent hallucinating a framework API from stale training.\n *\n * Keyword-scored over each example's name/topic/slug (curated identifiers, heavily weighted) and its\n * code body. No query → a grouped index of what's available (the cheap discovery call).\n */"
3528
3596
  },
3597
+ {
3598
+ "name": "examplesAppTool",
3599
+ "kind": "function",
3600
+ "package": "@nifrajs/cli",
3601
+ "signature": "export declare function examplesAppTool(loadExamples: () => Promise<Example[] | undefined>): McpTool;",
3602
+ "doc": "/** The widget-backed tool. `loadExamples` is injected (disk on the CLI, cached fetch on the edge). */"
3603
+ },
3604
+ {
3605
+ "name": "examplesWidget",
3606
+ "kind": "const",
3607
+ "package": "@nifrajs/cli",
3608
+ "signature": "export declare const examplesWidget: import(\"@nifrajs/mcp\").McpWidget;",
3609
+ "doc": "/** The widget: renders `structuredContent.examples` as a filterable list of example cards. */"
3610
+ },
3529
3611
  {
3530
3612
  "name": "executeCapability",
3531
3613
  "kind": "function",
@@ -3732,6 +3814,27 @@
3732
3814
  "signature": "export interface FileType {\n /** Detected MIME type, e.g. `\"image/png\"`. */\n readonly mime: string;\n /** Canonical extension (no dot), e.g. `\"png\"`. */\n readonly ext: string;\n}",
3733
3815
  "doc": "/**\n * Magic-byte file-type detection - trust the bytes, not the `Content-Type` header (which a client\n * sets freely). Reads only the leading bytes; dependency-free + edge-safe. Covers the common upload\n * types; returns `null` for anything unrecognized (incl. text formats like SVG/CSV that have no magic\n * number - handle those explicitly if you accept them).\n */"
3734
3816
  },
3817
+ {
3818
+ "name": "filterDevToolsEvents",
3819
+ "kind": "function",
3820
+ "package": "@nifrajs/devtools",
3821
+ "signature": "export declare function filterDevToolsEvents(events: readonly DevToolsEvent[], query?: DevToolsQuery): DevToolsEvent[];",
3822
+ "doc": "/**\n * Filter a DevTools event buffer for a snapshot query - by `path` prefix and/or the most recent\n * `limit`. Pure, so the `/state` endpoint and its tests (and the `nifra_inspect` MCP tool that reads\n * that endpoint) share ONE definition of what a query returns.\n */"
3823
+ },
3824
+ {
3825
+ "name": "findRoutePathLiteral",
3826
+ "kind": "function",
3827
+ "package": "@nifrajs/ts-plugin",
3828
+ "signature": "export declare function findRoutePathLiteral(tsm: typeof ts, source: ts.SourceFile, position: number): {\n text: string;\n span: ts.TextSpan;\n} | undefined;",
3829
+ "doc": "/**\n * If `position` sits on a string-literal route path (`\"/...\"`), return its text and the span INSIDE the\n * quotes (so the editor highlights the path, not the quotes). Exported for unit testing against a\n * `SourceFile` built with `ts.createSourceFile`.\n */"
3830
+ },
3831
+ {
3832
+ "name": "findRoutesDir",
3833
+ "kind": "function",
3834
+ "package": "@nifrajs/ts-plugin",
3835
+ "signature": "export declare function findRoutesDir(fromFile: string, exists?: (p: string) => boolean): string | undefined;",
3836
+ "doc": "/** Walk up from a source file to the nearest directory that has a `routes/` folder - the app root. */"
3837
+ },
3735
3838
  {
3736
3839
  "name": "fixedBackoff",
3737
3840
  "kind": "const",
@@ -4054,7 +4157,7 @@
4054
4157
  "kind": "function",
4055
4158
  "package": "@nifrajs/cli",
4056
4159
  "signature": "export declare function handleMcpHttp(request: Request): Promise<Response>;",
4057
- "doc": "/** The CLI HTTP handler: serves the disk-backed corpus tools. (`nifra docs-mcp` / `bun run` this file.) */"
4160
+ "doc": "/** The CLI HTTP handler: serves the disk-backed corpus tools + registers the examples widget's `ui://`\n * resource so MCP Apps hosts can render `nifra_examples_app`. (`nifra docs-mcp` / `bun run` this file.) */"
4058
4161
  },
4059
4162
  {
4060
4163
  "name": "Handler",
@@ -4937,7 +5040,7 @@
4937
5040
  "name": "JsonRpcResponse",
4938
5041
  "kind": "type",
4939
5042
  "package": "@nifrajs/mcp",
4940
- "signature": "export type JsonRpcResponse = {\n jsonrpc: \"2.0\";\n id: JsonRpcId;\n result: unknown;\n} | {\n jsonrpc: \"2.0\";\n id: JsonRpcId;\n error: {\n code: number;\n message: string;\n };\n};"
5043
+ "signature": "export type JsonRpcResponse = {\n jsonrpc: \"2.0\";\n id: JsonRpcId;\n result: unknown;\n} | {\n jsonrpc: \"2.0\";\n id: JsonRpcId;\n error: {\n code: number;\n message: string;\n data?: unknown;\n };\n};"
4941
5044
  },
4942
5045
  {
4943
5046
  "name": "JsonSchema",
@@ -5071,6 +5174,13 @@
5071
5174
  "package": "@nifrajs/middleware",
5072
5175
  "signature": "export interface LanguageOptions<L extends readonly string[]> {\n readonly supported: L;\n readonly defaultLanguage: L[number];\n /** Emit `Content-Language`. Default `true`. */\n readonly header?: boolean;\n}"
5073
5176
  },
5177
+ {
5178
+ "name": "LAST_ERROR_PATH",
5179
+ "kind": "const",
5180
+ "package": "@nifrajs/web",
5181
+ "signature": "export declare const LAST_ERROR_PATH = \"/__nifra/last-error\";",
5182
+ "doc": "/** Shared endpoint name used by both dev pipelines and the agent-facing MCP tools. */"
5183
+ },
5074
5184
  {
5075
5185
  "name": "LAYOUT_DATA_GLOBAL",
5076
5186
  "kind": "const",
@@ -5317,6 +5427,13 @@
5317
5427
  "signature": "export declare const MCP_CONFIG: McpConfig;",
5318
5428
  "doc": "/** The one canonical MCP config object both registries serialize - the anti-drift seam. */"
5319
5429
  },
5430
+ {
5431
+ "name": "MCP_ERROR",
5432
+ "kind": "const",
5433
+ "package": "@nifrajs/mcp",
5434
+ "signature": "export declare const MCP_ERROR: {\n readonly HEADER_MISMATCH: -32020;\n readonly UNSUPPORTED_VERSION: -32022;\n};",
5435
+ "doc": "/** MCP-reserved JSON-RPC error codes (2026-07-28 error-code policy carves out -32020..-32099 for the spec). */"
5436
+ },
5320
5437
  {
5321
5438
  "name": "MCP_JSON_PATH",
5322
5439
  "kind": "const",
@@ -5375,13 +5492,13 @@
5375
5492
  "name": "McpHttpOptions",
5376
5493
  "kind": "interface",
5377
5494
  "package": "@nifrajs/cli",
5378
- "signature": "export interface McpHttpOptions {\n /** Maximum JSON-RPC request body size in bytes. Default 1 MB. */\n readonly maxBodyBytes?: number;\n /** Resources / prompts / the MCP Apps UI extension served alongside the tools. */\n readonly features?: McpServerFeatures;\n /** Shown on the GET health page so each host can describe itself. */\n readonly health?: string;\n}"
5495
+ "signature": "export interface McpHttpOptions {\n /** Maximum JSON-RPC request body size in bytes. Default 1 MB. */\n readonly maxBodyBytes?: number;\n /** Resources / prompts / the MCP Apps UI extension served alongside the tools. */\n readonly features?: McpServerFeatures;\n /** Shown on the GET health page so each host can describe itself. */\n readonly health?: string;\n /**\n * Origin allowlist for the DNS-rebinding guard. Omit (the default) to allow any origin - the right\n * choice for a public, unauthenticated docs server that can't enumerate its browser clients' origins.\n * Set it (e.g. a localhost origin for a hardened local host) to reject any other browser origin with 403.\n */\n readonly allowedOrigins?: readonly string[];\n}"
5379
5496
  },
5380
5497
  {
5381
5498
  "name": "McpHttpOptions",
5382
5499
  "kind": "interface",
5383
5500
  "package": "@nifrajs/mcp",
5384
- "signature": "export interface McpHttpOptions {\n /** Maximum JSON-RPC request body size in bytes. Default 1 MB. */\n readonly maxBodyBytes?: number;\n /** Resources / prompts / the MCP Apps UI extension served alongside the tools. */\n readonly features?: McpServerFeatures;\n /** Shown on the GET health page so each host can describe itself. */\n readonly health?: string;\n}"
5501
+ "signature": "export interface McpHttpOptions {\n /** Maximum JSON-RPC request body size in bytes. Default 1 MB. */\n readonly maxBodyBytes?: number;\n /** Resources / prompts / the MCP Apps UI extension served alongside the tools. */\n readonly features?: McpServerFeatures;\n /** Shown on the GET health page so each host can describe itself. */\n readonly health?: string;\n /**\n * Origin allowlist for the DNS-rebinding guard. Omit (the default) to allow any origin - the right\n * choice for a public, unauthenticated docs server that can't enumerate its browser clients' origins.\n * Set it (e.g. a localhost origin for a hardened local host) to reject any other browser origin with 403.\n */\n readonly allowedOrigins?: readonly string[];\n}"
5385
5502
  },
5386
5503
  {
5387
5504
  "name": "mcpJson",
@@ -5465,7 +5582,7 @@
5465
5582
  "name": "McpServerFeatures",
5466
5583
  "kind": "interface",
5467
5584
  "package": "@nifrajs/mcp",
5468
- "signature": "export interface McpServerFeatures {\n readonly resources?: readonly McpResource[];\n readonly prompts?: readonly McpPrompt[];\n /** Advertise the MCP Apps UI extension in `initialize`. Present when the server serves `ui://` widgets;\n * `mimeTypes` defaults to `[UI_MIME]`. */\n readonly ui?: {\n readonly mimeTypes?: readonly string[];\n };\n}"
5585
+ "signature": "export interface McpServerFeatures {\n readonly resources?: readonly McpResource[];\n readonly prompts?: readonly McpPrompt[];\n /** Advertise the MCP Apps UI extension in `initialize`. Present when the server serves `ui://` widgets;\n * `mimeTypes` defaults to `[UI_MIME]`. */\n readonly ui?: {\n readonly mimeTypes?: readonly string[];\n };\n /** Natural-language guidance for LLMs on how to use this server. Surfaced in the modern\n * `server/discover` result (2026-07-28); legacy `initialize` does not carry it. */\n readonly instructions?: string;\n}"
5469
5586
  },
5470
5587
  {
5471
5588
  "name": "McpTool",
@@ -5831,7 +5948,21 @@
5831
5948
  "name": "MockServerOptions",
5832
5949
  "kind": "interface",
5833
5950
  "package": "@nifrajs/mock",
5834
- "signature": "export interface MockServerOptions {\n /** Seed for deterministic mock generation. */\n readonly seed?: number | undefined;\n /** Log mock requests to console. */\n readonly verbose?: boolean | undefined;\n}"
5951
+ "signature": "export interface MockServerOptions {\n /** Seed for deterministic mock generation. */\n readonly seed?: number | undefined;\n /** Log mock requests to console. */\n readonly verbose?: boolean | undefined;\n /**\n * Derive a JSON Schema from a response validator that carries none (zod/valibot expose a Standard Schema\n * validator but no `.jsonSchema`), so their routes mock real data instead of `{}`. Fail-safe: throwing or\n * returning undefined falls back to `{}`, as today. Ready-made for zod: `@nifrajs/testing/zod`.\n *\n * Defaults to {@link autoReflectJsonSchema}, which recognizes zod by its Standard Schema vendor tag and\n * converts via `z.toJSONSchema` when zod is installed - so zod routes mock real data with no wiring.\n * Pass a hook to override, or `() => undefined` to force every opaque schema back to `{}`.\n */\n readonly reflectJsonSchema?: (schema: unknown) => JsonSchema | undefined;\n}"
5952
+ },
5953
+ {
5954
+ "name": "MODERN_PROTOCOL_VERSION",
5955
+ "kind": "const",
5956
+ "package": "@nifrajs/mcp",
5957
+ "signature": "export declare const MODERN_PROTOCOL_VERSION = \"2026-07-28\";",
5958
+ "doc": "/** The stateless per-request revision (2026-07-28+). A request carrying its version in `_meta`\n * (`io.modelcontextprotocol/protocolVersion`) is served in modern mode; an `initialize` request stays\n * legacy. This server is dual-era: `handleRpc` answers both on the same dispatch, per request. */"
5959
+ },
5960
+ {
5961
+ "name": "modernVersionOf",
5962
+ "kind": "function",
5963
+ "package": "@nifrajs/mcp",
5964
+ "signature": "export declare function modernVersionOf(params: Record<string, unknown> | undefined): string | undefined;",
5965
+ "doc": "/** The protocol version a modern (2026-07-28+) request declares in `_meta`, or `undefined` for a legacy\n * request. Its presence is what puts the dispatch in modern mode for that one request. */"
5835
5966
  },
5836
5967
  {
5837
5968
  "name": "mountIslands",
@@ -6542,6 +6673,13 @@
6542
6673
  "package": "@nifrajs/content",
6543
6674
  "signature": "export interface ParseEntryOptions<S extends StandardSchemaV1> {\n readonly raw: string;\n readonly slug: string;\n /** Standard Schema validating the frontmatter - a typo'd/missing field throws (a build-time failure,\n * never a broken page). */\n readonly schema: S;\n}"
6544
6675
  },
6676
+ {
6677
+ "name": "parseFrames",
6678
+ "kind": "function",
6679
+ "package": "@nifrajs/web",
6680
+ "signature": "export declare function parseFrames(stack: string): DiagnosticFrame[];",
6681
+ "doc": "/**\n * Parse a V8/Node stack into structured frames. Handles the `at fn (path:line:col)`, bare\n * `at path:line:col`, and `at async fn (...)` shapes; a frame that doesn't match keeps its raw text\n * with no location (so nothing is silently dropped).\n */"
6682
+ },
6545
6683
  {
6546
6684
  "name": "parseFrontmatter",
6547
6685
  "kind": "function",
@@ -6918,8 +7056,8 @@
6918
7056
  "name": "PROTOCOL_VERSION",
6919
7057
  "kind": "const",
6920
7058
  "package": "@nifrajs/mcp",
6921
- "signature": "export declare const PROTOCOL_VERSION = \"2024-11-05\";",
6922
- "doc": "/**\n * The pure MCP (Model Context Protocol) JSON-RPC dispatch - no I/O, no `Bun.*`, no side effects, so it\n * unit-tests cleanly. A transport (stdio in `@nifrajs/cli`'s `mcp.ts`, Streamable-HTTP in {@link ./http.ts})\n * wires this to a byte stream; the tools/resources are injected, so the protocol logic is exercised\n * without spawning or reading streams.\n *\n * Beyond base MCP this also implements the server half of **MCP Apps (SEP-1865)** - interactive `ui://`\n * widgets: a tool links a UI resource via `_meta.ui.resourceUri`, `tools/call` returns `structuredContent`\n * for the widget to render, and `initialize` advertises the `io.modelcontextprotocol/ui` extension. All of\n * it is additive - a tool whose handler returns a plain `string` behaves exactly as before.\n */"
7059
+ "signature": "export declare const PROTOCOL_VERSION = \"2025-06-18\";",
7060
+ "doc": "/** The protocol revision this server advertises by default. A handshake-era version - the HTTP transport\n * still uses `initialize`; current clients reach it through their backward-compatibility fallback.\n * `initialize` echoes the client's own requested version instead when it's one this server also speaks\n * ({@link SUPPORTED_PROTOCOL_VERSIONS}). */"
6923
7061
  },
6924
7062
  {
6925
7063
  "name": "PublicDirCache",
@@ -6933,7 +7071,7 @@
6933
7071
  "kind": "function",
6934
7072
  "package": "@nifrajs/cli",
6935
7073
  "signature": "export declare function publicDocsTools(): McpTool[];",
6936
- "doc": "/** The two project-independent tools, reading the package's bundled corpus from disk (CLI use). */"
7074
+ "doc": "/** The project-independent tools, reading the package's bundled corpus from disk (CLI use): the text\n * docs tools plus the `nifra_examples_app` MCP Apps widget tool. */"
6937
7075
  },
6938
7076
  {
6939
7077
  "name": "publicEnvDefines",
@@ -7601,6 +7739,13 @@
7601
7739
  "signature": "export declare function resolvePublicPath(root: string, pathname: string): string | undefined;",
7602
7740
  "doc": "/**\n * Resolve a URL pathname to an absolute path **confined** to `root`, or `undefined` if it escapes.\n *\n * This is a user-path-to-filesystem sink, which makes it the one part of this feature with a security\n * consequence. Decode first (`%2e%2e%2f` is `../`), then normalize, then verify the result is still\n * under `root` by prefix - checking the *resolved* path rather than scanning the input for `..`,\n * because a blocklist over encodings is exactly the kind of check that gets bypassed.\n */"
7603
7741
  },
7742
+ {
7743
+ "name": "resolveRouteFile",
7744
+ "kind": "function",
7745
+ "package": "@nifrajs/ts-plugin",
7746
+ "signature": "export declare function resolveRouteFile(path: string, routes: readonly RouteLocation[]): string | undefined;",
7747
+ "doc": "/**\n * Resolve a route path literal to the first route whose pattern matches it. Query/hash are ignored\n * (a link's `?tab=x` does not change which route file serves it). Returns the route's `file`, or\n * undefined when the value is not a path or matches no route.\n */"
7748
+ },
7604
7749
  {
7605
7750
  "name": "respondMcpHttp",
7606
7751
  "kind": "function",
@@ -7613,7 +7758,7 @@
7613
7758
  "kind": "function",
7614
7759
  "package": "@nifrajs/mcp",
7615
7760
  "signature": "export declare function respondMcpHttp(request: Request, tools: McpTool[], serverInfo: {\n name: string;\n version: string;\n}, options?: McpHttpOptions): Promise<Response>;",
7616
- "doc": "/**\n * Handle one MCP request over HTTP against the given `tools`/`features`. POST a JSON-RPC body → JSON-RPC\n * response; GET → a plain-text health page; OPTIONS → CORS preflight. Never throws - a bad body becomes a\n * JSON-RPC parse error. The dispatch is the shared, transport-agnostic {@link handleRpc}.\n */"
7761
+ "doc": "/**\n * Handle one MCP request over HTTP against the given `tools`/`features`. POST a JSON-RPC body → JSON-RPC\n * response; GET → a plain-text health page; OPTIONS → CORS preflight. Never throws - a bad body becomes a\n * JSON-RPC parse error. Dual-era: a modern (2026-07-28) POST that mirrors its method/name/version into\n * headers is validated against the body before dispatch and gets spec HTTP statuses; legacy requests are\n * served unchanged. The dispatch is the shared, transport-agnostic {@link handleRpc}.\n */"
7617
7762
  },
7618
7763
  {
7619
7764
  "name": "responseCache",
@@ -7920,6 +8065,13 @@
7920
8065
  "signature": "export interface RouteLike {\n readonly method: string;\n readonly path: string;\n readonly schema?: {\n readonly body?: unknown;\n readonly query?: unknown;\n readonly response?: unknown;\n } | undefined;\n}",
7921
8066
  "doc": "/** A registered route as seen by {@link buildOpenApiDocument} - structurally a `@nifrajs/core`\n * `RouteDescriptor` (so `app.routes()` is passed straight through). */"
7922
8067
  },
8068
+ {
8069
+ "name": "RouteLocation",
8070
+ "kind": "interface",
8071
+ "package": "@nifrajs/ts-plugin",
8072
+ "signature": "export interface RouteLocation {\n readonly pattern: string;\n readonly file: string;\n}",
8073
+ "doc": "/** A route as the plugin needs it: its URL pattern and the source file that serves it. */"
8074
+ },
7923
8075
  {
7924
8076
  "name": "RouteManifest",
7925
8077
  "kind": "interface",
@@ -7999,8 +8151,7 @@
7999
8151
  "name": "RouterContext",
8000
8152
  "kind": "const",
8001
8153
  "package": "@nifrajs/web-react",
8002
- "signature": "export declare const RouterContext: import(\"react\").Context<RouterContextValue>;",
8003
- "doc": "/** Router context. The default ({} params, \"\" path, {} search) is what a component sees when rendered\n * outside a nifra route tree (the hooks stay defined, no throw, so a stray `useParams` degrades\n * gracefully). */"
8154
+ "signature": "export declare const RouterContext: import(\"react\").Context<RouterContextValue>;"
8004
8155
  },
8005
8156
  {
8006
8157
  "name": "RouterContextValue",
@@ -8074,7 +8225,7 @@
8074
8225
  "name": "rpcError",
8075
8226
  "kind": "const",
8076
8227
  "package": "@nifrajs/mcp",
8077
- "signature": "export declare const rpcError: (id: JsonRpcId, code: number, message: string) => JsonRpcResponse;"
8228
+ "signature": "export declare const rpcError: (id: JsonRpcId, code: number, message: string, data?: unknown) => JsonRpcResponse;"
8078
8229
  },
8079
8230
  {
8080
8231
  "name": "rpcResult",
@@ -8374,15 +8525,13 @@
8374
8525
  "name": "SearchContext",
8375
8526
  "kind": "const",
8376
8527
  "package": "@nifrajs/web-preact",
8377
- "signature": "export declare const SearchContext: import(\"preact\").Context<Record<string, unknown>>;",
8378
- "doc": "/** The current route's validated search, provided by `compose` on SSR + client mount alike (derived from\n * the URL via the shared `searchOfChain`). `{}` outside a nifra route tree. Read via {@link useSearch}. */"
8528
+ "signature": "export declare const SearchContext: import(\"preact\").Context<Record<string, unknown>>;"
8379
8529
  },
8380
8530
  {
8381
8531
  "name": "SearchContext",
8382
8532
  "kind": "const",
8383
8533
  "package": "@nifrajs/web-solid",
8384
- "signature": "export declare const SearchContext: import(\"solid-js\").Context<Accessor<Record<string, unknown>> | undefined>;",
8385
- "doc": "/** The current route's validated search accessor, provided by `compose` on SSR + client mount alike\n * (derived from the URL via the shared `searchOfChain`). Read via {@link useSearch}. */"
8534
+ "signature": "export declare const SearchContext: Context<Accessor<Record<string, unknown>> | undefined>;"
8386
8535
  },
8387
8536
  {
8388
8537
  "name": "searchOf",
@@ -9130,6 +9279,20 @@
9130
9279
  "signature": "export declare function solidSvgComponentBunPlugin(generate: \"dom\" | \"ssr\"): BunPlugin;",
9131
9280
  "doc": "/** The Solid SVG-component plugin. `generate` selects the Solid `\"dom\"`/`\"ssr\"` output, matching\n * `solidBunPlugin`. A plain `import \"./icon.svg\"` (asset URL) is untouched - only `?component` matches. */"
9132
9281
  },
9282
+ {
9283
+ "name": "sortRoutesBySpecificity",
9284
+ "kind": "function",
9285
+ "package": "@nifrajs/core",
9286
+ "signature": "export declare function sortRoutesBySpecificity<T extends {\n readonly pattern: CompiledRoutePattern;\n}>(routes: T[]): T[];",
9287
+ "doc": "/**\n * Sort compiled routes most-specific-first - a static segment beats a dynamic one, the order the router\n * resolves a path in. The single home for that precedence: the web router, the mock server, and the\n * editor plugin all order routes through this one comparator, so which file a path resolves to can never\n * diverge between runtime, client, and editor. Sorts in place and returns the same array.\n */"
9288
+ },
9289
+ {
9290
+ "name": "SourceReader",
9291
+ "kind": "type",
9292
+ "package": "@nifrajs/web",
9293
+ "signature": "export type SourceReader = (file: string) => string | undefined;",
9294
+ "doc": "/** Reads a source file's text, or returns undefined if it cannot (missing, binary, permission). */"
9295
+ },
9133
9296
  {
9134
9297
  "name": "SpanStatus",
9135
9298
  "kind": "type",
@@ -9855,6 +10018,13 @@
9855
10018
  "signature": "export declare class TopicRegistry {\n private readonly topics;\n private readonly memberships;\n subscribe(topic: string, ws: NifraWebSocket): void;\n unsubscribe(topic: string, ws: NifraWebSocket): void;\n /** Drop every subscription for a connection - called on close so a registry never leaks dead sockets. */\n unsubscribeAll(ws: NifraWebSocket): void;\n /** Send `data` to every connection subscribed to `topic`. Per-socket send errors are isolated. */\n publish(topic: string, data: string | ArrayBufferView | ArrayBuffer): void;\n}",
9856
10019
  "doc": "/**\n * In-process pub/sub for `ws.subscribe(topic)` + `app.publish(topic, data)`. **Single-instance only** -\n * topics live in this process's memory, so a multi-instance deploy (multiple servers behind a load\n * balancer) needs an external fan-out (Redis pub/sub, a Cloudflare Durable Object, NATS, …) bridged to\n * `app.publish`. The same registry instance is shared by an app's connections and its `publish`.\n */"
9857
10020
  },
10021
+ {
10022
+ "name": "topUserFrame",
10023
+ "kind": "function",
10024
+ "package": "@nifrajs/web",
10025
+ "signature": "export declare function topUserFrame(frames: readonly DiagnosticFrame[], root: string | undefined): DiagnosticFrame | undefined;",
10026
+ "doc": "/** The first frame that points at the user's own source - what the codeframe should show. */"
10027
+ },
9858
10028
  {
9859
10029
  "name": "TraceContext",
9860
10030
  "kind": "type",
@@ -10048,8 +10218,7 @@
10048
10218
  "name": "UnsupportedMockSchemaError",
10049
10219
  "kind": "class",
10050
10220
  "package": "@nifrajs/mock",
10051
- "signature": "export declare class UnsupportedMockSchemaError extends Error {\n readonly keyword: string;\n constructor(keyword: string, message: string);\n}",
10052
- "doc": "/**\n * `@nifrajs/mock` - contract-based mock server.\n *\n * Reads a Nifra app's registered routes and their Standard Schema / JSON Schema\n * response definitions, then generates fake responses from supported JSON Schema keywords. Use it\n * for frontend-first development, agent testing, or CI smoke tests.\n */"
10221
+ "signature": "export declare class UnsupportedMockSchemaError extends Error {\n readonly keyword: string;\n constructor(keyword: string, message: string);\n}"
10053
10222
  },
10054
10223
  {
10055
10224
  "name": "UploadResult",
@@ -10631,14 +10800,14 @@
10631
10800
  "name": "VERSION",
10632
10801
  "kind": "const",
10633
10802
  "package": "@nifrajs/core",
10634
- "signature": "export declare const VERSION: \"2.3.0\";",
10803
+ "signature": "export declare const VERSION: \"2.5.0\";",
10635
10804
  "doc": "/**\n * Current package version. A hardcoded literal on purpose - core runs on the edge (no fs), so it can't\n * read its own package.json at runtime. `scripts/version.ts` rewrites it on every release bump and\n * `check:publish` asserts it equals `@nifrajs/core`'s package version.\n */"
10636
10805
  },
10637
10806
  {
10638
10807
  "name": "VERSION",
10639
10808
  "kind": "const",
10640
10809
  "package": "nifra",
10641
- "signature": "export declare const VERSION: \"2.3.0\";",
10810
+ "signature": "export declare const VERSION: \"2.5.0\";",
10642
10811
  "doc": "/**\n * Current package version. A hardcoded literal on purpose - core runs on the edge (no fs), so it can't\n * read its own package.json at runtime. `scripts/version.ts` rewrites it on every release bump and\n * `check:publish` asserts it equals `@nifrajs/core`'s package version.\n */"
10643
10812
  },
10644
10813
  {
@@ -10933,5 +11102,12 @@
10933
11102
  "package": "@nifrajs/client",
10934
11103
  "signature": "export interface WsHandle<In, Out> {\n /** Send one typed frame (JSON-encoded). Queued until the socket opens, so it never throws. */\n send(message: In): void;\n /** Async-iterate incoming typed frames. Ends when the socket closes (or `signal` aborts). */\n messages(options?: {\n signal?: AbortSignal;\n }): AsyncIterableIterator<Out>;\n /** Callback form of {@link messages}; returns an unsubscribe. */\n onMessage(callback: (message: Out) => void): () => void;\n /** Resolves when the socket is open (rejects on a connect failure). */\n readonly opened: Promise<void>;\n close(code?: number, reason?: string): void;\n /** The underlying socket, for anything off the typed contract (binary frames, bufferedAmount…). */\n readonly raw: WebSocket;\n}",
10935
11104
  "doc": "/**\n * A live typed WebSocket connection to an `app.ws()` route. `send` accepts the route's\n * `messageSchema` input type (validated server-side at the trust boundary); received frames are\n * typed from its `sendSchema` and JSON-parsed. Binary frames are not part of the typed contract\n * and are ignored by `messages()`/`onMessage` - use `raw` for them.\n */"
11105
+ },
11106
+ {
11107
+ "name": "zodJsonSchema",
11108
+ "kind": "function",
11109
+ "package": "@nifrajs/testing",
11110
+ "signature": "export declare function zodJsonSchema(schema: unknown): JsonSchema | undefined;",
11111
+ "doc": "/**\n * Convert a zod schema to a JSON Schema the mock/mutation generators understand, or `undefined` when the\n * value is not a convertible zod schema. Options chosen for this consumer:\n * - target \"draft-7\" → the keyword set `@nifrajs/mock` + the laboratory's `candidateMutations` read\n * - io \"input\" → the request-side constraints a hostile payload must violate (pre-transform)\n * - reused \"inline\" → no `$ref`/`$defs`; the mock generator fails closed on `$ref`\n *\n * Fail-safe: a non-zod value, or an unconvertible schema, returns `undefined` → the schema stays opaque\n * (NO_WITNESS / `{}`), exactly as before this bridge existed.\n */"
10936
11112
  }
10937
11113
  ]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nifrajs/cli",
3
- "version": "2.3.0",
3
+ "version": "2.5.0",
4
4
  "description": "The nifra CLI - zero-config dev/build/start for a nifra app (`nifra dev`, `nifra build`, `nifra start`).",
5
5
  "keywords": [
6
6
  "bun",
@@ -41,14 +41,14 @@
41
41
  "access": "public"
42
42
  },
43
43
  "dependencies": {
44
- "@nifrajs/client": "2.3.0",
45
- "@nifrajs/core": "2.3.0",
46
- "@nifrajs/mcp": "2.3.0",
47
- "@nifrajs/runner": "2.3.0",
48
- "@nifrajs/schema": "2.3.0",
49
- "@nifrajs/testing": "2.3.0",
50
- "@nifrajs/web": "2.3.0",
51
- "create-nifra": "2.3.0"
44
+ "@nifrajs/client": "2.5.0",
45
+ "@nifrajs/core": "2.5.0",
46
+ "@nifrajs/mcp": "2.5.0",
47
+ "@nifrajs/runner": "2.5.0",
48
+ "@nifrajs/schema": "2.5.0",
49
+ "@nifrajs/testing": "2.5.0",
50
+ "@nifrajs/web": "2.5.0",
51
+ "create-nifra": "2.5.0"
52
52
  },
53
53
  "repository": {
54
54
  "type": "git",
package/src/check.ts CHANGED
@@ -1204,6 +1204,7 @@ export interface CheckDiagnostic {
1204
1204
  | "response-route"
1205
1205
  | "undeclared-dependency"
1206
1206
  | "duplicate-install"
1207
+ | "stale-workspace-dist"
1207
1208
  | "server-manifest-drift"
1208
1209
  | "manifest-drift"
1209
1210
  | "capability-assurance"
@@ -1709,10 +1710,14 @@ export async function collectCheckResult(
1709
1710
  }
1710
1711
  for (const finding of dr.duplicateInstalls) {
1711
1712
  const copies = finding.copies.map((copy) => `${copy.version} at ${copy.path}`).join("; ")
1713
+ const identityHint =
1714
+ finding.package === "@nifrajs/core"
1715
+ ? " (a second @nifrajs/core collapses `typeof backend` to `any` at `.merge()` - usually that error's root cause)"
1716
+ : ""
1712
1717
  diagnostics.push({
1713
1718
  rule: "duplicate-install",
1714
1719
  severity: "error",
1715
- message: `${finding.package} resolves to multiple physical copies (${copies}) - module identity is unsafe`,
1720
+ message: `${finding.package} resolves to multiple physical copies (${copies}) - module identity is unsafe${identityHint}`,
1716
1721
  fix: "align dependency ranges and reinstall from the workspace root",
1717
1722
  suggestion: {
1718
1723
  kind: "manual",
@@ -1725,6 +1730,28 @@ export async function collectCheckResult(
1725
1730
  },
1726
1731
  })
1727
1732
  }
1733
+ // Advisory (never fails the gate): while actively editing a linked package its dist is always
1734
+ // momentarily behind. The finding earns its keep when a dev server is about to start against it -
1735
+ // Bun reads live `src` while Vite's SSR runner reads the artifact, so a stale one 500s inside
1736
+ // framework/shared-package code and reads exactly like an upstream regression.
1737
+ for (const f of dr.staleDists) {
1738
+ diagnostics.push({
1739
+ rule: "stale-workspace-dist",
1740
+ severity: "warning",
1741
+ message: f.missing
1742
+ ? `${f.package} was never built - ${f.distFile} is missing, but its export map serves it to Vite SSR/node consumers while Bun reads src - rebuild ${f.package}`
1743
+ : `${f.package} has a stale build artifact - ${f.distFile} is ${f.behindSeconds}s older than ${f.sourceFile}, and Vite SSR/node consumers read the artifact while Bun reads src - rebuild ${f.package}`,
1744
+ fix: `rebuild ${f.package}`,
1745
+ suggestion: {
1746
+ kind: "manual",
1747
+ title: `Rebuild ${f.package}`,
1748
+ steps: [
1749
+ `Run the package's build (usually \`bun run build\` in its directory) so ${f.distFile} matches its source again.`,
1750
+ "Only workspace-linked installs drift; npm tarballs are immutable and never flagged.",
1751
+ ],
1752
+ },
1753
+ })
1754
+ }
1728
1755
  }
1729
1756
  // #7: a committed server-manifest.ts that drifted from routes/ - name the exact missing/extra routes.
1730
1757
  for (const f of manifestDrift) {
@@ -1944,6 +1971,7 @@ export async function runCheck(
1944
1971
  ["response-route", "route returns a raw Response (typed client → data: never)"],
1945
1972
  ["undeclared-dependency", "undeclared dependency in package.json"],
1946
1973
  ["duplicate-install", "duplicate identity-sensitive dependency install"],
1974
+ ["stale-workspace-dist", "workspace-linked dist older than its source"],
1947
1975
  ["server-manifest-drift", "server-manifest.ts drifted from routes/"],
1948
1976
  ["manifest-drift", "versioned trust manifest drift"],
1949
1977
  ["capability-assurance", "effect/capability assurance"],
@@ -1951,7 +1979,7 @@ export async function runCheck(
1951
1979
  ["check-config", "nifra.check.json"],
1952
1980
  ] as const) {
1953
1981
  const ds = counts(rule)
1954
- if (rule === "response-route") {
1982
+ if (rule === "response-route" || rule === "stale-workspace-dist") {
1955
1983
  // Advisory: surfaced with ⚠, never folded into pass/fail.
1956
1984
  console.log(ds.length === 0 ? `✓ ${label}: none` : `⚠ ${label}: ${ds.length} (advisory)`)
1957
1985
  } else if (rule !== "typecheck") {