@nifrajs/cli 2.12.1 → 2.14.1

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 (74) hide show
  1. package/dist/capabilities-tool.d.ts +54 -1
  2. package/dist/capabilities-tool.d.ts.map +1 -1
  3. package/dist/capabilities-tool.js +146 -18
  4. package/dist/capabilities-tool.js.map +1 -1
  5. package/dist/check.d.ts +11 -11
  6. package/dist/check.d.ts.map +1 -1
  7. package/dist/check.js +235 -47
  8. package/dist/check.js.map +1 -1
  9. package/dist/cli.d.ts +11 -0
  10. package/dist/cli.d.ts.map +1 -1
  11. package/dist/cli.js +96 -406
  12. package/dist/cli.js.map +1 -1
  13. package/dist/command-catalog.d.ts +244 -0
  14. package/dist/command-catalog.d.ts.map +1 -0
  15. package/dist/command-catalog.js +1014 -0
  16. package/dist/command-catalog.js.map +1 -0
  17. package/dist/contracts.d.ts +9 -0
  18. package/dist/contracts.d.ts.map +1 -1
  19. package/dist/contracts.js +22 -6
  20. package/dist/contracts.js.map +1 -1
  21. package/dist/doctor.d.ts +18 -11
  22. package/dist/doctor.d.ts.map +1 -1
  23. package/dist/doctor.js +30 -230
  24. package/dist/doctor.js.map +1 -1
  25. package/dist/load.d.ts.map +1 -1
  26. package/dist/load.js +22 -5
  27. package/dist/load.js.map +1 -1
  28. package/dist/manifest-tool.d.ts +15 -1
  29. package/dist/manifest-tool.d.ts.map +1 -1
  30. package/dist/manifest-tool.js +23 -9
  31. package/dist/manifest-tool.js.map +1 -1
  32. package/dist/mcp-http.js +1 -1
  33. package/dist/mcp.d.ts +8 -0
  34. package/dist/mcp.d.ts.map +1 -1
  35. package/dist/mcp.js +59 -349
  36. package/dist/mcp.js.map +1 -1
  37. package/dist/project-facts.d.ts +40 -0
  38. package/dist/project-facts.d.ts.map +1 -0
  39. package/dist/project-facts.js +36 -0
  40. package/dist/project-facts.js.map +1 -0
  41. package/dist/release-verification.d.ts +45 -0
  42. package/dist/release-verification.d.ts.map +1 -0
  43. package/dist/release-verification.js +303 -0
  44. package/dist/release-verification.js.map +1 -0
  45. package/dist/rules/comment-markers.d.ts +8 -0
  46. package/dist/rules/comment-markers.d.ts.map +1 -1
  47. package/dist/rules/comment-markers.js +36 -0
  48. package/dist/rules/comment-markers.js.map +1 -1
  49. package/dist/rules/index.d.ts +2 -3
  50. package/dist/rules/index.d.ts.map +1 -1
  51. package/dist/rules/index.js.map +1 -1
  52. package/dist/rules/routes.d.ts.map +1 -1
  53. package/dist/rules/routes.js +12 -14
  54. package/dist/rules/routes.js.map +1 -1
  55. package/dist/rules/security.js +14 -14
  56. package/dist/rules/security.js.map +1 -1
  57. package/docs/types.json +28 -15
  58. package/package.json +9 -9
  59. package/src/capabilities-tool.ts +229 -21
  60. package/src/check.ts +286 -64
  61. package/src/cli.ts +106 -399
  62. package/src/command-catalog.ts +1415 -0
  63. package/src/contracts.ts +28 -7
  64. package/src/doctor.ts +56 -264
  65. package/src/load.ts +25 -6
  66. package/src/manifest-tool.ts +43 -12
  67. package/src/mcp-http.ts +1 -1
  68. package/src/mcp.ts +82 -414
  69. package/src/project-facts.ts +86 -0
  70. package/src/release-verification.ts +377 -0
  71. package/src/rules/comment-markers.ts +36 -0
  72. package/src/rules/index.ts +3 -4
  73. package/src/rules/routes.ts +13 -14
  74. package/src/rules/security.ts +14 -14
@@ -19,8 +19,8 @@ export function formatManifestDiff(diff) {
19
19
  : "No breaking manifest changes.");
20
20
  return lines.join("\n");
21
21
  }
