@nifrajs/cli 4.0.2 → 5.1.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 (364) hide show
  1. package/README.md +10 -2
  2. package/dist/agent-files.d.ts +107 -0
  3. package/dist/agent-files.d.ts.map +1 -0
  4. package/dist/agent-files.js +162 -0
  5. package/dist/agent-files.js.map +1 -0
  6. package/dist/app-files.d.ts +6 -0
  7. package/dist/app-files.d.ts.map +1 -1
  8. package/dist/app-files.js +27 -2
  9. package/dist/app-files.js.map +1 -1
  10. package/dist/assure-hydration.d.ts.map +1 -1
  11. package/dist/assure-hydration.js +9 -2
  12. package/dist/assure-hydration.js.map +1 -1
  13. package/dist/assure.d.ts +12 -2
  14. package/dist/assure.d.ts.map +1 -1
  15. package/dist/assure.js +59 -8
  16. package/dist/assure.js.map +1 -1
  17. package/dist/capabilities-tool.d.ts.map +1 -1
  18. package/dist/capabilities-tool.js +36 -9
  19. package/dist/capabilities-tool.js.map +1 -1
  20. package/dist/check-diagnostics.d.ts +22 -2
  21. package/dist/check-diagnostics.d.ts.map +1 -1
  22. package/dist/check-diagnostics.js +54 -7
  23. package/dist/check-diagnostics.js.map +1 -1
  24. package/dist/check-scan.d.ts +83 -24
  25. package/dist/check-scan.d.ts.map +1 -1
  26. package/dist/check-scan.js +1516 -318
  27. package/dist/check-scan.js.map +1 -1
  28. package/dist/check.d.ts +2 -2
  29. package/dist/check.d.ts.map +1 -1
  30. package/dist/check.js +203 -70
  31. package/dist/check.js.map +1 -1
  32. package/dist/cli.d.ts +9 -0
  33. package/dist/cli.d.ts.map +1 -1
  34. package/dist/cli.js +215 -50
  35. package/dist/cli.js.map +1 -1
  36. package/dist/client-module.d.ts +20 -0
  37. package/dist/client-module.d.ts.map +1 -0
  38. package/dist/client-module.js +73 -0
  39. package/dist/client-module.js.map +1 -0
  40. package/dist/command-catalog.d.ts +24 -5
  41. package/dist/command-catalog.d.ts.map +1 -1
  42. package/dist/command-catalog.js +194 -45
  43. package/dist/command-catalog.js.map +1 -1
  44. package/dist/component-check.d.ts +13 -0
  45. package/dist/component-check.d.ts.map +1 -0
  46. package/dist/component-check.js +59 -0
  47. package/dist/component-check.js.map +1 -0
  48. package/dist/contract-proof.d.ts +1 -1
  49. package/dist/contract-proof.d.ts.map +1 -1
  50. package/dist/contract-proof.js +2 -24
  51. package/dist/contract-proof.js.map +1 -1
  52. package/dist/contracts.d.ts +19 -0
  53. package/dist/contracts.d.ts.map +1 -1
  54. package/dist/contracts.js +50 -3
  55. package/dist/contracts.js.map +1 -1
  56. package/dist/db-tool.d.ts.map +1 -1
  57. package/dist/db-tool.js +4 -3
  58. package/dist/db-tool.js.map +1 -1
  59. package/dist/dev-bun-config.d.ts +5 -1
  60. package/dist/dev-bun-config.d.ts.map +1 -1
  61. package/dist/dev-bun-config.js +44 -11
  62. package/dist/dev-bun-config.js.map +1 -1
  63. package/dist/dev-feed-tool.d.ts +18 -1
  64. package/dist/dev-feed-tool.d.ts.map +1 -1
  65. package/dist/dev-feed-tool.js +93 -0
  66. package/dist/dev-feed-tool.js.map +1 -1
  67. package/dist/diagnostics.d.ts +3 -0
  68. package/dist/diagnostics.d.ts.map +1 -1
  69. package/dist/diagnostics.js.map +1 -1
  70. package/dist/diff-tool.d.ts +9 -8
  71. package/dist/diff-tool.d.ts.map +1 -1
  72. package/dist/diff-tool.js +28 -24
  73. package/dist/diff-tool.js.map +1 -1
  74. package/dist/doctor.d.ts +13 -0
  75. package/dist/doctor.d.ts.map +1 -1
  76. package/dist/doctor.js +23 -15
  77. package/dist/doctor.js.map +1 -1
  78. package/dist/env-file.d.ts +6 -0
  79. package/dist/env-file.d.ts.map +1 -1
  80. package/dist/env-file.js +6 -0
  81. package/dist/env-file.js.map +1 -1
  82. package/dist/fix-recipes.d.ts +2 -0
  83. package/dist/fix-recipes.d.ts.map +1 -1
  84. package/dist/fix-recipes.js +102 -2
  85. package/dist/fix-recipes.js.map +1 -1
  86. package/dist/hand-built-manifest.d.ts +27 -0
  87. package/dist/hand-built-manifest.d.ts.map +1 -0
  88. package/dist/hand-built-manifest.js +124 -0
  89. package/dist/hand-built-manifest.js.map +1 -0
  90. package/dist/hooks-report.d.ts +23 -0
  91. package/dist/hooks-report.d.ts.map +1 -0
  92. package/dist/hooks-report.js +104 -0
  93. package/dist/hooks-report.js.map +1 -0
  94. package/dist/init-agents.d.ts +18 -2
  95. package/dist/init-agents.d.ts.map +1 -1
  96. package/dist/init-agents.js +20 -13
  97. package/dist/init-agents.js.map +1 -1
  98. package/dist/internal/config-paths.d.ts +16 -0
  99. package/dist/internal/config-paths.d.ts.map +1 -0
  100. package/dist/internal/config-paths.js +124 -0
  101. package/dist/internal/config-paths.js.map +1 -0
  102. package/dist/internal/installed-package.d.ts +4 -0
  103. package/dist/internal/installed-package.d.ts.map +1 -0
  104. package/dist/internal/installed-package.js +17 -0
  105. package/dist/internal/installed-package.js.map +1 -0
  106. package/dist/internal/nifra-project.d.ts +41 -0
  107. package/dist/internal/nifra-project.d.ts.map +1 -0
  108. package/dist/internal/nifra-project.js +183 -0
  109. package/dist/internal/nifra-project.js.map +1 -0
  110. package/dist/internal/project-specifiers.d.ts +29 -0
  111. package/dist/internal/project-specifiers.d.ts.map +1 -0
  112. package/dist/internal/project-specifiers.js +435 -0
  113. package/dist/internal/project-specifiers.js.map +1 -0
  114. package/dist/internal/script-kind.d.ts.map +1 -1
  115. package/dist/internal/script-kind.js +2 -1
  116. package/dist/internal/script-kind.js.map +1 -1
  117. package/dist/internal/source-facts.d.ts +8 -1
  118. package/dist/internal/source-facts.d.ts.map +1 -1
  119. package/dist/internal/source-facts.js +22 -5
  120. package/dist/internal/source-facts.js.map +1 -1
  121. package/dist/internal/typescript-import.d.ts +1 -1
  122. package/dist/internal/typescript-import.d.ts.map +1 -1
  123. package/dist/internal/typescript-import.js +10 -6
  124. package/dist/internal/typescript-import.js.map +1 -1
  125. package/dist/introspect.d.ts +15 -6
  126. package/dist/introspect.d.ts.map +1 -1
  127. package/dist/introspect.js +87 -25
  128. package/dist/introspect.js.map +1 -1
  129. package/dist/levels-tool.d.ts.map +1 -1
  130. package/dist/levels-tool.js +98 -23
  131. package/dist/levels-tool.js.map +1 -1
  132. package/dist/load.d.ts +33 -10
  133. package/dist/load.d.ts.map +1 -1
  134. package/dist/load.js +60 -10
  135. package/dist/load.js.map +1 -1
  136. package/dist/mcp-context.d.ts +5 -5
  137. package/dist/mcp-context.d.ts.map +1 -1
  138. package/dist/mcp-context.js +8 -23
  139. package/dist/mcp-context.js.map +1 -1
  140. package/dist/mcp-delegate.d.ts +2 -2
  141. package/dist/mcp-delegate.d.ts.map +1 -1
  142. package/dist/mcp-delegate.js +19 -7
  143. package/dist/mcp-delegate.js.map +1 -1
  144. package/dist/mcp-exec.d.ts +13 -4
  145. package/dist/mcp-exec.d.ts.map +1 -1
  146. package/dist/mcp-exec.js +56 -14
  147. package/dist/mcp-exec.js.map +1 -1
  148. package/dist/mcp-http.js +1 -1
  149. package/dist/mcp-io.d.ts +30 -0
  150. package/dist/mcp-io.d.ts.map +1 -1
  151. package/dist/mcp-io.js +63 -0
  152. package/dist/mcp-io.js.map +1 -1
  153. package/dist/mcp-isolate.d.ts.map +1 -1
  154. package/dist/mcp-isolate.js +3 -2
  155. package/dist/mcp-isolate.js.map +1 -1
  156. package/dist/mcp-path.d.ts +2 -1
  157. package/dist/mcp-path.d.ts.map +1 -1
  158. package/dist/mcp-path.js +9 -24
  159. package/dist/mcp-path.js.map +1 -1
  160. package/dist/mcp-project-child.js +1 -1
  161. package/dist/mcp-project-child.js.map +1 -1
  162. package/dist/mcp-root.d.ts.map +1 -1
  163. package/dist/mcp-root.js +50 -10
  164. package/dist/mcp-root.js.map +1 -1
  165. package/dist/mcp-ws.d.ts.map +1 -1
  166. package/dist/mcp-ws.js +3 -1
  167. package/dist/mcp-ws.js.map +1 -1
  168. package/dist/mcp.d.ts.map +1 -1
  169. package/dist/mcp.js +48 -19
  170. package/dist/mcp.js.map +1 -1
  171. package/dist/migrate-layout.d.ts +47 -1
  172. package/dist/migrate-layout.d.ts.map +1 -1
  173. package/dist/migrate-layout.js +1972 -170
  174. package/dist/migrate-layout.js.map +1 -1
  175. package/dist/output-schemas.d.ts +128 -0
  176. package/dist/output-schemas.d.ts.map +1 -0
  177. package/dist/output-schemas.js +975 -0
  178. package/dist/output-schemas.js.map +1 -0
  179. package/dist/port.d.ts.map +1 -1
  180. package/dist/port.js +3 -2
  181. package/dist/port.js.map +1 -1
  182. package/dist/project-facts.d.ts +15 -4
  183. package/dist/project-facts.d.ts.map +1 -1
  184. package/dist/project-facts.js +1 -0
  185. package/dist/project-facts.js.map +1 -1
  186. package/dist/project-path.d.ts +6 -0
  187. package/dist/project-path.d.ts.map +1 -1
  188. package/dist/project-path.js +48 -14
  189. package/dist/project-path.js.map +1 -1
  190. package/dist/recipes/5.0.0.d.ts +11 -0
  191. package/dist/recipes/5.0.0.d.ts.map +1 -0
  192. package/dist/recipes/5.0.0.js +34 -0
  193. package/dist/recipes/5.0.0.js.map +1 -0
  194. package/dist/recipes/index.d.ts +7 -0
  195. package/dist/recipes/index.d.ts.map +1 -1
  196. package/dist/recipes/index.js +2 -0
  197. package/dist/recipes/index.js.map +1 -1
  198. package/dist/release-verification.d.ts +5 -0
  199. package/dist/release-verification.d.ts.map +1 -1
  200. package/dist/release-verification.js +14 -0
  201. package/dist/release-verification.js.map +1 -1
  202. package/dist/replay.d.ts.map +1 -1
  203. package/dist/replay.js +12 -3
  204. package/dist/replay.js.map +1 -1
  205. package/dist/review.d.ts +3 -1
  206. package/dist/review.d.ts.map +1 -1
  207. package/dist/review.js +43 -7
  208. package/dist/review.js.map +1 -1
  209. package/dist/rules/codes.d.ts +12 -0
  210. package/dist/rules/codes.d.ts.map +1 -1
  211. package/dist/rules/codes.js +12 -0
  212. package/dist/rules/codes.js.map +1 -1
  213. package/dist/rules/comment-markers.d.ts +16 -7
  214. package/dist/rules/comment-markers.d.ts.map +1 -1
  215. package/dist/rules/comment-markers.js +58 -19
  216. package/dist/rules/comment-markers.js.map +1 -1
  217. package/dist/rules/contract-visibility.d.ts +3 -0
  218. package/dist/rules/contract-visibility.d.ts.map +1 -0
  219. package/dist/rules/contract-visibility.js +337 -0
  220. package/dist/rules/contract-visibility.js.map +1 -0
  221. package/dist/rules/data-guard.d.ts +13 -0
  222. package/dist/rules/data-guard.d.ts.map +1 -1
  223. package/dist/rules/data-guard.js +356 -37
  224. package/dist/rules/data-guard.js.map +1 -1
  225. package/dist/rules/layout.d.ts +4 -0
  226. package/dist/rules/layout.d.ts.map +1 -0
  227. package/dist/rules/layout.js +19 -0
  228. package/dist/rules/layout.js.map +1 -0
  229. package/dist/rules/legacy.d.ts.map +1 -1
  230. package/dist/rules/legacy.js +178 -44
  231. package/dist/rules/legacy.js.map +1 -1
  232. package/dist/rules/pages.d.ts +50 -0
  233. package/dist/rules/pages.d.ts.map +1 -1
  234. package/dist/rules/pages.js +436 -4
  235. package/dist/rules/pages.js.map +1 -1
  236. package/dist/rules/routes.d.ts +6 -0
  237. package/dist/rules/routes.d.ts.map +1 -1
  238. package/dist/rules/routes.js +111 -16
  239. package/dist/rules/routes.js.map +1 -1
  240. package/dist/rules/secrets.js +2 -2
  241. package/dist/rules/secrets.js.map +1 -1
  242. package/dist/rules/security.d.ts +13 -1
  243. package/dist/rules/security.d.ts.map +1 -1
  244. package/dist/rules/security.js +313 -69
  245. package/dist/rules/security.js.map +1 -1
  246. package/dist/rules/zones.d.ts +13 -0
  247. package/dist/rules/zones.d.ts.map +1 -1
  248. package/dist/rules/zones.js +218 -44
  249. package/dist/rules/zones.js.map +1 -1
  250. package/dist/scaffold.d.ts.map +1 -1
  251. package/dist/scaffold.js +6 -4
  252. package/dist/scaffold.js.map +1 -1
  253. package/dist/sdk.d.ts.map +1 -1
  254. package/dist/sdk.js +14 -5
  255. package/dist/sdk.js.map +1 -1
  256. package/dist/stylex-migrate.d.ts.map +1 -1
  257. package/dist/stylex-migrate.js +290 -24
  258. package/dist/stylex-migrate.js.map +1 -1
  259. package/dist/test-tool.d.ts.map +1 -1
  260. package/dist/test-tool.js +6 -3
  261. package/dist/test-tool.js.map +1 -1
  262. package/dist/typecheck-coverage.d.ts +20 -0
  263. package/dist/typecheck-coverage.d.ts.map +1 -0
  264. package/dist/typecheck-coverage.js +129 -0
  265. package/dist/typecheck-coverage.js.map +1 -0
  266. package/dist/upgrade.d.ts +84 -0
  267. package/dist/upgrade.d.ts.map +1 -1
  268. package/dist/upgrade.js +498 -78
  269. package/dist/upgrade.js.map +1 -1
  270. package/dist/verification-plan.d.ts.map +1 -1
  271. package/dist/verification-plan.js +16 -2
  272. package/dist/verification-plan.js.map +1 -1
  273. package/dist/version-drift.d.ts +22 -0
  274. package/dist/version-drift.d.ts.map +1 -0
  275. package/dist/version-drift.js +94 -0
  276. package/dist/version-drift.js.map +1 -0
  277. package/dist/web-app-options.d.ts +1 -1
  278. package/dist/web-app-options.d.ts.map +1 -1
  279. package/dist/web-app-options.js +1 -1
  280. package/dist/web-app-options.js.map +1 -1
  281. package/dist/work-graph.d.ts.map +1 -1
  282. package/dist/work-graph.js +20 -8
  283. package/dist/work-graph.js.map +1 -1
  284. package/docs/examples.json +55 -7
  285. package/docs/llms-full.txt +2043 -222
  286. package/docs/types.json +1573 -246
  287. package/package.json +11 -12
  288. package/src/agent-files.ts +203 -0
  289. package/src/app-files.ts +34 -4
  290. package/src/assure-hydration.ts +18 -2
  291. package/src/assure.ts +88 -10
  292. package/src/capabilities-tool.ts +47 -9
  293. package/src/check-diagnostics.ts +75 -9
  294. package/src/check-scan.ts +1816 -377
  295. package/src/check.ts +258 -81
  296. package/src/cli.ts +242 -37
  297. package/src/client-module.ts +97 -0
  298. package/src/command-catalog.ts +237 -52
  299. package/src/component-check.ts +79 -0
  300. package/src/contract-proof.ts +3 -25
  301. package/src/contracts.ts +60 -3
  302. package/src/db-tool.ts +5 -2
  303. package/src/dev-bun-config.ts +51 -10
  304. package/src/dev-feed-tool.ts +112 -1
  305. package/src/diagnostics.ts +3 -0
  306. package/src/diff-tool.ts +33 -26
  307. package/src/doctor.ts +43 -21
  308. package/src/env-file.ts +7 -0
  309. package/src/fix-recipes.ts +116 -2
  310. package/src/hand-built-manifest.ts +144 -0
  311. package/src/hooks-report.ts +135 -0
  312. package/src/init-agents.ts +21 -14
  313. package/src/internal/config-paths.ts +154 -0
  314. package/src/internal/installed-package.ts +15 -0
  315. package/src/internal/nifra-project.ts +191 -0
  316. package/src/internal/project-specifiers.ts +477 -0
  317. package/src/internal/script-kind.ts +2 -1
  318. package/src/internal/source-facts.ts +48 -5
  319. package/src/internal/typescript-import.ts +15 -6
  320. package/src/introspect.ts +104 -28
  321. package/src/levels-tool.ts +124 -29
  322. package/src/load.ts +107 -17
  323. package/src/mcp-context.ts +12 -25
  324. package/src/mcp-delegate.ts +22 -10
  325. package/src/mcp-exec.ts +68 -18
  326. package/src/mcp-http.ts +1 -1
  327. package/src/mcp-io.ts +74 -0
  328. package/src/mcp-isolate.ts +9 -2
  329. package/src/mcp-path.ts +8 -22
  330. package/src/mcp-project-child.ts +1 -1
  331. package/src/mcp-root.ts +58 -8
  332. package/src/mcp-ws.ts +4 -2
  333. package/src/mcp.ts +56 -22
  334. package/src/migrate-layout.ts +2257 -176
  335. package/src/output-schemas.ts +1240 -0
  336. package/src/port.ts +3 -2
  337. package/src/project-facts.ts +17 -3
  338. package/src/project-path.ts +44 -13
  339. package/src/recipes/5.0.0.ts +35 -0
  340. package/src/recipes/index.ts +10 -0
  341. package/src/release-verification.ts +20 -0
  342. package/src/replay.ts +14 -3
  343. package/src/review.ts +44 -10
  344. package/src/rules/codes.ts +12 -0
  345. package/src/rules/comment-markers.ts +57 -21
  346. package/src/rules/contract-visibility.ts +383 -0
  347. package/src/rules/data-guard.ts +391 -42
  348. package/src/rules/layout.ts +22 -0
  349. package/src/rules/legacy.ts +206 -52
  350. package/src/rules/pages.ts +481 -4
  351. package/src/rules/routes.ts +124 -16
  352. package/src/rules/secrets.ts +2 -2
  353. package/src/rules/security.ts +391 -72
  354. package/src/rules/zones.ts +232 -42
  355. package/src/scaffold.ts +6 -4
  356. package/src/sdk.ts +14 -5
  357. package/src/stylex-migrate.ts +251 -24
  358. package/src/test-tool.ts +12 -3
  359. package/src/typecheck-coverage.ts +134 -0
  360. package/src/upgrade.ts +576 -72
  361. package/src/verification-plan.ts +33 -2
  362. package/src/version-drift.ts +118 -0
  363. package/src/web-app-options.ts +2 -2
  364. package/src/work-graph.ts +17 -12
