@nifrajs/cli 3.3.0 → 4.0.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 (425) hide show
  1. package/README.md +42 -3
  2. package/dist/app-files.d.ts +11 -0
  3. package/dist/app-files.d.ts.map +1 -0
  4. package/dist/app-files.js +34 -0
  5. package/dist/app-files.js.map +1 -0
  6. package/dist/assure-hydration.d.ts +7 -1
  7. package/dist/assure-hydration.d.ts.map +1 -1
  8. package/dist/assure-hydration.js +128 -43
  9. package/dist/assure-hydration.js.map +1 -1
  10. package/dist/assure.d.ts.map +1 -1
  11. package/dist/assure.js +16 -2
  12. package/dist/assure.js.map +1 -1
  13. package/dist/capabilities-tool.d.ts +5 -1
  14. package/dist/capabilities-tool.d.ts.map +1 -1
  15. package/dist/capabilities-tool.js +32 -10
  16. package/dist/capabilities-tool.js.map +1 -1
  17. package/dist/cdn-check.d.ts +41 -0
  18. package/dist/cdn-check.d.ts.map +1 -0
  19. package/dist/cdn-check.js +207 -0
  20. package/dist/cdn-check.js.map +1 -0
  21. package/dist/check-diagnostics.d.ts +7 -15
  22. package/dist/check-diagnostics.d.ts.map +1 -1
  23. package/dist/check-diagnostics.js +162 -713
  24. package/dist/check-diagnostics.js.map +1 -1
  25. package/dist/check-scan.d.ts +58 -7
  26. package/dist/check-scan.d.ts.map +1 -1
  27. package/dist/check-scan.js +323 -62
  28. package/dist/check-scan.js.map +1 -1
  29. package/dist/check.d.ts +1 -0
  30. package/dist/check.d.ts.map +1 -1
  31. package/dist/check.js +145 -54
  32. package/dist/check.js.map +1 -1
  33. package/dist/cli.d.ts +42 -16
  34. package/dist/cli.d.ts.map +1 -1
  35. package/dist/cli.js +306 -87
  36. package/dist/cli.js.map +1 -1
  37. package/dist/command-catalog.d.ts +60 -5
  38. package/dist/command-catalog.d.ts.map +1 -1
  39. package/dist/command-catalog.js +298 -33
  40. package/dist/command-catalog.js.map +1 -1
  41. package/dist/config-target.d.ts +8 -0
  42. package/dist/config-target.d.ts.map +1 -0
  43. package/dist/config-target.js +45 -0
  44. package/dist/config-target.js.map +1 -0
  45. package/dist/contracts.d.ts +3 -2
  46. package/dist/contracts.d.ts.map +1 -1
  47. package/dist/contracts.js +56 -16
  48. package/dist/contracts.js.map +1 -1
  49. package/dist/db-audit.d.ts +37 -0
  50. package/dist/db-audit.d.ts.map +1 -0
  51. package/dist/db-audit.js +99 -0
  52. package/dist/db-audit.js.map +1 -0
  53. package/dist/db-child.d.ts +64 -0
  54. package/dist/db-child.d.ts.map +1 -0
  55. package/dist/db-child.js +213 -0
  56. package/dist/db-child.js.map +1 -0
  57. package/dist/db-config.d.ts +60 -0
  58. package/dist/db-config.d.ts.map +1 -0
  59. package/dist/db-config.js +176 -0
  60. package/dist/db-config.js.map +1 -0
  61. package/dist/db-tool.d.ts +84 -0
  62. package/dist/db-tool.d.ts.map +1 -0
  63. package/dist/db-tool.js +537 -0
  64. package/dist/db-tool.js.map +1 -0
  65. package/dist/dev-bun-config.d.ts +20 -13
  66. package/dist/dev-bun-config.d.ts.map +1 -1
  67. package/dist/dev-bun-config.js +98 -41
  68. package/dist/dev-bun-config.js.map +1 -1
  69. package/dist/dev-feed-tool.d.ts +106 -0
  70. package/dist/dev-feed-tool.d.ts.map +1 -0
  71. package/dist/dev-feed-tool.js +713 -0
  72. package/dist/dev-feed-tool.js.map +1 -0
  73. package/dist/dev-server-client.d.ts +67 -0
  74. package/dist/dev-server-client.d.ts.map +1 -0
  75. package/dist/dev-server-client.js +195 -0
  76. package/dist/dev-server-client.js.map +1 -0
  77. package/dist/diagnostics.d.ts +86 -0
  78. package/dist/diagnostics.d.ts.map +1 -1
  79. package/dist/diagnostics.js +85 -0
  80. package/dist/diagnostics.js.map +1 -1
  81. package/dist/diff-tool.d.ts +3 -3
  82. package/dist/diff-tool.d.ts.map +1 -1
  83. package/dist/diff-tool.js +6 -5
  84. package/dist/diff-tool.js.map +1 -1
  85. package/dist/docs-search.d.ts.map +1 -1
  86. package/dist/docs-search.js +12 -4
  87. package/dist/docs-search.js.map +1 -1
  88. package/dist/doctor.d.ts +17 -0
  89. package/dist/doctor.d.ts.map +1 -1
  90. package/dist/doctor.js +76 -15
  91. package/dist/doctor.js.map +1 -1
  92. package/dist/env-file.d.ts +15 -0
  93. package/dist/env-file.d.ts.map +1 -1
  94. package/dist/env-file.js +73 -1
  95. package/dist/env-file.js.map +1 -1
  96. package/dist/fix-recipes.d.ts.map +1 -1
  97. package/dist/fix-recipes.js +41 -21
  98. package/dist/fix-recipes.js.map +1 -1
  99. package/dist/frontend-guidance.js +5 -5
  100. package/dist/frontend-guidance.js.map +1 -1
  101. package/dist/i18n-check.d.ts +16 -0
  102. package/dist/i18n-check.d.ts.map +1 -0
  103. package/dist/i18n-check.js +154 -0
  104. package/dist/i18n-check.js.map +1 -0
  105. package/dist/init-agents.d.ts +49 -13
  106. package/dist/init-agents.d.ts.map +1 -1
  107. package/dist/init-agents.js +239 -34
  108. package/dist/init-agents.js.map +1 -1
  109. package/dist/internal/code-unit-order.d.ts +4 -0
  110. package/dist/internal/code-unit-order.d.ts.map +1 -0
  111. package/dist/internal/code-unit-order.js +6 -0
  112. package/dist/internal/code-unit-order.js.map +1 -0
  113. package/dist/internal/script-kind.d.ts +7 -0
  114. package/dist/internal/script-kind.d.ts.map +1 -0
  115. package/dist/internal/script-kind.js +12 -0
  116. package/dist/internal/script-kind.js.map +1 -0
  117. package/dist/internal/source-facts.d.ts +5 -0
  118. package/dist/internal/source-facts.d.ts.map +1 -1
  119. package/dist/internal/source-facts.js +61 -5
  120. package/dist/internal/source-facts.js.map +1 -1
  121. package/dist/internal/typescript-import.d.ts +49 -24
  122. package/dist/internal/typescript-import.d.ts.map +1 -1
  123. package/dist/internal/typescript-import.js +507 -67
  124. package/dist/internal/typescript-import.js.map +1 -1
  125. package/dist/introspect.d.ts +6 -4
  126. package/dist/introspect.d.ts.map +1 -1
  127. package/dist/introspect.js +93 -74
  128. package/dist/introspect.js.map +1 -1
  129. package/dist/learn.js +3 -3
  130. package/dist/learn.js.map +1 -1
  131. package/dist/levels-tool.d.ts +10 -0
  132. package/dist/levels-tool.d.ts.map +1 -1
  133. package/dist/levels-tool.js +33 -3
  134. package/dist/levels-tool.js.map +1 -1
  135. package/dist/load.d.ts +71 -16
  136. package/dist/load.d.ts.map +1 -1
  137. package/dist/load.js +78 -18
  138. package/dist/load.js.map +1 -1
  139. package/dist/manifest-tool.d.ts.map +1 -1
  140. package/dist/manifest-tool.js +3 -2
  141. package/dist/manifest-tool.js.map +1 -1
  142. package/dist/mcp-app-tool.d.ts.map +1 -1
  143. package/dist/mcp-app-tool.js +2 -0
  144. package/dist/mcp-app-tool.js.map +1 -1
  145. package/dist/mcp-context.d.ts.map +1 -1
  146. package/dist/mcp-context.js +5 -6
  147. package/dist/mcp-context.js.map +1 -1
  148. package/dist/mcp-delegate.d.ts +57 -0
  149. package/dist/mcp-delegate.d.ts.map +1 -0
  150. package/dist/mcp-delegate.js +130 -0
  151. package/dist/mcp-delegate.js.map +1 -0
  152. package/dist/mcp-docs-tools.d.ts.map +1 -1
  153. package/dist/mcp-docs-tools.js +23 -5
  154. package/dist/mcp-docs-tools.js.map +1 -1
  155. package/dist/mcp-exec.d.ts +3 -0
  156. package/dist/mcp-exec.d.ts.map +1 -1
  157. package/dist/mcp-exec.js +263 -131
  158. package/dist/mcp-exec.js.map +1 -1
  159. package/dist/mcp-http.d.ts.map +1 -1
  160. package/dist/mcp-http.js +20 -5
  161. package/dist/mcp-http.js.map +1 -1
  162. package/dist/mcp-io.d.ts +26 -0
  163. package/dist/mcp-io.d.ts.map +1 -1
  164. package/dist/mcp-io.js +113 -0
  165. package/dist/mcp-io.js.map +1 -1
  166. package/dist/mcp-isolate.d.ts +48 -0
  167. package/dist/mcp-isolate.d.ts.map +1 -0
  168. package/dist/mcp-isolate.js +222 -0
  169. package/dist/mcp-isolate.js.map +1 -0
  170. package/dist/mcp-path.d.ts +10 -0
  171. package/dist/mcp-path.d.ts.map +1 -0
  172. package/dist/mcp-path.js +51 -0
  173. package/dist/mcp-path.js.map +1 -0
  174. package/dist/mcp-project-child.d.ts +74 -0
  175. package/dist/mcp-project-child.d.ts.map +1 -0
  176. package/dist/mcp-project-child.js +130 -0
  177. package/dist/mcp-project-child.js.map +1 -0
  178. package/dist/mcp-reflect.d.ts.map +1 -1
  179. package/dist/mcp-reflect.js +2 -2
  180. package/dist/mcp-reflect.js.map +1 -1
  181. package/dist/mcp-render.d.ts.map +1 -1
  182. package/dist/mcp-render.js +64 -45
  183. package/dist/mcp-render.js.map +1 -1
  184. package/dist/mcp-root.d.ts +37 -9
  185. package/dist/mcp-root.d.ts.map +1 -1
  186. package/dist/mcp-root.js +124 -25
  187. package/dist/mcp-root.js.map +1 -1
  188. package/dist/mcp-run.d.ts +18 -1
  189. package/dist/mcp-run.d.ts.map +1 -1
  190. package/dist/mcp-run.js +159 -55
  191. package/dist/mcp-run.js.map +1 -1
  192. package/dist/mcp-ws.js +4 -1
  193. package/dist/mcp-ws.js.map +1 -1
  194. package/dist/mcp.d.ts +18 -5
  195. package/dist/mcp.d.ts.map +1 -1
  196. package/dist/mcp.js +165 -82
  197. package/dist/mcp.js.map +1 -1
  198. package/dist/migrate-layout.d.ts +44 -0
  199. package/dist/migrate-layout.d.ts.map +1 -0
  200. package/dist/migrate-layout.js +982 -0
  201. package/dist/migrate-layout.js.map +1 -0
  202. package/dist/openapi-tool.d.ts +2 -2
  203. package/dist/openapi-tool.d.ts.map +1 -1
  204. package/dist/openapi-tool.js.map +1 -1
  205. package/dist/openapi-types.d.ts +2 -1
  206. package/dist/openapi-types.d.ts.map +1 -1
  207. package/dist/openapi-types.js +240 -12
  208. package/dist/openapi-types.js.map +1 -1
  209. package/dist/pipeline-report.d.ts +3 -2
  210. package/dist/pipeline-report.d.ts.map +1 -1
  211. package/dist/pipeline-report.js +19 -16
  212. package/dist/pipeline-report.js.map +1 -1
  213. package/dist/port.d.ts +5 -4
  214. package/dist/port.d.ts.map +1 -1
  215. package/dist/port.js +46 -29
  216. package/dist/port.js.map +1 -1
  217. package/dist/project-facts.d.ts +28 -4
  218. package/dist/project-facts.d.ts.map +1 -1
  219. package/dist/project-facts.js +12 -2
  220. package/dist/project-facts.js.map +1 -1
  221. package/dist/recipes/4.0.0.d.ts +9 -0
  222. package/dist/recipes/4.0.0.d.ts.map +1 -0
  223. package/dist/recipes/4.0.0.js +34 -0
  224. package/dist/recipes/4.0.0.js.map +1 -0
  225. package/dist/recipes/index.d.ts.map +1 -1
  226. package/dist/recipes/index.js +2 -0
  227. package/dist/recipes/index.js.map +1 -1
  228. package/dist/release-verification.d.ts +11 -1
  229. package/dist/release-verification.d.ts.map +1 -1
  230. package/dist/release-verification.js +47 -4
  231. package/dist/release-verification.js.map +1 -1
  232. package/dist/review.d.ts +21 -0
  233. package/dist/review.d.ts.map +1 -0
  234. package/dist/review.js +823 -0
  235. package/dist/review.js.map +1 -0
  236. package/dist/route-types.d.ts +28 -0
  237. package/dist/route-types.d.ts.map +1 -0
  238. package/dist/route-types.js +106 -0
  239. package/dist/route-types.js.map +1 -0
  240. package/dist/rules/codes.d.ts +9 -0
  241. package/dist/rules/codes.d.ts.map +1 -1
  242. package/dist/rules/codes.js +9 -0
  243. package/dist/rules/codes.js.map +1 -1
  244. package/dist/rules/data-guard.d.ts +12 -0
  245. package/dist/rules/data-guard.d.ts.map +1 -0
  246. package/dist/rules/data-guard.js +213 -0
  247. package/dist/rules/data-guard.js.map +1 -0
  248. package/dist/rules/index.d.ts +3 -0
  249. package/dist/rules/index.d.ts.map +1 -1
  250. package/dist/rules/index.js +17 -5
  251. package/dist/rules/index.js.map +1 -1
  252. package/dist/rules/islands.d.ts.map +1 -1
  253. package/dist/rules/islands.js +7 -4
  254. package/dist/rules/islands.js.map +1 -1
  255. package/dist/rules/legacy.d.ts +3 -1
  256. package/dist/rules/legacy.d.ts.map +1 -1
  257. package/dist/rules/legacy.js +829 -34
  258. package/dist/rules/legacy.js.map +1 -1
  259. package/dist/rules/nano.d.ts.map +1 -1
  260. package/dist/rules/nano.js +15 -6
  261. package/dist/rules/nano.js.map +1 -1
  262. package/dist/rules/pages.d.ts +15 -0
  263. package/dist/rules/pages.d.ts.map +1 -0
  264. package/dist/rules/pages.js +88 -0
  265. package/dist/rules/pages.js.map +1 -0
  266. package/dist/rules/routes.d.ts +11 -0
  267. package/dist/rules/routes.d.ts.map +1 -1
  268. package/dist/rules/routes.js +180 -2
  269. package/dist/rules/routes.js.map +1 -1
  270. package/dist/rules/secrets.d.ts +10 -0
  271. package/dist/rules/secrets.d.ts.map +1 -0
  272. package/dist/rules/secrets.js +169 -0
  273. package/dist/rules/secrets.js.map +1 -0
  274. package/dist/rules/security.d.ts +7 -0
  275. package/dist/rules/security.d.ts.map +1 -1
  276. package/dist/rules/security.js +171 -65
  277. package/dist/rules/security.js.map +1 -1
  278. package/dist/rules/typescript.d.ts +16 -0
  279. package/dist/rules/typescript.d.ts.map +1 -0
  280. package/dist/rules/typescript.js +59 -0
  281. package/dist/rules/typescript.js.map +1 -0
  282. package/dist/rules/zones.d.ts +17 -0
  283. package/dist/rules/zones.d.ts.map +1 -0
  284. package/dist/rules/zones.js +174 -0
  285. package/dist/rules/zones.js.map +1 -0
  286. package/dist/scaffold.d.ts +25 -12
  287. package/dist/scaffold.d.ts.map +1 -1
  288. package/dist/scaffold.js +154 -90
  289. package/dist/scaffold.js.map +1 -1
  290. package/dist/sdk.d.ts +17 -3
  291. package/dist/sdk.d.ts.map +1 -1
  292. package/dist/sdk.js +941 -153
  293. package/dist/sdk.js.map +1 -1
  294. package/dist/search-terms.d.ts +2 -0
  295. package/dist/search-terms.d.ts.map +1 -1
  296. package/dist/search-terms.js +23 -8
  297. package/dist/search-terms.js.map +1 -1
  298. package/dist/smoke.d.ts +79 -0
  299. package/dist/smoke.d.ts.map +1 -0
  300. package/dist/smoke.js +494 -0
  301. package/dist/smoke.js.map +1 -0
  302. package/dist/sync-manifest.d.ts.map +1 -1
  303. package/dist/sync-manifest.js +2 -1
  304. package/dist/sync-manifest.js.map +1 -1
  305. package/dist/sync-routes.d.ts +4 -3
  306. package/dist/sync-routes.d.ts.map +1 -1
  307. package/dist/sync-routes.js +4 -3
  308. package/dist/sync-routes.js.map +1 -1
  309. package/dist/test-tool.d.ts.map +1 -1
  310. package/dist/test-tool.js +30 -11
  311. package/dist/test-tool.js.map +1 -1
  312. package/dist/types-search.d.ts.map +1 -1
  313. package/dist/types-search.js +11 -5
  314. package/dist/types-search.js.map +1 -1
  315. package/dist/upgrade.d.ts +36 -6
  316. package/dist/upgrade.d.ts.map +1 -1
  317. package/dist/upgrade.js +167 -29
  318. package/dist/upgrade.js.map +1 -1
  319. package/dist/verification-plan.d.ts.map +1 -1
  320. package/dist/verification-plan.js +8 -0
  321. package/dist/verification-plan.js.map +1 -1
  322. package/dist/verification.d.ts.map +1 -1
  323. package/dist/verification.js +45 -12
  324. package/dist/verification.js.map +1 -1
  325. package/dist/web-app-options.d.ts +19 -0
  326. package/dist/web-app-options.d.ts.map +1 -0
  327. package/dist/web-app-options.js +49 -0
  328. package/dist/web-app-options.js.map +1 -0
  329. package/dist/work-graph.d.ts +3 -0
  330. package/dist/work-graph.d.ts.map +1 -1
  331. package/dist/work-graph.js +33 -16
  332. package/dist/work-graph.js.map +1 -1
  333. package/docs/examples.json +185 -29
  334. package/docs/llms-full.txt +4123 -548
  335. package/docs/types.json +14391 -10060
  336. package/package.json +19 -11
  337. package/src/app-files.ts +40 -0
  338. package/src/assure-hydration.ts +139 -46
  339. package/src/assure.ts +17 -2
  340. package/src/capabilities-tool.ts +35 -9
  341. package/src/cdn-check.ts +268 -0
  342. package/src/check-diagnostics.ts +199 -797
  343. package/src/check-scan.ts +394 -63
  344. package/src/check.ts +146 -58
  345. package/src/cli.ts +347 -92
  346. package/src/command-catalog.ts +407 -45
  347. package/src/config-target.ts +47 -0
  348. package/src/contracts.ts +66 -15
  349. package/src/db-audit.ts +128 -0
  350. package/src/db-child.ts +285 -0
  351. package/src/db-config.ts +224 -0
  352. package/src/db-tool.ts +690 -0
  353. package/src/dev-bun-config.ts +116 -40
  354. package/src/dev-feed-tool.ts +912 -0
  355. package/src/dev-server-client.ts +267 -0
  356. package/src/diagnostics.ts +179 -0
  357. package/src/diff-tool.ts +6 -5
  358. package/src/docs-search.ts +10 -3
  359. package/src/doctor.ts +98 -15
  360. package/src/env-file.ts +78 -1
  361. package/src/fix-recipes.ts +44 -31
  362. package/src/frontend-guidance.ts +5 -5
  363. package/src/i18n-check.ts +179 -0
  364. package/src/init-agents.ts +301 -38
  365. package/src/internal/code-unit-order.ts +5 -0
  366. package/src/internal/script-kind.ts +11 -0
  367. package/src/internal/source-facts.ts +96 -22
  368. package/src/internal/typescript-import.ts +663 -68
  369. package/src/introspect.ts +93 -82
  370. package/src/learn.ts +3 -3
  371. package/src/levels-tool.ts +47 -2
  372. package/src/load.ts +144 -32
  373. package/src/manifest-tool.ts +3 -2
  374. package/src/mcp-app-tool.ts +2 -0
  375. package/src/mcp-context.ts +5 -7
  376. package/src/mcp-delegate.ts +162 -0
  377. package/src/mcp-docs-tools.ts +23 -5
  378. package/src/mcp-exec.ts +301 -161
  379. package/src/mcp-http.ts +22 -5
  380. package/src/mcp-io.ts +130 -0
  381. package/src/mcp-isolate.ts +287 -0
  382. package/src/mcp-path.ts +50 -0
  383. package/src/mcp-project-child.ts +182 -0
  384. package/src/mcp-reflect.ts +3 -2
  385. package/src/mcp-render.ts +72 -42
  386. package/src/mcp-root.ts +130 -24
  387. package/src/mcp-run.ts +190 -56
  388. package/src/mcp-ws.ts +4 -1
  389. package/src/mcp.ts +193 -91
  390. package/src/migrate-layout.ts +1121 -0
  391. package/src/openapi-tool.ts +7 -3
  392. package/src/openapi-types.ts +315 -13
  393. package/src/pipeline-report.ts +22 -18
  394. package/src/port.ts +53 -30
  395. package/src/project-facts.ts +42 -8
  396. package/src/recipes/4.0.0.ts +35 -0
  397. package/src/recipes/index.ts +2 -0
  398. package/src/release-verification.ts +74 -5
  399. package/src/review.ts +988 -0
  400. package/src/route-types.ts +133 -0
  401. package/src/rules/codes.ts +9 -0
  402. package/src/rules/data-guard.ts +230 -0
  403. package/src/rules/index.ts +18 -5
  404. package/src/rules/islands.ts +12 -4
  405. package/src/rules/legacy.ts +947 -35
  406. package/src/rules/nano.ts +18 -6
  407. package/src/rules/pages.ts +99 -0
  408. package/src/rules/routes.ts +181 -2
  409. package/src/rules/secrets.ts +174 -0
  410. package/src/rules/security.ts +198 -70
  411. package/src/rules/typescript.ts +77 -0
  412. package/src/rules/zones.ts +195 -0
  413. package/src/scaffold.ts +175 -98
  414. package/src/sdk.ts +1215 -158
  415. package/src/search-terms.ts +24 -7
  416. package/src/smoke.ts +607 -0
  417. package/src/sync-manifest.ts +2 -1
  418. package/src/sync-routes.ts +4 -3
  419. package/src/test-tool.ts +32 -11
  420. package/src/types-search.ts +11 -4
  421. package/src/upgrade.ts +205 -25
  422. package/src/verification-plan.ts +27 -0
  423. package/src/verification.ts +50 -12
  424. package/src/web-app-options.ts +57 -0
  425. package/src/work-graph.ts +40 -16
