@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,8 +1,8 @@
1
1
  /**
2
- * `nifra_docs` — searchable framework documentation for agents. The corpus is the generated
2
+ * `nifra_docs` - searchable framework documentation for agents. The corpus is the generated
3
3
  * `llms-full.txt` (written into this package by `bun run gen:llms`, so it can't drift from the
4
4
  * source it's generated from). Instead of dumping the whole ~150 KB into an agent's context, the
5
- * tool keyword-scores the document's `##` sections and returns only the top matches — or, with no
5
+ * tool keyword-scores the document's `##` sections and returns only the top matches - or, with no
6
6
  * query, just the section index (the cheap discovery call).
7
7
  *
8
8
  * Deliberately NOT a vector search: the corpus is one curated document with descriptive headings;
@@ -56,7 +56,7 @@ export function searchSections(sections, query, limit) {
56
56
  }
57
57
  return scored.sort((a, b) => b.score - a.score).slice(0, limit);
58
58
  }
59
- /** Cap a section body for the tool result — whole sections can be pages long; the agent can ask
59
+ /** Cap a section body for the tool result - whole sections can be pages long; the agent can ask
60
60
  * again with a narrower query if the trim cuts what it needed. */
61
61
  const MAX_SECTION_CHARS = 2800;
62
62
  /** Render the tool result: matched sections, or the index when no query was given. */
