@ontrails/warden 1.0.0-beta.14 → 1.0.0-beta.16

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 (351) hide show
  1. package/CHANGELOG.md +149 -0
  2. package/README.md +77 -34
  3. package/bin/warden.ts +22 -0
  4. package/package.json +26 -6
  5. package/src/ast.ts +28 -0
  6. package/src/cli.ts +1169 -116
  7. package/src/command.ts +927 -0
  8. package/src/config.ts +184 -0
  9. package/src/drift.ts +76 -34
  10. package/src/formatters.ts +54 -7
  11. package/src/guide.ts +234 -0
  12. package/src/index.ts +151 -38
  13. package/src/project-context.ts +163 -0
  14. package/src/resolve.ts +530 -0
  15. package/src/rules/activation-orphan.ts +97 -0
  16. package/src/rules/ast.ts +2884 -124
  17. package/src/rules/circular-refs.ts +154 -0
  18. package/src/rules/{context-no-trailhead-types.ts → context-no-surface-types.ts} +72 -12
  19. package/src/rules/contour-exists.ts +251 -0
  20. package/src/rules/contour-ids.ts +15 -0
  21. package/src/rules/cross-declarations.ts +277 -69
  22. package/src/rules/dead-internal-trail.ts +141 -0
  23. package/src/rules/draft-file-marking.ts +51 -3
  24. package/src/rules/draft-visible-debt.ts +40 -15
  25. package/src/rules/error-mapping-completeness.ts +288 -0
  26. package/src/rules/example-valid.ts +401 -0
  27. package/src/rules/fires-declarations.ts +758 -0
  28. package/src/rules/implementation-returns-result.ts +1042 -122
  29. package/src/rules/incomplete-accessor-for-standard-op.ts +272 -0
  30. package/src/rules/incomplete-crud.ts +580 -0
  31. package/src/rules/index.ts +156 -19
  32. package/src/rules/intent-propagation.ts +127 -0
  33. package/src/rules/layer-field-name-drift.ts +96 -0
  34. package/src/rules/metadata.ts +508 -0
  35. package/src/rules/missing-reconcile.ts +98 -0
  36. package/src/rules/missing-visibility.ts +110 -0
  37. package/src/rules/no-dev-permit-in-source.ts +99 -0
  38. package/src/rules/no-direct-implementation-call.ts +1 -1
  39. package/src/rules/no-legacy-layer-imports.ts +193 -0
  40. package/src/rules/no-native-error-result.ts +111 -0
  41. package/src/rules/no-sync-result-assumption.ts +1131 -96
  42. package/src/rules/no-throw-in-detour-recover.ts +225 -0
  43. package/src/rules/no-throw-in-implementation.ts +6 -4
  44. package/src/rules/on-references-exist.ts +194 -0
  45. package/src/rules/orphaned-signal.ts +150 -0
  46. package/src/rules/owner-projection-parity.ts +146 -0
  47. package/src/rules/permit-governance.ts +25 -0
  48. package/src/rules/public-internal-deep-imports.ts +517 -0
  49. package/src/rules/public-output-schema.ts +29 -0
  50. package/src/rules/public-union-output-discriminants.ts +150 -0
  51. package/src/rules/read-intent-fires.ts +187 -0
  52. package/src/rules/reference-exists.ts +98 -0
  53. package/src/rules/registry-names.ts +113 -0
  54. package/src/rules/resolved-import-boundary.ts +146 -0
  55. package/src/rules/{provision-declarations.ts → resource-declarations.ts} +208 -138
  56. package/src/rules/{provision-exists.ts → resource-exists.ts} +46 -51
  57. package/src/rules/resource-id-grammar.ts +65 -0
  58. package/src/rules/scan.ts +0 -26
  59. package/src/rules/scheduled-destroy-intent.ts +44 -0
  60. package/src/rules/signal-graph-coaching.ts +191 -0
  61. package/src/rules/specs.ts +5 -1
  62. package/src/rules/static-resource-accessor-preference.ts +657 -0
  63. package/src/rules/types.ts +187 -7
  64. package/src/rules/unmaterialized-activation-source.ts +84 -0
  65. package/src/rules/unreachable-detour-shadowing.ts +344 -0
  66. package/src/rules/valid-describe-refs.ts +160 -32
  67. package/src/rules/valid-detour-contract.ts +78 -0
  68. package/src/rules/warden-export-symmetry.ts +533 -0
  69. package/src/rules/warden-rules-use-ast.ts +996 -0
  70. package/src/rules/webhook-route-collision.ts +243 -0
  71. package/src/trails/activation-orphan.trail.ts +81 -0
  72. package/src/trails/circular-refs.trail.ts +29 -0
  73. package/src/trails/{context-no-trailhead-types.trail.ts → context-no-surface-types.trail.ts} +4 -4
  74. package/src/trails/contour-exists.trail.ts +21 -0
  75. package/src/trails/dead-internal-trail.trail.ts +26 -0
  76. package/src/trails/draft-file-marking.trail.ts +16 -0
  77. package/src/trails/draft-visible-debt.trail.ts +16 -0
  78. package/src/trails/error-mapping-completeness.trail.ts +29 -0
  79. package/src/trails/example-valid.trail.ts +25 -0
  80. package/src/trails/fires-declarations.trail.ts +23 -0
  81. package/src/trails/incomplete-accessor-for-standard-op.trail.ts +76 -0
  82. package/src/trails/incomplete-crud.trail.ts +39 -0
  83. package/src/trails/index.ts +56 -8
  84. package/src/trails/intent-propagation.trail.ts +30 -0
  85. package/src/trails/layer-field-name-drift.trail.ts +39 -0
  86. package/src/trails/missing-reconcile.trail.ts +33 -0
  87. package/src/trails/missing-visibility.trail.ts +22 -0
  88. package/src/trails/no-dev-permit-in-source.trail.ts +16 -0
  89. package/src/trails/no-legacy-layer-imports.trail.ts +35 -0
  90. package/src/trails/no-native-error-result.trail.ts +18 -0
  91. package/src/trails/no-throw-in-detour-recover.trail.ts +24 -0
  92. package/src/trails/on-references-exist.trail.ts +21 -0
  93. package/src/trails/orphaned-signal.trail.ts +36 -0
  94. package/src/trails/owner-projection-parity.trail.ts +26 -0
  95. package/src/trails/permit-governance.trail.ts +51 -0
  96. package/src/trails/public-internal-deep-imports.trail.ts +94 -0
  97. package/src/trails/public-output-schema.trail.ts +55 -0
  98. package/src/trails/public-union-output-discriminants.trail.ts +33 -0
  99. package/src/trails/read-intent-fires.trail.ts +20 -0
  100. package/src/trails/reference-exists.trail.ts +25 -0
  101. package/src/trails/resolved-import-boundary.trail.ts +109 -0
  102. package/src/trails/{provision-declarations.trail.ts → resource-declarations.trail.ts} +6 -6
  103. package/src/trails/{provision-exists.trail.ts → resource-exists.trail.ts} +7 -7
  104. package/src/trails/resource-id-grammar.trail.ts +39 -0
  105. package/src/trails/run.ts +133 -24
  106. package/src/trails/scheduled-destroy-intent.trail.ts +56 -0
  107. package/src/trails/schema.ts +122 -4
  108. package/src/trails/signal-graph-coaching.trail.ts +74 -0
  109. package/src/trails/static-resource-accessor-preference.trail.ts +25 -0
  110. package/src/trails/unmaterialized-activation-source.trail.ts +69 -0
  111. package/src/trails/unreachable-detour-shadowing.trail.ts +45 -0
  112. package/src/trails/valid-detour-contract.trail.ts +71 -0
  113. package/src/trails/warden-export-symmetry.trail.ts +16 -0
  114. package/src/trails/warden-rules-use-ast.trail.ts +45 -0
  115. package/src/trails/webhook-route-collision.trail.ts +50 -0
  116. package/src/trails/wrap-rule.ts +133 -14
  117. package/src/workspaces.ts +238 -0
  118. package/.turbo/turbo-build.log +0 -1
  119. package/.turbo/turbo-lint.log +0 -3
  120. package/.turbo/turbo-typecheck.log +0 -1
  121. package/dist/cli.d.ts +0 -46
  122. package/dist/cli.d.ts.map +0 -1
  123. package/dist/cli.js +0 -247
  124. package/dist/cli.js.map +0 -1
  125. package/dist/draft.d.ts +0 -5
  126. package/dist/draft.d.ts.map +0 -1
  127. package/dist/draft.js +0 -16
  128. package/dist/draft.js.map +0 -1
  129. package/dist/drift.d.ts +0 -29
  130. package/dist/drift.d.ts.map +0 -1
  131. package/dist/drift.js +0 -61
  132. package/dist/drift.js.map +0 -1
  133. package/dist/formatters.d.ts +0 -30
  134. package/dist/formatters.d.ts.map +0 -1
  135. package/dist/formatters.js +0 -98
  136. package/dist/formatters.js.map +0 -1
  137. package/dist/index.d.ts +0 -37
  138. package/dist/index.d.ts.map +0 -1
  139. package/dist/index.js +0 -38
  140. package/dist/index.js.map +0 -1
  141. package/dist/rules/ast.d.ts +0 -82
  142. package/dist/rules/ast.d.ts.map +0 -1
  143. package/dist/rules/ast.js +0 -363
  144. package/dist/rules/ast.js.map +0 -1
  145. package/dist/rules/context-no-surface-types.d.ts +0 -12
  146. package/dist/rules/context-no-surface-types.d.ts.map +0 -1
  147. package/dist/rules/context-no-surface-types.js +0 -96
  148. package/dist/rules/context-no-surface-types.js.map +0 -1
  149. package/dist/rules/context-no-trailhead-types.d.ts +0 -12
  150. package/dist/rules/context-no-trailhead-types.d.ts.map +0 -1
  151. package/dist/rules/context-no-trailhead-types.js +0 -96
  152. package/dist/rules/context-no-trailhead-types.js.map +0 -1
  153. package/dist/rules/cross-declarations.d.ts +0 -13
  154. package/dist/rules/cross-declarations.d.ts.map +0 -1
  155. package/dist/rules/cross-declarations.js +0 -264
  156. package/dist/rules/cross-declarations.js.map +0 -1
  157. package/dist/rules/draft-file-marking.d.ts +0 -6
  158. package/dist/rules/draft-file-marking.d.ts.map +0 -1
  159. package/dist/rules/draft-file-marking.js +0 -65
  160. package/dist/rules/draft-file-marking.js.map +0 -1
  161. package/dist/rules/draft-visible-debt.d.ts +0 -12
  162. package/dist/rules/draft-visible-debt.d.ts.map +0 -1
  163. package/dist/rules/draft-visible-debt.js +0 -45
  164. package/dist/rules/draft-visible-debt.js.map +0 -1
  165. package/dist/rules/follow-declarations.d.ts +0 -13
  166. package/dist/rules/follow-declarations.d.ts.map +0 -1
  167. package/dist/rules/follow-declarations.js +0 -264
  168. package/dist/rules/follow-declarations.js.map +0 -1
  169. package/dist/rules/implementation-returns-result.d.ts +0 -13
  170. package/dist/rules/implementation-returns-result.d.ts.map +0 -1
  171. package/dist/rules/implementation-returns-result.js +0 -277
  172. package/dist/rules/implementation-returns-result.js.map +0 -1
  173. package/dist/rules/index.d.ts +0 -20
  174. package/dist/rules/index.d.ts.map +0 -1
  175. package/dist/rules/index.js +0 -49
  176. package/dist/rules/index.js.map +0 -1
  177. package/dist/rules/no-direct-impl-in-route.d.ts +0 -12
  178. package/dist/rules/no-direct-impl-in-route.d.ts.map +0 -1
  179. package/dist/rules/no-direct-impl-in-route.js +0 -47
  180. package/dist/rules/no-direct-impl-in-route.js.map +0 -1
  181. package/dist/rules/no-direct-implementation-call.d.ts +0 -12
  182. package/dist/rules/no-direct-implementation-call.d.ts.map +0 -1
  183. package/dist/rules/no-direct-implementation-call.js +0 -39
  184. package/dist/rules/no-direct-implementation-call.js.map +0 -1
  185. package/dist/rules/no-sync-result-assumption.d.ts +0 -6
  186. package/dist/rules/no-sync-result-assumption.d.ts.map +0 -1
  187. package/dist/rules/no-sync-result-assumption.js +0 -98
  188. package/dist/rules/no-sync-result-assumption.js.map +0 -1
  189. package/dist/rules/no-throw-in-detour-target.d.ts +0 -12
  190. package/dist/rules/no-throw-in-detour-target.d.ts.map +0 -1
  191. package/dist/rules/no-throw-in-detour-target.js +0 -87
  192. package/dist/rules/no-throw-in-detour-target.js.map +0 -1
  193. package/dist/rules/no-throw-in-implementation.d.ts +0 -9
  194. package/dist/rules/no-throw-in-implementation.d.ts.map +0 -1
  195. package/dist/rules/no-throw-in-implementation.js +0 -34
  196. package/dist/rules/no-throw-in-implementation.js.map +0 -1
  197. package/dist/rules/prefer-schema-inference.d.ts +0 -7
  198. package/dist/rules/prefer-schema-inference.d.ts.map +0 -1
  199. package/dist/rules/prefer-schema-inference.js +0 -86
  200. package/dist/rules/prefer-schema-inference.js.map +0 -1
  201. package/dist/rules/provision-declarations.d.ts +0 -14
  202. package/dist/rules/provision-declarations.d.ts.map +0 -1
  203. package/dist/rules/provision-declarations.js +0 -344
  204. package/dist/rules/provision-declarations.js.map +0 -1
  205. package/dist/rules/provision-exists.d.ts +0 -6
  206. package/dist/rules/provision-exists.d.ts.map +0 -1
  207. package/dist/rules/provision-exists.js +0 -90
  208. package/dist/rules/provision-exists.js.map +0 -1
  209. package/dist/rules/scan.d.ts +0 -8
  210. package/dist/rules/scan.d.ts.map +0 -1
  211. package/dist/rules/scan.js +0 -32
  212. package/dist/rules/scan.js.map +0 -1
  213. package/dist/rules/service-declarations.d.ts +0 -16
  214. package/dist/rules/service-declarations.d.ts.map +0 -1
  215. package/dist/rules/service-declarations.js +0 -346
  216. package/dist/rules/service-declarations.js.map +0 -1
  217. package/dist/rules/service-exists.d.ts +0 -8
  218. package/dist/rules/service-exists.d.ts.map +0 -1
  219. package/dist/rules/service-exists.js +0 -91
  220. package/dist/rules/service-exists.js.map +0 -1
  221. package/dist/rules/specs.d.ts +0 -29
  222. package/dist/rules/specs.d.ts.map +0 -1
  223. package/dist/rules/specs.js +0 -192
  224. package/dist/rules/specs.js.map +0 -1
  225. package/dist/rules/structure.d.ts +0 -13
  226. package/dist/rules/structure.d.ts.map +0 -1
  227. package/dist/rules/structure.js +0 -142
  228. package/dist/rules/structure.js.map +0 -1
  229. package/dist/rules/types.d.ts +0 -54
  230. package/dist/rules/types.d.ts.map +0 -1
  231. package/dist/rules/types.js +0 -2
  232. package/dist/rules/types.js.map +0 -1
  233. package/dist/rules/valid-describe-refs.d.ts +0 -7
  234. package/dist/rules/valid-describe-refs.d.ts.map +0 -1
  235. package/dist/rules/valid-describe-refs.js +0 -51
  236. package/dist/rules/valid-describe-refs.js.map +0 -1
  237. package/dist/rules/valid-detour-refs.d.ts +0 -6
  238. package/dist/rules/valid-detour-refs.d.ts.map +0 -1
  239. package/dist/rules/valid-detour-refs.js +0 -117
  240. package/dist/rules/valid-detour-refs.js.map +0 -1
  241. package/dist/trails/context-no-surface-types.trail.d.ts +0 -13
  242. package/dist/trails/context-no-surface-types.trail.d.ts.map +0 -1
  243. package/dist/trails/context-no-surface-types.trail.js +0 -21
  244. package/dist/trails/context-no-surface-types.trail.js.map +0 -1
  245. package/dist/trails/context-no-trailhead-types.trail.d.ts +0 -13
  246. package/dist/trails/context-no-trailhead-types.trail.d.ts.map +0 -1
  247. package/dist/trails/context-no-trailhead-types.trail.js +0 -21
  248. package/dist/trails/context-no-trailhead-types.trail.js.map +0 -1
  249. package/dist/trails/cross-declarations.trail.d.ts +0 -13
  250. package/dist/trails/cross-declarations.trail.d.ts.map +0 -1
  251. package/dist/trails/cross-declarations.trail.js +0 -22
  252. package/dist/trails/cross-declarations.trail.js.map +0 -1
  253. package/dist/trails/follow-declarations.trail.d.ts +0 -13
  254. package/dist/trails/follow-declarations.trail.d.ts.map +0 -1
  255. package/dist/trails/follow-declarations.trail.js +0 -22
  256. package/dist/trails/follow-declarations.trail.js.map +0 -1
  257. package/dist/trails/implementation-returns-result.trail.d.ts +0 -13
  258. package/dist/trails/implementation-returns-result.trail.d.ts.map +0 -1
  259. package/dist/trails/implementation-returns-result.trail.js +0 -20
  260. package/dist/trails/implementation-returns-result.trail.js.map +0 -1
  261. package/dist/trails/index.d.ts +0 -16
  262. package/dist/trails/index.d.ts.map +0 -1
  263. package/dist/trails/index.js +0 -15
  264. package/dist/trails/index.js.map +0 -1
  265. package/dist/trails/no-direct-impl-in-route.trail.d.ts +0 -13
  266. package/dist/trails/no-direct-impl-in-route.trail.d.ts.map +0 -1
  267. package/dist/trails/no-direct-impl-in-route.trail.js +0 -22
  268. package/dist/trails/no-direct-impl-in-route.trail.js.map +0 -1
  269. package/dist/trails/no-direct-implementation-call.trail.d.ts +0 -13
  270. package/dist/trails/no-direct-implementation-call.trail.d.ts.map +0 -1
  271. package/dist/trails/no-direct-implementation-call.trail.js +0 -16
  272. package/dist/trails/no-direct-implementation-call.trail.js.map +0 -1
  273. package/dist/trails/no-sync-result-assumption.trail.d.ts +0 -13
  274. package/dist/trails/no-sync-result-assumption.trail.d.ts.map +0 -1
  275. package/dist/trails/no-sync-result-assumption.trail.js +0 -19
  276. package/dist/trails/no-sync-result-assumption.trail.js.map +0 -1
  277. package/dist/trails/no-throw-in-detour-target.trail.d.ts +0 -15
  278. package/dist/trails/no-throw-in-detour-target.trail.d.ts.map +0 -1
  279. package/dist/trails/no-throw-in-detour-target.trail.js +0 -20
  280. package/dist/trails/no-throw-in-detour-target.trail.js.map +0 -1
  281. package/dist/trails/no-throw-in-implementation.trail.d.ts +0 -13
  282. package/dist/trails/no-throw-in-implementation.trail.d.ts.map +0 -1
  283. package/dist/trails/no-throw-in-implementation.trail.js +0 -20
  284. package/dist/trails/no-throw-in-implementation.trail.js.map +0 -1
  285. package/dist/trails/prefer-schema-inference.trail.d.ts +0 -13
  286. package/dist/trails/prefer-schema-inference.trail.d.ts.map +0 -1
  287. package/dist/trails/prefer-schema-inference.trail.js +0 -21
  288. package/dist/trails/prefer-schema-inference.trail.js.map +0 -1
  289. package/dist/trails/provision-declarations.trail.d.ts +0 -13
  290. package/dist/trails/provision-declarations.trail.d.ts.map +0 -1
  291. package/dist/trails/provision-declarations.trail.js +0 -25
  292. package/dist/trails/provision-declarations.trail.js.map +0 -1
  293. package/dist/trails/provision-exists.trail.d.ts +0 -15
  294. package/dist/trails/provision-exists.trail.d.ts.map +0 -1
  295. package/dist/trails/provision-exists.trail.js +0 -27
  296. package/dist/trails/provision-exists.trail.js.map +0 -1
  297. package/dist/trails/run.d.ts +0 -17
  298. package/dist/trails/run.d.ts.map +0 -1
  299. package/dist/trails/run.js +0 -39
  300. package/dist/trails/run.js.map +0 -1
  301. package/dist/trails/schema.d.ts +0 -53
  302. package/dist/trails/schema.d.ts.map +0 -1
  303. package/dist/trails/schema.js +0 -42
  304. package/dist/trails/schema.js.map +0 -1
  305. package/dist/trails/service-declarations.trail.d.ts +0 -26
  306. package/dist/trails/service-declarations.trail.d.ts.map +0 -1
  307. package/dist/trails/service-declarations.trail.js +0 -27
  308. package/dist/trails/service-declarations.trail.js.map +0 -1
  309. package/dist/trails/service-exists.trail.d.ts +0 -32
  310. package/dist/trails/service-exists.trail.d.ts.map +0 -1
  311. package/dist/trails/service-exists.trail.js +0 -29
  312. package/dist/trails/service-exists.trail.js.map +0 -1
  313. package/dist/trails/topo.d.ts +0 -3
  314. package/dist/trails/topo.d.ts.map +0 -1
  315. package/dist/trails/topo.js +0 -5
  316. package/dist/trails/topo.js.map +0 -1
  317. package/dist/trails/valid-describe-refs.trail.d.ts +0 -15
  318. package/dist/trails/valid-describe-refs.trail.d.ts.map +0 -1
  319. package/dist/trails/valid-describe-refs.trail.js +0 -18
  320. package/dist/trails/valid-describe-refs.trail.js.map +0 -1
  321. package/dist/trails/valid-detour-refs.trail.d.ts +0 -15
  322. package/dist/trails/valid-detour-refs.trail.d.ts.map +0 -1
  323. package/dist/trails/valid-detour-refs.trail.js +0 -24
  324. package/dist/trails/valid-detour-refs.trail.js.map +0 -1
  325. package/dist/trails/wrap-rule.d.ts +0 -29
  326. package/dist/trails/wrap-rule.d.ts.map +0 -1
  327. package/dist/trails/wrap-rule.js +0 -47
  328. package/dist/trails/wrap-rule.js.map +0 -1
  329. package/src/__tests__/cli.test.ts +0 -311
  330. package/src/__tests__/cross-declarations.test.ts +0 -303
  331. package/src/__tests__/drift.test.ts +0 -144
  332. package/src/__tests__/formatters.test.ts +0 -157
  333. package/src/__tests__/implementation-returns-result.test.ts +0 -129
  334. package/src/__tests__/no-direct-implementation-call.test.ts +0 -83
  335. package/src/__tests__/no-sync-result-assumption.test.ts +0 -85
  336. package/src/__tests__/no-throw-in-detour-target.test.ts +0 -78
  337. package/src/__tests__/prefer-schema-inference.test.ts +0 -84
  338. package/src/__tests__/provision-declarations.test.ts +0 -318
  339. package/src/__tests__/provision-exists.test.ts +0 -122
  340. package/src/__tests__/rules.test.ts +0 -227
  341. package/src/__tests__/trails.test.ts +0 -19
  342. package/src/__tests__/valid-describe-refs.test.ts +0 -60
  343. package/src/__tests__/wrap-rule.test.ts +0 -41
  344. package/src/rules/no-direct-impl-in-route.ts +0 -81
  345. package/src/rules/no-throw-in-detour-target.ts +0 -150
  346. package/src/rules/valid-detour-refs.ts +0 -189
  347. package/src/trails/no-direct-impl-in-route.trail.ts +0 -22
  348. package/src/trails/no-throw-in-detour-target.trail.ts +0 -20
  349. package/src/trails/valid-detour-refs.trail.ts +0 -24
  350. package/tsconfig.json +0 -9
  351. package/tsconfig.tsbuildinfo +0 -1