@@ -13,14 +13,23 @@ import type {
13
13
  CapabilityExplainCommandResult,
14
14
  CapabilitySnapshotCommandResult,
15
15
  } from "./capabilities-tool.ts"
16
+ import { cdnCheckSpec } from "./cdn-check.ts"
16
17
  import { type CheckResult, renderCheckReport } from "./check.ts"
17
18
  import type { ContractsLock } from "./contracts.ts"
19
+ import { dbAuditSpec, dbQuerySpec, dbRoleSpec, dbSchemaSpec } from "./db-tool.ts"
20
+ import { errorsSpec, logsSpec } from "./dev-feed-tool.ts"
21
+ import { type Diagnostic, diagnostic, normalizeSeverity, toSarifLog } from "./diagnostics.ts"
18
22
  import type { DoctorResult } from "./doctor.ts"
23
+ import { type I18nCheckOutput, i18nCheckPassed, renderI18nCheck } from "./i18n-check.ts"
19
24
  import type { VerificationLevelsResult } from "./levels-tool.ts"
20
25
  import type { LoadedApp } from "./load.ts"
21
26
  import type { ManifestEmitCommandResult } from "./manifest-tool.ts"
27
+ import type { LayoutMigrationResult } from "./migrate-layout.ts"
22
28
  import { collectPortResult, type PortResult, renderReport } from "./port.ts"
