@nifrajs/cli 2.2.0 → 2.3.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 (117) hide show
  1. package/README.md +25 -25
  2. package/dist/assure.d.ts +2 -2
  3. package/dist/assure.d.ts.map +1 -1
  4. package/dist/assure.js +17 -10
  5. package/dist/assure.js.map +1 -1
  6. package/dist/capabilities-tool.d.ts +7 -0
  7. package/dist/capabilities-tool.d.ts.map +1 -1
  8. package/dist/capabilities-tool.js +68 -16
  9. package/dist/capabilities-tool.js.map +1 -1
  10. package/dist/check.d.ts +61 -22
  11. package/dist/check.d.ts.map +1 -1
  12. package/dist/check.js +332 -86
  13. package/dist/check.js.map +1 -1
  14. package/dist/cli.d.ts +30 -2
  15. package/dist/cli.d.ts.map +1 -1
  16. package/dist/cli.js +132 -36
  17. package/dist/cli.js.map +1 -1
  18. package/dist/diff-tool.d.ts +3 -3
  19. package/dist/diff-tool.js +7 -7
  20. package/dist/docs-search.d.ts +3 -3
  21. package/dist/docs-search.js +6 -6
  22. package/dist/doctor.d.ts +3 -3
  23. package/dist/doctor.js +8 -8
  24. package/dist/examples.d.ts +5 -5
  25. package/dist/examples.js +5 -5
  26. package/dist/init-agents.d.ts +14 -14
  27. package/dist/init-agents.js +14 -14
  28. package/dist/internal/typescript-import.d.ts +24 -0
  29. package/dist/internal/typescript-import.d.ts.map +1 -0
  30. package/dist/internal/typescript-import.js +32 -0
  31. package/dist/internal/typescript-import.js.map +1 -0
  32. package/dist/introspect.d.ts +12 -12
  33. package/dist/introspect.js +24 -24
  34. package/dist/levels-tool.d.ts +8 -8
  35. package/dist/levels-tool.d.ts.map +1 -1
  36. package/dist/levels-tool.js +33 -30
  37. package/dist/levels-tool.js.map +1 -1
  38. package/dist/load.d.ts +6 -6
  39. package/dist/load.d.ts.map +1 -1
  40. package/dist/load.js +15 -7
  41. package/dist/load.js.map +1 -1
  42. package/dist/manifest-tool.d.ts +1 -1
  43. package/dist/manifest-tool.d.ts.map +1 -1
  44. package/dist/manifest-tool.js +4 -2
  45. package/dist/manifest-tool.js.map +1 -1
  46. package/dist/mcp-docs-tools.d.ts +1 -1
  47. package/dist/mcp-docs-tools.js +9 -9
  48. package/dist/mcp-http.d.ts +5 -5
  49. package/dist/mcp-http.js +7 -7
  50. package/dist/mcp-protocol.d.ts +2 -2
  51. package/dist/mcp-protocol.js +2 -2
  52. package/dist/mcp-render.d.ts +3 -3
  53. package/dist/mcp-render.js +6 -6
  54. package/dist/mcp-run.d.ts +2 -2
  55. package/dist/mcp-run.js +2 -2
  56. package/dist/mcp.d.ts +14 -14
  57. package/dist/mcp.d.ts.map +1 -1
  58. package/dist/mcp.js +75 -45
  59. package/dist/mcp.js.map +1 -1
  60. package/dist/openapi-tool.d.ts +1 -1
  61. package/dist/openapi-tool.js +2 -2
  62. package/dist/pipeline-guard.d.ts +1 -1
  63. package/dist/pipeline-guard.js +1 -1
  64. package/dist/port.d.ts +9 -9
  65. package/dist/port.js +27 -27
  66. package/dist/recipes/1.8.0.d.ts +2 -2
  67. package/dist/recipes/1.8.0.js +5 -5
  68. package/dist/recipes/index.d.ts +2 -2
  69. package/dist/recipes/index.js +1 -1
  70. package/dist/scaffold.d.ts +3 -3
  71. package/dist/scaffold.js +8 -8
  72. package/dist/sync-routes.d.ts +28 -0
  73. package/dist/sync-routes.d.ts.map +1 -0
  74. package/dist/sync-routes.js +55 -0
  75. package/dist/sync-routes.js.map +1 -0
  76. package/dist/types-search.d.ts +5 -5
  77. package/dist/types-search.js +7 -7
  78. package/dist/upgrade.d.ts +7 -7
  79. package/dist/upgrade.js +12 -12
  80. package/dist/verification.d.ts +51 -0
  81. package/dist/verification.d.ts.map +1 -0
  82. package/dist/verification.js +80 -0
  83. package/dist/verification.js.map +1 -0
  84. package/docs/examples.json +174 -90
  85. package/docs/llms-full.txt +1696 -747
  86. package/docs/types.json +1892 -3105
  87. package/package.json +22 -11
  88. package/src/assure.ts +17 -18
  89. package/src/capabilities-tool.ts +82 -16
  90. package/src/check.ts +405 -103
  91. package/src/cli.ts +141 -38
  92. package/src/diff-tool.ts +8 -8
  93. package/src/docs-search.ts +6 -6
  94. package/src/doctor.ts +10 -10
  95. package/src/examples.ts +7 -7
  96. package/src/init-agents.ts +17 -17
  97. package/src/internal/typescript-import.ts +33 -0
  98. package/src/introspect.ts +28 -28
  99. package/src/levels-tool.ts +37 -31
  100. package/src/load.ts +21 -12
  101. package/src/manifest-tool.ts +4 -2
  102. package/src/mcp-docs-tools.ts +9 -9
  103. package/src/mcp-http.ts +7 -7
  104. package/src/mcp-protocol.ts +2 -2
  105. package/src/mcp-render.ts +6 -6
  106. package/src/mcp-run.ts +2 -2
  107. package/src/mcp.ts +82 -45
  108. package/src/openapi-tool.ts +2 -2
  109. package/src/pipeline-guard.ts +1 -1
  110. package/src/port.ts +31 -31
  111. package/src/recipes/1.8.0.ts +5 -5
  112. package/src/recipes/index.ts +2 -2
  113. package/src/scaffold.ts +8 -8
  114. package/src/sync-routes.ts +69 -0
  115. package/src/types-search.ts +8 -8
  116. package/src/upgrade.ts +12 -12
  117. package/src/verification.ts +123 -0