@@ -1,311 +0,0 @@
1
- import { describe, expect, test } from 'bun:test';
2
- import { mkdirSync, rmSync, writeFileSync } from 'node:fs';
3
- import { tmpdir } from 'node:os';
4
- import { join } from 'node:path';
5
-
6
- import { formatWardenReport, runWarden } from '../cli.js';
7
-
8
- const isDraftFileMarking = (rule: string): boolean =>
9
- rule === 'draft-file-marking';
10
-
11
- const isDraftFileMarkingError = (diagnostic: {
12
- rule: string;
13
- severity?: string;
14
- }): boolean =>
15
- isDraftFileMarking(diagnostic.rule) && diagnostic.severity === 'error';
16
-
17
- const isDraftFileMarkingWarn = (diagnostic: {
18
- rule: string;
19
- severity?: string;
20
- }): boolean =>
21
- isDraftFileMarking(diagnostic.rule) && diagnostic.severity === 'warn';
22
-
23
- const isDraftVisibleDebt = (rule: string): boolean =>
24
- rule === 'draft-visible-debt';
25
-
26
- const makeTempDir = (): string => {
27
- const dir = join(
28
- tmpdir(),
29
- `warden-test-${Date.now()}-${Math.random().toString(36).slice(2)}`
30
- );
31
- mkdirSync(dir, { recursive: true });
32
- return dir;
33
- };
34
-
35
- describe('runWarden', () => {
36
- test('produces a report with diagnostics for bad code', async () => {
37
- const dir = makeTempDir();
38
- try {
39
- writeFileSync(
40
- join(dir, 'bad.ts'),
41
- `trail("entity.show", {
42
- blaze: async (input, ctx) => {
43
- throw new Error("boom");
44
- }
45
- })`
46
- );
47
-
48
- const report = await runWarden({ rootDir: dir });
49
- expect(report.errorCount).toBeGreaterThan(0);
50
- expect(report.passed).toBe(false);
51
- } finally {
52
- rmSync(dir, { force: true, recursive: true });
53
- }
54
- });
55
-
56
- test('passes for clean code', async () => {
57
- const dir = makeTempDir();
58
- try {
59
- writeFileSync(
60
- join(dir, 'good.ts'),
61
- `trail("entity.show", {
62
- blaze: async (input, ctx) => {
63
- return Result.ok(data);
64
- }
65
- })`
66
- );
67
-
68
- const report = await runWarden({ rootDir: dir });
69
- expect(report.errorCount).toBe(0);
70
- expect(report.passed).toBe(true);
71
- } finally {
72
- rmSync(dir, { force: true, recursive: true });
73
- }
74
- });
75
-
76
- test('lintOnly skips drift check', async () => {
77
- const dir = makeTempDir();
78
- try {
79
- writeFileSync(join(dir, 'empty.ts'), 'export {}');
80
- const report = await runWarden({ lintOnly: true, rootDir: dir });
81
- expect(report.drift).toBeNull();
82
- } finally {
83
- rmSync(dir, { force: true, recursive: true });
84
- }
85
- });
86
-
87
- test('driftOnly skips lint', async () => {
88
- const dir = makeTempDir();
89
- try {
90
- // Even with bad code, driftOnly should produce 0 diagnostics
91
- writeFileSync(
92
- join(dir, 'bad.ts'),
93
- `trail("x", { blaze: async () => { throw new Error("x"); } })`
94
- );
95
- const report = await runWarden({ driftOnly: true, rootDir: dir });
96
- expect(report.diagnostics.length).toBe(0);
97
- expect(report.drift).not.toBeNull();
98
- } finally {
99
- rmSync(dir, { force: true, recursive: true });
100
- }
101
- });
102
-
103
- test('uses project context for detour references across files', async () => {
104
- const dir = makeTempDir();
105
- try {
106
- writeFileSync(
107
- join(dir, 'show.ts'),
108
- `trail("entity.show", {
109
- detours: { NotFoundError: ["entity.search"] },
110
- blaze: async (input, ctx) => {
111
- return Result.ok(data);
112
- }
113
- })`
114
- );
115
- writeFileSync(
116
- join(dir, 'search.ts'),
117
- `trail("entity.search", {
118
- blaze: async (input, ctx) => {
119
- return Result.ok(data);
120
- }
121
- })`
122
- );
123
-
124
- const report = await runWarden({ rootDir: dir });
125
- const detourRefErrors = report.diagnostics.filter(
126
- (diagnostic) => diagnostic.rule === 'valid-detour-refs'
127
- );
128
-
129
- expect(detourRefErrors).toHaveLength(0);
130
- } finally {
131
- rmSync(dir, { force: true, recursive: true });
132
- }
133
- });
134
-
135
- test('flags throws in detour targets declared in another file', async () => {
136
- const dir = makeTempDir();
137
- try {
138
- writeFileSync(
139
- join(dir, 'show.ts'),
140
- `trail("entity.show", {
141
- detours: { NotFoundError: ["entity.search"] },
142
- blaze: async (input, ctx) => {
143
- return Result.ok(data);
144
- }
145
- })`
146
- );
147
- writeFileSync(
148
- join(dir, 'search.ts'),
149
- `trail("entity.search", {
150
- blaze: async (input, ctx) => {
151
- throw new Error("boom");
152
- }
153
- })`
154
- );
155
-
156
- const report = await runWarden({ rootDir: dir });
157
- const detourThrowRules = report.diagnostics.filter(
158
- (diagnostic) => diagnostic.rule === 'no-throw-in-detour-target'
159
- );
160
-
161
- expect(detourThrowRules).toHaveLength(1);
162
- expect(detourThrowRules[0]?.message).toContain('entity.search');
163
- } finally {
164
- rmSync(dir, { force: true, recursive: true });
165
- }
166
- });
167
-
168
- test('requires draft-bearing files to be visibly marked', async () => {
169
- const dir = makeTempDir();
170
- try {
171
- writeFileSync(
172
- join(dir, 'draft-id.ts'),
173
- `trail("_draft.entity.prepare", {
174
- blaze: async () => Result.ok({ ok: true }),
175
- input: z.object({})
176
- })`
177
- );
178
-
179
- const report = await runWarden({ rootDir: dir });
180
-
181
- const hasDraftFileMarking = report.diagnostics.some((diagnostic) =>
182
- isDraftFileMarking(diagnostic.rule)
183
- );
184
- const hasDraftVisibleDebt = report.diagnostics.some((diagnostic) =>
185
- isDraftVisibleDebt(diagnostic.rule)
186
- );
187
-
188
- expect(hasDraftFileMarking).toBe(true);
189
- expect(hasDraftVisibleDebt).toBe(true);
190
- } finally {
191
- rmSync(dir, { force: true, recursive: true });
192
- }
193
- });
194
-
195
- test('allows correctly marked draft files while keeping the debt visible', async () => {
196
- const dir = makeTempDir();
197
- try {
198
- writeFileSync(
199
- join(dir, '_draft.entity.ts'),
200
- `trail("_draft.entity.prepare", {
201
- blaze: async () => Result.ok({ ok: true }),
202
- input: z.object({})
203
- })`
204
- );
205
-
206
- const report = await runWarden({ rootDir: dir });
207
-
208
- const hasDraftFileMarkingError = report.diagnostics.some((diagnostic) =>
209
- isDraftFileMarkingError(diagnostic)
210
- );
211
- const hasDraftVisibleDebt = report.diagnostics.some((diagnostic) =>
212
- isDraftVisibleDebt(diagnostic.rule)
213
- );
214
-
215
- expect(hasDraftFileMarkingError).toBe(false);
216
- expect(hasDraftVisibleDebt).toBe(true);
217
- } finally {
218
- rmSync(dir, { force: true, recursive: true });
219
- }
220
- });
221
-
222
- test('warns when a draft-marked file no longer contains draft ids', async () => {
223
- const dir = makeTempDir();
224
- try {
225
- writeFileSync(
226
- join(dir, 'entity.draft.ts'),
227
- `trail("entity.prepare", {
228
- blaze: async () => Result.ok({ ok: true }),
229
- input: z.object({})
230
- })`
231
- );
232
-
233
- const report = await runWarden({ rootDir: dir });
234
-
235
- const hasDraftFileMarkingWarn = report.diagnostics.some((diagnostic) =>
236
- isDraftFileMarkingWarn(diagnostic)
237
- );
238
-
239
- expect(hasDraftFileMarkingWarn).toBe(true);
240
- } finally {
241
- rmSync(dir, { force: true, recursive: true });
242
- }
243
- });
244
- });
245
-
246
- describe('formatWardenReport', () => {
247
- test('formats a clean report', () => {
248
- const output = formatWardenReport({
249
- diagnostics: [],
250
- drift: { committedHash: null, currentHash: 'stub', stale: false },
251
- errorCount: 0,
252
- passed: true,
253
- warnCount: 0,
254
- });
255
- expect(output).toContain('Warden Report');
256
- expect(output).toContain('Lint: clean');
257
- expect(output).toContain('Drift: clean');
258
- expect(output).toContain('Result: PASS');
259
- });
260
-
261
- test('formats a report with errors', () => {
262
- const output = formatWardenReport({
263
- diagnostics: [
264
- {
265
- filePath: 'src/trails/entity.ts',
266
- line: 3,
267
- message: 'Do not throw inside implementation.',
268
- rule: 'no-throw-in-implementation',
269
- severity: 'error',
270
- },
271
- ],
272
- drift: { committedHash: null, currentHash: 'stub', stale: false },
273
- errorCount: 1,
274
- passed: false,
275
- warnCount: 0,
276
- });
277
- expect(output).toContain('1 errors');
278
- expect(output).toContain('Result: FAIL');
279
- expect(output).toContain('entity.ts:3');
280
- });
281
-
282
- test('formats a report with stale drift', () => {
283
- const output = formatWardenReport({
284
- diagnostics: [],
285
- drift: { committedHash: 'abc', currentHash: 'def', stale: true },
286
- errorCount: 0,
287
- passed: false,
288
- warnCount: 0,
289
- });
290
- expect(output).toContain('trails.lock is stale');
291
- expect(output).toContain('Result: FAIL');
292
- });
293
-
294
- test('formats a report with blocked established exports', () => {
295
- const output = formatWardenReport({
296
- diagnostics: [],
297
- drift: {
298
- blockedReason:
299
- 'Established topo validation failed with 1 draft issue(s)',
300
- committedHash: null,
301
- currentHash: 'blocked',
302
- stale: true,
303
- },
304
- errorCount: 0,
305
- passed: false,
306
- warnCount: 0,
307
- });
308
- expect(output).toContain('Drift: blocked');
309
- expect(output).toContain('established exports blocked');
310
- });
311
- });
@@ -1,303 +0,0 @@
1
- import { describe, expect, test } from 'bun:test';
2
-
3
- import { crossDeclarations } from '../rules/cross-declarations.js';
4
-
5
- const TEST_FILE = 'test.ts';
6
-
7
- describe('cross-declarations', () => {
8
- describe('clean cases', () => {
9
- test('declared and called match exactly', () => {
10
- const code = `
11
- import { trail, Result } from '@ontrails/core';
12
- const t = trail('onboard', {
13
- crosses: ['entity.add', 'search'],
14
- input: z.object({ name: z.string() }),
15
- blaze: async (input, ctx) => {
16
- await ctx.cross('entity.add', { name: input.name });
17
- await ctx.cross('search', { query: input.name });
18
- return Result.ok({});
19
- },
20
- });
21
- `;
22
-
23
- const diagnostics = crossDeclarations.check(code, TEST_FILE);
24
-
25
- expect(diagnostics.length).toBe(0);
26
- });
27
-
28
- test('no crosses declaration and no ctx.cross() calls', () => {
29
- const code = `
30
- trail('simple', {
31
- input: z.object({ name: z.string() }),
32
- blaze: async (input, ctx) => {
33
- return Result.ok({ greeting: 'hello ' + input.name });
34
- },
35
- });
36
- `;
37
-
38
- const diagnostics = crossDeclarations.check(code, TEST_FILE);
39
-
40
- expect(diagnostics.length).toBe(0);
41
- });
42
- });
43
-
44
- describe('error cases', () => {
45
- test('called but not declared produces error', () => {
46
- const code = `
47
- trail('onboard', {
48
- input: z.object({ name: z.string() }),
49
- blaze: async (input, ctx) => {
50
- await ctx.cross('entity.add', { name: input.name });
51
- return Result.ok({});
52
- },
53
- });
54
- `;
55
-
56
- const diagnostics = crossDeclarations.check(code, TEST_FILE);
57
-
58
- expect(diagnostics.length).toBe(1);
59
- expect(diagnostics[0]?.severity).toBe('error');
60
- expect(diagnostics[0]?.rule).toBe('cross-declarations');
61
- expect(diagnostics[0]?.message).toContain("ctx.cross('entity.add')");
62
- expect(diagnostics[0]?.message).toContain('not declared in crosses');
63
- });
64
- });
65
-
66
- describe('warn cases', () => {
67
- test('declared but not called produces warning', () => {
68
- const code = `
69
- trail('onboard', {
70
- crosses: ['entity.add', 'search'],
71
- blaze: async (input, ctx) => {
72
- await ctx.cross('entity.add', { name: input.name });
73
- return Result.ok({});
74
- },
75
- });
76
- `;
77
-
78
- const diagnostics = crossDeclarations.check(code, TEST_FILE);
79
-
80
- expect(diagnostics.length).toBe(1);
81
- expect(diagnostics[0]?.severity).toBe('warn');
82
- expect(diagnostics[0]?.rule).toBe('cross-declarations');
83
- expect(diagnostics[0]?.message).toContain("'search' declared in crosses");
84
- expect(diagnostics[0]?.message).toContain('never called');
85
- });
86
- });
87
-
88
- describe('single-object overload', () => {
89
- test('recognizes trail({ id, crosses, blaze }) form', () => {
90
- const code = `
91
- trail({
92
- id: 'onboard',
93
- crosses: ['entity.add'],
94
- input: z.object({ name: z.string() }),
95
- blaze: async (input, ctx) => {
96
- await ctx.cross('entity.add', { name: input.name });
97
- return Result.ok({});
98
- },
99
- });
100
- `;
101
-
102
- const diagnostics = crossDeclarations.check(code, TEST_FILE);
103
-
104
- expect(diagnostics.length).toBe(0);
105
- });
106
-
107
- test('detects undeclared crossings in single-object form', () => {
108
- const code = `
109
- trail({
110
- id: 'onboard',
111
- input: z.object({ name: z.string() }),
112
- blaze: async (input, ctx) => {
113
- await ctx.cross('entity.add', { name: input.name });
114
- return Result.ok({});
115
- },
116
- });
117
- `;
118
-
119
- const diagnostics = crossDeclarations.check(code, TEST_FILE);
120
-
121
- expect(diagnostics.length).toBe(1);
122
- expect(diagnostics[0]?.severity).toBe('error');
123
- expect(diagnostics[0]?.message).toContain("'entity.add'");
124
- });
125
- });
126
-
127
- describe('context parameter naming', () => {
128
- test('recognizes context.cross() when second param is named context', () => {
129
- const code = `
130
- trail('onboard', {
131
- crosses: ['entity.add'],
132
- input: z.object({ name: z.string() }),
133
- blaze: async (input, context) => {
134
- await context.cross('entity.add', { name: input.name });
135
- return Result.ok({});
136
- },
137
- });
138
- `;
139
-
140
- const diagnostics = crossDeclarations.check(code, TEST_FILE);
141
-
142
- expect(diagnostics.length).toBe(0);
143
- });
144
-
145
- test('detects undeclared context.cross() calls', () => {
146
- const code = `
147
- trail('onboard', {
148
- input: z.object({ name: z.string() }),
149
- blaze: async (input, context) => {
150
- await context.cross('entity.add', { name: input.name });
151
- return Result.ok({});
152
- },
153
- });
154
- `;
155
-
156
- const diagnostics = crossDeclarations.check(code, TEST_FILE);
157
-
158
- expect(diagnostics.length).toBe(1);
159
- expect(diagnostics[0]?.severity).toBe('error');
160
- });
161
-
162
- test('recognizes destructured cross() calls', () => {
163
- const code = `
164
- trail('onboard', {
165
- crosses: ['entity.add'],
166
- input: z.object({ name: z.string() }),
167
- blaze: async (input, ctx) => {
168
- const { cross } = ctx;
169
- await cross('entity.add', { name: input.name });
170
- return Result.ok({});
171
- },
172
- });
173
- `;
174
-
175
- const diagnostics = crossDeclarations.check(code, TEST_FILE);
176
-
177
- expect(diagnostics.length).toBe(0);
178
- });
179
- });
180
-
181
- describe('nested run false positives', () => {
182
- test('meta.run does not trigger false positives', () => {
183
- const code = `
184
- trail('onboard', {
185
- crosses: ['entity.add'],
186
- input: z.object({ name: z.string() }),
187
- meta: { blaze: async () => ctx.cross('phantom') },
188
- blaze: async (input, ctx) => {
189
- await ctx.cross('entity.add', { name: input.name });
190
- return Result.ok({});
191
- },
192
- });
193
- `;
194
-
195
- const diagnostics = crossDeclarations.check(code, TEST_FILE);
196
-
197
- expect(diagnostics.length).toBe(0);
198
- });
199
- });
200
-
201
- describe('identifier resolution in crosses arrays', () => {
202
- test('resolves const identifiers in crosses array', () => {
203
- const code = `
204
- const ENTITY_ADD = 'entity.add';
205
- trail('onboard', {
206
- crosses: [ENTITY_ADD],
207
- input: z.object({ name: z.string() }),
208
- blaze: async (input, ctx) => {
209
- await ctx.cross('entity.add', { name: input.name });
210
- return Result.ok({});
211
- },
212
- });
213
- `;
214
-
215
- const diagnostics = crossDeclarations.check(code, TEST_FILE);
216
-
217
- expect(diagnostics.length).toBe(0);
218
- });
219
-
220
- test('reports error when resolved identifier does not match called cross', () => {
221
- const code = `
222
- const ENTITY_ADD = 'entity.add';
223
- trail('onboard', {
224
- crosses: [ENTITY_ADD],
225
- input: z.object({ name: z.string() }),
226
- blaze: async (input, ctx) => {
227
- await ctx.cross('search', { name: input.name });
228
- return Result.ok({});
229
- },
230
- });
231
- `;
232
-
233
- const diagnostics = crossDeclarations.check(code, TEST_FILE);
234
-
235
- // 'search' called but not declared, 'entity.add' declared but not called
236
- expect(diagnostics.length).toBe(2);
237
- });
238
- });
239
-
240
- describe('edge cases', () => {
241
- test('dynamic cross IDs are skipped', () => {
242
- const code = `
243
- trail('dispatch', {
244
- crosses: ['entity.add'],
245
- blaze: async (input, ctx) => {
246
- const trailId = input.target;
247
- await ctx.cross(trailId, input);
248
- await ctx.cross('entity.add', input);
249
- return Result.ok({});
250
- },
251
- });
252
- `;
253
-
254
- const diagnostics = crossDeclarations.check(code, TEST_FILE);
255
-
256
- expect(diagnostics.length).toBe(0);
257
- });
258
-
259
- test('multiple trails in one file are validated independently', () => {
260
- const code = `
261
- trail('alpha', {
262
- crosses: ['shared'],
263
- blaze: async (input, ctx) => {
264
- await ctx.cross('shared', input);
265
- return Result.ok({});
266
- },
267
- });
268
-
269
- trail('beta', {
270
- blaze: async (input, ctx) => {
271
- await ctx.cross('undeclared', input);
272
- return Result.ok({});
273
- },
274
- });
275
- `;
276
-
277
- const diagnostics = crossDeclarations.check(code, TEST_FILE);
278
-
279
- expect(diagnostics.length).toBe(1);
280
- expect(diagnostics[0]?.message).toContain('Trail "beta"');
281
- expect(diagnostics[0]?.message).toContain("'undeclared'");
282
- expect(diagnostics[0]?.severity).toBe('error');
283
- });
284
-
285
- test('skips test files', () => {
286
- const code = `
287
- trail('onboard', {
288
- blaze: async (input, ctx) => {
289
- await ctx.cross('entity.add', input);
290
- return Result.ok({});
291
- },
292
- });
293
- `;
294
-
295
- const diagnostics = crossDeclarations.check(
296
- code,
297
- 'src/__tests__/trails.test.ts'
298
- );
299
-
300
- expect(diagnostics.length).toBe(0);
301
- });
302
- });
303
- });