23
29
  import type { ReplayResult } from "./replay.ts"
30
+ import { reviewSpec } from "./review.ts"
31
+ import type { RouteTypesReport } from "./route-types.ts"
32
+ import type { SmokeReport } from "./smoke.ts"
24
33
  import type { StylexMigrationResult } from "./stylex-migrate.ts"
25
34
  import {
26
35
  collectProjectWorkGraph,
@@ -77,9 +86,16 @@ export interface CommandSpec<Input, Output> {
77
86
  readonly transports: readonly CommandTransport[]
78
87
  readonly stability: CommandStability
79
88
  readonly argv?: CommandArgvBinding<Input>
89
+ /**
90
+ * Input fields only the CLI may set: MCP neither advertises nor accepts them. For a field that acts
91
+ * with the operator's own credentials, such as a signing key, which an agent must not invoke.
92
+ */
93
+ readonly cliOnlyFields?: readonly string[]
80
94
  readonly run: (input: Input, ctx: CommandCtx) => Promise<Output>
81
95
  readonly render: (out: Output, input?: Input) => readonly string[]
82
96
  readonly success?: (out: Output, input: Input) => boolean
97
+ /** Optional command-specific process status. Legacy commands retain the boolean fallback. */
98
+ readonly exitCode?: (out: Output, input: Input) => number
83
99
  /** JSON-compatible view. Defaults to the raw command result. */
84
100
  readonly json?: (out: Output, input: Input) => unknown
85
101
  }
@@ -91,6 +107,8 @@ export interface CommandCatalogEntry {
91
107
  readonly outputVersion: number
92
108
  readonly transports: readonly CommandTransport[]
93
109
  readonly stability: CommandStability
110
+ /** {@link CommandSpec.cliOnlyFields}: present in `inputSchema`, absent from the MCP tool's. */
111
+ readonly cliOnlyFields: readonly string[]
94
112
  }
95
113
 
96
114
  function freezeJson<T>(value: T): T {
@@ -112,6 +130,20 @@ export function toCommandCatalogEntry<Input, Output>(
112
130
  outputVersion: spec.output.version,
113
131
  transports: Object.freeze([...spec.transports]),
114
132
  stability: spec.stability,
133
+ cliOnlyFields: Object.freeze([...(spec.cliOnlyFields ?? [])]),
134
+ })
135
+ }
136
+
137
+ /** The input schema a command's MCP tool advertises: its catalog schema without CLI-only fields. */
138
+ export function commandMcpInputSchema(entry: CommandCatalogEntry): CommandJsonSchema {
139
+ const properties = entry.inputSchema.properties
140
+ if (entry.cliOnlyFields.length === 0 || typeof properties !== "object" || properties === null)
141
+ return entry.inputSchema
142
+ return freezeJson({
143
+ ...entry.inputSchema,
144
+ properties: Object.fromEntries(
145
+ Object.entries(properties).filter(([key]) => !entry.cliOnlyFields.includes(key)),
146
+ ),
115
147
  })
116
148
  }
