@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
package/src/check.ts CHANGED
@@ -17,7 +17,8 @@
17
17
  */
18
18
 
19
19
  import { existsSync, readFileSync } from "node:fs"
20
- import { dirname, isAbsolute, join } from "node:path"
20
+ import { dirname, isAbsolute, join, relative } from "node:path"
21
+ import { BACKEND_APP_FILE } from "./app-files.ts"
21
22
  import {
22
23
  type CheckAssuranceContext,
23
24
  type CheckConfig,
@@ -26,11 +27,13 @@ import {
26
27
  collectCheckDiagnostics,
27
28
  type RuleOverride,
28
29
  } from "./check-diagnostics.ts"
30
+ import { checkContractsLock, DEFAULT_CONTRACTS_LOCK } from "./contracts.ts"
31
+ import { type Diagnostic, normalizeSeverity, toSarifLog } from "./diagnostics.ts"
29
32
  import { createSourceFacts } from "./internal/source-facts.ts"
30
- import { importProjectTypeScript, type TypeScriptApi } from "./internal/typescript-import.ts"
33
+ import { loadProjectTypeScript, type TypeScriptApi } from "./internal/typescript-import.ts"
31
34
  // Type-only: `pipeline-report.ts` imports this module's source scanners, so a value import here would
32
35
  // close a cycle. Doctor is what actually runs the collector (see the `pipeline` rule below).
33
- import type { ProjectFactsSeed } from "./project-facts.ts"
36
+ import type { ContractCheckFacts, ProjectFactsSeed } from "./project-facts.ts"
34
37
  import { RULE_CODES } from "./rules/codes.ts"
35
38
  import { sourceIndex } from "./rules/index.ts"
36
39
 
@@ -48,6 +51,7 @@ export type {
48
51
 
49
52
  import {
50
53
  createProjectSqlImports,
54
+ isBrowserSource,
51
55
  type ModuleReader,
52
56
  type ModuleResolver,
53
57
  resolveServerOnlyChains,
@@ -59,6 +63,7 @@ import {
59
63
  scanResponseRoutes,
60
64
  scanServerManifestDrift,
61
65
  scanStaticRouteText,
66
+ scanStreamText,
62
67
  scanUntypedClient,
63
68
  type TransitiveServerImportFinding,
64
69
  walkSource,
@@ -66,7 +71,6 @@ import {
66
71
 
67
72
  export * from "./check-scan.ts"
68
73
 
69
- const ROUTE_FILE = /(^|\/)routes\//
70
74
  interface TypecheckResult {
71
75
  readonly ran: boolean
72
76
  readonly ok: boolean
@@ -101,6 +105,9 @@ async function typecheck(cwd: string, signal?: AbortSignal): Promise<TypecheckRe
101
105
  const tsconfig = join(cwd, "tsconfig.json")
102
106
  if (!(await Bun.file(tsconfig).exists()))
103
107
  return { ran: false, ok: true, note: "no tsconfig.json" }
108
+ // Routes import their generated `./+types` modules; bring them up to date before tsc reads them.
109
+ const { refreshRouteTypes } = await import("./route-types.ts")
110
+ refreshRouteTypes(cwd, () => {})
104
111
  const tscBin = resolveTscBin(cwd)
105
112
  if (tscBin === undefined) {
106
113
  return {
@@ -111,7 +118,7 @@ async function typecheck(cwd: string, signal?: AbortSignal): Promise<TypecheckRe
111
118
  }
112
119
  }
113
120
  if (signal?.aborted) return { ran: true, ok: false, cancelled: true, output: "cancelled" }
114
- const proc = Bun.spawn(["bun", tscBin, "--noEmit", "-p", tsconfig], {
121
+ const proc = Bun.spawn([process.execPath, tscBin, "--noEmit", "-p", tsconfig], {
115
122
  cwd,
116
123
  stdout: "pipe",
117
124
  stderr: "pipe",
@@ -230,10 +237,30 @@ interface CheckCollectionOptions {
230
237
 
231
238
  interface ProjectScan {
232
239
  readonly facts: ProjectFactsSeed
233
- readonly typecheck: TypecheckResult
234
- readonly sqlCompiler: TypeScriptApi | undefined
235
- readonly checkConfigError?: string
236
- readonly checkConfigWarnings: readonly string[]
240
+ }
241
+
242
+ async function collectContractFacts(cwd: string): Promise<ContractCheckFacts> {
243
+ const hasBackend = existsSync(join(cwd, BACKEND_APP_FILE))
244
+ const hasLock = existsSync(join(cwd, DEFAULT_CONTRACTS_LOCK))
245
+ if (!hasBackend && !hasLock) return { present: false, vacuous: false, diagnostics: [] }
246
+ try {
247
+ const result = await checkContractsLock(cwd)
248
+ return {
249
+ present: result.present,
250
+ vacuous: result.vacuous,
251
+ diagnostics: result.diagnostics,
252
+ }
253
+ } catch (error) {
254
+ if (error instanceof Error && error.message === "contract source not configured") {
255
+ return { present: false, vacuous: false, diagnostics: [] }
256
+ }
257
+ return {
258
+ present: hasLock,
259
+ vacuous: false,
260
+ diagnostics: [],
261
+ error: error instanceof Error ? error.message : String(error),
262
+ }
263
+ }
237
264
  }
238
265
 
239
266
  /** Build the rule input from one source walk and the other check-wide scans. */
@@ -242,79 +269,111 @@ async function buildProjectScan(
242
269
  opts: Pick<CheckCollectionOptions, "lintsOnly" | "signal" | "loadTypeScript">,
243
270
  ): Promise<ProjectScan> {
244
271
  const fetches: SourceFinding[] = []
272
+ const streams: SourceFinding[] = []
245
273
  const staticRoutes: StaticRouteFinding[] = []
246
274
  const untypedClients: SourceFinding[] = []
247
275
  const removedImports: SourceFinding[] = []
248
276
  const serverImports: TransitiveServerImportFinding[] = []
249
277
  const responseRoutes: SourceFinding[] = []
250
278
  const interpolatedSql: SourceFinding[] = []
251
- const routeModules: Array<{ rel: string; content: string }> = []
279
+ const browserModules: Array<{ rel: string; content: string }> = []
252
280
  const sourceFiles: Array<{ file: string; content: string }> = []
253
281
  const {
254
282
  config: checkConfig,
255
283
  error: checkConfigError,
256
284
  warnings: checkConfigWarnings,
257
285
  } = await loadCheckConfig(cwd)
258
- const sqlCompiler = await (opts.loadTypeScript ?? (() => importProjectTypeScript(cwd)))()
259
- const sourceFacts = sqlCompiler === undefined ? undefined : createSourceFacts(sqlCompiler)
260
- const sqlImports = sqlCompiler === undefined ? undefined : createProjectSqlImports(cwd)
261
-
262
- const [typecheckResult, _, doctor, manifestDrift] = await Promise.all([
286
+ const [typecheckResult, _, doctor, manifestDrift, contracts] = await Promise.all([
263
287
  opts.lintsOnly
264
288
  ? Promise.resolve<TypecheckResult>({ ran: false, ok: true, note: "lints-only mode" })
265
289
  : typecheck(cwd, opts.signal),
266
290
  walkSource(cwd, (rel, content) => {
267
291
  sourceFiles.push({ file: rel, content })
268
292
  fetches.push(...scanFetchText(rel, content, checkConfig.externalMounts))
269
- staticRoutes.push(...scanStaticRouteText(rel, content, sourceFacts))
293
+ streams.push(...scanStreamText(rel, content, checkConfig.externalMounts))
270
294
  untypedClients.push(...scanUntypedClient(rel, content))
271
295
  removedImports.push(...scanRemovedImports(rel, content))
272
- if (ROUTE_FILE.test(rel)) routeModules.push({ rel, content })
273
- responseRoutes.push(...scanResponseRoutes(rel, content, sourceFacts))
274
- if (sqlCompiler !== undefined)
275
- interpolatedSql.push(...scanInterpolatedSql(rel, content, sqlCompiler, sqlImports))
296
+ if (isBrowserSource(rel)) browserModules.push({ rel, content })
276
297
  }),
277
298
  import("./doctor.ts").then((m) => m.collectDoctorResult(cwd)),
278
299
  scanServerManifestDrift(cwd),
300
+ collectContractFacts(cwd),
279
301
  ])
280
302
 
281
- const resolveModule: ModuleResolver = (fromFile, specifier) => {
282
- try {
283
- const fromAbs = isAbsolute(fromFile) ? fromFile : join(cwd, fromFile)
284
- return Bun.resolveSync(specifier, dirname(fromAbs))
285
- } catch {
286
- return undefined
303
+ // TypeScript 7's unstable API materializes source files through one project session. Build the
304
+ // source index first so every syntax-backed scanner (including the independent rule registry)
305
+ // shares that session instead of spawning one compiler process per rule.
306
+ const source = sourceIndex(sourceFiles)
307
+ const loadedTypeScript = await loadProjectTypeScript(cwd, opts.loadTypeScript, source)
308
+ try {
309
+ const sqlCompiler = loadedTypeScript.compiler
310
+ const sourceFacts = sqlCompiler === undefined ? undefined : createSourceFacts(sqlCompiler)
311
+ const sqlImports = sqlCompiler === undefined ? undefined : createProjectSqlImports(cwd)
312
+ for (const { file, content } of sourceFiles) {
313
+ staticRoutes.push(...scanStaticRouteText(file, content, sourceFacts))
314
+ responseRoutes.push(...scanResponseRoutes(file, content, sourceFacts))
315
+ if (sqlCompiler !== undefined)
316
+ interpolatedSql.push(...scanInterpolatedSql(file, content, sqlCompiler, sqlImports))
287
317
  }
288
- }
289
- const readModule: ModuleReader = (absPath) => {
290
- try {
291
- return readFileSync(absPath, "utf8")
292
- } catch {
293
- return undefined
318
+
319
+ // Resolved modules stay project-relative, as the scanned files are: the zone of a path is read
320
+ // from its folders, and a checkout under a folder named `backend/` must not make it all backend.
321
+ const resolveModule: ModuleResolver = (fromFile, specifier) => {
322
+ try {
323
+ const fromAbs = isAbsolute(fromFile) ? fromFile : join(cwd, fromFile)
324
+ const resolved = Bun.resolveSync(specifier, dirname(fromAbs))
325
+ const rel = relative(cwd, resolved).replaceAll("\\", "/")
326
+ return rel.startsWith("../") || isAbsolute(rel) ? resolved : rel
327
+ } catch {
328
+ return undefined
329
+ }
330
+ }
331
+ const readModule: ModuleReader = (path) => {
332
+ try {
333
+ return readFileSync(isAbsolute(path) ? path : join(cwd, path), "utf8")
334
+ } catch {
335
+ return undefined
336
+ }
337
+ }
338
+ for (const { rel, content } of browserModules) {
339
+ serverImports.push(
340
+ ...resolveServerOnlyChains(rel, content, resolveModule, readModule, sourceFacts),
341
+ )
294
342
  }
295
- }
296
- for (const { rel, content } of routeModules) {
297
- serverImports.push(
298
- ...resolveServerOnlyChains(rel, content, resolveModule, readModule, sourceFacts),
299
- )
300
- }
301
343
 
302
- const source = sourceIndex(sourceFiles)
303
- const facts: ProjectFactsSeed = {
304
- source,
305
- routes: staticRoutes,
306
- importGraph: serverImports,
307
- packages: { doctor, manifestDrift },
308
- ...(doctor.pipeline === undefined ? {} : { pipeline: doctor.pipeline }),
309
- policies: { checkConfig, rulePacks: [] },
310
- sourceFindings: { fetches, untypedClients, removedImports, responseRoutes, interpolatedSql },
311
- }
312
- return {
313
- facts,
314
- typecheck: typecheckResult,
315
- sqlCompiler,
316
- ...(checkConfigError === undefined ? {} : { checkConfigError }),
317
- checkConfigWarnings,
344
+ const facts: ProjectFactsSeed = {
345
+ source,
346
+ routes: staticRoutes,
347
+ importGraph: serverImports,
348
+ packages: { doctor, manifestDrift },
349
+ ...(doctor.pipeline === undefined ? {} : { pipeline: doctor.pipeline }),
350
+ policies: { checkConfig, rulePacks: [] },
351
+ check: {
352
+ typecheck: typecheckResult,
353
+ sqlCompilerAvailable: sqlCompiler !== undefined,
354
+ ...(loadedTypeScript.unsupported === undefined
355
+ ? {}
356
+ : { unsupportedTypeScriptVersion: loadedTypeScript.unsupported.version }),
357
+ ...(loadedTypeScript.session === undefined
358
+ ? {}
359
+ : { typescriptSession: loadedTypeScript.session }),
360
+ ...(checkConfigError === undefined ? {} : { checkConfigError }),
361
+ checkConfigWarnings,
362
+ contracts,
363
+ },
364
+ sourceFindings: {
365
+ fetches,
366
+ streams,
367
+ untypedClients,
368
+ removedImports,
369
+ responseRoutes,
370
+ interpolatedSql,
371
+ },
372
+ }
373
+ return { facts }
374
+ } catch (error) {
375
+ await loadedTypeScript.session?.close()
376
+ throw error
318
377
  }
319
378
  }
320
379
 
@@ -326,7 +385,11 @@ export async function collectCheckResult(
326
385
  opts: CheckCollectionOptions = {},
327
386
  ): Promise<CheckResult> {
328
387
  const scan = await buildProjectScan(cwd, opts)
329
- return collectCheckDiagnostics(cwd, scan, opts)
388
+ try {
389
+ return await collectCheckDiagnostics(cwd, scan, opts)
390
+ } finally {
391
+ await scan.facts.check.typescriptSession?.close()
392
+ }
330
393
  }
331
394
 
332
395
  /** The named rule sections of the human report, in print order. A rule absent from this list is NOT
@@ -334,9 +397,9 @@ export async function collectCheckResult(
334
397
  * its rule code, so a finding that can flip the exit code is never invisible in the default output. */
335
398
  const REPORT_SECTIONS = [
336
399
  ["typecheck", "typecheck"],
337
- ["typed-client", "hand-rolled fetch() to your own API"],
400
+ ["typed-client", "hand-rolled fetch()/EventSource/WebSocket to your own API"],
338
401
  ["untyped-client", 'client("…") missing its <typeof app> type argument'],
339
- ["server-only-import", "server-only import in a route module"],
402
+ ["server-only-import", "backend code in browser code"],
340
403
  ["interpolated-sql", "SQL built by interpolating a value into the statement"],
341
404
  ["response-route", "route returns a raw Response (typed client → data: never)"],
342
405
  ["undeclared-dependency", "undeclared dependency in package.json"],
@@ -380,6 +443,14 @@ export function renderCheckReport(result: CheckResult): string[] {
380
443
  `• intentional external mounts (not typed-client checked): ${result.externalMounts.join(", ")}`,
381
444
  )
382
445
  }
446
+ for (const gap of result.knownGaps ?? []) {
447
+ lines.push(`• known gap, not capability-analyzed: ${gap.path} - ${gap.reason}`)
448
+ }
449
+ const deduplicated = result.identityPreflight?.deduplicated ?? []
450
+ if (deduplicated.length > 0) {
451
+ const packages = deduplicated.map((finding) => finding.package).join(", ")
452
+ lines.push(`• identity-sensitive duplicate installs handled by singleCopy: ${packages}`)
453
+ }
383
454
  if (result.ruleOverrides !== undefined) {
384
455
  const active = Object.entries(result.ruleOverrides).map(([rule, override]) => {
385
456
  const parts: string[] = []
@@ -468,12 +539,29 @@ export async function runCheck(
468
539
  readonly json?: boolean
469
540
  readonly lintsOnly?: boolean
470
541
  readonly structured?: boolean
542
+ readonly sarif?: boolean
471
543
  } = {},
472
544
  ): Promise<boolean> {
473
545
  // The check view over the one project verification: the same collector `assure` and `levels` read.
474
546
  const { collectProjectVerification } = await import("./verification.ts")
475
547
  const verification = await collectProjectVerification(cwd, { lintsOnly: opts.lintsOnly ?? false })
476
548
  const result = await verification.check()
549
+ if (opts.sarif) {
550
+ const diagnostics: readonly Diagnostic[] =
551
+ result.structuredDiagnostics ??
552
+ result.diagnostics.map((value) => ({
553
+ code: value.code ?? value.rule,
554
+ severity: normalizeSeverity(value.severity),
555
+ message: value.message,
556
+ ...(value.file === undefined ? {} : { file: value.file }),
557
+ ...(value.line === undefined ? {} : { line: value.line }),
558
+ ...(value.evidence === undefined ? {} : { evidence: value.evidence }),
559
+ ...(value.fix === undefined ? {} : { fix: { recipe: value.fix } }),
560
+ ...(value.verify === undefined ? {} : { verify: value.verify }),
561
+ }))
562
+ console.log(JSON.stringify(toSarifLog(diagnostics), null, 2))
563
+ return result.ok
564
+ }
477
565
  if (opts.json) {
478
566
  console.log(
479
567
  JSON.stringify(
@@ -489,7 +577,7 @@ export async function runCheck(
489
577
 
490
578
  console.log(renderCheckReport(result).join("\n"))
491
579
  // Discoverability nudge: a project with no `.mcp.json` hasn't wired its nifra MCP for coding agents.
492
- // `nifra init-agents` writes it (+ .cursor/mcp.json + a CLAUDE.md preamble), no-clobber. A non-fatal
580
+ // `nifra init-agents` writes it (+ .cursor/mcp.json + each agent's pointer to AGENTS.md), no-clobber. A non-fatal
493
581
  // one-line tip in the human report only (the `--json` path returns above, unaffected).
494
582
  if (!existsSync(join(cwd, ".mcp.json"))) {
495
583
  console.log(