@@ -1,5 +1,5 @@
1
1
  /**
2
- * The two project-INDEPENDENT MCP tools — `nifra_docs` + `nifra_example` — as a single factory shared
2
+ * The two project-INDEPENDENT MCP tools - `nifra_docs` + `nifra_example` - as a single factory shared
3
3
  * by every transport: the project stdio server ({@link projectTools}), the CLI HTTP server
4
4
  * (`nifra docs-mcp`), and the site's Cloudflare-Pages worker (`/mcp`). The corpus source is injected as
5
5
  * loaders, so the SAME tool definitions work whether the corpus comes from disk (`Bun.file`, on the
@@ -15,7 +15,7 @@ export function docsTools(loadDocs, loadExamples, loadTypes) {
15
15
  return [
16
16
  {
17
17
  name: "nifra_docs",
18
- description: "Search nifra's framework documentation and get back ONLY the matching sections — auth, uploads, ISR, WebSockets, loaders, deployment, anything. Call with no query for the cheap section index; pass query (e.g. \"isr revalidate\") for the top matching sections. Use this instead of reading llms-full.txt (~150 KB) whole. For the EXACT TypeScript of a type/interface/function, call nifra_types instead (don't read .d.ts files).",
18
+ description: "Search nifra's framework documentation and get back ONLY the matching sections - auth, uploads, ISR, WebSockets, loaders, deployment, anything. Call with no query for the cheap section index; pass query (e.g. \"isr revalidate\") for the top matching sections. Use this instead of reading llms-full.txt (~150 KB) whole. For the EXACT TypeScript of a type/interface/function, call nifra_types instead (don't read .d.ts files).",
19
19
  inputSchema: {
20
20
  type: "object",
21
21
  properties: {
@@ -30,7 +30,7 @@ export function docsTools(loadDocs, loadExamples, loadTypes) {
30
30
  handler: async (args) => {
31
31
  const corpus = await loadDocs();
32
32
  if (corpus === undefined) {
33
- return "docs corpus missing — run `bun run gen:llms` in the nifra repo (published builds ship it).";
33
+ return "docs corpus missing - run `bun run gen:llms` in the nifra repo (published builds ship it).";
34
34
  }
35
35
  const { query, limit } = args;
36
36
  return renderDocsResult(corpus, query, clamp(limit, 1, 5, 3));
@@ -38,7 +38,7 @@ export function docsTools(loadDocs, loadExamples, loadTypes) {
38
38
  },
39
39
  {
40
40
  name: "nifra_example",
41
- description: 'Get a VERIFIED, copy-pasteable nifra code example for a task — auth route, file upload, ISR page, loader/action, typed client, SSE, deployment, etc. Every snippet is typechecked against the installed nifra version, so it compiles as-is. PREFER THIS over writing nifra code from memory (training data drifts). Call with no query for the grouped index; pass query (e.g. "protected route", "upload", "isr revalidate") for matching snippets.',
41
+ description: 'Get a VERIFIED, copy-pasteable nifra code example for a task - auth route, file upload, ISR page, loader/action, typed client, SSE, deployment, etc. Every snippet is typechecked against the installed nifra version, so it compiles as-is. PREFER THIS over writing nifra code from memory (training data drifts). Call with no query for the grouped index; pass query (e.g. "protected route", "upload", "isr revalidate") for matching snippets.',
42
42
  inputSchema: {
43
43
  type: "object",
44
44
  properties: {
@@ -53,7 +53,7 @@ export function docsTools(loadDocs, loadExamples, loadTypes) {
53
53
  handler: async (args) => {
54
54
  const corpus = await loadExamples();
55
55
  if (corpus === undefined) {
56
- return "examples corpus missing — run `bun run gen:llms` in the nifra repo (published builds ship it).";
56
+ return "examples corpus missing - run `bun run gen:llms` in the nifra repo (published builds ship it).";
57
57
  }
58
58
  const { query, limit } = args;
59
59
  return renderExamplesResult(corpus, query, clamp(limit, 1, 5, 3));
@@ -61,13 +61,13 @@ export function docsTools(loadDocs, loadExamples, loadTypes) {
61
61
  },
62
62
  {
63
63
  name: "nifra_types",
64
- description: 'Get the EXACT TypeScript declaration of any exported @nifrajs/* symbol — interface, type, class, function, const. Each signature is generated from the package\'s built .d.ts, so it is the LITERAL declaration: complete, authoritative, never prose and never truncated. THIS IS THE SOURCE OF TRUTH for nifra\'s types — do NOT read node_modules/@nifrajs/**/*.d.ts; call this instead. Pass `name` for an exact symbol (e.g. "RateLimitStore", "RouteSchema", "Context", "rateLimit"); pass `query` to search by keyword; omit both for the per-package index of names. A `name` lookup is always the complete declaration; a `query` returns a one-line summary plus the signature, collapsing an oversized body — pass `full: true` to override.',
64
+ description: 'Get the EXACT TypeScript declaration of any exported @nifrajs/* symbol - interface, type, class, function, const. Each signature is generated from the package\'s built .d.ts, so it is the LITERAL declaration: complete, authoritative, never prose and never truncated. THIS IS THE SOURCE OF TRUTH for nifra\'s types - do NOT read node_modules/@nifrajs/**/*.d.ts; call this instead. Pass `name` for an exact symbol (e.g. "RateLimitStore", "RouteSchema", "Context", "rateLimit"); pass `query` to search by keyword; omit both for the per-package index of names. A `name` lookup is always the complete declaration; a `query` returns a one-line summary plus the signature, collapsing an oversized body - pass `full: true` to override.',
65
65
  inputSchema: {
66
66
  type: "object",
67
67
  properties: {
68
68
  name: {
69
69
  type: "string",
70
- description: "Exact symbol name — returns its literal declaration (e.g. RateLimitStore).",
70
+ description: "Exact symbol name - returns its literal declaration (e.g. RateLimitStore).",
71
71
  },
72
72
  query: {
73
73
  type: "string",
@@ -79,7 +79,7 @@ export function docsTools(loadDocs, loadExamples, loadTypes) {
79
79
  },
80
80
  full: {
81
81
  type: "boolean",
82
- description: "Query mode only: return whole declarations instead of collapsed ones. Off by default — a search is for picking a symbol, and one match can be tens of thousands of characters.",
82
+ description: "Query mode only: return whole declarations instead of collapsed ones. Off by default - a search is for picking a symbol, and one match can be tens of thousands of characters.",
83
83
  },
84
84
  },
85
85
  additionalProperties: false,
@@ -87,7 +87,7 @@ export function docsTools(loadDocs, loadExamples, loadTypes) {
87
87
  handler: async (args) => {
88
88
  const corpus = await loadTypes();
89
89
  if (corpus === undefined) {
90
- return "types corpus missing — run `bun run build && bun run gen:llms` in the nifra repo (published builds ship it).";
90
+ return "types corpus missing - run `bun run build && bun run gen:llms` in the nifra repo (published builds ship it).";
91
91
  }
92
92
  const { name, query, limit, full } = args;
93
93
  return renderTypesResult(corpus, name, query, clamp(limit, 1, 8, 5), full === true);
@@ -1,13 +1,13 @@
1
1
  /**
2
- * The PUBLIC nifra docs MCP — Streamable-HTTP transport exposing the project-INDEPENDENT tools
2
+ * The PUBLIC nifra docs MCP - Streamable-HTTP transport exposing the project-INDEPENDENT tools
3
3
  * (`nifra_docs` + `nifra_example`) so any remote AI agent can learn nifra without a local checkout.
4
4
  * Both tools read the bundled corpus (llms-full.txt / examples.json shipped in this package) and ignore
5
- * `cwd`, so they reuse the exact definitions from {@link projectTools} — one source, no drift.
5
+ * `cwd`, so they reuse the exact definitions from {@link projectTools} - one source, no drift.
6
6
  *
7
7
  * The transport itself (body cap, CORS, JSON-RPC dispatch) lives in `@nifrajs/mcp/http`; this module is
8
8
  * the docs-specific layer over it: it supplies the bundled corpus tools and the `nifra-docs` server info.
9
- * {@link handleMcpHttp} self-hosts ANYWHERE Bun runs — the simplest path is `nifra docs-mcp` on a VPS; the
10
- * same handler runs on Cloudflare/Vercel edge (there inline the corpus as string imports — a Worker has no
9
+ * {@link handleMcpHttp} self-hosts ANYWHERE Bun runs - the simplest path is `nifra docs-mcp` on a VPS; the
10
+ * same handler runs on Cloudflare/Vercel edge (there inline the corpus as string imports - a Worker has no
11
11
  * filesystem). `export default { port, fetch }` also lets `bun run mcp-http.ts` serve it directly.
12
12
  */
13
13
  import { type McpHttpOptions } from "@nifrajs/mcp/http";
@@ -29,7 +29,7 @@ export declare function handleMcpHttp(request: Request): Promise<Response>;
29
29
  /**
30
30
  * Worker/edge + local entry. `export default { fetch }` is the universal server shape: Cloudflare /
31
31
  * Vercel edge / Deno deploy use `fetch` (and ignore `port`); `bun run mcp-http.ts` auto-serves it on
32
- * `port` (PORT env, default 8787) — Bun serves a module's default-exported server, so NO manual
32
+ * `port` (PORT env, default 8787) - Bun serves a module's default-exported server, so NO manual
33
33
  * `Bun.serve` here (that would double-bind the port).
34
34
  */
35
35
  declare const _default: {
package/dist/mcp-http.js CHANGED
@@ -1,13 +1,13 @@
1
1
  /**
2
- * The PUBLIC nifra docs MCP — Streamable-HTTP transport exposing the project-INDEPENDENT tools
2
+ * The PUBLIC nifra docs MCP - Streamable-HTTP transport exposing the project-INDEPENDENT tools
3
3
  * (`nifra_docs` + `nifra_example`) so any remote AI agent can learn nifra without a local checkout.
4
4
  * Both tools read the bundled corpus (llms-full.txt / examples.json shipped in this package) and ignore
5
- * `cwd`, so they reuse the exact definitions from {@link projectTools} — one source, no drift.
5
+ * `cwd`, so they reuse the exact definitions from {@link projectTools} - one source, no drift.
6
6
  *
7
7
  * The transport itself (body cap, CORS, JSON-RPC dispatch) lives in `@nifrajs/mcp/http`; this module is
8
8
  * the docs-specific layer over it: it supplies the bundled corpus tools and the `nifra-docs` server info.
9
- * {@link handleMcpHttp} self-hosts ANYWHERE Bun runs — the simplest path is `nifra docs-mcp` on a VPS; the
10
- * same handler runs on Cloudflare/Vercel edge (there inline the corpus as string imports — a Worker has no
9
+ * {@link handleMcpHttp} self-hosts ANYWHERE Bun runs - the simplest path is `nifra docs-mcp` on a VPS; the
10
+ * same handler runs on Cloudflare/Vercel edge (there inline the corpus as string imports - a Worker has no
11
11
  * filesystem). `export default { port, fetch }` also lets `bun run mcp-http.ts` serve it directly.
12
12
  */
13
13
  import { respondMcpHttp as respondMcpHttpCore } from "@nifrajs/mcp/http";
@@ -19,9 +19,9 @@ import { loadTypesCorpus } from "./types-search.js";
19
19
  // tool factory + the transport core from one entry: `import { respondMcpHttp, docsTools } from "@nifrajs/cli/mcp"`.
20
20
  export { docsTools } from "./mcp-docs-tools.js";
21
21
  // Kept in lockstep with packages/cli/package.json by check:publish's version-consistency gate.
22
- const VERSION = "2.2.0";
22
+ const VERSION = "2.3.0";
23
23
  const SERVER_INFO = { name: "nifra-docs", version: VERSION };
24
- const DOCS_HEALTH = "nifra docs MCP — POST JSON-RPC 2.0 here (methods: initialize, tools/list, tools/call). Tools: nifra_docs, nifra_example.";
24
+ const DOCS_HEALTH = "nifra docs MCP - POST JSON-RPC 2.0 here (methods: initialize, tools/list, tools/call). Tools: nifra_docs, nifra_example.";
25
25
  /** The two project-independent tools, reading the package's bundled corpus from disk (CLI use). */
26
26
  export function publicDocsTools() {
27
27
  return docsTools(loadDocsCorpus, loadExamplesCorpus, loadTypesCorpus);
@@ -41,7 +41,7 @@ export function handleMcpHttp(request) {
41
41
  /**
42
42
  * Worker/edge + local entry. `export default { fetch }` is the universal server shape: Cloudflare /
43
43
  * Vercel edge / Deno deploy use `fetch` (and ignore `port`); `bun run mcp-http.ts` auto-serves it on
44
- * `port` (PORT env, default 8787) — Bun serves a module's default-exported server, so NO manual
44
+ * `port` (PORT env, default 8787) - Bun serves a module's default-exported server, so NO manual
45
45
  * `Bun.serve` here (that would double-bind the port).
46
46
  */
47
47
  export default {
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * The MCP JSON-RPC dispatch now lives in `@nifrajs/mcp` (shared with nifra apps that expose their own
3
- * MCP server / MCP Apps widgets). This module is a thin re-export so the CLI's existing imports — and
4
- * the `@nifrajs/cli/mcp` self-host surface — keep resolving here unchanged.
3
+ * MCP server / MCP Apps widgets). This module is a thin re-export so the CLI's existing imports - and
4
+ * the `@nifrajs/cli/mcp` self-host surface - keep resolving here unchanged.
5
5
  */
6
6
  export * from "@nifrajs/mcp/protocol";
7
7
  //# sourceMappingURL=mcp-protocol.d.ts.map
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * The MCP JSON-RPC dispatch now lives in `@nifrajs/mcp` (shared with nifra apps that expose their own
3
- * MCP server / MCP Apps widgets). This module is a thin re-export so the CLI's existing imports — and
4
- * the `@nifrajs/cli/mcp` self-host surface — keep resolving here unchanged.
3
+ * MCP server / MCP Apps widgets). This module is a thin re-export so the CLI's existing imports - and
4
+ * the `@nifrajs/cli/mcp` self-host surface - keep resolving here unchanged.
5
5
  */
6
6
  export * from "@nifrajs/mcp/protocol";
7
7
  //# sourceMappingURL=mcp-protocol.js.map
@@ -1,12 +1,12 @@
1
1
  /**
2
- * The `nifra_render` engine + its child-process entry — the page half of `nifra_run`. Where `nifra_run`
2
+ * The `nifra_render` engine + its child-process entry - the page half of `nifra_run`. Where `nifra_run`
3
3
  * runs requests through the BACKEND, this SSRs page routes through the WEB app (`createWebApp`), so an
4
4
  * agent can verify a page renders and its loader ran after an edit. Spawned fresh per call (`bun
5
5
  * mcp-render.ts <cwd>`) so the project's CURRENT route/loader code is loaded.
6
6
  *
7
- * No build required: a placeholder `clientEntry` is injected (the SSR HTML renders regardless — the
7
+ * No build required: a placeholder `clientEntry` is injected (the SSR HTML renders regardless - the
8
8
  * client bundle only matters for hydration in the browser, not for "did this page render server-side").
9
- * {@link renderPages} never throws — every failure (no config, a loader error, a render throw) becomes
9
+ * {@link renderPages} never throws - every failure (no config, a loader error, a render throw) becomes
10
10
  * `{ error }` so the agent gets the actionable message.
11
11
  */
12
12
  export declare function renderPages(cwd: string, requests: unknown): Promise<{
@@ -1,12 +1,12 @@
1
1
  /**
2
- * The `nifra_render` engine + its child-process entry — the page half of `nifra_run`. Where `nifra_run`
2
+ * The `nifra_render` engine + its child-process entry - the page half of `nifra_run`. Where `nifra_run`
3
3
  * runs requests through the BACKEND, this SSRs page routes through the WEB app (`createWebApp`), so an
4
4
  * agent can verify a page renders and its loader ran after an edit. Spawned fresh per call (`bun
5
5
  * mcp-render.ts <cwd>`) so the project's CURRENT route/loader code is loaded.
6
6
  *
7
- * No build required: a placeholder `clientEntry` is injected (the SSR HTML renders regardless — the
7
+ * No build required: a placeholder `clientEntry` is injected (the SSR HTML renders regardless - the
8
8
  * client bundle only matters for hydration in the browser, not for "did this page render server-side").
9
- * {@link renderPages} never throws — every failure (no config, a loader error, a render throw) becomes
9
+ * {@link renderPages} never throws - every failure (no config, a loader error, a render throw) becomes
10
10
  * `{ error }` so the agent gets the actionable message.
11
11
  */
12
12
  import { inProcessClient } from "@nifrajs/client";
@@ -15,7 +15,7 @@ import { createWebApp } from "@nifrajs/web";
15
15
  import { discoverRoutes } from "@nifrajs/web/fs";
16
16
  import { loadApp, resolvePlugins } from "./load.js";
17
17
  const errString = (err) => err instanceof Error ? `${err.name}: ${err.message}` : String(err);
18
- /** Load the project, register its SSR plugins, and build the SSR `createWebApp` once — the cost the warm
18
+ /** Load the project, register its SSR plugins, and build the SSR `createWebApp` once - the cost the warm
19
19
  * worker amortizes across calls. Never throws (every failure → `{ error }`). Factored out of
20
20
  * {@link renderPages} so the cold path (fresh process per call) and the warm worker share one builder. */
21
21
  async function buildWebApp(cwd) {
@@ -79,7 +79,7 @@ function redirectConsoleToStderr() {
79
79
  /**
80
80
  * Warm-worker loop (`--worker`): build the web app ONCE, then SSR each `{ id, input: { requests } }` line
81
81
  * against that hot app and reply `{ id, output }`. The parent (`mcp.ts`'s warm-render handler) spawns this,
82
- * fingerprints the source tree, and restarts the worker when a file changes — so warm reuse never serves a
82
+ * fingerprints the source tree, and restarts the worker when a file changes - so warm reuse never serves a
83
83
  * stale render. Mirrors `mcp-run.ts`'s worker (single backend entry → no per-call entry key needed here).
84
84
  */
85
85
  async function runWorker(cwd) {
@@ -136,7 +136,7 @@ if (import.meta.main) {
136
136
  // The parent (mcp.ts) spawns `bun mcp-render.ts <cwd>` WITHOUT setting the subprocess `cwd` option, so
137
137
  // this process inherits the MCP server's working dir, not the app's. SSR resolves `react-dom/server`
138
138
  // from `process.cwd()` to share ONE React core with the app's route components (the dual-React fix in
139
- // @nifrajs/web-react) — so we must chdir to the app root before any render. Best-effort: a bad path
139
+ // @nifrajs/web-react) - so we must chdir to the app root before any render. Best-effort: a bad path
140
140
  // surfaces later as a load error rather than crashing the child here.
141
141
  if (cwd !== process.cwd()) {
142
142
  try {
package/dist/mcp-run.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * The `nifra_run` engine + its child-process entry. The MCP server spawns this file fresh on every
3
- * `nifra_run` call (`bun mcp-run.ts <cwd>`) so the project's CURRENT backend code is loaded — picking
3
+ * `nifra_run` call (`bun mcp-run.ts <cwd>`) so the project's CURRENT backend code is loaded - picking
4
4
  * up the agent's latest edits. {@link runBackend} is the pure-ish core (resolve entry → import → run),
5
5
  * exported so it's unit-testable in-process; the entry below wires it to stdin/stdout.
6
6
  */
@@ -12,7 +12,7 @@ export declare function loadBackend(cwd: string, entry?: string): Promise<{
12
12
  }>;
13
13
  /**
14
14
  * Resolve the backend entry under `cwd`, import it, and run `requests` through its exported app. Never
15
- * throws — every failure (missing/invalid entry, import/compile/runtime error) becomes `{ error }`, so
15
+ * throws - every failure (missing/invalid entry, import/compile/runtime error) becomes `{ error }`, so
16
16
  * the caller (and the agent) always gets actionable output. An import error here IS the failure the
17
17
  * agent needs to see and fix.
18
18
  */
package/dist/mcp-run.js CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * The `nifra_run` engine + its child-process entry. The MCP server spawns this file fresh on every
3
- * `nifra_run` call (`bun mcp-run.ts <cwd>`) so the project's CURRENT backend code is loaded — picking
3
+ * `nifra_run` call (`bun mcp-run.ts <cwd>`) so the project's CURRENT backend code is loaded - picking
4
4
  * up the agent's latest edits. {@link runBackend} is the pure-ish core (resolve entry → import → run),
5
5
  * exported so it's unit-testable in-process; the entry below wires it to stdin/stdout.
6
6
  */
@@ -47,7 +47,7 @@ export async function loadBackend(cwd, entry) {
47
47
  }
48
48
  /**
49
49
  * Resolve the backend entry under `cwd`, import it, and run `requests` through its exported app. Never
50
- * throws — every failure (missing/invalid entry, import/compile/runtime error) becomes `{ error }`, so
50
+ * throws - every failure (missing/invalid entry, import/compile/runtime error) becomes `{ error }`, so
51
51
  * the caller (and the agent) always gets actionable output. An import error here IS the failure the
52
52
  * agent needs to see and fix.
53
53
  */
package/dist/mcp.d.ts CHANGED
@@ -1,27 +1,27 @@
1
1
  /**
2
- * `nifra mcp` — a Model Context Protocol server (stdio) that lets a coding agent (Claude, Cursor, …)
2
+ * `nifra mcp` - a Model Context Protocol server (stdio) that lets a coding agent (Claude, Cursor, …)
3
3
  * act on a nifra project, not just read about it:
4
4
  *
5
- * - `nifra_context` — this project's API routes + page routes + conventions (see {@link describeProject}).
6
- * - `nifra_run` — run HTTP requests through the project's backend and return structured results
5
+ * - `nifra_context` - this project's API routes + page routes + conventions (see {@link describeProject}).
6
+ * - `nifra_run` - run HTTP requests through the project's backend and return structured results
7
7
  * (status, body, errors). The write → run → see-the-failure → fix loop, powered by `@nifrajs/runner`.
8
8
  * Each call runs the backend in a FRESH subprocess, so it reflects the agent's latest edits.
9
- * - `nifra_render` — SSR a page route through the project's web app; returns the rendered HTML (the
9
+ * - `nifra_render` - SSR a page route through the project's web app; returns the rendered HTML (the
10
10
  * page half of `nifra_run`). Fresh subprocess per call; no build needed.
11
- * - `nifra_ws` — verify a WebSocket route with a real Bun WebSocket round-trip.
12
- * - `nifra_docs` — keyword-search the framework docs; returns only the matching sections.
13
- * - `nifra_example` — a verified, copy-pasteable snippet for a task (typechecked against the live API,
11
+ * - `nifra_ws` - verify a WebSocket route with a real Bun WebSocket round-trip.
12
+ * - `nifra_docs` - keyword-search the framework docs; returns only the matching sections.
13
+ * - `nifra_example` - a verified, copy-pasteable snippet for a task (typechecked against the live API,
14
14
  * so it can't hallucinate a drifted nifra API).
15
- * - `nifra_scaffold`— map a URL path to the correct `routes/` file + a contract-correct page stub.
16
- * - `nifra_check` — the drift gate (typecheck + lints, each with a structured fix), for an agent to fix against.
17
- * - `nifra_assure` — route classification + enforcement-evidence gate.
15
+ * - `nifra_scaffold`- map a URL path to the correct `routes/` file + a contract-correct page stub.
16
+ * - `nifra_check` - the drift gate (typecheck + lints, each with a structured fix), for an agent to fix against.
17
+ * - `nifra_assure` - route classification + enforcement-evidence gate.
18
18
  * - `nifra_levels` - the cumulative verification ladder (L0 contract → L4 invariants): what the
19
19
  * project actually proves, and why each level it misses does not hold.
20
- * - `nifra_doctor` — package.json dependency drift detector, with safe local-version auto-fix.
20
+ * - `nifra_doctor` - package.json dependency drift detector, with safe local-version auto-fix.
21
21
  *
22
22
  * Wire it into a client (e.g. Claude Desktop / Cursor) as: command `nifra`, args `["mcp"]`, run in the
23
23
  * project root. The protocol is hand-rolled (newline-delimited JSON-RPC 2.0 over stdio), including
24
- * standard MCP progress notifications and request cancellation — no SDK dependency, the same
24
+ * standard MCP progress notifications and request cancellation - no SDK dependency, the same
25
25
  * minimal-surface choice as the rest of nifra. The pure dispatch lives in `./mcp-protocol.ts`; this
26
26
  * module is the I/O shell (stdin loop, tool wiring, the run subprocess).
27
27
  */
@@ -29,7 +29,7 @@ import type { LoadAppOptions, LoadedApp } from "./load.js";
29
29
  import { type McpPrompt, type McpResource, type McpServerFeatures, type McpTool } from "./mcp-protocol.js";
30
30
  /** A persistent `mcp-run`/`mcp-render` `--worker` subprocess: the backend/web app is loaded ONCE and
31
31
  * reused across newline-delimited `{ id, input }` requests, replying `{ id, output }`. The same machinery
32
- * powers both `nifra_run warm` and `nifra_render warm` — `child` selects which engine, `label` shapes the
32
+ * powers both `nifra_run warm` and `nifra_render warm` - `child` selects which engine, `label` shapes the
33
33
  * cancellation message. The owning handler ({@link createWarmHandler}) fingerprints the source tree and
34
34
  * replaces the worker when a file changes, so warm reuse never serves a stale result. Exported for the
35
35
  * concurrency test that proves a single per-request cancel doesn't tear down the shared worker. */
@@ -60,7 +60,7 @@ export declare function projectResources(cwd: string, loadAppCached?: (outDirNam
60
60
  export declare function projectPrompts(): McpPrompt[];
61
61
  export declare function projectFeatures(cwd: string, loadAppCached?: (outDirName?: string) => Promise<LoadedApp>): McpServerFeatures;
62
62
  /**
63
- * Resolve an optional `dir` tool argument — a subdirectory of the project root the caller wants to scope a
63
+ * Resolve an optional `dir` tool argument - a subdirectory of the project root the caller wants to scope a
64
64
  * check/test to (e.g. `nifra check` on `app/` when the MCP server's root is a monorepo). Returns the
65
65
  * absolute target, or `null` if `dir` escapes the root (a path-traversal guard: no `..` out, no absolute
66
66
  * path elsewhere). `undefined`/empty → the root itself.
package/dist/mcp.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"mcp.d.ts","sourceRoot":"","sources":["../src/mcp.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAUH,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,WAAW,CAAA;AAG1D,OAAO,EAML,KAAK,SAAS,EAEd,KAAK,WAAW,EAChB,KAAK,iBAAiB,EACtB,KAAK,OAAO,EAIb,MAAM,mBAAmB,CAAA;AA0G1B;;;;;mGAKmG;AACnG,qBAAa,UAAU;IAkBnB,QAAQ,CAAC,WAAW,EAAE,MAAM;IAC5B,OAAO,CAAC,QAAQ,CAAC,KAAK;IAlBxB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAgB;IACrC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAOrB;IACH,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,MAAM,CAAI;IAClB,OAAO,CAAC,MAAM,CAAQ;gBAGpB,KAAK,EAAE,SAAS,GAAG,YAAY,EAC/B,GAAG,EAAE,MAAM,EACF,WAAW,EAAE,MAAM,EACX,KAAK,EAAE,MAAM;IAsBhC,IAAI,IAAI,IAAI;IAIN,OAAO,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC;YAwCtD,UAAU;YAiBV,UAAU;IAUxB,OAAO,CAAC,UAAU;CAkBnB;AAsFD,KAAK,eAAe,GAAG,CACrB,GAAG,EAAE,MAAM,EACX,UAAU,CAAC,EAAE,MAAM,EACnB,OAAO,CAAC,EAAE,cAAc,KACrB,OAAO,CAAC,SAAS,CAAC,CAAA;AAEvB,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,OAAO,CAAC,EAAE,eAAe,CAAA;IAClC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;CACxD;AAmCD,qGAAqG;AACrG,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,MAAM,EACX,OAAO,GAAE,sBAA2B,GACnC,CAAC,UAAU,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,SAAS,CAAC,CA+B7C;AA0CD,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,MAAM,EACX,aAAa,GAAE,CAAC,UAAU,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,SAAS,CAA8B,GACtF,WAAW,EAAE,CAmCf;AAED,wBAAgB,cAAc,IAAI,SAAS,EAAE,CAkE5C;AAED,wBAAgB,eAAe,CAC7B,GAAG,EAAE,MAAM,EACX,aAAa,GAAE,CAAC,UAAU,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,SAAS,CAA8B,GACtF,iBAAiB,CAEnB;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAItF;AAWD,gDAAgD;AAChD,wBAAgB,YAAY,CAC1B,GAAG,EAAE,MAAM,EACX,aAAa,GAAE,CAAC,UAAU,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,SAAS,CAA8B,GACtF,OAAO,EAAE,CAqdX;AAOD;yFACyF;AACzF,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,EAAE,CAoD/D;AAYD,gHAAgH;AAChH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,GAAG,WAAW,EAAE,CAqBvE;AAWD,4GAA4G;AAC5G,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,SAAS,EAAE,CAgBnE;AA2BD,uGAAuG;AACvG,wBAAsB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAyF9E"}
1
+ {"version":3,"file":"mcp.d.ts","sourceRoot":"","sources":["../src/mcp.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAUH,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,WAAW,CAAA;AAG1D,OAAO,EAML,KAAK,SAAS,EAEd,KAAK,WAAW,EAChB,KAAK,iBAAiB,EACtB,KAAK,OAAO,EAIb,MAAM,mBAAmB,CAAA;AA0G1B;;;;;mGAKmG;AACnG,qBAAa,UAAU;IAkBnB,QAAQ,CAAC,WAAW,EAAE,MAAM;IAC5B,OAAO,CAAC,QAAQ,CAAC,KAAK;IAlBxB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAgB;IACrC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAOrB;IACH,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,MAAM,CAAI;IAClB,OAAO,CAAC,MAAM,CAAQ;gBAGpB,KAAK,EAAE,SAAS,GAAG,YAAY,EAC/B,GAAG,EAAE,MAAM,EACF,WAAW,EAAE,MAAM,EACX,KAAK,EAAE,MAAM;IAsBhC,IAAI,IAAI,IAAI;IAIN,OAAO,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC;YAwCtD,UAAU;YAiBV,UAAU;IAUxB,OAAO,CAAC,UAAU;CAkBnB;AAsFD,KAAK,eAAe,GAAG,CACrB,GAAG,EAAE,MAAM,EACX,UAAU,CAAC,EAAE,MAAM,EACnB,OAAO,CAAC,EAAE,cAAc,KACrB,OAAO,CAAC,SAAS,CAAC,CAAA;AAEvB,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,OAAO,CAAC,EAAE,eAAe,CAAA;IAClC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;CACxD;AAmCD,qGAAqG;AACrG,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,MAAM,EACX,OAAO,GAAE,sBAA2B,GACnC,CAAC,UAAU,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,SAAS,CAAC,CA+B7C;AA0CD,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,MAAM,EACX,aAAa,GAAE,CAAC,UAAU,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,SAAS,CAA8B,GACtF,WAAW,EAAE,CAmCf;AAED,wBAAgB,cAAc,IAAI,SAAS,EAAE,CAkE5C;AAED,wBAAgB,eAAe,CAC7B,GAAG,EAAE,MAAM,EACX,aAAa,GAAE,CAAC,UAAU,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,SAAS,CAA8B,GACtF,iBAAiB,CAEnB;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAItF;AAWD,gDAAgD;AAChD,wBAAgB,YAAY,CAC1B,GAAG,EAAE,MAAM,EACX,aAAa,GAAE,CAAC,UAAU,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,SAAS,CAA8B,GACtF,OAAO,EAAE,CAqdX;AAOD;yFACyF;AACzF,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,EAAE,CAoD/D;AAYD,gHAAgH;AAChH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,GAAG,WAAW,EAAE,CAqBvE;AAWD,4GAA4G;AAC5G,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,SAAS,EAAE,CAgBnE;AA2ED,uGAAuG;AACvG,wBAAsB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA8E9E"}
package/dist/mcp.js CHANGED
@@ -1,27 +1,27 @@
1
1
  /**
2
- * `nifra mcp` — a Model Context Protocol server (stdio) that lets a coding agent (Claude, Cursor, …)
2
+ * `nifra mcp` - a Model Context Protocol server (stdio) that lets a coding agent (Claude, Cursor, …)
3
3
  * act on a nifra project, not just read about it:
4
4
  *
5
- * - `nifra_context` — this project's API routes + page routes + conventions (see {@link describeProject}).
6
- * - `nifra_run` — run HTTP requests through the project's backend and return structured results
5
+ * - `nifra_context` - this project's API routes + page routes + conventions (see {@link describeProject}).
6
+ * - `nifra_run` - run HTTP requests through the project's backend and return structured results
7
7
  * (status, body, errors). The write → run → see-the-failure → fix loop, powered by `@nifrajs/runner`.
8
8
  * Each call runs the backend in a FRESH subprocess, so it reflects the agent's latest edits.
9
- * - `nifra_render` — SSR a page route through the project's web app; returns the rendered HTML (the
9
+ * - `nifra_render` - SSR a page route through the project's web app; returns the rendered HTML (the
10
10
  * page half of `nifra_run`). Fresh subprocess per call; no build needed.
11
- * - `nifra_ws` — verify a WebSocket route with a real Bun WebSocket round-trip.
12
- * - `nifra_docs` — keyword-search the framework docs; returns only the matching sections.
13
- * - `nifra_example` — a verified, copy-pasteable snippet for a task (typechecked against the live API,
11
+ * - `nifra_ws` - verify a WebSocket route with a real Bun WebSocket round-trip.
12
+ * - `nifra_docs` - keyword-search the framework docs; returns only the matching sections.
13
+ * - `nifra_example` - a verified, copy-pasteable snippet for a task (typechecked against the live API,
14
14
  * so it can't hallucinate a drifted nifra API).
15
- * - `nifra_scaffold`— map a URL path to the correct `routes/` file + a contract-correct page stub.
16
- * - `nifra_check` — the drift gate (typecheck + lints, each with a structured fix), for an agent to fix against.
17
- * - `nifra_assure` — route classification + enforcement-evidence gate.
15
+ * - `nifra_scaffold`- map a URL path to the correct `routes/` file + a contract-correct page stub.
16
+ * - `nifra_check` - the drift gate (typecheck + lints, each with a structured fix), for an agent to fix against.
17
+ * - `nifra_assure` - route classification + enforcement-evidence gate.
18
18
  * - `nifra_levels` - the cumulative verification ladder (L0 contract → L4 invariants): what the
19
19
  * project actually proves, and why each level it misses does not hold.
20
- * - `nifra_doctor` — package.json dependency drift detector, with safe local-version auto-fix.
20
+ * - `nifra_doctor` - package.json dependency drift detector, with safe local-version auto-fix.
21
21
  *
22
22
  * Wire it into a client (e.g. Claude Desktop / Cursor) as: command `nifra`, args `["mcp"]`, run in the
23
23
  * project root. The protocol is hand-rolled (newline-delimited JSON-RPC 2.0 over stdio), including
24
- * standard MCP progress notifications and request cancellation — no SDK dependency, the same
24
+ * standard MCP progress notifications and request cancellation - no SDK dependency, the same
25
25
  * minimal-surface choice as the rest of nifra. The pure dispatch lives in `./mcp-protocol.ts`; this
26
26
  * module is the I/O shell (stdin loop, tool wiring, the run subprocess).
27
27
  */
@@ -127,7 +127,7 @@ function boundedAppend(current, next, max = 12_000) {
127
127
  }
128
128
  /** A persistent `mcp-run`/`mcp-render` `--worker` subprocess: the backend/web app is loaded ONCE and
129
129
  * reused across newline-delimited `{ id, input }` requests, replying `{ id, output }`. The same machinery
130
- * powers both `nifra_run warm` and `nifra_render warm` — `child` selects which engine, `label` shapes the
130
+ * powers both `nifra_run warm` and `nifra_render warm` - `child` selects which engine, `label` shapes the
131
131
  * cancellation message. The owning handler ({@link createWarmHandler}) fingerprints the source tree and
132
132
  * replaces the worker when a file changes, so warm reuse never serves a stale result. Exported for the
133
133
  * concurrency test that proves a single per-request cancel doesn't tear down the shared worker. */
@@ -175,7 +175,7 @@ export class WarmWorker {
175
175
  // Per-request cancel: drop just THIS id and resolve its cancellation. The worker is shared
176
176
  // across concurrent requests (`pending` is id-keyed for exactly this reason), so killing the
177
177
  // process here would reject every OTHER in-flight request via the `exited` handler and force a
178
- // cold rebuild. Leave it hot — `createWarmHandler` already replaces it on file change.
178
+ // cold rebuild. Leave it hot - `createWarmHandler` already replaces it on file change.
179
179
  this.pending.delete(id);
180
180
  clearTimeout(timer);
181
181
  const reason = typeof signal?.reason === "string" ? `: ${signal.reason}` : "";
@@ -256,7 +256,7 @@ export class WarmWorker {
256
256
  }
257
257
  /** A warm handler that reuses a hot {@link WarmWorker} across calls and falls back to a one-shot fresh
258
258
  * subprocess on any worker failure. Shared by `nifra_run` (`child: "mcp-run"`, `label: "run"`) and
259
- * `nifra_render` (`child: "mcp-render"`, `label: "render"`) — the only differences are the engine and the
259
+ * `nifra_render` (`child: "mcp-render"`, `label: "render"`) - the only differences are the engine and the
260
260
  * message label, so there's a single source for the reuse + auto-restart-on-file-change logic. */
261
261
  function createWarmHandler(child, cwd, label) {
262
262
  let worker;
@@ -484,7 +484,7 @@ export function projectFeatures(cwd, loadAppCached = createCachedAppLoader(cwd))
484
484
  return { resources: projectResources(cwd, loadAppCached), prompts: projectPrompts() };
485
485
  }
486
486
  /**
487
- * Resolve an optional `dir` tool argument — a subdirectory of the project root the caller wants to scope a
487
+ * Resolve an optional `dir` tool argument - a subdirectory of the project root the caller wants to scope a
488
488
  * check/test to (e.g. `nifra check` on `app/` when the MCP server's root is a monorepo). Returns the
489
489
  * absolute target, or `null` if `dir` escapes the root (a path-traversal guard: no `..` out, no absolute
490
490
  * path elsewhere). `undefined`/empty → the root itself.
@@ -497,7 +497,7 @@ export function resolveProjectDir(root, dir) {
497
497
  }
498
498
  /** Consistent error string for a `dir` that escapes the project root. */
499
499
  function dirError(dir) {
500
- return JSON.stringify({ ok: false, error: `dir must be a subdirectory of the project root — "${dir}" escapes it.` }, null, 2);
500
+ return JSON.stringify({ ok: false, error: `dir must be a subdirectory of the project root - "${dir}" escapes it.` }, null, 2);
501
501
  }
502
502
  /** Build the project-scoped tools for `cwd`. */
503
503
  export function projectTools(cwd, loadAppCached = createCachedAppLoader(cwd)) {
@@ -506,7 +506,7 @@ export function projectTools(cwd, loadAppCached = createCachedAppLoader(cwd)) {
506
506
  return [
507
507
  {
508
508
  name: "nifra_context",
509
- description: "Get this nifra project's surface. Call it once UNFILTERED for a tight INDEX: the route list (API routes as `METHOD path`, page routes as `pattern → file`) + framework conventions + a pointer — cheap even on a big app, no per-route schema dump. Then pass `path` (a route prefix like /api/orders) and/or `kind` (api|pages) to fetch the FULL contract for that slice (body/query/response TS shapes + the exact typed-client call form).",
509
+ description: "Get this nifra project's surface. Call it once UNFILTERED for a tight INDEX: the route list (API routes as `METHOD path`, page routes as `pattern → file`) + framework conventions + a pointer - cheap even on a big app, no per-route schema dump. Then pass `path` (a route prefix like /api/orders) and/or `kind` (api|pages) to fetch the FULL contract for that slice (body/query/response TS shapes + the exact typed-client call form).",
510
510
  inputSchema: {
511
511
  type: "object",
512
512
  properties: {
@@ -529,7 +529,7 @@ export function projectTools(cwd, loadAppCached = createCachedAppLoader(cwd)) {
529
529
  },
530
530
  {
531
531
  name: "nifra_routes",
532
- description: "List this project's API routes as STRUCTURED JSON — each `{ method, path, call, body?, query?, response? }`, where `call` is the exact typed-client call form and the shapes are compact TS-typed contracts. For programmatic use (list_routes / get_route_schema) instead of parsing the nifra_context Markdown. No args = every route; pass `path` (a path or prefix like /api/orders) to narrow to those routes.",
532
+ description: "List this project's API routes as STRUCTURED JSON - each `{ method, path, call, body?, query?, response? }`, where `call` is the exact typed-client call form and the shapes are compact TS-typed contracts. For programmatic use (list_routes / get_route_schema) instead of parsing the nifra_context Markdown. No args = every route; pass `path` (a path or prefix like /api/orders) to narrow to those routes.",
533
533
  inputSchema: {
534
534
  type: "object",
535
535
  properties: {
@@ -694,7 +694,7 @@ export function projectTools(cwd, loadAppCached = createCachedAppLoader(cwd)) {
694
694
  ...docsTools(loadDocsCorpus, loadExamplesCorpus, loadTypesCorpus),
695
695
  {
696
696
  name: "nifra_scaffold",
697
- description: 'Map a URL path to the CORRECT routes/ file and get a contract-correct page stub. Agents routinely place file routes wrong — this applies the convention for you: ":id"/"[id]" → [id], "*rest" → [...rest], "/" → index. Pass path (e.g. "/users/:id"). Returns the file to create + the route-module contract (loader/action/meta/default) + a stub (ready-to-write for react/preact/solid; path+contract for vue/svelte/vanilla — use nifra_example for those bodies).',
697
+ description: 'Map a URL path to the CORRECT routes/ file and get a contract-correct page stub. Agents routinely place file routes wrong - this applies the convention for you: ":id"/"[id]" → [id], "*rest" → [...rest], "/" → index. Pass path (e.g. "/users/:id"). Returns the file to create + the route-module contract (loader/action/meta/default) + a stub (ready-to-write for react/preact/solid; path+contract for vue/svelte/vanilla - use nifra_example for those bodies).',
698
698
  inputSchema: {
699
699
  type: "object",
700
700
  properties: {
@@ -728,7 +728,7 @@ export function projectTools(cwd, loadAppCached = createCachedAppLoader(cwd)) {
728
728
  },
729
729
  {
730
730
  name: "nifra_check",
731
- description: 'Run the project\'s drift gate and return a structured result { ok, typecheck, diagnostics[] }: typecheck (the frontend↔backend contract), plus lints for hand-rolled fetch() to your own API, untyped client("…") calls missing <typeof app>, and server-only imports in routes/. Pass lintsOnly:true for a near-instant lint pass while iterating; run the full gate (default) to confirm the work is done — fix every diagnostic before finishing.',
731
+ description: 'Run the project\'s drift gate and return a structured result { ok, typecheck, diagnostics[] }: typecheck (the frontend↔backend contract), plus lints for hand-rolled fetch() to your own API, untyped client("…") calls missing <typeof app>, and server-only imports in routes/. Pass lintsOnly:true for a near-instant lint pass while iterating; run the full gate (default) to confirm the work is done - fix every diagnostic before finishing.',
732
732
  inputSchema: {
733
733
  type: "object",
734
734
  properties: {
@@ -897,7 +897,7 @@ export function projectTools(cwd, loadAppCached = createCachedAppLoader(cwd)) {
897
897
  },
898
898
  {
899
899
  name: "nifra_doctor",
900
- description: "Check this project for packages imported in source but NOT declared in package.json — the Bun-workspace trap where an import resolves at runtime (hoisting/workspace) so tests pass and `bun install` says no changes, yet tsc fails and a fresh/standalone install can't resolve it. Returns { ok, ran, findings[], fixed?, skippedFixes? }. Pass autoFix:true to update package.json only when the dependency version can be inferred locally from an ancestor package.json or installed package metadata; otherwise the tool returns the exact bun add command to run.",
900
+ description: "Check this project for packages imported in source but NOT declared in package.json - the Bun-workspace trap where an import resolves at runtime (hoisting/workspace) so tests pass and `bun install` says no changes, yet tsc fails and a fresh/standalone install can't resolve it. Returns { ok, ran, findings[], fixed?, skippedFixes? }. Pass autoFix:true to update package.json only when the dependency version can be inferred locally from an ancestor package.json or installed package metadata; otherwise the tool returns the exact bun add command to run.",
901
901
  inputSchema: {
902
902
  type: "object",
903
903
  properties: {
@@ -1028,6 +1028,51 @@ function namespaceForApp(name, tools, features) {
1028
1028
  features: { resources: namespacedResources, prompts: namespacedPrompts },
1029
1029
  };
1030
1030
  }
1031
+ /**
1032
+ * Merge the backend-derived resources/prompts into `base` - and keep a project that cannot be loaded
1033
+ * from deciding whether the server starts at all.
1034
+ *
1035
+ * Resources and prompts are OPTIONAL capabilities extracted from the app's backend, so this load is a
1036
+ * best-effort enrichment. It was awaited unguarded, which made it a hard boot requirement: `loadApp`
1037
+ * needs a web config (`adapter` + `clientModule`) and a `routes/` directory, and a backend-only
1038
+ * project - the shape `create-nifra`'s DEFAULT template produces - has neither. On those projects the
1039
+ * throw escaped before the JSON-RPC loop began, so the server wrote nothing and exited. `nifra
1040
+ * init-agents` would happily register an `.mcp.json` pointing at a server that could never start, and
1041
+ * every tool went with it, including the twelve that never touch the app (docs, examples, types,
1042
+ * check, doctor, levels, test).
1043
+ *
1044
+ * Degrading costs only the backend resources/prompts. Tools that genuinely need the app still fail,
1045
+ * but per call, with their own message - which is a diagnosis instead of a silence.
1046
+ */
1047
+ async function backendFeatures(loader, base) {
1048
+ const resources = [...(base.resources ?? [])];
1049
+ const prompts = [...(base.prompts ?? [])];
1050
+ try {
1051
+ const app = await loader();
1052
+ resources.push(...extractBackendResources(app.backend));
1053
+ prompts.push(...extractBackendPrompts(app.backend));
1054
+ }
1055
+ catch {
1056
+ // Not loadable here (no web config, or the config itself throws). The server still serves.
1057
+ }
1058
+ return { resources, prompts };
1059
+ }
1060
+ /**
1061
+ * The MCP tools the app itself declares via `app.tool(...)`, or none when the app cannot be loaded.
1062
+ *
1063
+ * Same reasoning as {@link backendFeatures}, on the hotter path: this ran on EVERY JSON-RPC message,
1064
+ * so an unloadable project failed `initialize` itself with a `-32603` and the session never opened.
1065
+ * An app's own tools are an extension of the built-in set, so their absence must not withdraw the
1066
+ * built-ins - a backend-only project still gets docs, examples, types, check, doctor, levels and test.
1067
+ */
1068
+ async function appDeclaredTools(loader) {
1069
+ try {
1070
+ return extractBackendTools((await loader()).backend);
1071
+ }
1072
+ catch {
1073
+ return [];
1074
+ }
1075
+ }
1031
1076
  /** Run the stdio MCP server: read newline-delimited JSON-RPC from stdin, write responses to stdout. */
1032
1077
  export async function runMcpServer(cwd, version) {
1033
1078
  let features;
@@ -1038,12 +1083,7 @@ export async function runMcpServer(cwd, version) {
1038
1083
  const allPrompts = [];
1039
1084
  for (const { name, cwd: appCwd } of appEntries) {
1040
1085
  const loader = createCachedAppLoader(appCwd);
1041
- const app = await loader();
1042
- const base = projectFeatures(appCwd, loader);
1043
- const ns = namespaceForApp(name, [], {
1044
- resources: [...(base.resources ?? []), ...extractBackendResources(app.backend)],
1045
- prompts: [...(base.prompts ?? []), ...extractBackendPrompts(app.backend)],
1046
- });
1086
+ const ns = namespaceForApp(name, [], await backendFeatures(loader, projectFeatures(appCwd, loader)));
1047
1087
  allResources.push(...(ns.features.resources ?? []));
1048
1088
  allPrompts.push(...(ns.features.prompts ?? []));
1049
1089
  }
@@ -1051,12 +1091,7 @@ export async function runMcpServer(cwd, version) {
1051
1091
  }
1052
1092
  else {
1053
1093
  const loadAppCached = createCachedAppLoader(cwd);
1054
- const base = projectFeatures(cwd, loadAppCached);
1055
- const app = await loadAppCached();
1056
- features = {
1057
- resources: [...(base.resources ?? []), ...extractBackendResources(app.backend)],
1058
- prompts: [...(base.prompts ?? []), ...extractBackendPrompts(app.backend)],
1059
- };
1094
+ features = await backendFeatures(loadAppCached, projectFeatures(cwd, loadAppCached));
1060
1095
  }
1061
1096
  const loadAppCached = createCachedAppLoader(cwd);
1062
1097
  const serverInfo = { name: "nifra", version };
@@ -1071,22 +1106,17 @@ export async function runMcpServer(cwd, version) {
1071
1106
  const allTools = [];
1072
1107
  for (const { name, cwd: appCwd } of appEntries) {
1073
1108
  const loader = createCachedAppLoader(appCwd);
1074
- const app = await loader();
1075
- const baseTools = projectTools(appCwd, loader);
1076
- const backendTools = extractBackendTools(app.backend);
1077
- const ns = namespaceForApp(name, [...baseTools, ...backendTools], {
1078
- resources: [],
1079
- prompts: [],
1080
- });
1109
+ const tools = [...projectTools(appCwd, loader), ...(await appDeclaredTools(loader))];
1110
+ const ns = namespaceForApp(name, tools, { resources: [], prompts: [] });
1081
1111
  allTools.push(...ns.tools);
1082
1112
  }
1083
1113
  activeTools = [...docsTools(loadDocsCorpus, loadExamplesCorpus, loadTypesCorpus), ...allTools];
1084
1114
  }
1085
1115
  else {
1086
- const app = await loadAppCached();
1087
- const baseTools = projectTools(cwd, loadAppCached);
1088
- const backendTools = extractBackendTools(app.backend);
1089
- activeTools = [...baseTools, ...backendTools];
1116
+ activeTools = [
1117
+ ...projectTools(cwd, loadAppCached),
1118
+ ...(await appDeclaredTools(loadAppCached)),
1119
+ ];
1090
1120
  }
1091
1121
  const response = await handleRpc(message, activeTools, serverInfo, features, {
1092
1122
  state,