117
149
 
@@ -240,6 +272,7 @@ function parseBooleanFlags(
240
272
  interface CheckInput {
241
273
  readonly lintsOnly?: boolean | undefined
242
274
  readonly json?: boolean | undefined
275
+ readonly sarif?: boolean | undefined
243
276
  readonly maxDiagnostics?: number | undefined
244
277
  readonly dir?: string | undefined
245
278
  }
@@ -319,14 +352,38 @@ interface FixInput {
319
352
  readonly dir?: string | undefined
320
353
  }
321
354
 
355
+ /** `migrate layout`, or `migrate --from tailwind --to stylex`; the parser admits only those two. */
322
356
  interface MigrateInput {
323
- readonly from: "tailwind"
324
- readonly to: "stylex"
357
+ readonly kind?: "layout" | undefined
358
+ readonly from?: "tailwind" | undefined
359
+ readonly to?: "stylex" | undefined
325
360
  readonly write?: boolean | undefined
326
361
  readonly json?: boolean | undefined
327
362
  readonly dir?: string | undefined
328
363
  }
329
364
 
365
+ interface TargetInput {
366
+ readonly target?: string | undefined
367
+ readonly json?: boolean | undefined
368
+ readonly dir?: string | undefined
369
+ }
370
+
371
+ interface TargetOutput {
372
+ readonly ok: boolean
373
+ readonly target: string
374
+ /** The target before this run, when it switched one; `undefined` when none was declared. */
375
+ readonly previous?: string | undefined
376
+ readonly changed: boolean
377
+ /** What `nifra build` now emits, and how it deploys. */
378
+ readonly run: string
379
+ }
380
+
381
+ interface TypesInput {
382
+ readonly check?: boolean | undefined
383
+ readonly json?: boolean | undefined
384
+ readonly dir?: string | undefined
385
+ }
386
+
330
387
  interface SnapshotInput {
331
388
  readonly out?: string | undefined
332
389
  readonly json?: boolean | undefined
@@ -371,6 +428,21 @@ interface PortInput {
371
428
  readonly dir?: string | undefined
372
429
  }
373
430
 
431
+ interface SmokeInput {
432
+ readonly fixture?: string | undefined
433
+ readonly inProcess?: boolean | undefined
434
+ readonly json?: boolean | undefined
435
+ readonly dir?: string | undefined
436
+ }
437
+
438
+ interface I18nInput {
439
+ readonly action: "check"
440
+ readonly entry?: string | undefined
441
+ readonly json?: boolean | undefined
442
+ readonly strict?: boolean | undefined
443
+ readonly dir?: string | undefined
444
+ }
445
+
374
446
  interface CheckCommandOutput extends CheckResult {}
375
447
  interface AssureCommandOutput {
376
448
  readonly report?: unknown
@@ -401,7 +473,9 @@ interface FixCommandOutput {
401
473
  readonly failed: readonly { readonly code: string; readonly reason: string }[]
402
474
  readonly diagnostics: readonly unknown[]
403
475
  }
404
- interface MigrateCommandOutput extends StylexMigrationResult {}
476
+ type MigrateCommandOutput =
477
+ | (StylexMigrationResult & { readonly kind?: undefined })
478
+ | (LayoutMigrationResult & { readonly kind: "layout" })
405
479
  interface SnapshotCommandOutput {
406
480
  readonly ok: true
407
481
  readonly file: string
@@ -425,10 +499,13 @@ interface SyncCommandOutput {
425
499
  readonly results: readonly unknown[]
426
500
  }
427
501
 
502
+ interface SmokeCommandOutput extends SmokeReport {}
503
+
428
504
  const CHECK_SCHEMA = input<CheckInput>(
429
505
  objectSchema({
430
506
  lintsOnly: { type: "boolean" },
431
507
  json: { type: "boolean" },
508
+ sarif: { type: "boolean" },
432
509
  maxDiagnostics: { type: "integer", minimum: 1 },
433
510
  dir: { type: "string" },
434
511
  }),
@@ -438,7 +515,7 @@ const CHECK_SCHEMA = input<CheckInput>(
438
515
  if (maxDiagnostics !== undefined && (!Number.isInteger(maxDiagnostics) || maxDiagnostics < 1))
439
516
  throw new TypeError("maxDiagnostics must be a positive integer")
440
517
  return {
441
- ...parseBooleanFlags(raw, ["lintsOnly", "json"]),
518
+ ...parseBooleanFlags(raw, ["lintsOnly", "json", "sarif"]),
442
519
  ...(maxDiagnostics === undefined ? {} : { maxDiagnostics }),
443
520
  ...(raw.dir === undefined ? {} : { dir: raw.dir }),
444
521
  } as CheckInput
@@ -642,26 +719,53 @@ const FIX_SCHEMA = input<FixInput>(
642
719
  )
643
720
 
644
721
  const MIGRATE_SCHEMA = input<MigrateInput>(
645
- objectSchema(
646
- {
647
- from: { type: "string", enum: ["tailwind"] },
648
- to: { type: "string", enum: ["stylex"] },
649
- write: { type: "boolean" },
650
- json: { type: "boolean" },
651
- dir: { type: "string" },
652
- },
653
- ["from", "to"],
654
- ),
722
+ objectSchema({
723
+ kind: { type: "string", enum: ["layout"] },
724
+ from: { type: "string", enum: ["tailwind"] },
725
+ to: { type: "string", enum: ["stylex"] },
726
+ write: { type: "boolean" },
727
+ json: { type: "boolean" },
728
+ dir: { type: "string" },
729
+ }),
655
730
  (value) => {
656
731
  const raw = withDir(record(value))
732
+ const flags = {
733
+ ...parseBooleanFlags(raw, ["write", "json"]),
734
+ ...(raw.dir === undefined ? {} : { dir: raw.dir }),
735
+ }
736
+ const kind = optionalString(raw.kind, "kind")
737
+ if (kind !== undefined) {
738
+ if (kind !== "layout")
739
+ throw new TypeError("the migration must be `layout` (or --from tailwind --to stylex)")
740
+ return { kind, ...flags }
741
+ }
657
742
  const from = optionalString(raw.from, "from")
658
743
  const to = optionalString(raw.to, "to")
659
744
  if (from !== "tailwind") throw new TypeError("from must be tailwind")
660
745
  if (to !== "stylex") throw new TypeError("to must be stylex")
746
+ return { from, to, ...flags }
747
+ },
748
+ )
749
+
750
+ const TARGET_SCHEMA = input<TargetInput>(
751
+ objectSchema({ target: { type: "string" }, json: { type: "boolean" }, dir: { type: "string" } }),
752
+ (value) => {
753
+ const raw = withDir(record(value))
754
+ const target = optionalString(raw.target, "target")
661
755
  return {
662
- from,
663
- to,
664
- ...parseBooleanFlags(raw, ["write", "json"]),
756
+ ...parseBooleanFlags(raw, ["json"]),
757
+ ...(raw.dir === undefined ? {} : { dir: raw.dir }),
758
+ ...(target === undefined ? {} : { target }),
759
+ }
760
+ },
761
+ )
762
+
763
+ const TYPES_SCHEMA = input<TypesInput>(
764
+ objectSchema({ check: { type: "boolean" }, json: { type: "boolean" }, dir: { type: "string" } }),
765
+ (value) => {
766
+ const raw = withDir(record(value))
767
+ return {
768
+ ...parseBooleanFlags(raw, ["check", "json"]),
665
769
  ...(raw.dir === undefined ? {} : { dir: raw.dir }),
666
770
  }
667
771
  },
@@ -784,15 +888,76 @@ const PORT_SCHEMA = input<PortInput>(
784
888
  },
785
889
  )
786
890
 
891
+ const SMOKE_SCHEMA = input<SmokeInput>(
892
+ objectSchema({
893
+ fixture: { type: "string" },
894
+ inProcess: { type: "boolean" },
895
+ json: { type: "boolean" },
896
+ dir: { type: "string" },
897
+ }),
898
+ (value) => {
899
+ const raw = withDir(record(value))
900
+ return {
901
+ fixture: optionalString(raw.fixture, "fixture"),
902
+ ...parseBooleanFlags(raw, ["inProcess", "json"]),
903
+ ...(raw.dir === undefined ? {} : { dir: raw.dir }),
904
+ }
905
+ },
906
+ )
907
+
908
+ const I18N_SCHEMA = input<I18nInput>(
909
+ objectSchema(
910
+ {
911
+ action: { type: "string", enum: ["check"] },
912
+ entry: { type: "string" },
913
+ json: { type: "boolean" },
914
+ strict: { type: "boolean" },
915
+ dir: { type: "string" },
916
+ },
917
+ ["action"],
918
+ ),
919
+ (value) => {
920
+ const raw = withDir(record(value))
921
+ if (raw.action !== "check") {
922
+ throw new TypeError(
923
+ raw.action === undefined
924
+ ? "i18n needs an action: nifra i18n check [entry]"
925
+ : `unknown i18n action: ${String(raw.action)} (the one action is check)`,
926
+ )
927
+ }
928
+ return {
929
+ action: "check",
930
+ entry: optionalString(raw.entry, "entry"),
931
+ ...parseBooleanFlags(raw, ["json", "strict"]),
932
+ ...(raw.dir === undefined ? {} : { dir: raw.dir }),
933
+ }
934
+ },
935
+ )
936
+
787
937
  function loadAppFor(ctx: CommandCtx): Promise<LoadedApp> {
788
938
  if (ctx.loadApp !== undefined) return ctx.loadApp()
789
939
  return import("./load.ts").then(({ loadApp }) => loadApp(ctx.cwd))
790
940
  }
791
941
 
792
- function checkJson(out: CheckCommandOutput): unknown {
793
- return out.structuredDiagnostics === undefined
794
- ? out
795
- : { ...out, diagnostics: out.structuredDiagnostics }
942
+ function structuredDiagnostics(out: CheckCommandOutput): readonly Diagnostic[] {
943
+ if (out.structuredDiagnostics !== undefined) return out.structuredDiagnostics
944
+ return out.diagnostics.map((value) =>
945
+ diagnostic({
946
+ code: value.code ?? value.rule,
947
+ severity: normalizeSeverity(value.severity),
948
+ message: value.message,
949
+ ...(value.file === undefined ? {} : { file: value.file }),
950
+ ...(value.line === undefined ? {} : { line: value.line }),
951
+ ...(value.evidence === undefined ? {} : { evidence: value.evidence }),
952
+ ...(value.fix === undefined ? {} : { fix: { recipe: value.fix } }),
953
+ ...(value.verify === undefined ? {} : { verify: value.verify }),
954
+ }),
955
+ )
956
+ }
957
+
958
+ function checkJson(out: CheckCommandOutput, input?: CheckInput): unknown {
959
+ if (input?.sarif === true) return toSarifLog(structuredDiagnostics(out))
960
+ return { ...out, diagnostics: structuredDiagnostics(out) }
796
961
  }
797
962
 
798
963
  const checkSpec: CommandSpec<CheckInput, CheckCommandOutput> = {
@@ -810,6 +975,7 @@ const checkSpec: CommandSpec<CheckInput, CheckCommandOutput> = {
810
975
  flags: [
811
976
  { name: "lints-only", field: "lintsOnly", type: "boolean" },
812
977
  { name: "json", field: "json", type: "boolean" },
978
+ { name: "sarif", field: "sarif", type: "boolean" },
813
979
  ],
814
980
  },
815
981
  async run(value, ctx) {
@@ -822,9 +988,12 @@ const checkSpec: CommandSpec<CheckInput, CheckCommandOutput> = {
822
988
  })
823
989
  ).check()
824
990
  },
825
- render: (out) => renderCheckReport(out),
991
+ render: (out, input) =>
992
+ input?.sarif === true
993
+ ? JSON.stringify(checkJson(out, input), null, 2).split("\n")
994
+ : renderCheckReport(out),
826
995
  success: (out) => out.ok,
827
- json: (out) => checkJson(out),
996
+ json: (out, input) => checkJson(out, input),
828
997
  }
829
998
 
830
999
  const assureSpec: CommandSpec<AssureInput, AssureCommandOutput> = {
@@ -970,6 +1139,8 @@ const manifestSpec: CommandSpec<ManifestInput, ManifestEmitCommandResult | DiffC
970
1139
  output: output({ type: "object" }),
971
1140
  transports: ["cli", "mcp"],
972
1141
  stability: "stable",
1142
+ // The signer is the operator's KMS/HSM callback; an agent signing its own manifest defeats it.
1143
+ cliOnlyFields: ["sign"],
973
1144
  argv: {
974
1145
  positionals: ["action", "before", "after"],
975
1146
  flags: [
@@ -1111,7 +1282,10 @@ const openApiSpec: CommandSpec<OpenApiInput, OpenApiCommandOutput> = {
1111
1282
  ],
1112
1283
  },
1113
1284
  async run(value, ctx) {
1114
- const app = await loadAppFor(ctx)
1285
+ const app =
1286
+ ctx.loadApp !== undefined
1287
+ ? await ctx.loadApp()
1288
+ : await import("./load.ts").then(({ loadBackendApp }) => loadBackendApp(ctx.cwd))
1115
1289
  const { renderOpenApiWithTypes } = await import("./openapi-tool.ts")
1116
1290
  const format = value.format ?? "json"
1117
1291
  return {
@@ -1227,12 +1401,14 @@ const fixSpec: CommandSpec<FixInput, FixCommandOutput> = {
1227
1401
 
1228
1402
  const migrateSpec: CommandSpec<MigrateInput, MigrateCommandOutput> = {
1229
1403
  name: "migrate",
1230
- summary: "Migrate safe static Tailwind className utilities to native StyleX props.",
1404
+ summary:
1405
+ "Move an app onto the frontend/backend split (`migrate layout`), or migrate static Tailwind utilities to StyleX.",
1231
1406
  input: MIGRATE_SCHEMA,
1232
1407
  output: output({
1233
1408
  type: "object",
1234
1409
  properties: {
1235
1410
  ok: { type: "boolean" },
1411
+ kind: { type: "string" },
1236
1412
  from: { type: "string" },
1237
1413
  to: { type: "string" },
1238
1414
  write: { type: "boolean" },
@@ -1241,12 +1417,16 @@ const migrateSpec: CommandSpec<MigrateInput, MigrateCommandOutput> = {
1241
1417
  written: { type: "array" },
1242
1418
  issues: { type: "array" },
1243
1419
  files: { type: "array" },
1420
+ splits: { type: "array" },
1421
+ moves: { type: "array" },
1422
+ rewritten: { type: "array" },
1244
1423
  },
1245
- required: ["ok", "from", "to", "write", "scanned", "changed", "written", "issues", "files"],
1424
+ required: ["ok", "write", "issues"],
1246
1425
  }),
1247
1426
  transports: ["cli"],
1248
1427
  stability: "stable",
1249
1428
  argv: {
1429
+ positionals: ["kind"],
1250
1430
  flags: [
1251
1431
  { name: "from", field: "from", type: "string" },
1252
1432
  { name: "to", field: "to", type: "string" },
@@ -1256,27 +1436,142 @@ const migrateSpec: CommandSpec<MigrateInput, MigrateCommandOutput> = {
1256
1436
  ],
1257
1437
  },
1258
1438
  async run(value, ctx) {
1439
+ const dir = resolve(ctx.cwd, value.dir ?? ".")
1440
+ const write = value.write === undefined ? {} : { write: value.write }
1441
+ if (value.kind === "layout") {
1442
+ const { migrateLayout } = await import("./migrate-layout.ts")
1443
+ return { kind: "layout" as const, ...(await migrateLayout(dir, write)) }
1444
+ }
1259
1445
  const { migrateTailwindToStylex } = await import("./stylex-migrate.ts")
1260
- return migrateTailwindToStylex(resolve(ctx.cwd, value.dir ?? "."), {
1261
- ...(value.write === undefined ? {} : { write: value.write }),
1262
- })
1446
+ return migrateTailwindToStylex(dir, write)
1263
1447
  },
1264
- render: (out) => [
1265
- out.ok
1266
- ? `✓ migrated ${out.changed.length} file${out.changed.length === 1 ? "" : "s"}`
1267
- : `⚠ migrated ${out.changed.length} safe file${out.changed.length === 1 ? "" : "s"}; ${out.issues.length} manual issue${out.issues.length === 1 ? "" : "s"} remain`,
1268
- ` scanned ${out.scanned} source file${out.scanned === 1 ? "" : "s"}`,
1269
- ...(out.write
1270
- ? [` wrote ${out.written.length} file${out.written.length === 1 ? "" : "s"}`]
1271
- : out.changed.length > 0
1272
- ? [" dry run: pass --write to apply the safe changes"]
1448
+ render: (out) => {
1449
+ if (out.kind === "layout") {
1450
+ return [
1451
+ out.ok
1452
+ ? `✓ ${out.splits.length} route file${out.splits.length === 1 ? "" : "s"} split, ${out.moves.length} file${out.moves.length === 1 ? "" : "s"} moved`
1453
+ : `⚠ ${out.issues.length} issue${out.issues.length === 1 ? "" : "s"} need a decision; everything else is planned`,
1454
+ ...out.splits.map(
1455
+ (split) => ` split ${split.file} → ${split.backend} (${split.moved.join(", ")})`,
1456
+ ),
1457
+ ...out.moves.map((move) => ` move ${move.from} → ${move.to}`),
1458
+ ...out.rewritten.map((file) => ` edit ${file} (imports)`),
1459
+ ...out.issues.map((issue) => ` ⚠ ${issue.file}: ${issue.reason}`),
1460
+ ...(out.write ? [] : [" dry run: pass --write to apply"]),
1461
+ ]
1462
+ }
1463
+ return [
1464
+ out.ok
1465
+ ? `✓ migrated ${out.changed.length} file${out.changed.length === 1 ? "" : "s"}`
1466
+ : `⚠ migrated ${out.changed.length} safe file${out.changed.length === 1 ? "" : "s"}; ${out.issues.length} manual issue${out.issues.length === 1 ? "" : "s"} remain`,
1467
+ ` scanned ${out.scanned} source file${out.scanned === 1 ? "" : "s"}`,
1468
+ ...(out.write
1469
+ ? [` wrote ${out.written.length} file${out.written.length === 1 ? "" : "s"}`]
1470
+ : out.changed.length > 0
1471
+ ? [" dry run: pass --write to apply the safe changes"]
1472
+ : []),
1473
+ ...out.issues
1474
+ .slice(0, 30)
1475
+ .map((issue) => ` ${issue.file}:${issue.line} ${issue.token} — ${issue.reason}`),
1476
+ ...(out.issues.length > 30
1477
+ ? [` … ${out.issues.length - 30} more issue${out.issues.length - 30 === 1 ? "" : "s"}`]
1273
1478
  : []),
1274
- ...out.issues
1275
- .slice(0, 30)
1276
- .map((issue) => ` ${issue.file}:${issue.line} ${issue.token} — ${issue.reason}`),
1277
- ...(out.issues.length > 30
1278
- ? [` … ${out.issues.length - 30} more issue${out.issues.length - 30 === 1 ? "" : "s"}`]
1279
- : []),
1479
+ ]
1480
+ },
1481
+ success: (out) => out.ok,
1482
+ }
1483
+
1484
+ const targetSpec: CommandSpec<TargetInput, TargetOutput> = {
1485
+ name: "target",
1486
+ summary:
1487
+ "Show the app's deploy target, or switch it (`nifra target cloudflare`): the `target` `nifra build` emits, kept in nifra.config.ts.",
1488
+ input: TARGET_SCHEMA,
1489
+ output: output({
1490
+ type: "object",
1491
+ properties: {
1492
+ ok: { type: "boolean" },
1493
+ target: { type: "string" },
1494
+ previous: { type: "string" },
1495
+ changed: { type: "boolean" },
1496
+ run: { type: "string" },
1497
+ },
1498
+ required: ["ok", "target", "changed", "run"],
1499
+ }),
1500
+ transports: ["cli"],
1501
+ stability: "stable",
1502
+ argv: {
1503
+ positionals: ["target"],
1504
+ flags: [
1505
+ { name: "json", field: "json", type: "boolean" },
1506
+ { name: "dir", field: "dir", type: "string" },
1507
+ ],
1508
+ },
1509
+ async run(value, ctx) {
1510
+ const dir = resolve(ctx.cwd, value.dir ?? ".")
1511
+ const { parseBuildTarget, planBuildTarget } = await import("@nifrajs/web/build")
1512
+ const { readConfigTarget, writeConfigTarget } = await import("./config-target.ts")
1513
+ const current = readConfigTarget(dir)
1514
+ const target = parseBuildTarget(
1515
+ value.target ?? current ?? "bun",
1516
+ value.target === undefined ? "target in nifra.config.ts" : "target",
1517
+ )
1518
+ const changed = value.target !== undefined && value.target !== current
1519
+ if (changed) writeConfigTarget(dir, target)
1520
+ return {
1521
+ ok: true,
1522
+ target,
1523
+ ...(changed && current !== undefined ? { previous: current } : {}),
1524
+ changed,
1525
+ run: planBuildTarget(target, "dist").run,
1526
+ }
1527
+ },
1528
+ render: (out) => [
1529
+ out.changed
1530
+ ? `✓ target: ${out.target}${out.previous === undefined ? "" : ` (was ${out.previous})`}`
1531
+ : `target: ${out.target}`,
1532
+ ` nifra build → ${out.run}`,
1533
+ ],
1534
+ success: (out) => out.ok,
1535
+ }
1536
+
1537
+ const typesSpec: CommandSpec<TypesInput, RouteTypesReport> = {
1538
+ name: "types",
1539
+ summary:
1540
+ "Generate each route's `./+types` module (params, schema-typed data, typed `api`); `--check` fails when one is stale.",
1541
+ input: TYPES_SCHEMA,
1542
+ output: output({
1543
+ type: "object",
1544
+ properties: {
1545
+ ok: { type: "boolean" },
1546
+ check: { type: "boolean" },
1547
+ written: { type: "array" },
1548
+ removed: { type: "array" },
1549
+ stale: { type: "array" },
1550
+ tsconfig: { type: "string" },
1551
+ },
1552
+ required: ["ok", "check", "written", "removed", "stale"],
1553
+ }),
1554
+ transports: ["cli"],
1555
+ stability: "stable",
1556
+ argv: {
1557
+ flags: [
1558
+ { name: "check", field: "check", type: "boolean" },
1559
+ { name: "json", field: "json", type: "boolean" },
1560
+ { name: "dir", field: "dir", type: "string" },
1561
+ ],
1562
+ },
1563
+ async run(value, ctx) {
1564
+ const { routeTypes } = await import("./route-types.ts")
1565
+ return routeTypes(resolve(ctx.cwd, value.dir ?? "."), { check: value.check === true })
1566
+ },
1567
+ render: (out) => [
1568
+ out.check
1569
+ ? out.ok
1570
+ ? "✓ route types are up to date"
1571
+ : `✖ ${out.stale.length} route type file${out.stale.length === 1 ? " is" : "s are"} stale - run \`nifra types\``
1572
+ : `✓ route types: ${out.written.length} written, ${out.removed.length} removed`,
1573
+ ...out.stale.map((file) => ` stale ${file}`),
1574
+ ...(out.tsconfig === undefined ? [] : [` ⚠ ${out.tsconfig}`]),
1280
1575
  ],
1281
1576
  success: (out) => out.ok,
1282
1577
  }
@@ -1468,6 +1763,34 @@ const replaySpec: CommandSpec<ReplayInput, ReplayResult> = {
1468
1763
  success: (out) => out.ok,
1469
1764
  }
1470
1765
 
1766
+ const smokeSpec: CommandSpec<SmokeInput, SmokeCommandOutput> = {
1767
+ name: "smoke",
1768
+ summary:
1769
+ "Run the declared production SSR, mounted-API, 404, header, auth, contract, and hydration smoke checks.",
1770
+ input: SMOKE_SCHEMA,
1771
+ output: output({ type: "object" }),
1772
+ transports: ["cli", "mcp"],
1773
+ stability: "stable",
1774
+ argv: {
1775
+ flags: [
1776
+ { name: "fixture", field: "fixture", type: "string" },
1777
+ { name: "in-process", field: "inProcess", type: "boolean" },
1778
+ { name: "json", field: "json", type: "boolean" },
1779
+ ],
1780
+ },
1781
+ async run(value, ctx) {
1782
+ const { loadSmokeFixture, runSmoke } = await import("./smoke.ts")
1783
+ const fixture = await loadSmokeFixture(ctx.cwd, value.fixture)
1784
+ return runSmoke(fixture, { inProcess: value.inProcess === true })
1785
+ },
1786
+ render: (out) =>
1787
+ out.checks.map(
1788
+ (check) =>
1789
+ `${check.status === "pass" ? "✓" : check.status === "skip" ? "•" : "✖"} ${check.id}${check.message === undefined ? "" : ` - ${check.message}`}`,
1790
+ ),
1791
+ success: (out) => out.ok,
1792
+ }
1793
+
1471
1794
  const portSpec: CommandSpec<PortInput, PortResult> = {
1472
1795
  name: "port",
1473
1796
  summary:
@@ -1497,7 +1820,7 @@ const portSpec: CommandSpec<PortInput, PortResult> = {
1497
1820
  return [
1498
1821
  report,
1499
1822
  "",
1500
- "[nifra] --ci needs a deploy target to gate against, and none was detected. Pass --target <bun|node|deno|cf-pages|vercel>.",
1823
+ "[nifra] --ci needs a deploy target to gate against, and none was detected. Pass --target <bun|node|deno|cloudflare|vercel>.",
1501
1824
  ]
1502
1825
  return [report]
1503
1826
  },
@@ -1510,8 +1833,36 @@ const portSpec: CommandSpec<PortInput, PortResult> = {
1510
1833
  json: (out) => out.json,
1511
1834
  }
1512
1835
 
1836
+ // CLI only: it imports app code (the catalogs), which a long-lived MCP server process would cache.
1837
+ const i18nSpec: CommandSpec<I18nInput, I18nCheckOutput> = {
1838
+ name: "i18n",
1839
+ summary:
1840
+ "Check i18n catalogs (imports the module exporting `locales` and `catalogs`): coverage, missing and unused keys, ICU syntax, placeholder and tag parity, plural cases, script purity, untranslated messages.",
1841
+ input: I18N_SCHEMA,
1842
+ output: output({ type: "object" }),
1843
+ transports: ["cli"],
1844
+ stability: "stable",
1845
+ argv: {
1846
+ positionals: ["action", "entry"],
1847
+ flags: [
1848
+ { name: "json", field: "json", type: "boolean" },
1849
+ { name: "strict", field: "strict", type: "boolean" },
1850
+ ],
1851
+ },
1852
+ async run(value, ctx) {
1853
+ const { runI18nCheck } = await import("./i18n-check.ts")
1854
+ return runI18nCheck(value.dir === undefined ? ctx.cwd : resolve(ctx.cwd, value.dir), {
1855
+ entry: value.entry,
1856
+ })
1857
+ },
1858
+ render: (out) => renderI18nCheck(out),
1859
+ success: (out, input) => i18nCheckPassed(out, input.strict === true),
1860
+ json: (out) => out,
1861
+ }
1862
+
1513
1863
  export const commandSpecs = Object.freeze([
1514
1864
  checkSpec,
1865
+ reviewSpec,
1515
1866
  assureSpec,
1516
1867
  levelsSpec,
1517
1868
  capabilitiesSpec,
@@ -1522,6 +1873,8 @@ export const commandSpecs = Object.freeze([
1522
1873
  doctorSpec,
1523
1874
  fixSpec,
1524
1875
  migrateSpec,
1876
+ targetSpec,
1877
+ typesSpec,
1525
1878
  snapshotSpec,
1526
1879
  diffSpec,
1527
1880
  contractsSpec,
@@ -1529,7 +1882,16 @@ export const commandSpecs = Object.freeze([
1529
1882
  syncRoutesSpec,
1530
1883
  proveSpec,
1531
1884
  replaySpec,
1885
+ smokeSpec,
1532
1886
  portSpec,
1887
+ i18nSpec,
1888
+ errorsSpec,
1889
+ logsSpec,
1890
+ cdnCheckSpec,
1891
+ dbSchemaSpec,
1892
+ dbQuerySpec,
1893
+ dbRoleSpec,
1894
+ dbAuditSpec,
1533
1895
  ] as const)
1534
1896
 
1535
1897
  const commandByName = new Map(commandSpecs.map((spec) => [spec.name, spec]))