@ontrails/warden 1.0.0-beta.13 → 1.0.0-beta.15

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 (474) hide show
  1. package/.turbo/turbo-lint.log +1 -1
  2. package/CHANGELOG.md +30 -0
  3. package/README.md +31 -20
  4. package/dist/cli.d.ts +19 -2
  5. package/dist/cli.d.ts.map +1 -1
  6. package/dist/cli.js +261 -64
  7. package/dist/cli.js.map +1 -1
  8. package/dist/draft.d.ts +5 -0
  9. package/dist/draft.d.ts.map +1 -0
  10. package/dist/draft.js +16 -0
  11. package/dist/draft.js.map +1 -0
  12. package/dist/drift.d.ts +10 -7
  13. package/dist/drift.d.ts.map +1 -1
  14. package/dist/drift.js +50 -16
  15. package/dist/drift.js.map +1 -1
  16. package/dist/formatters.d.ts +2 -1
  17. package/dist/formatters.d.ts.map +1 -1
  18. package/dist/formatters.js +15 -4
  19. package/dist/formatters.js.map +1 -1
  20. package/dist/index.d.ts +9 -17
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +10 -17
  23. package/dist/index.js.map +1 -1
  24. package/dist/rules/ast.d.ts +412 -7
  25. package/dist/rules/ast.d.ts.map +1 -1
  26. package/dist/rules/ast.js +1847 -102
  27. package/dist/rules/ast.js.map +1 -1
  28. package/dist/rules/circular-refs.d.ts +6 -0
  29. package/dist/rules/circular-refs.d.ts.map +1 -0
  30. package/dist/rules/circular-refs.js +83 -0
  31. package/dist/rules/circular-refs.js.map +1 -0
  32. package/dist/rules/context-no-surface-types.d.ts.map +1 -1
  33. package/dist/rules/context-no-surface-types.js +59 -3
  34. package/dist/rules/context-no-surface-types.js.map +1 -1
  35. package/dist/rules/contour-exists.d.ts +7 -0
  36. package/dist/rules/contour-exists.d.ts.map +1 -0
  37. package/dist/rules/contour-exists.js +113 -0
  38. package/dist/rules/contour-exists.js.map +1 -0
  39. package/dist/rules/contour-ids.d.ts +10 -0
  40. package/dist/rules/contour-ids.d.ts.map +1 -0
  41. package/dist/rules/contour-ids.js +12 -0
  42. package/dist/rules/contour-ids.js.map +1 -0
  43. package/dist/rules/cross-declarations.d.ts.map +1 -1
  44. package/dist/rules/cross-declarations.js +171 -57
  45. package/dist/rules/cross-declarations.js.map +1 -1
  46. package/dist/rules/dead-internal-trail.d.ts +3 -0
  47. package/dist/rules/dead-internal-trail.d.ts.map +1 -0
  48. package/dist/rules/dead-internal-trail.js +80 -0
  49. package/dist/rules/dead-internal-trail.js.map +1 -0
  50. package/dist/rules/draft-file-marking.d.ts +6 -0
  51. package/dist/rules/draft-file-marking.d.ts.map +1 -0
  52. package/dist/rules/draft-file-marking.js +87 -0
  53. package/dist/rules/draft-file-marking.js.map +1 -0
  54. package/dist/rules/draft-visible-debt.d.ts +12 -0
  55. package/dist/rules/draft-visible-debt.d.ts.map +1 -0
  56. package/dist/rules/draft-visible-debt.js +50 -0
  57. package/dist/rules/draft-visible-debt.js.map +1 -0
  58. package/dist/rules/error-mapping-completeness.d.ts +13 -0
  59. package/dist/rules/error-mapping-completeness.d.ts.map +1 -0
  60. package/dist/rules/error-mapping-completeness.js +160 -0
  61. package/dist/rules/error-mapping-completeness.js.map +1 -0
  62. package/dist/rules/example-valid.d.ts +6 -0
  63. package/dist/rules/example-valid.d.ts.map +1 -0
  64. package/dist/rules/example-valid.js +203 -0
  65. package/dist/rules/example-valid.js.map +1 -0
  66. package/dist/rules/fires-declarations.d.ts +16 -0
  67. package/dist/rules/fires-declarations.d.ts.map +1 -0
  68. package/dist/rules/fires-declarations.js +444 -0
  69. package/dist/rules/fires-declarations.js.map +1 -0
  70. package/dist/rules/implementation-returns-result.d.ts +9 -0
  71. package/dist/rules/implementation-returns-result.d.ts.map +1 -1
  72. package/dist/rules/implementation-returns-result.js +638 -76
  73. package/dist/rules/implementation-returns-result.js.map +1 -1
  74. package/dist/rules/incomplete-accessor-for-standard-op.d.ts +30 -0
  75. package/dist/rules/incomplete-accessor-for-standard-op.d.ts.map +1 -0
  76. package/dist/rules/incomplete-accessor-for-standard-op.js +226 -0
  77. package/dist/rules/incomplete-accessor-for-standard-op.js.map +1 -0
  78. package/dist/rules/incomplete-crud.d.ts +21 -0
  79. package/dist/rules/incomplete-crud.d.ts.map +1 -0
  80. package/dist/rules/incomplete-crud.js +368 -0
  81. package/dist/rules/incomplete-crud.js.map +1 -0
  82. package/dist/rules/index.d.ts +40 -7
  83. package/dist/rules/index.d.ts.map +1 -1
  84. package/dist/rules/index.js +91 -15
  85. package/dist/rules/index.js.map +1 -1
  86. package/dist/rules/intent-propagation.d.ts +3 -0
  87. package/dist/rules/intent-propagation.d.ts.map +1 -0
  88. package/dist/rules/intent-propagation.js +57 -0
  89. package/dist/rules/intent-propagation.js.map +1 -0
  90. package/dist/rules/missing-reconcile.d.ts +3 -0
  91. package/dist/rules/missing-reconcile.d.ts.map +1 -0
  92. package/dist/rules/missing-reconcile.js +44 -0
  93. package/dist/rules/missing-reconcile.js.map +1 -0
  94. package/dist/rules/missing-visibility.d.ts +3 -0
  95. package/dist/rules/missing-visibility.d.ts.map +1 -0
  96. package/dist/rules/missing-visibility.js +63 -0
  97. package/dist/rules/missing-visibility.js.map +1 -0
  98. package/dist/rules/no-direct-impl-in-route.d.ts.map +1 -1
  99. package/dist/rules/no-direct-impl-in-route.js +0 -3
  100. package/dist/rules/no-direct-impl-in-route.js.map +1 -1
  101. package/dist/rules/no-direct-implementation-call.js +1 -1
  102. package/dist/rules/no-direct-implementation-call.js.map +1 -1
  103. package/dist/rules/no-sync-result-assumption.d.ts.map +1 -1
  104. package/dist/rules/no-sync-result-assumption.js +870 -61
  105. package/dist/rules/no-sync-result-assumption.js.map +1 -1
  106. package/dist/rules/no-throw-in-detour-recover.d.ts +3 -0
  107. package/dist/rules/no-throw-in-detour-recover.d.ts.map +1 -0
  108. package/dist/rules/no-throw-in-detour-recover.js +147 -0
  109. package/dist/rules/no-throw-in-detour-recover.js.map +1 -0
  110. package/dist/rules/no-throw-in-detour-target.d.ts +4 -1
  111. package/dist/rules/no-throw-in-detour-target.d.ts.map +1 -1
  112. package/dist/rules/no-throw-in-detour-target.js +6 -3
  113. package/dist/rules/no-throw-in-detour-target.js.map +1 -1
  114. package/dist/rules/no-throw-in-implementation.d.ts +4 -2
  115. package/dist/rules/no-throw-in-implementation.d.ts.map +1 -1
  116. package/dist/rules/no-throw-in-implementation.js +6 -4
  117. package/dist/rules/no-throw-in-implementation.js.map +1 -1
  118. package/dist/rules/on-references-exist.d.ts +14 -0
  119. package/dist/rules/on-references-exist.d.ts.map +1 -0
  120. package/dist/rules/on-references-exist.js +109 -0
  121. package/dist/rules/on-references-exist.js.map +1 -0
  122. package/dist/rules/orphaned-signal.d.ts +3 -0
  123. package/dist/rules/orphaned-signal.d.ts.map +1 -0
  124. package/dist/rules/orphaned-signal.js +67 -0
  125. package/dist/rules/orphaned-signal.js.map +1 -0
  126. package/dist/rules/permit-governance.d.ts +3 -0
  127. package/dist/rules/permit-governance.d.ts.map +1 -0
  128. package/dist/rules/permit-governance.js +15 -0
  129. package/dist/rules/permit-governance.js.map +1 -0
  130. package/dist/rules/reference-exists.d.ts +6 -0
  131. package/dist/rules/reference-exists.d.ts.map +1 -0
  132. package/dist/rules/reference-exists.js +47 -0
  133. package/dist/rules/reference-exists.js.map +1 -0
  134. package/dist/rules/registry-names.d.ts +8 -0
  135. package/dist/rules/registry-names.d.ts.map +1 -0
  136. package/dist/rules/registry-names.js +83 -0
  137. package/dist/rules/registry-names.js.map +1 -0
  138. package/dist/rules/resource-declarations.d.ts +14 -0
  139. package/dist/rules/resource-declarations.d.ts.map +1 -0
  140. package/dist/rules/resource-declarations.js +413 -0
  141. package/dist/rules/resource-declarations.js.map +1 -0
  142. package/dist/rules/resource-exists.d.ts +6 -0
  143. package/dist/rules/resource-exists.d.ts.map +1 -0
  144. package/dist/rules/resource-exists.js +90 -0
  145. package/dist/rules/resource-exists.js.map +1 -0
  146. package/dist/rules/resource-id-grammar.d.ts +3 -0
  147. package/dist/rules/resource-id-grammar.d.ts.map +1 -0
  148. package/dist/rules/resource-id-grammar.js +39 -0
  149. package/dist/rules/resource-id-grammar.js.map +1 -0
  150. package/dist/rules/specs.d.ts.map +1 -1
  151. package/dist/rules/specs.js +5 -1
  152. package/dist/rules/specs.js.map +1 -1
  153. package/dist/rules/types.d.ts +53 -4
  154. package/dist/rules/types.d.ts.map +1 -1
  155. package/dist/rules/unreachable-detour-shadowing.d.ts +3 -0
  156. package/dist/rules/unreachable-detour-shadowing.d.ts.map +1 -0
  157. package/dist/rules/unreachable-detour-shadowing.js +202 -0
  158. package/dist/rules/unreachable-detour-shadowing.js.map +1 -0
  159. package/dist/rules/valid-describe-refs.d.ts.map +1 -1
  160. package/dist/rules/valid-describe-refs.js +132 -16
  161. package/dist/rules/valid-describe-refs.js.map +1 -1
  162. package/dist/rules/valid-detour-contract.d.ts +3 -0
  163. package/dist/rules/valid-detour-contract.d.ts.map +1 -0
  164. package/dist/rules/valid-detour-contract.js +47 -0
  165. package/dist/rules/valid-detour-contract.js.map +1 -0
  166. package/dist/rules/valid-detour-refs.d.ts.map +1 -1
  167. package/dist/rules/valid-detour-refs.js +73 -82
  168. package/dist/rules/valid-detour-refs.js.map +1 -1
  169. package/dist/rules/warden-export-symmetry.d.ts +7 -0
  170. package/dist/rules/warden-export-symmetry.d.ts.map +1 -0
  171. package/dist/rules/warden-export-symmetry.js +352 -0
  172. package/dist/rules/warden-export-symmetry.js.map +1 -0
  173. package/dist/rules/warden-rules-use-ast.d.ts +17 -0
  174. package/dist/rules/warden-rules-use-ast.d.ts.map +1 -0
  175. package/dist/rules/warden-rules-use-ast.js +778 -0
  176. package/dist/rules/warden-rules-use-ast.js.map +1 -0
  177. package/dist/trails/circular-refs.trail.d.ts +24 -0
  178. package/dist/trails/circular-refs.trail.d.ts.map +1 -0
  179. package/dist/trails/circular-refs.trail.js +29 -0
  180. package/dist/trails/circular-refs.trail.js.map +1 -0
  181. package/dist/trails/context-no-surface-types.trail.d.ts +2 -2
  182. package/dist/trails/context-no-surface-types.trail.d.ts.map +1 -1
  183. package/dist/trails/context-no-trailhead-types.trail.d.ts +2 -2
  184. package/dist/trails/context-no-trailhead-types.trail.d.ts.map +1 -1
  185. package/dist/trails/contour-exists.trail.d.ts +24 -0
  186. package/dist/trails/contour-exists.trail.d.ts.map +1 -0
  187. package/dist/trails/contour-exists.trail.js +21 -0
  188. package/dist/trails/contour-exists.trail.js.map +1 -0
  189. package/dist/trails/cross-declarations.trail.d.ts +2 -2
  190. package/dist/trails/cross-declarations.trail.d.ts.map +1 -1
  191. package/dist/trails/dead-internal-trail.trail.d.ts +24 -0
  192. package/dist/trails/dead-internal-trail.trail.d.ts.map +1 -0
  193. package/dist/trails/dead-internal-trail.trail.js +26 -0
  194. package/dist/trails/dead-internal-trail.trail.js.map +1 -0
  195. package/dist/trails/{provision-declarations.trail.d.ts → draft-file-marking.trail.d.ts} +3 -3
  196. package/dist/trails/draft-file-marking.trail.d.ts.map +1 -0
  197. package/dist/trails/draft-file-marking.trail.js +16 -0
  198. package/dist/trails/draft-file-marking.trail.js.map +1 -0
  199. package/dist/trails/draft-visible-debt.trail.d.ts +13 -0
  200. package/dist/trails/draft-visible-debt.trail.d.ts.map +1 -0
  201. package/dist/trails/draft-visible-debt.trail.js +16 -0
  202. package/dist/trails/draft-visible-debt.trail.js.map +1 -0
  203. package/dist/trails/error-mapping-completeness.trail.d.ts +13 -0
  204. package/dist/trails/error-mapping-completeness.trail.d.ts.map +1 -0
  205. package/dist/trails/error-mapping-completeness.trail.js +29 -0
  206. package/dist/trails/error-mapping-completeness.trail.js.map +1 -0
  207. package/dist/trails/{follow-declarations.trail.d.ts → example-valid.trail.d.ts} +3 -3
  208. package/dist/trails/example-valid.trail.d.ts.map +1 -0
  209. package/dist/trails/example-valid.trail.js +25 -0
  210. package/dist/trails/example-valid.trail.js.map +1 -0
  211. package/dist/trails/fires-declarations.trail.d.ts +13 -0
  212. package/dist/trails/fires-declarations.trail.d.ts.map +1 -0
  213. package/dist/trails/fires-declarations.trail.js +22 -0
  214. package/dist/trails/fires-declarations.trail.js.map +1 -0
  215. package/dist/trails/implementation-returns-result.trail.d.ts +2 -2
  216. package/dist/trails/implementation-returns-result.trail.d.ts.map +1 -1
  217. package/dist/trails/incomplete-accessor-for-standard-op.trail.d.ts +12 -0
  218. package/dist/trails/incomplete-accessor-for-standard-op.trail.d.ts.map +1 -0
  219. package/dist/trails/incomplete-accessor-for-standard-op.trail.js +60 -0
  220. package/dist/trails/incomplete-accessor-for-standard-op.trail.js.map +1 -0
  221. package/dist/trails/incomplete-crud.trail.d.ts +24 -0
  222. package/dist/trails/incomplete-crud.trail.d.ts.map +1 -0
  223. package/dist/trails/incomplete-crud.trail.js +39 -0
  224. package/dist/trails/incomplete-crud.trail.js.map +1 -0
  225. package/dist/trails/index.d.ts +29 -7
  226. package/dist/trails/index.d.ts.map +1 -1
  227. package/dist/trails/index.js +28 -6
  228. package/dist/trails/index.js.map +1 -1
  229. package/dist/trails/intent-propagation.trail.d.ts +24 -0
  230. package/dist/trails/intent-propagation.trail.d.ts.map +1 -0
  231. package/dist/trails/intent-propagation.trail.js +30 -0
  232. package/dist/trails/intent-propagation.trail.js.map +1 -0
  233. package/dist/trails/missing-reconcile.trail.d.ts +24 -0
  234. package/dist/trails/missing-reconcile.trail.d.ts.map +1 -0
  235. package/dist/trails/missing-reconcile.trail.js +33 -0
  236. package/dist/trails/missing-reconcile.trail.js.map +1 -0
  237. package/dist/trails/missing-visibility.trail.d.ts +24 -0
  238. package/dist/trails/missing-visibility.trail.d.ts.map +1 -0
  239. package/dist/trails/missing-visibility.trail.js +22 -0
  240. package/dist/trails/missing-visibility.trail.js.map +1 -0
  241. package/dist/trails/no-direct-impl-in-route.trail.d.ts +2 -2
  242. package/dist/trails/no-direct-impl-in-route.trail.d.ts.map +1 -1
  243. package/dist/trails/no-direct-implementation-call.trail.d.ts +2 -2
  244. package/dist/trails/no-direct-implementation-call.trail.d.ts.map +1 -1
  245. package/dist/trails/no-sync-result-assumption.trail.d.ts +2 -2
  246. package/dist/trails/no-sync-result-assumption.trail.d.ts.map +1 -1
  247. package/dist/trails/no-throw-in-detour-recover.trail.d.ts +13 -0
  248. package/dist/trails/no-throw-in-detour-recover.trail.d.ts.map +1 -0
  249. package/dist/trails/no-throw-in-detour-recover.trail.js +24 -0
  250. package/dist/trails/no-throw-in-detour-recover.trail.js.map +1 -0
  251. package/dist/trails/no-throw-in-detour-target.trail.d.ts +13 -3
  252. package/dist/trails/no-throw-in-detour-target.trail.d.ts.map +1 -1
  253. package/dist/trails/no-throw-in-implementation.trail.d.ts +2 -2
  254. package/dist/trails/no-throw-in-implementation.trail.d.ts.map +1 -1
  255. package/dist/trails/on-references-exist.trail.d.ts +24 -0
  256. package/dist/trails/on-references-exist.trail.d.ts.map +1 -0
  257. package/dist/trails/on-references-exist.trail.js +21 -0
  258. package/dist/trails/on-references-exist.trail.js.map +1 -0
  259. package/dist/trails/orphaned-signal.trail.d.ts +24 -0
  260. package/dist/trails/orphaned-signal.trail.d.ts.map +1 -0
  261. package/dist/trails/orphaned-signal.trail.js +36 -0
  262. package/dist/trails/orphaned-signal.trail.js.map +1 -0
  263. package/dist/trails/permit-governance.trail.d.ts +12 -0
  264. package/dist/trails/permit-governance.trail.d.ts.map +1 -0
  265. package/dist/trails/permit-governance.trail.js +47 -0
  266. package/dist/trails/permit-governance.trail.js.map +1 -0
  267. package/dist/trails/prefer-schema-inference.trail.d.ts +2 -2
  268. package/dist/trails/prefer-schema-inference.trail.d.ts.map +1 -1
  269. package/dist/trails/reference-exists.trail.d.ts +24 -0
  270. package/dist/trails/reference-exists.trail.d.ts.map +1 -0
  271. package/dist/trails/reference-exists.trail.js +25 -0
  272. package/dist/trails/reference-exists.trail.js.map +1 -0
  273. package/dist/trails/resource-declarations.trail.d.ts +13 -0
  274. package/dist/trails/resource-declarations.trail.d.ts.map +1 -0
  275. package/dist/trails/{provision-declarations.trail.js → resource-declarations.trail.js} +7 -7
  276. package/dist/trails/resource-declarations.trail.js.map +1 -0
  277. package/dist/trails/resource-exists.trail.d.ts +24 -0
  278. package/dist/trails/resource-exists.trail.d.ts.map +1 -0
  279. package/dist/trails/{provision-exists.trail.js → resource-exists.trail.js} +8 -8
  280. package/dist/trails/resource-exists.trail.js.map +1 -0
  281. package/dist/trails/resource-id-grammar.trail.d.ts +13 -0
  282. package/dist/trails/resource-id-grammar.trail.d.ts.map +1 -0
  283. package/dist/trails/resource-id-grammar.trail.js +38 -0
  284. package/dist/trails/resource-id-grammar.trail.js.map +1 -0
  285. package/dist/trails/run.d.ts +25 -9
  286. package/dist/trails/run.d.ts.map +1 -1
  287. package/dist/trails/run.js +63 -19
  288. package/dist/trails/run.js.map +1 -1
  289. package/dist/trails/schema.d.ts +28 -3
  290. package/dist/trails/schema.d.ts.map +1 -1
  291. package/dist/trails/schema.js +57 -4
  292. package/dist/trails/schema.js.map +1 -1
  293. package/dist/trails/unreachable-detour-shadowing.trail.d.ts +13 -0
  294. package/dist/trails/unreachable-detour-shadowing.trail.d.ts.map +1 -0
  295. package/dist/trails/unreachable-detour-shadowing.trail.js +44 -0
  296. package/dist/trails/unreachable-detour-shadowing.trail.js.map +1 -0
  297. package/dist/trails/valid-describe-refs.trail.d.ts +12 -3
  298. package/dist/trails/valid-describe-refs.trail.d.ts.map +1 -1
  299. package/dist/trails/valid-detour-contract.trail.d.ts +12 -0
  300. package/dist/trails/valid-detour-contract.trail.d.ts.map +1 -0
  301. package/dist/trails/valid-detour-contract.trail.js +66 -0
  302. package/dist/trails/valid-detour-contract.trail.js.map +1 -0
  303. package/dist/trails/valid-detour-refs.trail.d.ts +13 -3
  304. package/dist/trails/valid-detour-refs.trail.d.ts.map +1 -1
  305. package/dist/trails/warden-export-symmetry.trail.d.ts +13 -0
  306. package/dist/trails/warden-export-symmetry.trail.d.ts.map +1 -0
  307. package/dist/trails/warden-export-symmetry.trail.js +16 -0
  308. package/dist/trails/warden-export-symmetry.trail.js.map +1 -0
  309. package/dist/trails/warden-rules-use-ast.trail.d.ts +13 -0
  310. package/dist/trails/warden-rules-use-ast.trail.d.ts.map +1 -0
  311. package/dist/trails/warden-rules-use-ast.trail.js +41 -0
  312. package/dist/trails/warden-rules-use-ast.trail.js.map +1 -0
  313. package/dist/trails/wrap-rule.d.ts +16 -2
  314. package/dist/trails/wrap-rule.d.ts.map +1 -1
  315. package/dist/trails/wrap-rule.js +71 -11
  316. package/dist/trails/wrap-rule.js.map +1 -1
  317. package/package.json +7 -4
  318. package/src/__tests__/ast.test.ts +613 -0
  319. package/src/__tests__/circular-refs.test.ts +121 -0
  320. package/src/__tests__/cli.test.ts +360 -32
  321. package/src/__tests__/contour-exists.test.ts +203 -0
  322. package/src/__tests__/cross-declarations.test.ts +245 -0
  323. package/src/__tests__/dead-internal-trail.test.ts +81 -0
  324. package/src/__tests__/draft-rules-context.test.ts +150 -0
  325. package/src/__tests__/drift.test.ts +75 -5
  326. package/src/__tests__/error-mapping-completeness.test.ts +56 -0
  327. package/src/__tests__/example-valid.test.ts +101 -0
  328. package/src/__tests__/fires-declarations-param-destructure.test.ts +54 -0
  329. package/src/__tests__/fires-declarations.test.ts +652 -0
  330. package/src/__tests__/formatters.test.ts +2 -2
  331. package/src/__tests__/implementation-returns-result.test.ts +1016 -2
  332. package/src/__tests__/incomplete-accessor-for-standard-op.test.ts +337 -0
  333. package/src/__tests__/incomplete-crud.test.ts +498 -0
  334. package/src/__tests__/intent-propagation.test.ts +116 -0
  335. package/src/__tests__/missing-reconcile.test.ts +154 -0
  336. package/src/__tests__/missing-visibility.test.ts +108 -0
  337. package/src/__tests__/no-sync-result-assumption.test.ts +870 -39
  338. package/src/__tests__/no-throw-in-detour-recover.test.ts +93 -0
  339. package/src/__tests__/no-throw-in-implementation.test.ts +88 -0
  340. package/src/__tests__/on-references-exist.test.ts +151 -0
  341. package/src/__tests__/orphaned-signal.test.ts +137 -0
  342. package/src/__tests__/permit-governance.test.ts +66 -0
  343. package/src/__tests__/reference-exists.test.ts +281 -0
  344. package/src/__tests__/resource-declarations.test.ts +448 -0
  345. package/src/__tests__/resource-exists.test.ts +122 -0
  346. package/src/__tests__/resource-id-grammar.test.ts +50 -0
  347. package/src/__tests__/rules.test.ts +17 -77
  348. package/src/__tests__/topo-aware-rule.test.ts +257 -0
  349. package/src/__tests__/trails.test.ts +2 -2
  350. package/src/__tests__/unreachable-detour-shadowing.test.ts +128 -0
  351. package/src/__tests__/valid-describe-refs.test.ts +183 -0
  352. package/src/__tests__/valid-detour-contract.test.ts +86 -0
  353. package/src/__tests__/warden-export-symmetry.test.ts +251 -0
  354. package/src/__tests__/warden-rules-use-ast.test.ts +468 -0
  355. package/src/__tests__/wrap-rule.test.ts +3 -3
  356. package/src/cli.ts +458 -91
  357. package/src/draft.ts +22 -0
  358. package/src/drift.ts +63 -21
  359. package/src/formatters.ts +15 -4
  360. package/src/index.ts +62 -23
  361. package/src/rules/ast.ts +2715 -119
  362. package/src/rules/circular-refs.ts +154 -0
  363. package/src/rules/{context-no-trailhead-types.ts → context-no-surface-types.ts} +72 -12
  364. package/src/rules/contour-exists.ts +251 -0
  365. package/src/rules/contour-ids.ts +15 -0
  366. package/src/rules/cross-declarations.ts +277 -69
  367. package/src/rules/dead-internal-trail.ts +141 -0
  368. package/src/rules/draft-file-marking.ts +160 -0
  369. package/src/rules/draft-visible-debt.ts +87 -0
  370. package/src/rules/error-mapping-completeness.ts +273 -0
  371. package/src/rules/example-valid.ts +401 -0
  372. package/src/rules/fires-declarations.ts +609 -0
  373. package/src/rules/implementation-returns-result.ts +1042 -122
  374. package/src/rules/incomplete-accessor-for-standard-op.ts +315 -0
  375. package/src/rules/incomplete-crud.ts +579 -0
  376. package/src/rules/index.ts +95 -16
  377. package/src/rules/intent-propagation.ts +142 -0
  378. package/src/rules/missing-reconcile.ts +98 -0
  379. package/src/rules/missing-visibility.ts +110 -0
  380. package/src/rules/no-direct-impl-in-route.ts +0 -4
  381. package/src/rules/no-direct-implementation-call.ts +1 -1
  382. package/src/rules/no-sync-result-assumption.ts +1134 -96
  383. package/src/rules/no-throw-in-detour-recover.ts +225 -0
  384. package/src/rules/no-throw-in-implementation.ts +6 -4
  385. package/src/rules/on-references-exist.ts +194 -0
  386. package/src/rules/orphaned-signal.ts +150 -0
  387. package/src/rules/permit-governance.ts +25 -0
  388. package/src/rules/reference-exists.ts +98 -0
  389. package/src/rules/registry-names.ts +83 -0
  390. package/src/rules/{provision-declarations.ts → resource-declarations.ts} +208 -138
  391. package/src/rules/{provision-exists.ts → resource-exists.ts} +48 -51
  392. package/src/rules/resource-id-grammar.ts +65 -0
  393. package/src/rules/specs.ts +5 -1
  394. package/src/rules/types.ts +57 -4
  395. package/src/rules/unreachable-detour-shadowing.ts +375 -0
  396. package/src/rules/valid-describe-refs.ts +160 -32
  397. package/src/rules/valid-detour-contract.ts +78 -0
  398. package/src/rules/warden-export-symmetry.ts +533 -0
  399. package/src/rules/warden-rules-use-ast.ts +996 -0
  400. package/src/trails/circular-refs.trail.ts +29 -0
  401. package/src/trails/{context-no-trailhead-types.trail.ts → context-no-surface-types.trail.ts} +4 -4
  402. package/src/trails/contour-exists.trail.ts +21 -0
  403. package/src/trails/dead-internal-trail.trail.ts +26 -0
  404. package/src/trails/draft-file-marking.trail.ts +16 -0
  405. package/src/trails/draft-visible-debt.trail.ts +16 -0
  406. package/src/trails/error-mapping-completeness.trail.ts +29 -0
  407. package/src/trails/example-valid.trail.ts +25 -0
  408. package/src/trails/fires-declarations.trail.ts +22 -0
  409. package/src/trails/incomplete-accessor-for-standard-op.trail.ts +76 -0
  410. package/src/trails/incomplete-crud.trail.ts +39 -0
  411. package/src/trails/index.ts +40 -7
  412. package/src/trails/intent-propagation.trail.ts +30 -0
  413. package/src/trails/missing-reconcile.trail.ts +33 -0
  414. package/src/trails/missing-visibility.trail.ts +22 -0
  415. package/src/trails/no-throw-in-detour-recover.trail.ts +24 -0
  416. package/src/trails/on-references-exist.trail.ts +21 -0
  417. package/src/trails/orphaned-signal.trail.ts +36 -0
  418. package/src/trails/permit-governance.trail.ts +51 -0
  419. package/src/trails/reference-exists.trail.ts +25 -0
  420. package/src/trails/{provision-declarations.trail.ts → resource-declarations.trail.ts} +6 -6
  421. package/src/trails/{provision-exists.trail.ts → resource-exists.trail.ts} +7 -7
  422. package/src/trails/resource-id-grammar.trail.ts +39 -0
  423. package/src/trails/run.ts +121 -24
  424. package/src/trails/schema.ts +66 -4
  425. package/src/trails/unreachable-detour-shadowing.trail.ts +45 -0
  426. package/src/trails/valid-detour-contract.trail.ts +71 -0
  427. package/src/trails/warden-export-symmetry.trail.ts +16 -0
  428. package/src/trails/warden-rules-use-ast.trail.ts +45 -0
  429. package/src/trails/wrap-rule.ts +104 -12
  430. package/tsconfig.tests.json +10 -0
  431. package/tsconfig.tsbuildinfo +1 -1
  432. package/dist/rules/follow-declarations.d.ts +0 -13
  433. package/dist/rules/follow-declarations.d.ts.map +0 -1
  434. package/dist/rules/follow-declarations.js +0 -264
  435. package/dist/rules/follow-declarations.js.map +0 -1
  436. package/dist/rules/provision-declarations.d.ts +0 -14
  437. package/dist/rules/provision-declarations.d.ts.map +0 -1
  438. package/dist/rules/provision-declarations.js +0 -344
  439. package/dist/rules/provision-declarations.js.map +0 -1
  440. package/dist/rules/provision-exists.d.ts +0 -6
  441. package/dist/rules/provision-exists.d.ts.map +0 -1
  442. package/dist/rules/provision-exists.js +0 -89
  443. package/dist/rules/provision-exists.js.map +0 -1
  444. package/dist/rules/service-declarations.d.ts +0 -16
  445. package/dist/rules/service-declarations.d.ts.map +0 -1
  446. package/dist/rules/service-declarations.js +0 -346
  447. package/dist/rules/service-declarations.js.map +0 -1
  448. package/dist/rules/service-exists.d.ts +0 -8
  449. package/dist/rules/service-exists.d.ts.map +0 -1
  450. package/dist/rules/service-exists.js +0 -91
  451. package/dist/rules/service-exists.js.map +0 -1
  452. package/dist/trails/follow-declarations.trail.d.ts.map +0 -1
  453. package/dist/trails/follow-declarations.trail.js +0 -22
  454. package/dist/trails/follow-declarations.trail.js.map +0 -1
  455. package/dist/trails/provision-declarations.trail.d.ts.map +0 -1
  456. package/dist/trails/provision-declarations.trail.js.map +0 -1
  457. package/dist/trails/provision-exists.trail.d.ts +0 -15
  458. package/dist/trails/provision-exists.trail.d.ts.map +0 -1
  459. package/dist/trails/provision-exists.trail.js.map +0 -1
  460. package/dist/trails/service-declarations.trail.d.ts +0 -26
  461. package/dist/trails/service-declarations.trail.d.ts.map +0 -1
  462. package/dist/trails/service-declarations.trail.js +0 -27
  463. package/dist/trails/service-declarations.trail.js.map +0 -1
  464. package/dist/trails/service-exists.trail.d.ts +0 -32
  465. package/dist/trails/service-exists.trail.d.ts.map +0 -1
  466. package/dist/trails/service-exists.trail.js +0 -29
  467. package/dist/trails/service-exists.trail.js.map +0 -1
  468. package/src/__tests__/no-throw-in-detour-target.test.ts +0 -78
  469. package/src/__tests__/provision-declarations.test.ts +0 -318
  470. package/src/__tests__/provision-exists.test.ts +0 -122
  471. package/src/rules/no-throw-in-detour-target.ts +0 -150
  472. package/src/rules/valid-detour-refs.ts +0 -187
  473. package/src/trails/no-throw-in-detour-target.trail.ts +0 -20
  474. package/src/trails/valid-detour-refs.trail.ts +0 -24