@@ -64,7 +64,7 @@ export function renderDocsResult(markdown, query, limit) {
64
64
  const sections = splitSections(markdown);
65
65
  if (query === undefined || query.trim() === "") {
66
66
  const index = sections.map((s) => `- ${s.heading}`).join("\n");
67
- return `# nifra docs — section index (pass \`query\` to fetch matching sections)\n\n${index}`;
67
+ return `# nifra docs - section index (pass \`query\` to fetch matching sections)\n\n${index}`;
68
68
  }
69
69
  const matches = searchSections(sections, query, limit);
70
70
  if (matches.length === 0) {
@@ -73,14 +73,14 @@ export function renderDocsResult(markdown, query, limit) {
73
73
  return matches
74
74
  .map((m) => {
75
75
  const body = m.body.length > MAX_SECTION_CHARS
76
- ? `${m.body.slice(0, MAX_SECTION_CHARS)}\n…(trimmed — narrow the query for the rest)`
76
+ ? `${m.body.slice(0, MAX_SECTION_CHARS)}\n…(trimmed - narrow the query for the rest)`
77
77
  : m.body;
78
78
  return `## ${m.heading}\n\n${body}`;
79
79
  })
80
80
  .join("\n\n---\n\n");
81
81
  }
82
82
  /** Load the bundled corpus. Resolves relative to this module so it works from `src/` (repo dev)
83
- * and `dist/` (published) alike — `docs/llms-full.txt` sits at the package root next to both. */
83
+ * and `dist/` (published) alike - `docs/llms-full.txt` sits at the package root next to both. */
84
84
  export async function loadDocsCorpus() {
85
85
  const url = new URL("../docs/llms-full.txt", import.meta.url);
86
86
  try {
package/dist/doctor.d.ts CHANGED
@@ -6,7 +6,7 @@ import { type SourceFinding } from "./check.js";
6
6
  */
7
7
  export declare function packageOf(spec: string): string | undefined;
8
8
  /** Build a predicate matching tsconfig `paths` aliases (e.g. `@/*`, `~/utils`), which resolve to local
9
- * source, not npm packages — so doctor must not flag them. */
9
+ * source, not npm packages - so doctor must not flag them. */
10
10
  export declare function aliasMatcher(paths: Readonly<Record<string, unknown>> | undefined): (spec: string) => boolean;
11
11
  /**
12
12
  * Scan one file for bare imports whose resolved package is neither `declared` nor a path `alias`. Pure +
@@ -16,12 +16,12 @@ export declare function scanUndeclaredImports(file: string, content: string, dec
16
16
  export interface DoctorFinding {
17
17
  readonly file: string;
18
18
  readonly line: number;
19
- /** The undeclared package name — add it to `package.json` dependencies. */
19
+ /** The undeclared package name - add it to `package.json` dependencies. */
20
20
  readonly package: string;
21
21
  }
22
22
  export interface DoctorResult {
23
23
  readonly ok: boolean;
24
- /** `false` when no `package.json` was found at cwd — doctor can't run (reported, not a crash). */
24
+ /** `false` when no `package.json` was found at cwd - doctor can't run (reported, not a crash). */
25
25
  readonly ran: boolean;
26
26
  readonly findings: readonly DoctorFinding[];
27
27
  /** Packages that resolve to more than one physical install across this workspace. */
package/dist/doctor.js CHANGED
@@ -1,14 +1,14 @@
1
1
  /**
2
- * `nifra doctor` — catches the Bun-workspace footgun the typed-client lint can't: a package that's
2
+ * `nifra doctor` - catches the Bun-workspace footgun the typed-client lint can't: a package that's
3
3
  * IMPORTED in source but NOT declared in `package.json`. Bun resolves it at runtime (via hoisting or a
4
- * workspace), so tests pass and `bun install` reports "no changes" — false confidence — yet `tsc` fails
4
+ * workspace), so tests pass and `bun install` reports "no changes" - false confidence - yet `tsc` fails
5
5
  * and a fresh or standalone install can't resolve it. doctor diffs every bare import specifier against
6
6
  * the package's declared dependencies and flags the gap, with a stable `--json` shape for agents/CI.
7
7
  *
8
8
  * Scope is intentionally per-package: it checks the `package.json` at `cwd`, because a dependency must be
9
9
  * declared by the package that imports it (for `tsc` and for that package to install on its own) even
10
10
  * when a monorepo would hoist it. Relative paths, runtime builtins (node core, `node:`/`bun:`, `bun`),
11
- * the package's own name, and tsconfig `paths` aliases are excluded — none of them are npm deps.
11
+ * the package's own name, and tsconfig `paths` aliases are excluded - none of them are npm deps.
12
12
  */
13
13
  import { realpath, stat } from "node:fs/promises";
14
14
  import { builtinModules } from "node:module";
@@ -23,7 +23,7 @@ const BUILTINS = new Set([
23
23
  ]);
24
24
  // The four specifier-bearing forms: static import/re-export with a source, side-effect import, dynamic
25
25
  // import, and CJS require. Anchored with `(?<![.\w$])` so `myimport`/`.import`/`foorequire` never match.
26
- // Comments are stripped before these run (see stripComments) — else a doc-comment usage example would be
26
+ // Comments are stripped before these run (see stripComments) - else a doc-comment usage example would be
27
27
  // flagged as a real import.
28
28
  const IMPORT_PATTERNS = [
29
29
  /(?<![.\w$])(?:import|export)\b[^'"]*?\bfrom\s*['"]([^'"]+)['"]/g,
@@ -53,7 +53,7 @@ export function packageOf(spec) {
53
53
  if (spec.startsWith("@")) {
54
54
  const [scope, name] = spec.split("/");
55
55
  if (!scope || scope === "@" || !name)
56
- return undefined; // `@/alias`, `@foo` — not a real package
56
+ return undefined; // `@/alias`, `@foo` - not a real package
57
57
  const pkg = `${scope}/${name}`;
58
58
  return isNpmPackageName(pkg) ? pkg : undefined;
59
59
  }
@@ -63,7 +63,7 @@ export function packageOf(spec) {
63
63
  return BUILTINS.has(name) ? undefined : name;
64
64
  }
65
65
  /** Build a predicate matching tsconfig `paths` aliases (e.g. `@/*`, `~/utils`), which resolve to local
66
- * source, not npm packages — so doctor must not flag them. */
66
+ * source, not npm packages - so doctor must not flag them. */
67
67
  export function aliasMatcher(paths) {
68
68
  const prefixes = Object.keys(paths ?? {})
69
69
  .map((k) => k.replace(/\/?\*$/, ""))
@@ -504,7 +504,7 @@ export async function runDoctor(cwd, opts = {}) {
504
504
  }
505
505
  console.log("nifra doctor\n");
506
506
  if (!result.ran) {
507
- console.log("• no package.json at this directory — nothing to check");
507
+ console.log("• no package.json at this directory - nothing to check");
508
508
  return true;
509
509
  }
510
510
  if (result.fixed && result.fixed.length > 0) {
@@ -517,7 +517,7 @@ export async function runDoctor(cwd, opts = {}) {
517
517
  if (result.skippedFixes && result.skippedFixes.length > 0) {
518
518
  console.log("• not auto-fixed:");
519
519
  for (const f of result.skippedFixes) {
520
- console.log(` ${f.package} — ${f.reason}; run \`${f.command.join(" ")}\``);
520
+ console.log(` ${f.package} - ${f.reason}; run \`${f.command.join(" ")}\``);
521
521
  }
522
522
  console.log("");
523
523
  }
@@ -1,17 +1,17 @@
1
1
  /**
2
- * `nifra_example` — verified, copy-pasteable code examples for agents. The corpus is `examples.json`
2
+ * `nifra_example` - verified, copy-pasteable code examples for agents. The corpus is `examples.json`
3
3
  * (written into this package by `bun run gen:llms`): the same doc snippets `check:docs` typechecks
4
4
  * against the live `@nifrajs/*` API. So every example this returns is GUARANTEED to compile against the
5
- * installed version — the antidote to an agent hallucinating a framework API from stale training.
5
+ * installed version - the antidote to an agent hallucinating a framework API from stale training.
6
6
  *
7
7
  * Keyword-scored over each example's name/topic/slug (curated identifiers, heavily weighted) and its
8
8
  * code body. No query → a grouped index of what's available (the cheap discovery call).
9
9
  */
10
10
  export interface Example {
11
11
  readonly name: string;
12
- /** The doc page's title, e.g. "Nifra — Auth & sessions". */
12
+ /** The doc page's title, e.g. "Nifra - Auth & sessions". */
13
13
  readonly topic: string;
14
- /** The doc page slug, e.g. "auth" — a short, matchable tag. */
14
+ /** The doc page slug, e.g. "auth" - a short, matchable tag. */
15
15
  readonly slug: string;
16
16
  readonly code: string;
17
17
  }
@@ -25,6 +25,6 @@ export declare function searchExamples(examples: readonly Example[], query: stri
25
25
  /** Render the tool result: matched examples as fenced code blocks, or the grouped index when no query. */
26
26
  export declare function renderExamplesResult(examples: readonly Example[], query: string | undefined, limit: number): string;
27
27
  /** Load the bundled example corpus. Resolves relative to this module so it works from `src/` (repo dev)
28
- * and `dist/` (published) alike — `docs/examples.json` sits at the package root next to both. */
28
+ * and `dist/` (published) alike - `docs/examples.json` sits at the package root next to both. */
29
29
  export declare function loadExamplesCorpus(): Promise<Example[] | undefined>;
30
30
  //# sourceMappingURL=examples.d.ts.map
package/dist/examples.js CHANGED
@@ -1,8 +1,8 @@
1
1
  /**
2
- * `nifra_example` — verified, copy-pasteable code examples for agents. The corpus is `examples.json`
2
+ * `nifra_example` - verified, copy-pasteable code examples for agents. The corpus is `examples.json`
3
3
  * (written into this package by `bun run gen:llms`): the same doc snippets `check:docs` typechecks
4
4
  * against the live `@nifrajs/*` API. So every example this returns is GUARANTEED to compile against the
5
- * installed version — the antidote to an agent hallucinating a framework API from stale training.
5
+ * installed version - the antidote to an agent hallucinating a framework API from stale training.
6
6
  *
7
7
  * Keyword-scored over each example's name/topic/slug (curated identifiers, heavily weighted) and its
8
8
  * code body. No query → a grouped index of what's available (the cheap discovery call).
@@ -42,18 +42,18 @@ export function renderExamplesResult(examples, query, limit) {
42
42
  const index = [...byTopic.entries()]
43
43
  .map(([topic, names]) => `- **${topic}**: ${names.join(", ")}`)
44
44
  .join("\n");
45
- return `# nifra examples — index (${examples.length} verified snippets; pass \`query\` to fetch code)\n\nEvery snippet is typechecked against the installed nifra version — prefer these over recalling an API.\n\n${index}`;
45
+ return `# nifra examples - index (${examples.length} verified snippets; pass \`query\` to fetch code)\n\nEvery snippet is typechecked against the installed nifra version - prefer these over recalling an API.\n\n${index}`;
46
46
  }
47
47
  const matches = searchExamples(examples, query, limit);
48
48
  if (matches.length === 0) {
49
49
  return `No examples matched ${JSON.stringify(query)}. Call without a query for the index, or try a broader term (e.g. "auth", "upload", "isr", "client").`;
50
50
  }
51
51
  return matches
52
- .map((m) => `## ${m.name} — ${m.topic}\n\n\`\`\`ts\n${m.code}\n\`\`\``)
52
+ .map((m) => `## ${m.name} - ${m.topic}\n\n\`\`\`ts\n${m.code}\n\`\`\``)
53
53
  .join("\n\n---\n\n");
54
54
  }
55
55
  /** Load the bundled example corpus. Resolves relative to this module so it works from `src/` (repo dev)
56
- * and `dist/` (published) alike — `docs/examples.json` sits at the package root next to both. */
56
+ * and `dist/` (published) alike - `docs/examples.json` sits at the package root next to both. */
57
57
  export async function loadExamplesCorpus() {
58
58
  const url = new URL("../docs/examples.json", import.meta.url);
59
59
  try {
@@ -1,18 +1,18 @@
1
1
  /**
2
- * `nifra init-agents` — retrofit an EXISTING app with the agent-discovery files a freshly scaffolded
2
+ * `nifra init-agents` - retrofit an EXISTING app with the agent-discovery files a freshly scaffolded
3
3
  * app ships, so an already-built project adopts the nifra MCP in one command:
4
4
  *
5
- * .mcp.json — Claude Code's project MCP registry (launches `bunx @nifrajs/cli mcp`)
6
- * .cursor/mcp.json — Cursor's MCP registry (same server config)
7
- * CLAUDE.md — Claude's MCP-first preamble + `@AGENTS.md` import
8
- * AGENTS.md — a `## MCP server` section appended (or a minimal file if none exists)
5
+ * .mcp.json - Claude Code's project MCP registry (launches `bunx @nifrajs/cli mcp`)
6
+ * .cursor/mcp.json - Cursor's MCP registry (same server config)
7
+ * CLAUDE.md - Claude's MCP-first preamble + `@AGENTS.md` import
8
+ * AGENTS.md - a `## MCP server` section appended (or a minimal file if none exists)
9
9
  *
10
- * The generators are imported from `create-nifra/agent-files` — the SAME source of truth `create-nifra`
11
- * uses at scaffold time — so a retrofitted app and a freshly scaffolded one get byte-identical configs.
10
+ * The generators are imported from `create-nifra/agent-files` - the SAME source of truth `create-nifra`
11
+ * uses at scaffold time - so a retrofitted app and a freshly scaffolded one get byte-identical configs.
12
12
  *
13
13
  * Safety: this writes into the user's existing tree, so it NEVER silently clobbers a file they may have
14
14
  * customized. By default an existing `.mcp.json` / `CLAUDE.md` / `.cursor/mcp.json` is SKIPPED with a
15
- * notice; `--force` overwrites. `AGENTS.md` is special-cased — if it already has the MCP section it's
15
+ * notice; `--force` overwrites. `AGENTS.md` is special-cased - if it already has the MCP section it's
16
16
  * left alone, otherwise the section is APPENDED (never overwriting the user's conventions), and `--force`
17
17
  * is not needed for that append since it's additive. Every write path is resolved + confined under the
18
18
  * cwd (no `..` traversal escaping the project root).
@@ -21,10 +21,10 @@
21
21
  export interface InitAgentsFileResult {
22
22
  /** Project-root-relative POSIX path. */
23
23
  readonly path: string;
24
- /** `wrote` — created or (with --force) overwrote; `appended` — added the MCP section to an existing
25
- * AGENTS.md; `skipped` — already present and not forced; `present` — MCP section already there. */
24
+ /** `wrote` - created or (with --force) overwrote; `appended` - added the MCP section to an existing
25
+ * AGENTS.md; `skipped` - already present and not forced; `present` - MCP section already there. */
26
26
  readonly action: "wrote" | "appended" | "skipped" | "present";
27
- /** Why it was skipped/left, for the notice (e.g. "exists — pass --force to overwrite"). */
27
+ /** Why it was skipped/left, for the notice (e.g. "exists - pass --force to overwrite"). */
28
28
  readonly note?: string;
29
29
  }
30
30
  export interface InitAgentsResult {
@@ -36,19 +36,19 @@ export interface InitAgentsOptions {
36
36
  readonly force?: boolean;
37
37
  }
38
38
  /** Confine a project-relative path under `cwd` and return the absolute path. Rejects a spec that escapes
39
- * the root (defense-in-depth — these specs are constants, but the cwd-confinement invariant is enforced
39
+ * the root (defense-in-depth - these specs are constants, but the cwd-confinement invariant is enforced
40
40
  * at the seam, not assumed). Exported so the invariant is directly testable. */
41
41
  export declare function safeJoin(cwd: string, rel: string): string;
42
42
  /**
43
43
  * Retrofit `cwd` with the four agent-discovery files. Pure enough to unit-test (no argv, no process.exit,
44
- * no console) — the CLI wrapper handles printing + the exit code.
44
+ * no console) - the CLI wrapper handles printing + the exit code.
45
45
  */
46
46
  export declare function initAgents(cwd: string, opts?: InitAgentsOptions): Promise<InitAgentsResult>;
47
47
  /** Format the retrofit result for the terminal. */
48
48
  export declare function renderInitAgents(result: InitAgentsResult): string;
49
49
  /**
50
50
  * CLI entry: run the retrofit at `cwd` and print the result. Returns `true` (the command always succeeds
51
- * unless a write throws — which propagates as a non-zero exit via the dispatcher's catch). `--json`
51
+ * unless a write throws - which propagates as a non-zero exit via the dispatcher's catch). `--json`
52
52
  * emits the structured result for agents/CI.
53
53
  */
54
54
  export declare function runInitAgents(cwd: string, opts?: {
@@ -1,18 +1,18 @@
1
1
  /**
2
- * `nifra init-agents` — retrofit an EXISTING app with the agent-discovery files a freshly scaffolded
2
+ * `nifra init-agents` - retrofit an EXISTING app with the agent-discovery files a freshly scaffolded
3
3
  * app ships, so an already-built project adopts the nifra MCP in one command:
4
4
  *
5
- * .mcp.json — Claude Code's project MCP registry (launches `bunx @nifrajs/cli mcp`)
6
- * .cursor/mcp.json — Cursor's MCP registry (same server config)
7
- * CLAUDE.md — Claude's MCP-first preamble + `@AGENTS.md` import
8
- * AGENTS.md — a `## MCP server` section appended (or a minimal file if none exists)
5
+ * .mcp.json - Claude Code's project MCP registry (launches `bunx @nifrajs/cli mcp`)
6
+ * .cursor/mcp.json - Cursor's MCP registry (same server config)
7
+ * CLAUDE.md - Claude's MCP-first preamble + `@AGENTS.md` import
8
+ * AGENTS.md - a `## MCP server` section appended (or a minimal file if none exists)
9
9
  *
10
- * The generators are imported from `create-nifra/agent-files` — the SAME source of truth `create-nifra`
11
- * uses at scaffold time — so a retrofitted app and a freshly scaffolded one get byte-identical configs.
10
+ * The generators are imported from `create-nifra/agent-files` - the SAME source of truth `create-nifra`
11
+ * uses at scaffold time - so a retrofitted app and a freshly scaffolded one get byte-identical configs.
12
12
  *
13
13
  * Safety: this writes into the user's existing tree, so it NEVER silently clobbers a file they may have
14
14
  * customized. By default an existing `.mcp.json` / `CLAUDE.md` / `.cursor/mcp.json` is SKIPPED with a
15
- * notice; `--force` overwrites. `AGENTS.md` is special-cased — if it already has the MCP section it's
15
+ * notice; `--force` overwrites. `AGENTS.md` is special-cased - if it already has the MCP section it's
16
16
  * left alone, otherwise the section is APPENDED (never overwriting the user's conventions), and `--force`
17
17
  * is not needed for that append since it's additive. Every write path is resolved + confined under the
18
18
  * cwd (no `..` traversal escaping the project root).
@@ -21,7 +21,7 @@ import { mkdir, readFile, writeFile } from "node:fs/promises";
21
21
  import { isAbsolute, relative, resolve } from "node:path";
22
22
  import { AGENTS_MD_PATH, agentsMcpSection, CLAUDE_MD_PATH, CURSOR_MCP_JSON_PATH, claudeMd, MCP_JSON_PATH, mcpJson, } from "create-nifra/agent-files";
23
23
  /** Confine a project-relative path under `cwd` and return the absolute path. Rejects a spec that escapes
24
- * the root (defense-in-depth — these specs are constants, but the cwd-confinement invariant is enforced
24
+ * the root (defense-in-depth - these specs are constants, but the cwd-confinement invariant is enforced
25
25
  * at the seam, not assumed). Exported so the invariant is directly testable. */
26
26
  export function safeJoin(cwd, rel) {
27
27
  const abs = resolve(cwd, rel);
@@ -41,7 +41,7 @@ const fileExists = (path) => readFile(path)
41
41
  async function writeOwned(cwd, rel, content, force) {
42
42
  const abs = safeJoin(cwd, rel);
43
43
  if (!force && (await fileExists(abs))) {
44
- return { path: rel, action: "skipped", note: "exists — pass --force to overwrite" };
44
+ return { path: rel, action: "skipped", note: "exists - pass --force to overwrite" };
45
45
  }
46
46
  // `.cursor/mcp.json` needs its parent dir; `recursive` is a no-op for the root-level files.
47
47
  await mkdir(resolve(abs, ".."), { recursive: true });
@@ -51,7 +51,7 @@ async function writeOwned(cwd, rel, content, force) {
51
51
  /**
52
52
  * AGENTS.md is additive, not owned: if it exists and already has the MCP section, leave it; if it exists
53
53
  * without the section, append the section (preserving the user's conventions); if it's absent, write a
54
- * minimal AGENTS.md that is just the MCP section under a heading. `--force` is irrelevant here — we never
54
+ * minimal AGENTS.md that is just the MCP section under a heading. `--force` is irrelevant here - we never
55
55
  * overwrite the user's existing guidance.
56
56
  */
57
57
  async function ensureAgentsMd(cwd) {
@@ -78,7 +78,7 @@ async function ensureAgentsMd(cwd) {
78
78
  }
79
79
  /**
80
80
  * Retrofit `cwd` with the four agent-discovery files. Pure enough to unit-test (no argv, no process.exit,
81
- * no console) — the CLI wrapper handles printing + the exit code.
81
+ * no console) - the CLI wrapper handles printing + the exit code.
82
82
  */
83
83
  export async function initAgents(cwd, opts = {}) {
84
84
  const force = opts.force ?? false;
@@ -105,12 +105,12 @@ export function renderInitAgents(result) {
105
105
  const wroteAny = result.files.some((f) => f.action === "wrote" || f.action === "appended");
106
106
  const footer = wroteAny
107
107
  ? "\nThe nifra MCP is now registered. Restart your agent so it picks up .mcp.json, then prefer nifra_docs / nifra_example and gate on nifra check."
108
- : "\nNothing to do — every file was already present (use --force to overwrite the owned ones).";
108
+ : "\nNothing to do - every file was already present (use --force to overwrite the owned ones).";
109
109
  return `nifra init-agents\n\n${lines.join("\n")}\n${footer}`;
110
110
  }
111
111
  /**
112
112
  * CLI entry: run the retrofit at `cwd` and print the result. Returns `true` (the command always succeeds
113
- * unless a write throws — which propagates as a non-zero exit via the dispatcher's catch). `--json`
113
+ * unless a write throws - which propagates as a non-zero exit via the dispatcher's catch). `--json`
114
114
  * emits the structured result for agents/CI.
115
115
  */
116
116
  export async function runInitAgents(cwd, opts = {}) {
@@ -0,0 +1,24 @@
1
+ /**
2
+ * The one place the CLI imports TypeScript from.
3
+ *
4
+ * The interpolated-SQL rule parses source with the TypeScript compiler, because the regex it replaced
5
+ * could not tell `sql\`… ${id} …\`` (bound) from a plain template (injectable) without guessing at
6
+ * string and comment boundaries. That is the right tool, but it is a ~25 MB dependency, and the CLI's
7
+ * own typecheck step already treats `tsc` as something the PROJECT provides rather than something the
8
+ * CLI ships. Making it a hard dependency would have contradicted that for every install.
9
+ *
10
+ * So it is an optional peer, resolved at the moment the rule runs. Every Nifra project has TypeScript
11
+ * - the templates all ship a `typecheck` script - so this resolves in practice; the point is that an
12
+ * install does not pay for it.
13
+ */
14
+ /** The slice of the compiler the SQL scanner uses. Structural, so the peer stays optional. */
15
+ export type TypeScriptApi = typeof import("typescript");
16
+ /**
17
+ * Import the project's TypeScript, or `undefined` when it is not installed.
18
+ *
19
+ * Only a RESOLUTION failure is absence. A compiler that resolves and then fails while evaluating is a
20
+ * broken install, not a missing one, and telling that user to install what they already have sends
21
+ * them the wrong way - so that error is rethrown for the caller to report.
22
+ */
23
+ export declare function importTypeScript(): Promise<TypeScriptApi | undefined>;
24
+ //# sourceMappingURL=typescript-import.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"typescript-import.d.ts","sourceRoot":"","sources":["../../src/internal/typescript-import.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,8FAA8F;AAC9F,MAAM,MAAM,aAAa,GAAG,cAAc,YAAY,CAAC,CAAA;AAEvD;;;;;;GAMG;AACH,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,aAAa,GAAG,SAAS,CAAC,CAQ3E"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * The one place the CLI imports TypeScript from.
3
+ *
4
+ * The interpolated-SQL rule parses source with the TypeScript compiler, because the regex it replaced
5
+ * could not tell `sql\`… ${id} …\`` (bound) from a plain template (injectable) without guessing at
6
+ * string and comment boundaries. That is the right tool, but it is a ~25 MB dependency, and the CLI's
7
+ * own typecheck step already treats `tsc` as something the PROJECT provides rather than something the
8
+ * CLI ships. Making it a hard dependency would have contradicted that for every install.
9
+ *
10
+ * So it is an optional peer, resolved at the moment the rule runs. Every Nifra project has TypeScript
11
+ * - the templates all ship a `typecheck` script - so this resolves in practice; the point is that an
12
+ * install does not pay for it.
13
+ */
14
+ /**
15
+ * Import the project's TypeScript, or `undefined` when it is not installed.
16
+ *
17
+ * Only a RESOLUTION failure is absence. A compiler that resolves and then fails while evaluating is a
18
+ * broken install, not a missing one, and telling that user to install what they already have sends
19
+ * them the wrong way - so that error is rethrown for the caller to report.
20
+ */
21
+ export async function importTypeScript() {
22
+ try {
23
+ return (await import("typescript"));
24
+ }
25
+ catch (cause) {
26
+ const message = cause instanceof Error ? cause.message : String(cause);
27
+ if (/cannot find (?:package|module)|ERR_MODULE_NOT_FOUND/i.test(message))
28
+ return undefined;
29
+ throw cause;
30
+ }
31
+ }
32
+ //# sourceMappingURL=typescript-import.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"typescript-import.js","sourceRoot":"","sources":["../../src/internal/typescript-import.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAKH;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB;IACpC,IAAI,CAAC;QACH,OAAO,CAAC,MAAM,MAAM,CAAC,YAAY,CAAC,CAAkB,CAAA;IACtD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;QACtE,IAAI,sDAAsD,CAAC,IAAI,CAAC,OAAO,CAAC;YAAE,OAAO,SAAS,CAAA;QAC1F,MAAM,KAAK,CAAA;IACb,CAAC;AACH,CAAC"}
@@ -1,6 +1,6 @@
1
1
  /**
2
- * Introspect a loaded nifra project into a Markdown brief — the API routes (`backend.routes()`), the
3
- * page routes (`discoverRoutes`), and a conventions summary — for piping into an AI coding agent's
2
+ * Introspect a loaded nifra project into a Markdown brief - the API routes (`backend.routes()`), the
3
+ * page routes (`discoverRoutes`), and a conventions summary - for piping into an AI coding agent's
4
4
  * prompt. Used by `nifra context` (stdout) and the `nifra mcp` server (a tool result), so an agent sees
5
5
  * *this* project's actual surface, not just generic docs.
6
6
  */
@@ -10,31 +10,31 @@ import type { LoadedApp } from "./load.js";
10
10
  /** Read the backend's registered routes, if it's a `server()` with a `.routes()` method. */
11
11
  export declare function backendRoutes(backend: unknown): ReflectedRoute[];
12
12
  /**
13
- * Render a JSON Schema as a compact TypeScript-like type (`{ id: string, name?: string }`) — the
13
+ * Render a JSON Schema as a compact TypeScript-like type (`{ id: string, name?: string }`) - the
14
14
  * shape agents already think in, at a fraction of the raw JSON Schema's tokens (~70-80% smaller).
15
15
  * Anything the renderer doesn't model falls back to the raw JSON for that subtree, so the output
16
16
  * is always faithful, just not always minimal.
17
17
  */
18
18
  export declare function tsTypeOf(schema: unknown, depth?: number): string;
19
19
  /**
20
- * The typed-client call form for a route — the exact `client<typeof app>` proxy chain an agent should
20
+ * The typed-client call form for a route - the exact `client<typeof app>` proxy chain an agent should
21
21
  * write, derived from the same convention `@nifrajs/client` implements (so it never has to read the
22
22
  * client tests to learn it): a static segment is a property (`.users`), a path param/wildcard is a call
23
23
  * that appends the value (`({ id })`), the root path is `.index`, and the HTTP verb is the terminal call.
24
- * Body verbs (POST/PUT/PATCH) take the body first then call-options; other verbs take call-options first —
24
+ * Body verbs (POST/PUT/PATCH) take the body first then call-options; other verbs take call-options first -
25
25
  * so the `{ query }` argument lands in the right slot for each.
26
26
  */
27
27
  export declare function clientCall(method: string, path: string, schema: unknown): string;
28
28
  /** Markdown section listing the backend's API routes with their request + response field shapes. */
29
29
  export declare function apiRoutesSection(routes: readonly ReflectedRoute[]): string;
30
- /** Compact API-routes INDEX for the no-arg `nifra_context` call — `METHOD path` per route, WITHOUT the
30
+ /** Compact API-routes INDEX for the no-arg `nifra_context` call - `METHOD path` per route, WITHOUT the
31
31
  * body/query/response shapes or the per-route `call` line. Bounds the first-call payload on a big backend:
32
32
  * an agent gets the full route list (so it knows what's mounted) plus a pointer to fetch any route's full
33
33
  * contract via the `path`/`kind` slice, instead of every schema up front. */
34
34
  export declare function apiRoutesIndexSection(routes: readonly ReflectedRoute[]): string;
35
35
  /** Markdown section listing the file-routed pages (URL pattern → source file). */
36
36
  export declare function pageRoutesSection(manifest: Manifest | undefined): string;
37
- /** Optional narrowing for {@link describeProject} — a path prefix and/or one section. A filtered
37
+ /** Optional narrowing for {@link describeProject} - a path prefix and/or one section. A filtered
38
38
  * brief omits the conventions block (the agent already has it from its first full call), so
39
39
  * follow-up calls cost a fraction of the tokens. */
40
40
  export interface ContextFilter {
@@ -42,9 +42,9 @@ export interface ContextFilter {
42
42
  readonly kind?: "api" | "pages";
43
43
  }
44
44
  /**
45
- * Build the project brief as Markdown. A no-arg call returns a tight **INDEX** — the route list (API
45
+ * Build the project brief as Markdown. A no-arg call returns a tight **INDEX** - the route list (API
46
46
  * routes as `METHOD path`, page routes as `pattern → file`) + conventions + a pointer to fetch full
47
- * contracts via the slice — so learning a big project is cheap, not a full-surface schema dump. Passing a
47
+ * contracts via the slice - so learning a big project is cheap, not a full-surface schema dump. Passing a
48
48
  * {@link ContextFilter} (`path` prefix and/or `kind`) returns the narrowed, FULL detail (per-route
49
49
  * body/query/response shapes + the typed-client `call` form), and omits the conventions block (the agent
50
50
  * already has it from the index call).
@@ -61,7 +61,7 @@ export interface RouteJson {
61
61
  readonly response?: string;
62
62
  }
63
63
  /**
64
- * The backend's API routes as structured JSON — what the `nifra_routes` MCP tool returns, so an agent
64
+ * The backend's API routes as structured JSON - what the `nifra_routes` MCP tool returns, so an agent
65
65
  * consumes the contract programmatically (`list_routes` / `get_route_schema`) instead of parsing the
66
66
  * Markdown brief. Optionally filtered to routes whose path starts with `pathPrefix`. Reuses the exact
67
67
  * same `call` form and TS-shaped contracts the Markdown brief shows.
@@ -76,7 +76,7 @@ export interface RouteTableEntry {
76
76
  readonly path: string;
77
77
  /** Methods this route serves, sorted (page: `GET`, plus `POST` when it has an `action`). */
78
78
  readonly methods: readonly string[];
79
- /** Source file (page only — the route module path relative to `routes/`). */
79
+ /** Source file (page only - the route module path relative to `routes/`). */
80
80
  readonly file?: string;
81
81
  /** True when this API route is auto-mounted under the page router's `apiPrefix` (so it's reachable
82
82
  * through the same web app, no hand-wired `if (pathname.startsWith("/api/"))`). Pages are never
@@ -98,7 +98,7 @@ export interface RouteTableInput {
98
98
  readonly apiPrefix?: string;
99
99
  }
100
100
  /**
101
- * Build the unified, sorted route table from page routes + backend API routes. Pure — the caller
101
+ * Build the unified, sorted route table from page routes + backend API routes. Pure - the caller
102
102
  * supplies page `hasAction` (resolved via a module load) and the API descriptors (`backend.routes()`).
103
103
  * Page routes serve `GET` (+ `POST` when they export an `action`); API routes carry their declared
104
104
  * method, and are flagged `autoMounted` when at/under `apiPrefix`. Sorted by path, then kind, then the