22
- /** Build only after both assurance layers pass, then optionally create an operator-signed sidecar. */
23
- export async function runManifestEmit(cwd, options = {}) {
22
+ /** Emit the manifest without printing. This is the execution seam used by all adapters. */
23
+ export async function collectManifestEmit(cwd, options = {}) {
24
24
  const verification = await collectProjectVerification(cwd, {
25
25
  ...(options.config !== undefined ? { config: options.config } : {}),
26
26
  });
@@ -34,9 +34,9 @@ export async function runManifestEmit(cwd, options = {}) {
34
34
  if (assurance === undefined) {
35
35
  throw new Error("[nifra] assurance evaluation was unavailable for manifest emission");
36
36
  }
37
+ const path = resolve(cwd, options.out ?? config.manifest?.path ?? DEFAULT_MANIFEST_FILE);
37
38
  if (!assurance.ok || (capabilityProject !== undefined && !capabilityProject.report.ok)) {
38
- console.error("[nifra] refusing to emit a manifest from failing assurance");
39
- return false;
39
+ return { ok: false, path };
40
40
  }
41
41
  const manifest = await buildNifraManifest({
42
42
  source: config.source,
@@ -44,28 +44,42 @@ export async function runManifestEmit(cwd, options = {}) {
44
44
  assurance,
45
45
  ...(capabilityProject !== undefined ? { capabilities: capabilityProject.report } : {}),
46
46
  });
47
- const path = resolve(cwd, options.out ?? config.manifest?.path ?? DEFAULT_MANIFEST_FILE);
48
47
  await Bun.write(path, `${serializeNifraManifest(manifest)}\n`);
48
+ let signaturePath;
49
49
  if (options.sign !== undefined) {
50
50
  if (config.manifest?.signer === undefined) {
51
51
  throw new Error("[nifra] --sign requires manifest.signer in nifra.assurance.ts; keep private keys behind that KMS/HSM callback.");
52
52
  }
53
53
  const signer = await config.manifest.signer(options.sign);
54
54
  const signature = await signNifraManifest(manifest, signer);
55
- await Bun.write(`${path}.sig`, `${serializeNifraManifestSignature(signature)}\n`);
55
+ signaturePath = `${path}.sig`;
56
+ await Bun.write(signaturePath, `${serializeNifraManifestSignature(signature)}\n`);
56
57
  }
57
- console.log(`[nifra] wrote manifest to ${path}${options.sign === undefined ? "" : ` and ${path}.sig`}`);
58
+ return { ok: true, path, manifest, ...(signaturePath === undefined ? {} : { signaturePath }) };
59
+ }
60
+ /** Build only after both assurance layers pass, then optionally create an operator-signed sidecar. */
61
+ export async function runManifestEmit(cwd, options = {}) {
62
+ const result = await collectManifestEmit(cwd, options);
63
+ if (!result.ok) {
64
+ console.error("[nifra] refusing to emit a manifest from failing assurance");
65
+ return false;
66
+ }
67
+ console.log(`[nifra] wrote manifest to ${result.path}${result.signaturePath === undefined ? "" : ` and ${result.signaturePath}`}`);
58
68
  return true;
59
69
  }
60
70
  /** Diff two already-emitted, hash-verified artifacts. Suitable for deploy promotion gates. */
61
- export async function runManifestDiff(cwd, beforePath, afterPath, options = {}) {
71
+ export async function collectManifestDiff(cwd, beforePath, afterPath) {
62
72
  const before = resolve(cwd, beforePath);
63
73
  const after = resolve(cwd, afterPath);
64
74
  if (!existsSync(before))
65
75
  throw new Error(`[nifra] manifest not found: ${before}`);
66
76
  if (!existsSync(after))
67
77
  throw new Error(`[nifra] manifest not found: ${after}`);
68
- const diff = diffNifraManifests(await parseNifraManifest(await Bun.file(before).text(), before), await parseNifraManifest(await Bun.file(after).text(), after));
78
+ return diffNifraManifests(await parseNifraManifest(await Bun.file(before).text(), before), await parseNifraManifest(await Bun.file(after).text(), after));
79
+ }
80
+ /** Diff two already-emitted, hash-verified artifacts and print the result. */
81
+ export async function runManifestDiff(cwd, beforePath, afterPath, options = {}) {
82
+ const diff = await collectManifestDiff(cwd, beforePath, afterPath);
69
83
  console.log(options.json === true
70
84
  ? JSON.stringify({ hasBreaking: diff.hasBreaking, changes: diff.changes }, null, 2)
71
85
  : formatManifestDiff(diff));
@@ -1 +1 @@
1
- {"version":3,"file":"manifest-tool.js","sourceRoot":"","sources":["../src/manifest-tool.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"manifest-tool.js","sourceRoot":"","sources":["../src/manifest-tool.ts"],"names":[],"mappings":"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"}
package/dist/mcp-http.js CHANGED
@@ -23,7 +23,7 @@ import { loadTypesCorpus } from "./types-search.js";
23
23
  export { examplesAppTool, examplesWidget } from "./mcp-app-tool.js";
24
24
  export { docsTools } from "./mcp-docs-tools.js";
25
25
  // Kept in lockstep with packages/cli/package.json by check:publish's version-consistency gate.
26
- const VERSION = "2.12.1";
26
+ const VERSION = "2.14.1";
27
27
  const SERVER_INFO = { name: "nifra-docs", version: VERSION };
28
28
  // Derive the GET/health tool list from the tools actually served, so the line can never drift from them.
29
29
  const docsHealth = (tools) => `nifra docs MCP - POST JSON-RPC 2.0 here (methods: initialize, tools/list, tools/call). Tools: ${tools
package/dist/mcp.d.ts CHANGED
@@ -34,6 +34,7 @@
34
34
  * minimal-surface choice as the rest of nifra. The pure dispatch lives in `./mcp-protocol.ts`; this
35
35
  * module is the I/O shell (stdin loop, tool wiring, the run subprocess).
36
36
  */
37
+ import { type CommandCatalogEntry, type CommandSpec } from "./command-catalog.js";
37
38
  import type { LoadAppOptions, LoadedApp } from "./load.js";
38
39
  import { type McpPrompt, type McpResource, type McpServerFeatures, type McpTool } from "./mcp-protocol.js";
39
40
  import { type McpRootVerdict } from "./mcp-root.js";
@@ -85,6 +86,13 @@ export declare function projectFeatures(cwd: string, loadAppCached?: (outDirName
85
86
  */
86
87
  export declare function resolveProjectDir(root: string, dir: string | undefined): string | null;
87
88
  /** Build the project-scoped tools for `cwd`. */
89
+ export interface CommandMcpToolOptions {
90
+ readonly cwd: string;
91
+ readonly loadAppCached?: () => Promise<LoadedApp>;
92
+ }
93
+ /** Adapt one executable catalog spec to its MCP descriptor and project-scoped handler. */
94
+ export declare function toMcpTool(source: CommandSpec<unknown, unknown> | CommandCatalogEntry, options: CommandMcpToolOptions): McpTool;
95
+ export declare function catalogProjectTools(cwd: string, loadAppCached?: () => Promise<LoadedApp>): McpTool[];
88
96
  export declare function projectTools(cwd: string, loadAppCached?: (outDirName?: string) => Promise<LoadedApp>): McpTool[];
89
97
  /** Extract tools registered via .tool() routes on the Nifra backend. Exported for the test that proves a
90
98
  * `server().tool()` route surfaces in `tools/list` and executes through `tools/call`. */
package/dist/mcp.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"mcp.d.ts","sourceRoot":"","sources":["../src/mcp.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"mcp.d.ts","sourceRoot":"","sources":["../src/mcp.ts"],"names":[],"mappings":"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"}
package/dist/mcp.js CHANGED
@@ -40,16 +40,15 @@ import { basename, isAbsolute, relative, resolve, sep } from "node:path";
40
40
  import { fileURLToPath } from "node:url";
41
41
  import { reflectRoutes } from "@nifrajs/core/reflection";
42
42
  import { Glob } from "bun";
43
+ import { commandCatalog, commandMcpName, findCommandSpec, } from "./command-catalog.js";
43
44
  import { collectContractProof } from "./contract-proof.js";
44
45
  import { loadDocsCorpus } from "./docs-search.js";
45
46
  import { loadExamplesCorpus } from "./examples.js";
46
- import { describeProject } from "./introspect.js";
47
47
  import { detectMonorepo, loadMonorepoApps } from "./load.js";
48
48
  import { docsTools } from "./mcp-docs-tools.js";
49
49
  import { createMcpProtocolState, handleRpc, rpcError, } from "./mcp-protocol.js";
50
50
  import { applyClientRoots, detectToolingDrift, driftNote, pathsFromRootsResult, resolveRootState, rootInstructions, rootVerdict, } from "./mcp-root.js";
51
51
  import { loadTypesCorpus } from "./types-search.js";
52
- import { collectProjectWorkGraph } from "./work-graph.js";
53
52
  /** Path to a sibling child entry (`mcp-run` / `mcp-render` / `mcp-ws`), resolved next to this module (`.ts` in
54
53
  * dev, `.js` once built). Each runs in a FRESH subprocess per call so the project's current code loads. */
55
54
  function childPath(name) {
@@ -593,11 +592,66 @@ export function resolveProjectDir(root, dir) {
593
592
  function dirError(dir) {
594
593
  return JSON.stringify({ ok: false, error: `dir must be a subdirectory of the project root - "${dir}" escapes it.` }, null, 2);
595
594
  }
596
- /** Build the project-scoped tools for `cwd`. */
595
+ /** Adapt one executable catalog spec to its MCP descriptor and project-scoped handler. */
596
+ export function toMcpTool(source, options) {
597
+ const spec = "run" in source ? source : findCommandSpec(source.name);
598
+ if (spec === undefined)
599
+ throw new Error(`missing command spec for ${source.name}`);
600
+ const entry = "run" in source ? undefined : source;
601
+ const { cwd } = options;
602
+ const loadAppCached = options.loadAppCached ?? createCachedAppLoader(cwd);
603
+ return {
604
+ name: commandMcpName(entry?.name ?? spec.name),
605
+ description: entry?.summary ?? spec.summary,
606
+ inputSchema: entry?.inputSchema ?? spec.input.jsonSchema,
607
+ handler: async (args, context) => {
608
+ const raw = { ...args };
609
+ const dir = raw.dir;
610
+ if (dir !== undefined && typeof dir !== "string")
611
+ return dirError(undefined);
612
+ const target = resolveProjectDir(cwd, dir);
613
+ if (target === null)
614
+ return dirError(dir);
615
+ if (typeof raw.config === "string") {
616
+ const config = resolve(target, raw.config);
617
+ if (config !== target && !config.startsWith(`${target}${sep}`))
618
+ return JSON.stringify({ ok: false, error: "config must stay inside the selected project directory" }, null, 2);
619
+ }
620
+ delete raw.dir;
621
+ let input;
622
+ try {
623
+ input = spec.input.parse(raw);
624
+ }
625
+ catch (error) {
626
+ return JSON.stringify({ ok: false, error: error instanceof Error ? error.message : String(error) }, null, 2);
627
+ }
628
+ const commandContext = {
629
+ cwd: target,
630
+ signal: context.signal,
631
+ progress: () => context.reportProgress?.(0.5, 1),
632
+ ...(target === cwd ? { loadApp: loadAppCached } : {}),
633
+ };
634
+ const output = await spec.run(input, commandContext);
635
+ const value = spec.json?.(output, input) ?? output;
636
+ return typeof value === "string" ? value : JSON.stringify(value, null, 2);
637
+ },
638
+ };
639
+ }
640
+ export function catalogProjectTools(cwd, loadAppCached = createCachedAppLoader(cwd)) {
641
+ return commandCatalog
642
+ .filter((entry) => entry.transports.includes("mcp"))
643
+ .map((entry) => {
644
+ const spec = findCommandSpec(entry.name);
645
+ if (spec === undefined)
646
+ throw new Error(`missing command spec for ${entry.name}`);
647
+ return toMcpTool(spec, { cwd, loadAppCached });
648
+ });
649
+ }
597
650
  export function projectTools(cwd, loadAppCached = createCachedAppLoader(cwd)) {
598
651
  const warmRun = createWarmHandler("mcp-run", cwd, "run");
599
652
  const warmRender = createWarmHandler("mcp-render", cwd, "render");
600
- return [
653
+ const catalogTools = catalogProjectTools(cwd, () => loadAppCached());
654
+ const legacyTools = [
601
655
  {
602
656
  name: "nifra_contract_proof",
603
657
  description: "Compare the current route contract with its snapshot and join each changed route to its route-assurance and capability evidence. Returns hasBreaking as the gate. The typed-contract check is lazy and runs only when check is true.",
@@ -646,75 +700,6 @@ export function projectTools(cwd, loadAppCached = createCachedAppLoader(cwd)) {
646
700
  }
647
701
  },
648
702
  },
649
- {
650
- name: "nifra_prove",
651
- description: "Build the static verification work graph for this project. Pass changed files to get only impacted routes and the cheapest proof plan. The result includes a serializable evidence bundle and a machine-checkable stop condition. Requires a fresh build and never probes a running application.",
652
- inputSchema: {
653
- type: "object",
654
- properties: {
655
- files: {
656
- type: "array",
657
- items: { type: "string" },
658
- description: "Project-relative changed files. Omit for a graph overview.",
659
- },
660
- minLevel: {
661
- type: "integer",
662
- minimum: 0,
663
- maximum: 4,
664
- description: "Required proof level for the stop condition. Defaults to 1.",
665
- },
666
- },
667
- additionalProperties: false,
668
- },
669
- handler: async (args) => {
670
- const files = args.files;
671
- const minLevel = args.minLevel;
672
- if ((files !== undefined &&
673
- (!Array.isArray(files) || files.some((file) => typeof file !== "string"))) ||
674
- (minLevel !== undefined &&
675
- (typeof minLevel !== "number" ||
676
- !Number.isInteger(minLevel) ||
677
- minLevel < 0 ||
678
- minLevel > 4))) {
679
- return JSON.stringify({
680
- ok: false,
681
- error: "files must be strings and minLevel must be an integer from 0 to 4",
682
- }, null, 2);
683
- }
684
- try {
685
- return JSON.stringify(await collectProjectWorkGraph(cwd, {
686
- ...(files === undefined ? {} : { changedFiles: files }),
687
- ...(minLevel === undefined ? {} : { minLevel }),
688
- }), null, 2);
689
- }
690
- catch (error) {
691
- return JSON.stringify({ ok: false, error: error instanceof Error ? error.message : "work graph failed" }, null, 2);
692
- }
693
- },
694
- },
695
- {
696
- name: "nifra_context",
697
- 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).",
698
- inputSchema: {
699
- type: "object",
700
- properties: {
701
- path: {
702
- type: "string",
703
- description: "Only routes whose path/pattern starts with this prefix.",
704
- },
705
- kind: {
706
- type: "string",
707
- enum: ["api", "pages"],
708
- description: "Limit to API routes or page routes.",
709
- },
710
- },
711
- additionalProperties: false,
712
- },
713
- handler: async (args) => {
714
- const filter = args;
715
- return describeProject(await loadAppCached(), filter);
716
- },
717
- },
718
703
  {
719
704
  name: "nifra_explain",
720
705
  description: "Turn a nifra error into a STRUCTURED diagnostic instead of eyeballing a stack trace: a stable `code`, the top frame in YOUR source, a codeframe around the offending line, and - when nifra recognises the failure - the plain-language `cause` + `fix` + docs anchor. Pass `error` (and `stack` if you have it, e.g. from nifra_run/nifra_test output or a failing build) to explain a specific failure; or pass `port` to fetch the running dev server's most recent SSR failure from `/__nifra/last-error`. Returns the same JSON the dev overlay renders.",
@@ -844,25 +829,6 @@ export function projectTools(cwd, loadAppCached = createCachedAppLoader(cwd)) {
844
829
  }
845
830
  },
846
831
  },
847
- {
848
- name: "nifra_routes",
849
- 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.",
850
- inputSchema: {
851
- type: "object",
852
- properties: {
853
- path: {
854
- type: "string",
855
- description: "Only routes whose path starts with this prefix (omit for all routes).",
856
- },
857
- },
858
- additionalProperties: false,
859
- },
860
- handler: async (args) => {
861
- const { routesToJson } = await import("./introspect.js");
862
- const { path } = args;
863
- return JSON.stringify(routesToJson(await loadAppCached(), path), null, 2);
864
- },
865
- },
866
832
  {
867
833
  name: "nifra_openapi",
868
834
  description: 'Return this project\'s backend OpenAPI 3.1 document generated from backend.ts route schemas via @nifrajs/schema. Use `format:"json"` for machine edits (default) or `format:"yaml"` for humans. Pass `path` (a route prefix like /api/orders, mirroring nifra_routes) to narrow a large backend to operations under that prefix instead of the whole document. Frontend-only apps return a valid empty paths object.',
@@ -1043,180 +1009,6 @@ export function projectTools(cwd, loadAppCached = createCachedAppLoader(cwd)) {
1043
1009
  return `${status}\n\n${renderScaffold(path, framework)}`;
1044
1010
  },
1045
1011
  },
1046
- {
1047
- name: "nifra_check",
1048
- description: 'Run the project\'s drift gate and return a structured result { ok, typecheck, diagnostics[], pipeline? }: 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/. `pipeline` answers "which bundler does this app run on" (bun|vite, for dev AND build alike) without starting a server, and its `pipeline` diagnostics catch the hazards of having two: a plugin in the slot the other bundler reads (accepted, then never called), a dev toolchain imported by the file `nifra build` bundles into the production server, and `conditions` that cannot reach Bun\'s dev client bundle. Read it before adding a plugin or a compiler. 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.',
1049
- inputSchema: {
1050
- type: "object",
1051
- properties: {
1052
- lintsOnly: {
1053
- type: "boolean",
1054
- description: "Skip tsc; run only the near-instant source lints (inner-loop mode).",
1055
- },
1056
- dir: {
1057
- type: "string",
1058
- description: 'Run the check in this subdirectory (relative to the project root), e.g. "app" or "packages/api". Use it when the MCP server\'s root is a monorepo but you want to check just one app. Default: the project root.',
1059
- },
1060
- },
1061
- additionalProperties: false,
1062
- },
1063
- handler: async (args, context) => {
1064
- const { collectCheckResult } = await import("./check.js");
1065
- const opts = args;
1066
- const target = resolveProjectDir(cwd, opts.dir);
1067
- if (target === null)
1068
- return dirError(opts.dir);
1069
- const result = await collectCheckResult(target, {
1070
- lintsOnly: opts.lintsOnly ?? false,
1071
- signal: context.signal,
1072
- // Bound the result so a large project can't emit an MCP message big enough to break the stdio
1073
- // transport (`-32000: Connection closed`). If `truncated` comes back, fix the shown diagnostics
1074
- // and re-run. The scan already skips gitignored trees (walkSource), so this is the safety net.
1075
- maxDiagnostics: 100,
1076
- });
1077
- return JSON.stringify(result.structuredDiagnostics === undefined
1078
- ? result
1079
- : { ...result, diagnostics: result.structuredDiagnostics }, null, 2);
1080
- },
1081
- },
1082
- {
1083
- name: "nifra_fix",
1084
- description: "Automatically fix diagnostic lints (such as rewriting hand-rolled fetch() calls to the typed nifra client, adding generic types to client factory calls, and resolving dependency drift in package.json). Runs diagnostics, applies all mechanical edit suggestions, applies doctor dependency fixes, and returns the remaining unresolved diagnostics.",
1085
- inputSchema: {
1086
- type: "object",
1087
- properties: {
1088
- code: {
1089
- type: "string",
1090
- description: "Apply the registered recipe for one stable diagnostic code when available.",
1091
- },
1092
- },
1093
- additionalProperties: false,
1094
- },
1095
- handler: async (args, context) => {
1096
- const { collectCheckResult } = await import("./check.js");
1097
- const { applyDiagnosticRecipe } = await import("./fix-recipes.js");
1098
- const { applyDoctorAutoFix } = await import("./doctor.js");
1099
- const { resolveInsideProject } = await import("./project-path.js");
1100
- const { writeFile, readFile } = await import("node:fs/promises");
1101
- let doctorResult = null;
1102
- try {
1103
- doctorResult = await applyDoctorAutoFix(cwd);
1104
- }
1105
- catch {
1106
- // ignore doctor errors
1107
- }
1108
- const checkResult = await collectCheckResult(cwd, {
1109
- lintsOnly: true,
1110
- signal: context.signal,
1111
- maxDiagnostics: 100,
1112
- });
1113
- const requestedCode = args.code;
1114
- const recipeFixed = [];
1115
- if (typeof requestedCode === "string") {
1116
- for (const diagnostic of checkResult.structuredDiagnostics ?? []) {
1117
- if (diagnostic.code !== requestedCode)
1118
- continue;
1119
- recipeFixed.push(...(await applyDiagnosticRecipe(cwd, diagnostic)));
1120
- }
1121
- }
1122
- const fixed = [];
1123
- for (const diag of checkResult.diagnostics) {
1124
- if (diag.file && diag.line && diag.suggestion?.kind === "edit" && diag.suggestion.diff) {
1125
- try {
1126
- const diffLines = diag.suggestion.diff.split("\n");
1127
- const beforeLine = diffLines.find((l) => l.startsWith("-"))?.slice(1);
1128
- const afterLine = diffLines.find((l) => l.startsWith("+"))?.slice(1);
1129
- if (beforeLine !== undefined && afterLine !== undefined) {
1130
- const filePath = await resolveInsideProject(cwd, diag.file);
1131
- if (filePath === undefined)
1132
- continue;
1133
- const content = await readFile(filePath, "utf-8");
1134
- const lines = content.split("\n");
1135
- const idx = diag.line - 1;
1136
- if (lines[idx] === beforeLine) {
1137
- lines[idx] = afterLine;
1138
- await writeFile(filePath, lines.join("\n"), "utf-8");
1139
- fixed.push({
1140
- file: diag.file,
1141
- line: diag.line,
1142
- title: diag.suggestion.title,
1143
- });
1144
- }
1145
- }
1146
- }
1147
- catch {
1148
- // ignore edit errors
1149
- }
1150
- }
1151
- }
1152
- const finalResult = await collectCheckResult(cwd, {
1153
- lintsOnly: false,
1154
- signal: context.signal,
1155
- maxDiagnostics: 100,
1156
- });
1157
- return JSON.stringify({
1158
- ok: finalResult.ok,
1159
- fixed,
1160
- recipeFixed,
1161
- doctorFixed: doctorResult?.fixed ?? [],
1162
- remainingDiagnostics: finalResult.diagnostics,
1163
- }, null, 2);
1164
- },
1165
- },
1166
- {
1167
- name: "nifra_assure",
1168
- description: "Evaluate nifra.assurance.ts and return the complete route-assurance report: every reflected route's first matching policy rule, enforcement evidence, missing/forbidden evidence, and the fail-closed ok bit. Use after adding or changing routes/security middleware; fix every finding before finishing.",
1169
- inputSchema: {
1170
- type: "object",
1171
- properties: {
1172
- config: {
1173
- type: "string",
1174
- description: "Config path relative to the selected project directory. Default: nifra.assurance.ts.",
1175
- },
1176
- bundle: {
1177
- type: "boolean",
1178
- description: "Return the single gate bundle with verdict and explicit skipped gates.",
1179
- },
1180
- strict: {
1181
- type: "boolean",
1182
- description: "Treat warning diagnostics as failures in the bundle.",
1183
- },
1184
- hydration: {
1185
- type: "boolean",
1186
- description: "Run the hydration assurance gate.",
1187
- },
1188
- interact: {
1189
- type: "boolean",
1190
- description: "Run declared hydration probes after the client mounts.",
1191
- },
1192
- dir: {
1193
- type: "string",
1194
- description: 'Evaluate this project subdirectory (relative to the MCP root), e.g. "apps/api". Default: the project root.',
1195
- },
1196
- },
1197
- additionalProperties: false,
1198
- },
1199
- handler: async (args) => {
1200
- const opts = args;
1201
- const target = resolveProjectDir(cwd, opts.dir);
1202
- if (target === null)
1203
- return dirError(opts.dir);
1204
- const config = opts.config === undefined ? undefined : resolve(target, opts.config);
1205
- if (config !== undefined && config !== target && !config.startsWith(target + sep)) {
1206
- return JSON.stringify({ ok: false, error: "config must stay inside the selected project directory" }, null, 2);
1207
- }
1208
- const { collectAssuranceReport, collectAssureBundle } = await import("./assure.js");
1209
- if (opts.bundle === true || opts.strict === true || opts.hydration === true) {
1210
- return JSON.stringify(await collectAssureBundle(target, {
1211
- ...(config === undefined ? {} : { config }),
1212
- ...(opts.strict === undefined ? {} : { strict: opts.strict }),
1213
- ...(opts.hydration === undefined ? {} : { hydration: opts.hydration }),
1214
- ...(opts.interact === undefined ? {} : { interact: opts.interact }),
1215
- }), null, 2);
1216
- }
1217
- return JSON.stringify(await collectAssuranceReport(target, config), null, 2);
1218
- },
1219
- },
1220
1012
  {
1221
1013
  name: "nifra_hydrate",
1222
1014
  description: "Run the hydration assurance gate and return stable NF-H diagnostics.",
@@ -1237,90 +1029,8 @@ export function projectTools(cwd, loadAppCached = createCachedAppLoader(cwd)) {
1237
1029
  return JSON.stringify(await runHydrationAssurance(target, { interact: opts.interact === true }), null, 2);
1238
1030
  },
1239
1031
  },
1240
- {
1241
- name: "nifra_replay",
1242
- description: "Validate and dispatch a token-only replay metadata file.",
1243
- inputSchema: {
1244
- type: "object",
1245
- properties: {
1246
- file: { type: "string", description: "Replay file relative to the project root." },
1247
- },
1248
- required: ["file"],
1249
- additionalProperties: false,
1250
- },
1251
- handler: async (args) => {
1252
- const file = args.file;
1253
- if (typeof file !== "string" || file.trim() === "")
1254
- return JSON.stringify({ ok: false, error: "file is required" });
1255
- const { runReplay } = await import("./replay.js");
1256
- try {
1257
- return JSON.stringify(await runReplay(cwd, file), null, 2);
1258
- }
1259
- catch (error) {
1260
- return JSON.stringify({ ok: false, error: error instanceof Error ? error.message : String(error) }, null, 2);
1261
- }
1262
- },
1263
- },
1264
- {
1265
- name: "nifra_levels",
1266
- description: "Compute this project's verification ladder and return { achieved, levels[] }, where each level carries { level, name, ok, reasons[] }. The ladder is cumulative: L0 typed contract (nifra check), L1 route assurance, L2 reviewed capability lockfile, L3 route trust manifest in sync, L4 contract invariants, so a level only holds when every level below it holds, and `achieved` is -1 when even L0 fails. This is the single gate that reports what a change actually proves: run it after finishing work, and treat any `reasons` on a level the project already claimed as a regression to fix.",
1267
- inputSchema: {
1268
- type: "object",
1269
- properties: {
1270
- config: {
1271
- type: "string",
1272
- description: "Assurance config path relative to the selected project directory. Default: nifra.assurance.ts.",
1273
- },
1274
- seed: {
1275
- type: "number",
1276
- description: "Deterministic seed for the L4 invariant run. Default 1.",
1277
- },
1278
- dir: {
1279
- type: "string",
1280
- description: 'Evaluate this project subdirectory (relative to the MCP root), e.g. "apps/api". Default: the project root.',
1281
- },
1282
- },
1283
- additionalProperties: false,
1284
- },
1285
- handler: async (args) => {
1286
- const opts = args;
1287
- const target = resolveProjectDir(cwd, opts.dir);
1288
- if (target === null)
1289
- return dirError(opts.dir);
1290
- if (opts.seed !== undefined && !Number.isSafeInteger(opts.seed)) {
1291
- return JSON.stringify({ ok: false, error: "seed must be a safe integer" }, null, 2);
1292
- }
1293
- const config = opts.config === undefined ? undefined : resolve(target, opts.config);
1294
- if (config !== undefined && config !== target && !config.startsWith(target + sep)) {
1295
- return JSON.stringify({ ok: false, error: "config must stay inside the selected project directory" }, null, 2);
1296
- }
1297
- const { collectVerificationLevels } = await import("./levels-tool.js");
1298
- return JSON.stringify(await collectVerificationLevels(target, {
1299
- ...(opts.config === undefined ? {} : { config: opts.config }),
1300
- ...(opts.seed === undefined ? {} : { seed: opts.seed }),
1301
- }), null, 2);
1302
- },
1303
- },
1304
- {
1305
- name: "nifra_doctor",
1306
- 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. Also reports `pipeline`: which bundler (bun|vite) this app's dev AND build phases run on and why, plus config hazards that only exist because there are two - a plugin sitting in the slot the other bundler reads (accepted, then never called), a dev toolchain imported by the file `nifra build` bundles into the production server (builds clean, dies at startup), and `conditions` that cannot reach the client bundle Bun's dev server serves. Returns { ok, ran, findings[], pipeline?, 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.",
1307
- inputSchema: {
1308
- type: "object",
1309
- properties: {
1310
- autoFix: {
1311
- type: "boolean",
1312
- description: "When true, write safe package.json fixes using only locally inferred versions. Does not run install or use the network.",
1313
- },
1314
- },
1315
- additionalProperties: false,
1316
- },
1317
- handler: async (args) => {
1318
- const opts = args;
1319
- const { applyDoctorAutoFix, collectDoctorResult } = await import("./doctor.js");
1320
- return JSON.stringify(opts.autoFix === true ? await applyDoctorAutoFix(cwd) : await collectDoctorResult(cwd), null, 2);
1321
- },
1322
- },
1323
1032
  ];
1033
+ return [...catalogTools, ...legacyTools];
1324
1034
  }
1325
1035
  /** Extract tools registered via .tool() routes on the Nifra backend. Exported for the test that proves a
1326
1036
  * `server().tool()` route surfaces in `tools/list` and executes through `tools/call`. */