@@ -57,4 +57,187 @@ trail("entity.show", {
57
57
 
58
58
  expect(diagnostics).toHaveLength(0);
59
59
  });
60
+
61
+ test('ignores @see tags embedded in unrelated string literals', () => {
62
+ const code = `
63
+ const docs = "see also @see entity.ghost in the other doc";
64
+
65
+ trail("entity.show", {
66
+ input: z.object({ query: z.string() }),
67
+ blaze: (input) => Result.ok(input),
68
+ })`;
69
+
70
+ const diagnostics = validDescribeRefs.check(code, 'src/entity.ts');
71
+
72
+ expect(diagnostics).toHaveLength(0);
73
+ });
74
+
75
+ test('ignores @see tags inside template-literal payloads that mimic describe calls', () => {
76
+ const code = `
77
+ const example = \`
78
+ trail("entity.show", {
79
+ input: z.object({
80
+ query: z.string().describe("@see entity.ghost"),
81
+ }),
82
+ })\`;
83
+ `;
84
+
85
+ const diagnostics = validDescribeRefs.check(code, 'src/entity.ts');
86
+
87
+ expect(diagnostics).toHaveLength(0);
88
+ });
89
+
90
+ test('flags @see tags inside template-literal describe arguments', () => {
91
+ const code = `
92
+ trail("entity.show", {
93
+ input: z.object({
94
+ query: z.string().describe(\`Search query. @see entity.ghost\`),
95
+ }),
96
+ blaze: (input) => Result.ok(input),
97
+ })`;
98
+
99
+ const diagnostics = validDescribeRefs.check(code, 'src/entity.ts');
100
+
101
+ expect(diagnostics).toHaveLength(1);
102
+ expect(diagnostics[0]?.message).toContain('entity.ghost');
103
+ });
104
+
105
+ describe('template literals with expressions', () => {
106
+ test('detects @see refs with a leading interpolation', () => {
107
+ const code = `
108
+ trail("entity.show", {
109
+ input: z.object({
110
+ query: z.string().describe(\`search for \${query}. @see entity.ghost\`),
111
+ }),
112
+ blaze: (input) => Result.ok(input),
113
+ })`;
114
+
115
+ const diagnostics = validDescribeRefs.check(code, 'src/entity.ts');
116
+
117
+ expect(diagnostics).toHaveLength(1);
118
+ expect(diagnostics[0]?.message).toContain('entity.ghost');
119
+ });
120
+
121
+ test('detects @see refs across quasis separated by an interpolation', () => {
122
+ const code = `
123
+ trail("entity.show", {
124
+ input: z.object({
125
+ query: z.string().describe(\`\${prefix} @see missing.trail\`),
126
+ }),
127
+ blaze: (input) => Result.ok(input),
128
+ })`;
129
+
130
+ const diagnostics = validDescribeRefs.check(code, 'src/entity.ts');
131
+
132
+ expect(diagnostics).toHaveLength(1);
133
+ expect(diagnostics[0]?.message).toContain('missing.trail');
134
+ });
135
+
136
+ test('still detects @see refs across quasis containing escape sequences', () => {
137
+ const code = `
138
+ trail("entity.show", {
139
+ input: z.object({
140
+ query: z.string().describe(\`path\\\\to\\\\docs. @see missing.trail\`),
141
+ }),
142
+ blaze: (input) => Result.ok(input),
143
+ })`;
144
+
145
+ const diagnostics = validDescribeRefs.check(code, 'src/entity.ts');
146
+
147
+ expect(diagnostics).toHaveLength(1);
148
+ expect(diagnostics[0]?.message).toContain('missing.trail');
149
+ });
150
+
151
+ test('does not emit a phantom @see match when interpolation splits the marker', () => {
152
+ const code = `
153
+ trail("entity.show", {
154
+ input: z.object({
155
+ query: z.string().describe(\`@s\${x}ee missing\`),
156
+ }),
157
+ blaze: (input) => Result.ok(input),
158
+ })`;
159
+
160
+ const diagnostics = validDescribeRefs.check(code, 'src/entity.ts');
161
+
162
+ expect(diagnostics).toHaveLength(0);
163
+ });
164
+
165
+ test('does not emit a phantom @see match across multiple interpolations', () => {
166
+ const code = `
167
+ trail("entity.show", {
168
+ input: z.object({
169
+ query: z.string().describe(\`@s\${x}ee \${y} missing\`),
170
+ }),
171
+ blaze: (input) => Result.ok(input),
172
+ })`;
173
+
174
+ const diagnostics = validDescribeRefs.check(code, 'src/entity.ts');
175
+
176
+ expect(diagnostics).toHaveLength(0);
177
+ });
178
+
179
+ test('still matches @see when the marker is fully inside a single quasi', () => {
180
+ const code = `
181
+ trail("entity.show", {
182
+ input: z.object({
183
+ query: z.string().describe(\`@see missing.trail \${x}\`),
184
+ }),
185
+ blaze: (input) => Result.ok(input),
186
+ })`;
187
+
188
+ const diagnostics = validDescribeRefs.check(code, 'src/entity.ts');
189
+
190
+ expect(diagnostics).toHaveLength(1);
191
+ expect(diagnostics[0]?.message).toContain('missing.trail');
192
+ });
193
+
194
+ test('does not diagnose when quasis contain no @see token', () => {
195
+ const code = `
196
+ trail("entity.show", {
197
+ input: z.object({
198
+ query: z.string().describe(\`\${prefix}\${suffix}\`),
199
+ }),
200
+ blaze: (input) => Result.ok(input),
201
+ })`;
202
+
203
+ const diagnostics = validDescribeRefs.check(code, 'src/entity.ts');
204
+
205
+ expect(diagnostics).toHaveLength(0);
206
+ });
207
+ });
208
+
209
+ test('ignores describe calls whose argument is not a string literal', () => {
210
+ const code = `
211
+ const mapFn = (x) => x;
212
+ stream.describe(() => mapFn);
213
+
214
+ trail("entity.show", {
215
+ input: z.object({ query: z.string() }),
216
+ blaze: (input) => Result.ok(input),
217
+ })`;
218
+
219
+ const diagnostics = validDescribeRefs.check(code, 'src/entity.ts');
220
+
221
+ expect(diagnostics).toHaveLength(0);
222
+ });
223
+
224
+ test('does not treat legacy event declarations as valid trail refs', () => {
225
+ const code = `
226
+ event("entity.updated", {
227
+ payload: z.object({ id: z.string() }),
228
+ })
229
+
230
+ trail("entity.show", {
231
+ input: z.object({
232
+ query: z.string().describe("Search query. @see entity.updated"),
233
+ }),
234
+ blaze: (input) => Result.ok(input),
235
+ })`;
236
+
237
+ const diagnostics = validDescribeRefs.check(code, 'src/entity.ts');
238
+
239
+ expect(diagnostics).toHaveLength(1);
240
+ expect(diagnostics[0]?.rule).toBe('valid-describe-refs');
241
+ expect(diagnostics[0]?.message).toContain('entity.updated');
242
+ });
60
243
  });