@@ -10,7 +10,7 @@ import { formatAssuranceReport } from "./assure.js";
10
10
  import { cdnCheckSpec } from "./cdn-check.js";
11
11
  import { renderCheckReport } from "./check.js";
12
12
  import { dbAuditSpec, dbQuerySpec, dbRoleSpec, dbSchemaSpec } from "./db-tool.js";
13
- import { errorsSpec, logsSpec } from "./dev-feed-tool.js";
13
+ import { errorsSpec, explainSpec, logsSpec } from "./dev-feed-tool.js";
14
14
  import { diagnostic, normalizeSeverity, toSarifLog } from "./diagnostics.js";
15
15
  import { i18nCheckPassed, renderI18nCheck } from "./i18n-check.js";
16
16
  import { collectPortResult, renderReport } from "./port.js";
@@ -267,14 +267,19 @@ const ROUTES_SCHEMA = input(objectSchema({
267
267
  json: { type: "boolean" },
268
268
  graph: { type: "boolean" },
269
269
  modes: { type: "boolean" },
270
+ hooks: { type: "boolean" },
271
+ since: { type: "string" },
270
272
  target: { type: "string" },
271
273
  dir: { type: "string" },
272
274
  }), (value) => {
273
275
  const raw = withDir(record(value));
274
- if (raw.graph === true && raw.modes === true)
275
- throw new TypeError("graph and modes cannot be combined");
276
+ if ([raw.graph, raw.modes, raw.hooks].filter((flag) => flag === true).length > 1)
277
+ throw new TypeError("graph, modes and hooks cannot be combined");
278
+ if (raw.since !== undefined && raw.hooks !== true)
279
+ throw new TypeError("since compares hook reports and needs hooks");
276
280
  return {
277
- ...parseBooleanFlags(raw, ["json", "graph", "modes"]),
281
+ ...parseBooleanFlags(raw, ["json", "graph", "modes", "hooks"]),
282
+ since: optionalString(raw.since, "since"),
278
283
  target: optionalString(raw.target, "target"),
279
284
  ...(raw.dir === undefined ? {} : { dir: raw.dir }),
280
285
  };
@@ -334,12 +339,13 @@ const MIGRATE_SCHEMA = input(objectSchema({
334
339
  from: { type: "string", enum: ["tailwind"] },
335
340
  to: { type: "string", enum: ["stylex"] },
336
341
  write: { type: "boolean" },
342
+ zones: { type: "boolean" },
337
343
  json: { type: "boolean" },
338
344
  dir: { type: "string" },
339
345
  }), (value) => {
340
346
  const raw = withDir(record(value));
341
347
  const flags = {
342
- ...parseBooleanFlags(raw, ["write", "json"]),
348
+ ...parseBooleanFlags(raw, ["write", "zones", "json"]),
343
349
  ...(raw.dir === undefined ? {} : { dir: raw.dir }),
344
350
  };
345
351
  const kind = optionalString(raw.kind, "kind");
@@ -348,6 +354,8 @@ const MIGRATE_SCHEMA = input(objectSchema({
348
354
  throw new TypeError("the migration must be `layout` (or --from tailwind --to stylex)");
349
355
  return { kind, ...flags };
350
356
  }
357
+ if (raw.zones === true)
358
+ throw new TypeError("--zones applies to `migrate layout` only");
351
359
  const from = optionalString(raw.from, "from");
352
360
  const to = optionalString(raw.to, "to");
353
361
  if (from !== "tailwind")
@@ -372,6 +380,18 @@ const TYPES_SCHEMA = input(objectSchema({ check: { type: "boolean" }, json: { ty
372
380
  ...(raw.dir === undefined ? {} : { dir: raw.dir }),
373
381
  };
374
382
  });
383
+ const SCHEMAS_SCHEMA = input(objectSchema({
384
+ write: { type: "boolean" },
385
+ check: { type: "boolean" },
386
+ json: { type: "boolean" },
387
+ dir: { type: "string" },
388
+ }), (value) => {
389
+ const raw = withDir(record(value));
390
+ return {
391
+ ...parseBooleanFlags(raw, ["write", "check", "json"]),
392
+ ...(raw.dir === undefined ? {} : { dir: raw.dir }),
393
+ };
394
+ });
375
395
  const SNAPSHOT_SCHEMA = input(objectSchema({ out: { type: "string" }, json: { type: "boolean" }, dir: { type: "string" } }), (value) => {
376
396
  const raw = withDir(record(value));
377
397
  return {
@@ -492,10 +512,11 @@ const I18N_SCHEMA = input(objectSchema({
492
512
  ...(raw.dir === undefined ? {} : { dir: raw.dir }),
493
513
  };
494
514
  });
495
- function loadAppFor(ctx) {
515
+ /** The app as the backend readers see it: an API-only app is its `backend/app.ts` alone. */
516
+ function backendAppFor(ctx) {
496
517
  if (ctx.loadApp !== undefined)
497
- return ctx.loadApp();
498
- return import("./load.js").then(({ loadApp }) => loadApp(ctx.cwd));
518
+ return ctx.loadApp.backend();
519
+ return import("./load.js").then(({ loadBackendApp }) => loadBackendApp(ctx.cwd));
499
520
  }
500
521
  function structuredDiagnostics(out) {
501
522
  if (out.structuredDiagnostics !== undefined)
@@ -709,7 +730,7 @@ const manifestSpec = {
709
730
  };
710
731
  const routesSpec = {
711
732
  name: "routes",
712
- summary: "List, graph, or target-check every page and API route the app serves.",
733
+ summary: "List, graph, or target-check every page and API route the app serves, or the hooks each runs.",
713
734
  input: ROUTES_SCHEMA,
714
735
  output: output({
715
736
  type: "object",
@@ -723,12 +744,43 @@ const routesSpec = {
723
744
  { name: "json", field: "json", type: "boolean" },
724
745
  { name: "graph", field: "graph", type: "boolean" },
725
746
  { name: "modes", field: "modes", type: "boolean" },
747
+ { name: "hooks", field: "hooks", type: "boolean" },
748
+ { name: "since", field: "since", type: "string" },
726
749
  { name: "target", field: "target", type: "string" },
727
750
  ],
728
751
  },
729
752
  async run(value, ctx) {
730
- const app = await loadAppFor(ctx);
753
+ if (value.hooks === true) {
754
+ const app = await backendAppFor(ctx);
755
+ const hooks = await import("./hooks-report.js");
756
+ const report = await hooks.collectHookReport(app.backend);
757
+ if (value.since === undefined) {
758
+ return { ok: true, mode: "hooks", data: report, text: hooks.renderHookReport(report) };
759
+ }
760
+ const saved = hooks.parseHookReport(await Bun.file(resolve(ctx.cwd, value.since))
761
+ .text()
762
+ .catch(() => ""));
763
+ if (saved === undefined) {
764
+ return {
765
+ ok: false,
766
+ mode: "hooks",
767
+ data: { report, error: `${value.since} is not a saved hook report` },
768
+ text: `[nifra] ${value.since} is not a saved hook report (write one with \`nifra routes --hooks --json\`)`,
769
+ };
770
+ }
771
+ const changes = hooks.diffHookReports(saved, report);
772
+ return {
773
+ ok: true,
774
+ mode: "hooks",
775
+ data: { report, changes },
776
+ text: hooks.renderHookDiff(changes),
777
+ };
778
+ }
779
+ const app = await backendAppFor(ctx);
731
780
  if (value.modes === true) {
781
+ if (app.framework === undefined) {
782
+ throw new Error(`[nifra] routes --modes reads page routes, and ${ctx.cwd} is an API-only app (backend/app.ts, no web config)`);
783
+ }
732
784
  const { buildRouteManifest, renderRouteManifest } = await import("@nifrajs/web/route-manifest");
733
785
  const { discoverRoutes } = await import("@nifrajs/web/fs");
734
786
  const data = await buildRouteManifest(discoverRoutes(app.routesDir), value.target === undefined ? {} : { target: value.target });
@@ -771,7 +823,7 @@ const contextSpec = {
771
823
  async run(value, ctx) {
772
824
  return {
773
825
  ok: true,
774
- text: (await import("./introspect.js")).describeProject(await loadAppFor(ctx), {
826
+ text: (await import("./introspect.js")).describeProject(await backendAppFor(ctx), {
775
827
  ...(value.path === undefined ? {} : { path: value.path }),
776
828
  ...(value.kind === undefined ? {} : { kind: value.kind }),
777
829
  }),
@@ -797,9 +849,7 @@ const openApiSpec = {
797
849
  ],
798
850
  },
799
851
  async run(value, ctx) {
800
- const app = ctx.loadApp !== undefined
801
- ? await ctx.loadApp()
802
- : await import("./load.js").then(({ loadBackendApp }) => loadBackendApp(ctx.cwd));
852
+ const app = await backendAppFor(ctx);
803
853
  const { renderOpenApiWithTypes } = await import("./openapi-tool.js");
804
854
  const format = value.format ?? "json";
805
855
  return {
@@ -858,6 +908,7 @@ const fixSpec = {
858
908
  ok: { type: "boolean" },
859
909
  changed: { type: "array" },
860
910
  failed: { type: "array" },
911
+ skipped: { type: "array" },
861
912
  diagnostics: { type: "array" },
862
913
  },
863
914
  required: ["ok", "diagnostics"],
@@ -872,26 +923,38 @@ const fixSpec = {
872
923
  },
873
924
  async run(value, ctx) {
874
925
  const { collectCheckResult } = await import("./check.js");
875
- const { applyDiagnosticRecipe } = await import("./fix-recipes.js");
926
+ const { applyDiagnosticRecipe, getFixRecipe } = await import("./fix-recipes.js");
876
927
  const result = await collectCheckResult(ctx.cwd, {
877
928
  lintsOnly: true,
878
929
  ...(ctx.signal === undefined ? {} : { signal: ctx.signal }),
879
930
  });
880
931
  const changed = [];
881
932
  const failed = [];
882
- for (const diagnostic of result.structuredDiagnostics ?? [])
883
- if (value.code === undefined || diagnostic.code === value.code)
884
- // One recipe that cannot act must not cancel the others, and must not pass silently either:
885
- // record why and keep going, so a run over many diagnostics still reports every outcome.
886
- try {
887
- changed.push(...(await applyDiagnosticRecipe(ctx.cwd, diagnostic)));
888
- }
889
- catch (error) {
890
- failed.push({
933
+ const skipped = [];
934
+ for (const diagnostic of result.structuredDiagnostics ?? []) {
935
+ if (value.code !== undefined && diagnostic.code !== value.code)
936
+ continue;
937
+ const recipe = diagnostic.fix?.recipe === undefined ? undefined : getFixRecipe(diagnostic.fix.recipe);
938
+ if (value.code === undefined && recipe?.onlyWhenNamed === true) {
939
+ if (!skipped.some((entry) => entry.code === diagnostic.code))
940
+ skipped.push({
891
941
  code: diagnostic.code,
892
- reason: error instanceof Error ? error.message : String(error),
942
+ reason: `${recipe.description} Review the finding, then run \`nifra fix --code ${diagnostic.code}\`.`,
893
943
  });
894
- }
944
+ continue;
945
+ }
946
+ // One recipe that cannot act must not cancel the others, and must not pass silently either:
947
+ // record why and keep going, so a run over many diagnostics still reports every outcome.
948
+ try {
949
+ changed.push(...(await applyDiagnosticRecipe(ctx.cwd, diagnostic)));
950
+ }
951
+ catch (error) {
952
+ failed.push({
953
+ code: diagnostic.code,
954
+ reason: error instanceof Error ? error.message : String(error),
955
+ });
956
+ }
957
+ }
895
958
  const final = await collectCheckResult(ctx.cwd, {
896
959
  lintsOnly: true,
897
960
  ...(ctx.signal === undefined ? {} : { signal: ctx.signal }),
@@ -900,6 +963,7 @@ const fixSpec = {
900
963
  ok: final.ok,
901
964
  changed,
902
965
  failed,
966
+ skipped,
903
967
  diagnostics: final.structuredDiagnostics ?? final.diagnostics,
904
968
  };
905
969
  },
@@ -907,6 +971,7 @@ const fixSpec = {
907
971
  out.ok ? "✓ fixes applied" : "✖ unresolved diagnostics remain",
908
972
  ...out.changed.map((file) => ` changed ${file}`),
909
973
  ...out.failed.map((f) => ` could not fix ${f.code}: ${f.reason}`),
974
+ ...out.skipped.map((s) => ` needs review ${s.code}: ${s.reason}`),
910
975
  ...out.diagnostics.map((d) => ` ${String(d.code ?? "diagnostic")}`),
911
976
  ],
912
977
  success: (out) => out.ok,
@@ -931,6 +996,9 @@ const migrateSpec = {
931
996
  splits: { type: "array" },
932
997
  moves: { type: "array" },
933
998
  rewritten: { type: "array" },
999
+ tooling: { type: "array" },
1000
+ followUps: { type: "array" },
1001
+ mode: { type: "string" },
934
1002
  },
935
1003
  required: ["ok", "write", "issues"],
936
1004
  }),
@@ -942,6 +1010,7 @@ const migrateSpec = {
942
1010
  { name: "from", field: "from", type: "string" },
943
1011
  { name: "to", field: "to", type: "string" },
944
1012
  { name: "write", field: "write", type: "boolean" },
1013
+ { name: "zones", field: "zones", type: "boolean" },
945
1014
  { name: "json", field: "json", type: "boolean" },
946
1015
  { name: "dir", field: "dir", type: "string" },
947
1016
  ],
@@ -951,7 +1020,8 @@ const migrateSpec = {
951
1020
  const write = value.write === undefined ? {} : { write: value.write };
952
1021
  if (value.kind === "layout") {
953
1022
  const { migrateLayout } = await import("./migrate-layout.js");
954
- return { kind: "layout", ...(await migrateLayout(dir, write)) };
1023
+ const options = { ...write, zones: value.zones === true };
1024
+ return { kind: "layout", ...(await migrateLayout(dir, options)) };
955
1025
  }
956
1026
  const { migrateTailwindToStylex } = await import("./stylex-migrate.js");
957
1027
  return migrateTailwindToStylex(dir, write);
@@ -960,12 +1030,17 @@ const migrateSpec = {
960
1030
  if (out.kind === "layout") {
961
1031
  return [
962
1032
  out.ok
963
- ? `✓ ${out.splits.length} route file${out.splits.length === 1 ? "" : "s"} split, ${out.moves.length} file${out.moves.length === 1 ? "" : "s"} moved`
1033
+ ? `✓ ${out.mode === "api" ? "API-only app" : `${out.splits.length} route file${out.splits.length === 1 ? "" : "s"} split`}, ${out.moves.length} file${out.moves.length === 1 ? "" : "s"} moved`
964
1034
  : `⚠ ${out.issues.length} issue${out.issues.length === 1 ? "" : "s"} need a decision; everything else is planned`,
965
- ...out.splits.map((split) => ` split ${split.file} → ${split.backend} (${split.moved.join(", ")})`),
1035
+ ...out.splits.map((split) => ` split ${split.file} → ${split.backend} (${split.moved.join(", ")})${split.shared === undefined ? "" : `, shared data → ${split.shared}`}`),
966
1036
  ...out.moves.map((move) => ` move ${move.from} → ${move.to}`),
967
1037
  ...out.rewritten.map((file) => ` edit ${file} (imports)`),
1038
+ ...out.tooling.map((edit) => ` edit ${edit.file} (${edit.change})`),
1039
+ ...out.schemas
1040
+ .filter((entry) => entry.reason === undefined)
1041
+ .map((entry) => ` draft ${entry.file} ${entry.schema} - review what it lets the browser see${entry.omitted === undefined ? "" : `; leaves out ${entry.omitted.join(", ")}`}${entry.open === undefined ? "" : `; admits any value at ${entry.open.join(", ")}, typed any or unknown`}`),
968
1042
  ...out.issues.map((issue) => ` ⚠ ${issue.file}: ${issue.reason}`),
1043
+ ...out.followUps.map((step) => ` next ${step.file}: ${step.reason} - \`${step.command}\``),
969
1044
  ...(out.write ? [] : [" dry run: pass --write to apply"]),
970
1045
  ];
971
1046
  }
@@ -1078,6 +1153,82 @@ const typesSpec = {
1078
1153
  ],
1079
1154
  success: (out) => out.ok,
1080
1155
  };
1156
+ const SCHEMA_STATUS_LINE = {
1157
+ missing: "+ would write",
1158
+ generated: "+ wrote",
1159
+ stale: "↻ stale",
1160
+ regenerated: "↻ rewrote",
1161
+ current: "✓ current",
1162
+ declared: "· hand-written",
1163
+ "no-data": "· no data",
1164
+ unreadable: "⚠ unreadable",
1165
+ };
1166
+ const schemasSpec = {
1167
+ name: "schemas",
1168
+ summary: "Write a reviewable `loaderOutput`/`actionOutput` from the loader's or action's return type where a route has none (`--write`), keep generated ones current, and list returned fields a hand-written schema drops.",
1169
+ input: SCHEMAS_SCHEMA,
1170
+ output: output({
1171
+ type: "object",
1172
+ properties: {
1173
+ ok: { type: "boolean" },
1174
+ write: { type: "boolean" },
1175
+ entries: { type: "array" },
1176
+ written: { type: "array" },
1177
+ warnings: { type: "array" },
1178
+ },
1179
+ required: ["ok", "write", "entries", "written", "warnings"],
1180
+ }),
1181
+ transports: ["cli"],
1182
+ stability: "stable",
1183
+ argv: {
1184
+ flags: [
1185
+ { name: "write", field: "write", type: "boolean" },
1186
+ { name: "check", field: "check", type: "boolean" },
1187
+ { name: "json", field: "json", type: "boolean" },
1188
+ { name: "dir", field: "dir", type: "string" },
1189
+ ],
1190
+ },
1191
+ async run(value, ctx) {
1192
+ const { outputSchemas } = await import("./output-schemas.js");
1193
+ return outputSchemas(resolve(ctx.cwd, value.dir ?? "."), {
1194
+ write: value.write === true,
1195
+ check: value.check === true,
1196
+ });
1197
+ },
1198
+ render: (out) => {
1199
+ const shown = out.entries.filter((entry) => (entry.status !== "current" || entry.open !== undefined) &&
1200
+ entry.status !== "no-data" &&
1201
+ (entry.status !== "declared" || entry.dropped.length > 0));
1202
+ const current = out.entries.filter((entry) => entry.status === "current").length;
1203
+ return [
1204
+ ...out.warnings.map((warning) => `⚠ ${warning}`),
1205
+ `${out.ok ? "✓" : "✖"} ${out.entries.length} output schema${out.entries.length === 1 ? "" : "s"} read, ${current} generated and current`,
1206
+ ...shown.map((entry) => {
1207
+ const detail = entry.reason ??
1208
+ (entry.edited === true
1209
+ ? "edited by hand under its nifra:generated line"
1210
+ : entry.dropped.length > 0
1211
+ ? `drops ${entry.dropped.join(", ")}`
1212
+ : entry.omitted !== undefined
1213
+ ? `leaves out ${entry.omitted.join(", ")}`
1214
+ : undefined);
1215
+ const open = entry.open === undefined
1216
+ ? []
1217
+ : [
1218
+ ` admits any value at ${entry.open.join(", ")}: the type there is any or unknown, so the output guard drops nothing below it`,
1219
+ ];
1220
+ return [
1221
+ ` ${SCHEMA_STATUS_LINE[entry.status]} ${entry.file}:${entry.line} ${entry.schema}${detail === undefined ? "" : ` - ${detail}`}`,
1222
+ ...open,
1223
+ ].join("\n");
1224
+ }),
1225
+ ...(out.write || !out.entries.some((e) => e.status === "missing" || e.status === "stale")
1226
+ ? []
1227
+ : [" dry run: pass --write to apply"]),
1228
+ ];
1229
+ },
1230
+ success: (out) => out.ok,
1231
+ };
1081
1232
  const snapshotSpec = {
1082
1233
  name: "snapshot",
1083
1234
  summary: "Write the backend API contract as a versioned JSON baseline.",
@@ -1102,11 +1253,9 @@ const snapshotSpec = {
1102
1253
  },
1103
1254
  async run(value, ctx) {
1104
1255
  const mod = await import("./diff-tool.js");
1105
- const routes = await mod.snapshotBackend(ctx.cwd);
1106
1256
  const file = value.out ?? mod.DEFAULT_SNAPSHOT_FILE;
1107
- const snapshot = { nifraSnapshot: 1, routes };
1108
- await Bun.write(resolve(ctx.cwd, file), `${JSON.stringify(snapshot, null, 2)}\n`);
1109
- return { ok: true, file, routes: routes.length, snapshot };
1257
+ const snapshot = await mod.writeSnapshot(ctx.cwd, file);
1258
+ return { ok: true, file, routes: snapshot.routes.length, snapshot };
1110
1259
  },
1111
1260
  render: (out) => [
1112
1261
  `[nifra] wrote ${out.routes} route${out.routes === 1 ? "" : "s"} to ${out.file}`,
@@ -1129,19 +1278,17 @@ const diffSpec = {
1129
1278
  stability: "stable",
1130
1279
  argv: { positionals: ["baseline"], flags: [{ name: "json", field: "json", type: "boolean" }] },
1131
1280
  async run(value, ctx) {
1132
- const mod = await import("./diff-tool.js");
1133
- const baseline = value.baseline ?? mod.DEFAULT_SNAPSHOT_FILE;
1134
- const file = await Bun.file(resolve(ctx.cwd, baseline)).text();
1135
- const before = mod.parseSnapshotFile(file, baseline);
1136
- const current = await mod.snapshotBackend(ctx.cwd);
1137
- const { diffRouteSnapshots } = await import("@nifrajs/core/diff");
1138
- const diff = diffRouteSnapshots(before.routes, current);
1139
- return { ok: !diff.hasBreaking, hasBreaking: diff.hasBreaking, changes: diff.changes };
1281
+ const { diffAgainstBaseline, formatDiff } = await import("./diff-tool.js");
1282
+ const diff = await diffAgainstBaseline(ctx.cwd, value.baseline);
1283
+ return {
1284
+ ok: !diff.hasBreaking,
1285
+ hasBreaking: diff.hasBreaking,
1286
+ changes: diff.changes,
1287
+ text: formatDiff(diff),
1288
+ };
1140
1289
  },
1141
- render: (out) => [
1142
- out.ok ? "✓ no breaking changes" : "✖ breaking changes",
1143
- ...out.changes.map((c) => `${c.method} ${c.path}: ${c.message}`),
1144
- ],
1290
+ render: (out) => [out.text],
1291
+ json: ({ ok, hasBreaking, changes }) => ({ ok, hasBreaking, changes }),
1145
1292
  success: (out) => out.ok,
1146
1293
  };
1147
1294
  const contractsSpec = {
@@ -1354,6 +1501,7 @@ export const commandSpecs = Object.freeze([
1354
1501
  migrateSpec,
1355
1502
  targetSpec,
1356
1503
  typesSpec,
1504
+ schemasSpec,
1357
1505
  snapshotSpec,
1358
1506
  diffSpec,
1359
1507
  contractsSpec,
@@ -1365,6 +1513,7 @@ export const commandSpecs = Object.freeze([
1365
1513
  portSpec,
1366
1514
  i18nSpec,
1367
1515
  errorsSpec,
1516
+ explainSpec,
1368
1517
  logsSpec,
1369
1518
  cdnCheckSpec,
1370
1519
  dbSchemaSpec,