@@ -0,0 +1,86 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+
3
+ import { ConflictError, Result, topo, trail } from '@ontrails/core';
4
+ import { z } from 'zod';
5
+
6
+ import { validDetourContract } from '../rules/valid-detour-contract.js';
7
+
8
+ const validTrail = trail('entity.save', {
9
+ blaze: () => Result.ok({ ok: true }),
10
+ detours: [
11
+ {
12
+ on: ConflictError,
13
+ recover: () => Result.ok({ ok: true }),
14
+ },
15
+ ],
16
+ input: z.object({}),
17
+ output: z.object({ ok: z.boolean() }),
18
+ });
19
+
20
+ describe('valid-detour-contract', () => {
21
+ test('passes for detours with an error constructor and callable recover', () => {
22
+ const diagnostics = validDetourContract.checkTopo(
23
+ topo('valid-detour-contract', { validTrail })
24
+ );
25
+
26
+ expect(diagnostics).toEqual([]);
27
+ });
28
+
29
+ test('flags detours with a non-constructor on value', () => {
30
+ const malformed = {
31
+ ...validTrail,
32
+ detours: [
33
+ {
34
+ on: 'ConflictError',
35
+ recover: () => Result.ok({ ok: true }),
36
+ },
37
+ ],
38
+ } as typeof validTrail;
39
+
40
+ const diagnostics = validDetourContract.checkTopo(
41
+ topo('invalid-on', { malformed } as Record<string, unknown>)
42
+ );
43
+
44
+ expect(diagnostics).toHaveLength(1);
45
+ expect(diagnostics[0]?.rule).toBe('valid-detour-contract');
46
+ expect(diagnostics[0]?.message).toContain('error constructor');
47
+ });
48
+
49
+ test('flags detours with a non-callable recover value', () => {
50
+ const malformed = {
51
+ ...validTrail,
52
+ detours: [
53
+ {
54
+ on: ConflictError,
55
+ recover: 'not callable',
56
+ },
57
+ ],
58
+ } as typeof validTrail;
59
+
60
+ const diagnostics = validDetourContract.checkTopo(
61
+ topo('invalid-recover', { malformed } as Record<string, unknown>)
62
+ );
63
+
64
+ expect(diagnostics).toHaveLength(1);
65
+ expect(diagnostics[0]?.rule).toBe('valid-detour-contract');
66
+ expect(diagnostics[0]?.message).toContain('callable recover');
67
+ });
68
+
69
+ test('reports both issues when on and recover are malformed', () => {
70
+ const malformed = {
71
+ ...validTrail,
72
+ detours: [
73
+ {
74
+ on: 'ConflictError',
75
+ recover: 'not callable',
76
+ },
77
+ ],
78
+ } as typeof validTrail;
79
+
80
+ const diagnostics = validDetourContract.checkTopo(
81
+ topo('invalid-contract', { malformed } as Record<string, unknown>)
82
+ );
83
+
84
+ expect(diagnostics).toHaveLength(2);
85
+ });
86
+ });
@@ -0,0 +1,251 @@
1
+ import { resolve } from 'node:path';
2
+ import { describe, expect, test } from 'bun:test';
3
+
4
+ import { wardenRules, wardenTopoRules } from '../rules/index.js';
5
+ import { registeredRuleNames } from '../rules/registry-names.js';
6
+ import { wardenExportSymmetry } from '../rules/warden-export-symmetry.js';
7
+
8
+ const SELF_RULE_NAME = 'warden-export-symmetry';
9
+
10
+ // The rule anchors to this package's own on-disk `src/index.ts`. Tests must
11
+ // use the same resolved path so the rule actually engages.
12
+ const TARGET_FILE = resolve(
13
+ Bun.fileURLToPath(new URL('../index.ts', import.meta.url))
14
+ );
15
+ const UNRELATED_FILE = resolve(
16
+ Bun.fileURLToPath(new URL('../cli.ts', import.meta.url))
17
+ );
18
+
19
+ const kebabToCamel = (value: string): string =>
20
+ value.replaceAll(/-([a-z0-9])/g, (_, c: string) => c.toUpperCase());
21
+
22
+ const allRuleNames = [...wardenRules.keys(), ...wardenTopoRules.keys()];
23
+
24
+ const expectedTrailExports = allRuleNames
25
+ .map((name) => `${kebabToCamel(name)}Trail`)
26
+ .toSorted();
27
+
28
+ const [sampleTrailExport = 'missingTrailExportSentinel'] = expectedTrailExports;
29
+ const [sampleRuleName = 'missing-rule-sentinel'] = allRuleNames;
30
+ const sampleRawRuleCamel = kebabToCamel(sampleRuleName);
31
+
32
+ const buildIndexSource = (extraExports = '', skip: readonly string[] = []) => {
33
+ const skipSet = new Set(skip);
34
+ const rendered = expectedTrailExports
35
+ .filter((name) => !skipSet.has(name))
36
+ .map((name) => ` ${name},`)
37
+ .join('\n');
38
+ return `export {
39
+ ${rendered}
40
+ } from './trails/index.js';
41
+ ${extraExports}
42
+ `;
43
+ };
44
+
45
+ describe('warden-export-symmetry', () => {
46
+ describe('scope', () => {
47
+ test("only targets this package's own src/index.ts", () => {
48
+ const source = buildIndexSource();
49
+ const diagnostics = wardenExportSymmetry.check(source, UNRELATED_FILE);
50
+ expect(diagnostics).toEqual([]);
51
+ });
52
+
53
+ test('ignores a foreign packages/warden/src/index.ts in another repo', () => {
54
+ // Simulate a consumer repo that happens to have the same folder
55
+ // structure. A path suffix match would incorrectly engage the rule here
56
+ // and flood the consumer with bogus diagnostics. The rule reads source
57
+ // from its argument, not from disk, so no fs setup is needed — only the
58
+ // path-anchoring check runs against `foreignBarrel`.
59
+ const foreignBarrel = resolve(
60
+ '/tmp/warden-foreign-sim/packages/warden/src/index.ts'
61
+ );
62
+ const diagnostics = wardenExportSymmetry.check(
63
+ `export { ${sampleRawRuleCamel} } from './rules/index.js';\nexport * from './trails/index.js';\nexport default {};\n`,
64
+ foreignBarrel
65
+ );
66
+ expect(diagnostics).toEqual([]);
67
+ });
68
+
69
+ test('baseline: current warden barrel emits zero diagnostics', async () => {
70
+ const realPath = Bun.fileURLToPath(
71
+ new URL('../index.ts', import.meta.url)
72
+ );
73
+ const source = await Bun.file(realPath).text();
74
+ const diagnostics = wardenExportSymmetry.check(source, realPath);
75
+ expect(diagnostics).toEqual([]);
76
+ });
77
+
78
+ test('registry-names snapshot matches the live registry', () => {
79
+ const live = new Set([...wardenRules.keys(), ...wardenTopoRules.keys()]);
80
+ const snapshot = new Set([...registeredRuleNames, SELF_RULE_NAME]);
81
+ const missingFromSnapshot = [...live].filter((n) => !snapshot.has(n));
82
+ const extraInSnapshot = [...snapshot].filter((n) => !live.has(n));
83
+ expect(missingFromSnapshot).toEqual([]);
84
+ expect(extraInSnapshot).toEqual([]);
85
+ });
86
+ });
87
+
88
+ describe('symmetry diagnostics', () => {
89
+ test('fires when a registry entry has no matching trail export', () => {
90
+ const source = buildIndexSource('', [sampleTrailExport]);
91
+ const diagnostics = wardenExportSymmetry.check(source, TARGET_FILE);
92
+ const missing = diagnostics.filter((d) =>
93
+ d.message.includes(`missing trail export "${sampleTrailExport}"`)
94
+ );
95
+ expect(missing.length).toBe(1);
96
+ expect(missing[0]?.severity).toBe('error');
97
+ });
98
+
99
+ test('fires when a *Trail export has no matching registry entry', () => {
100
+ const source = buildIndexSource(
101
+ `export { fictitiousGhostTrail } from './trails/index.js';\n`
102
+ );
103
+ const diagnostics = wardenExportSymmetry.check(source, TARGET_FILE);
104
+ const orphans = diagnostics.filter((d) =>
105
+ d.message.includes('fictitiousGhostTrail')
106
+ );
107
+ expect(orphans.length).toBe(1);
108
+ expect(orphans[0]?.severity).toBe('error');
109
+ });
110
+
111
+ test('fires on orphan *Trail declared via `export const`', () => {
112
+ const source = buildIndexSource(
113
+ `export const fictitiousGhostTrail = {} as unknown;\n`
114
+ );
115
+ const diagnostics = wardenExportSymmetry.check(source, TARGET_FILE);
116
+ const orphans = diagnostics.filter((d) =>
117
+ d.message.includes('fictitiousGhostTrail')
118
+ );
119
+ expect(orphans.length).toBe(1);
120
+ expect(orphans[0]?.severity).toBe('error');
121
+ });
122
+
123
+ test('fires on orphan *Trail declared via destructured object pattern', () => {
124
+ // Regression: `sitesForDeclaration` previously only handled Identifier
125
+ // declarator ids. A destructured `export const { ... } = ...` silently
126
+ // bypassed the orphan-trail check.
127
+ const source = buildIndexSource(
128
+ `export const { fictitiousGhostTrail } = {} as { fictitiousGhostTrail: unknown };\n`
129
+ );
130
+ const diagnostics = wardenExportSymmetry.check(source, TARGET_FILE);
131
+ const orphans = diagnostics.filter((d) =>
132
+ d.message.includes('fictitiousGhostTrail')
133
+ );
134
+ expect(orphans.length).toBe(1);
135
+ expect(orphans[0]?.severity).toBe('error');
136
+ });
137
+
138
+ test('fires on orphan *Trail declared via destructured array pattern', () => {
139
+ const source = buildIndexSource(
140
+ `export const [fictitiousGhostTrail] = [] as unknown as [unknown];\n`
141
+ );
142
+ const diagnostics = wardenExportSymmetry.check(source, TARGET_FILE);
143
+ const orphans = diagnostics.filter((d) =>
144
+ d.message.includes('fictitiousGhostTrail')
145
+ );
146
+ expect(orphans.length).toBe(1);
147
+ expect(orphans[0]?.severity).toBe('error');
148
+ });
149
+ });
150
+
151
+ describe('raw-rule leaks', () => {
152
+ test('fires when a raw rule object is re-exported on the barrel', () => {
153
+ const source = buildIndexSource(
154
+ `export { ${sampleRawRuleCamel} } from './rules/index.js';\n`
155
+ );
156
+ const diagnostics = wardenExportSymmetry.check(source, TARGET_FILE);
157
+ const rawLeaks = diagnostics.filter((d) =>
158
+ d.message.includes(`raw rule export "${sampleRawRuleCamel}"`)
159
+ );
160
+ expect(rawLeaks.length).toBe(1);
161
+ expect(rawLeaks[0]?.severity).toBe('error');
162
+ });
163
+
164
+ test('fires on aliased raw-rule re-exports using the local binding name', () => {
165
+ const source = buildIndexSource(
166
+ `export { ${sampleRawRuleCamel} as disguisedRule } from './rules/index.js';\n`
167
+ );
168
+ const diagnostics = wardenExportSymmetry.check(source, TARGET_FILE);
169
+ const rawLeaks = diagnostics.filter((d) =>
170
+ d.message.includes(`raw rule export "${sampleRawRuleCamel}"`)
171
+ );
172
+ expect(rawLeaks.length).toBe(1);
173
+ expect(rawLeaks[0]?.severity).toBe('error');
174
+ });
175
+
176
+ test('fires on raw-rule leak via destructured object pattern', () => {
177
+ // Regression: destructuring was a silent bypass for raw-rule leaks —
178
+ // `export const { wardenExportSymmetry } = rulesModule` exposed the raw
179
+ // rule object without tripping the check.
180
+ const source = buildIndexSource(
181
+ `export const { ${sampleRawRuleCamel} } = {} as Record<string, unknown>;\n`
182
+ );
183
+ const diagnostics = wardenExportSymmetry.check(source, TARGET_FILE);
184
+ const rawLeaks = diagnostics.filter((d) =>
185
+ d.message.includes(`raw rule export "${sampleRawRuleCamel}"`)
186
+ );
187
+ expect(rawLeaks.length).toBe(1);
188
+ expect(rawLeaks[0]?.severity).toBe('error');
189
+ });
190
+ });
191
+
192
+ describe('forbidden barrel shapes', () => {
193
+ test('fires on namespace re-exports (export * from ...)', () => {
194
+ const source = `${buildIndexSource()}export * from './trails/index.js';\n`;
195
+ const diagnostics = wardenExportSymmetry.check(source, TARGET_FILE);
196
+ const nsReexports = diagnostics.filter((d) =>
197
+ d.message.includes('namespace re-export')
198
+ );
199
+ expect(nsReexports.length).toBe(1);
200
+ expect(nsReexports[0]?.severity).toBe('error');
201
+ // Regression guard: plain `export *` must not mention an ` as ` alias.
202
+ expect(nsReexports[0]?.message).not.toContain(' as ');
203
+ expect(nsReexports[0]?.message).toContain(
204
+ "export * from './trails/index.js'"
205
+ );
206
+ });
207
+
208
+ test('fires on aliased namespace re-exports and preserves the alias', () => {
209
+ const source = `${buildIndexSource()}export * as trailsNs from './trails/index.js';\n`;
210
+ const diagnostics = wardenExportSymmetry.check(source, TARGET_FILE);
211
+ const nsReexports = diagnostics.filter((d) =>
212
+ d.message.includes('namespace re-export')
213
+ );
214
+ expect(nsReexports.length).toBe(1);
215
+ expect(nsReexports[0]?.severity).toBe('error');
216
+ expect(nsReexports[0]?.message).toContain(
217
+ "export * as trailsNs from './trails/index.js'"
218
+ );
219
+ });
220
+
221
+ test('rejects `export default` on the warden barrel', () => {
222
+ const source = `${buildIndexSource()}export default {};\n`;
223
+ const diagnostics = wardenExportSymmetry.check(source, TARGET_FILE);
224
+ const defaults = diagnostics.filter((d) =>
225
+ d.message.includes('default export')
226
+ );
227
+ expect(defaults.length).toBe(1);
228
+ expect(defaults[0]?.severity).toBe('error');
229
+ });
230
+ });
231
+
232
+ describe('type-only namespace re-exports', () => {
233
+ test('allows `export type * from ...`', () => {
234
+ const source = `${buildIndexSource()}export type * from './rules/types.js';\n`;
235
+ const diagnostics = wardenExportSymmetry.check(source, TARGET_FILE);
236
+ const nsReexports = diagnostics.filter((d) =>
237
+ d.message.includes('namespace re-export')
238
+ );
239
+ expect(nsReexports).toEqual([]);
240
+ });
241
+
242
+ test('allows `export type * as ns from ...`', () => {
243
+ const source = `${buildIndexSource()}export type * as types from './rules/types.js';\n`;
244
+ const diagnostics = wardenExportSymmetry.check(source, TARGET_FILE);
245
+ const nsReexports = diagnostics.filter((d) =>
246
+ d.message.includes('namespace re-export')
247
+ );
248
+ expect(nsReexports).toEqual([]);
249
+ });
250
+ });
251
+ });