@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
@@ -0,0 +1,203 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+
3
+ import { contourExists } from '../rules/contour-exists.js';
4
+
5
+ const TEST_FILE = 'entity.ts';
6
+
7
+ describe('contour-exists', () => {
8
+ describe('local declarations', () => {
9
+ test('passes when a locally declared contour exists', () => {
10
+ const code = `
11
+ import { Result, contour, trail } from '@ontrails/core';
12
+ import { z } from 'zod';
13
+
14
+ const user = contour('user', {
15
+ id: z.string().uuid(),
16
+ }, { identity: 'id' });
17
+
18
+ trail('user.create', {
19
+ contours: [user],
20
+ blaze: async () => Result.ok({ ok: true }),
21
+ });
22
+ `;
23
+
24
+ expect(contourExists.check(code, TEST_FILE)).toEqual([]);
25
+ });
26
+
27
+ test('keeps local contour declarations when project context is present', () => {
28
+ const code = `
29
+ import { Result, contour, trail } from '@ontrails/core';
30
+ import { z } from 'zod';
31
+
32
+ const user = contour('user', {
33
+ id: z.string().uuid(),
34
+ }, { identity: 'id' });
35
+
36
+ trail('user.create', {
37
+ contours: [user],
38
+ blaze: async () => Result.ok({ ok: true }),
39
+ });
40
+ `;
41
+
42
+ expect(
43
+ contourExists.checkWithContext(code, TEST_FILE, {
44
+ knownContourIds: new Set<string>(),
45
+ knownTrailIds: new Set(['user.create']),
46
+ })
47
+ ).toEqual([]);
48
+ });
49
+ });
50
+
51
+ describe('named imports', () => {
52
+ test('flags a missing contour declaration', () => {
53
+ const code = `
54
+ import { Result, trail } from '@ontrails/core';
55
+ import { user } from './contours';
56
+
57
+ trail('user.create', {
58
+ contours: [user],
59
+ blaze: async () => Result.ok({ ok: true }),
60
+ });
61
+ `;
62
+
63
+ const diagnostics = contourExists.checkWithContext(code, TEST_FILE, {
64
+ knownContourIds: new Set<string>(),
65
+ knownTrailIds: new Set(['user.create']),
66
+ });
67
+
68
+ expect(diagnostics).toHaveLength(1);
69
+ expect(diagnostics[0]?.rule).toBe('contour-exists');
70
+ expect(diagnostics[0]?.message).toContain('user');
71
+ });
72
+
73
+ test('resolves aliased imports to the original contour name', () => {
74
+ const code = `
75
+ import { Result, trail } from '@ontrails/core';
76
+ import { user as userModel } from './contours';
77
+
78
+ trail('user.create', {
79
+ contours: [userModel],
80
+ blaze: async () => Result.ok({ ok: true }),
81
+ });
82
+ `;
83
+
84
+ expect(
85
+ contourExists.checkWithContext(code, TEST_FILE, {
86
+ knownContourIds: new Set(['user']),
87
+ knownTrailIds: new Set(['user.create']),
88
+ })
89
+ ).toEqual([]);
90
+ });
91
+
92
+ test('passes when project context includes an imported contour', () => {
93
+ const code = `
94
+ import { Result, trail } from '@ontrails/core';
95
+ import { user } from './contours';
96
+
97
+ trail('user.create', {
98
+ contours: [user],
99
+ blaze: async () => Result.ok({ ok: true }),
100
+ });
101
+ `;
102
+
103
+ expect(
104
+ contourExists.checkWithContext(code, TEST_FILE, {
105
+ knownContourIds: new Set(['user']),
106
+ knownTrailIds: new Set(['user.create']),
107
+ })
108
+ ).toEqual([]);
109
+ });
110
+ });
111
+
112
+ describe('default imports', () => {
113
+ test('flags missing default-imported contour declarations', () => {
114
+ const code = `
115
+ import { Result, trail } from '@ontrails/core';
116
+ import userModel from './contours';
117
+
118
+ trail('user.create', {
119
+ contours: [userModel],
120
+ blaze: async () => Result.ok({ ok: true }),
121
+ });
122
+ `;
123
+
124
+ const diagnostics = contourExists.checkWithContext(code, TEST_FILE, {
125
+ knownContourIds: new Set<string>(),
126
+ knownTrailIds: new Set(['user.create']),
127
+ });
128
+
129
+ expect(diagnostics).toHaveLength(1);
130
+ expect(diagnostics[0]?.rule).toBe('contour-exists');
131
+ expect(diagnostics[0]?.message).toContain('userModel');
132
+ });
133
+ });
134
+
135
+ describe('namespace imports', () => {
136
+ test('flags missing namespace-imported contour declarations', () => {
137
+ const code = `
138
+ import { Result, trail } from '@ontrails/core';
139
+ import * as contours from './contours';
140
+
141
+ trail('user.create', {
142
+ contours: [contours.user],
143
+ blaze: async () => Result.ok({ ok: true }),
144
+ });
145
+ `;
146
+
147
+ const diagnostics = contourExists.checkWithContext(code, TEST_FILE, {
148
+ knownContourIds: new Set<string>(),
149
+ knownTrailIds: new Set(['user.create']),
150
+ });
151
+
152
+ expect(diagnostics).toHaveLength(1);
153
+ expect(diagnostics[0]?.rule).toBe('contour-exists');
154
+ expect(diagnostics[0]?.message).toContain('user');
155
+ });
156
+
157
+ test('resolves namespace-imported contour declarations when known', () => {
158
+ const code = `
159
+ import { Result, trail } from '@ontrails/core';
160
+ import * as contours from './contours';
161
+
162
+ trail('user.create', {
163
+ contours: [contours.user],
164
+ blaze: async () => Result.ok({ ok: true }),
165
+ });
166
+ `;
167
+
168
+ expect(
169
+ contourExists.checkWithContext(code, TEST_FILE, {
170
+ knownContourIds: new Set(['user']),
171
+ knownTrailIds: new Set(['user.create']),
172
+ })
173
+ ).toEqual([]);
174
+ });
175
+
176
+ test('ignores namespace member access when receiver is shadowed by a local binding', () => {
177
+ const code = `
178
+ import { Result, trail } from '@ontrails/core';
179
+ import * as contours from './contours';
180
+
181
+ function makeTrail() {
182
+ const contours = { user: 'not-a-contour' };
183
+ return trail('user.create', {
184
+ contours: [contours.user],
185
+ blaze: async () => Result.ok({ ok: true }),
186
+ });
187
+ }
188
+
189
+ makeTrail();
190
+ `;
191
+
192
+ // The trail's \`contours: [contours.user]\` refers to a local
193
+ // \`const contours = ...\`, not the namespace import, so no
194
+ // missing-contour diagnostic should be produced.
195
+ expect(
196
+ contourExists.checkWithContext(code, TEST_FILE, {
197
+ knownContourIds: new Set<string>(),
198
+ knownTrailIds: new Set(['user.create']),
199
+ })
200
+ ).toEqual([]);
201
+ });
202
+ });
203
+ });
@@ -25,6 +25,26 @@ const t = trail('onboard', {
25
25
  expect(diagnostics.length).toBe(0);
26
26
  });
27
27
 
28
+ test('resolves batch ctx.cross() calls with string literals', () => {
29
+ const code = `
30
+ trail('onboard', {
31
+ crosses: ['entity.add', 'search'],
32
+ input: z.object({ name: z.string() }),
33
+ blaze: async (input, ctx) => {
34
+ await ctx.cross([
35
+ ['entity.add', { name: input.name }],
36
+ ['search', { query: input.name }],
37
+ ]);
38
+ return Result.ok({});
39
+ },
40
+ });
41
+ `;
42
+
43
+ const diagnostics = crossDeclarations.check(code, TEST_FILE);
44
+
45
+ expect(diagnostics.length).toBe(0);
46
+ });
47
+
28
48
  test('no crosses declaration and no ctx.cross() calls', () => {
29
49
  const code = `
30
50
  trail('simple', {
@@ -61,6 +81,28 @@ trail('onboard', {
61
81
  expect(diagnostics[0]?.message).toContain("ctx.cross('entity.add')");
62
82
  expect(diagnostics[0]?.message).toContain('not declared in crosses');
63
83
  });
84
+
85
+ test('undeclared batch crossings still report an error', () => {
86
+ const code = `
87
+ trail('onboard', {
88
+ crosses: ['entity.add'],
89
+ input: z.object({ name: z.string() }),
90
+ blaze: async (input, ctx) => {
91
+ await ctx.cross([
92
+ ['entity.add', { name: input.name }],
93
+ ['search', { query: input.name }],
94
+ ]);
95
+ return Result.ok({});
96
+ },
97
+ });
98
+ `;
99
+
100
+ const diagnostics = crossDeclarations.check(code, TEST_FILE);
101
+
102
+ expect(diagnostics.length).toBe(1);
103
+ expect(diagnostics[0]?.severity).toBe('error');
104
+ expect(diagnostics[0]?.message).toContain("ctx.cross('search')");
105
+ });
64
106
  });
65
107
 
66
108
  describe('warn cases', () => {
@@ -176,6 +218,87 @@ trail('onboard', {
176
218
 
177
219
  expect(diagnostics.length).toBe(0);
178
220
  });
221
+
222
+ test('blaze with no second parameter: unrelated closure ctx.cross is not tracked', () => {
223
+ const code = `
224
+ import { trail, Result } from '@ontrails/core';
225
+
226
+ const ctx = { cross: () => ({}) };
227
+
228
+ trail('demo', {
229
+ blaze: async () => {
230
+ ctx.cross('entity.add');
231
+ return Result.ok({ ok: true });
232
+ },
233
+ crosses: [],
234
+ });
235
+ `;
236
+
237
+ const diagnostics = crossDeclarations.check(code, TEST_FILE);
238
+ // The blaze has no context parameter, so `ctx` in the body is an
239
+ // unrelated closure-scoped binding, not the trail context. It must
240
+ // not be tracked — no diagnostics.
241
+ expect(diagnostics.length).toBe(0);
242
+ });
243
+
244
+ test('blaze with no second parameter: unrelated closure context.cross is not tracked', () => {
245
+ const code = `
246
+ import { trail, Result } from '@ontrails/core';
247
+
248
+ const context = { cross: () => ({}) };
249
+
250
+ trail('demo', {
251
+ blaze: async () => {
252
+ context.cross('entity.add');
253
+ return Result.ok({ ok: true });
254
+ },
255
+ crosses: [],
256
+ });
257
+ `;
258
+
259
+ const diagnostics = crossDeclarations.check(code, TEST_FILE);
260
+ expect(diagnostics.length).toBe(0);
261
+ });
262
+
263
+ test('real blaze ctx.cross to undeclared target is still flagged', () => {
264
+ const code = `
265
+ import { trail, Result } from '@ontrails/core';
266
+
267
+ trail('demo', {
268
+ blaze: async (_, ctx) => {
269
+ await ctx.cross('undeclared');
270
+ return Result.ok({ ok: true });
271
+ },
272
+ crosses: [],
273
+ });
274
+ `;
275
+
276
+ const diagnostics = crossDeclarations.check(code, TEST_FILE);
277
+ expect(diagnostics.length).toBe(1);
278
+ expect(diagnostics[0]?.severity).toBe('error');
279
+ expect(diagnostics[0]?.message).toContain("ctx.cross('undeclared')");
280
+ });
281
+
282
+ test('defaulted context param is detected (AssignmentPattern)', () => {
283
+ const code = `
284
+ import { trail, Result } from '@ontrails/core';
285
+
286
+ const fallbackCtx = { cross: async () => Result.ok({}) };
287
+
288
+ trail('demo', {
289
+ blaze: async (_input, ctx = fallbackCtx) => {
290
+ await ctx.cross('undeclared');
291
+ return Result.ok({ ok: true });
292
+ },
293
+ crosses: [],
294
+ });
295
+ `;
296
+
297
+ const diagnostics = crossDeclarations.check(code, TEST_FILE);
298
+ expect(diagnostics.length).toBe(1);
299
+ expect(diagnostics[0]?.severity).toBe('error');
300
+ expect(diagnostics[0]?.message).toContain("ctx.cross('undeclared')");
301
+ });
179
302
  });
180
303
 
181
304
  describe('nested run false positives', () => {
@@ -237,6 +360,128 @@ trail('onboard', {
237
360
  });
238
361
  });
239
362
 
363
+ describe('trail object references in crosses', () => {
364
+ test('unresolvable identifier in crosses softens undeclared to warn', () => {
365
+ const code = `
366
+ import { showGist } from '../gist/show';
367
+ trail('gist.fork', {
368
+ crosses: [showGist],
369
+ input: z.object({ id: z.string() }),
370
+ blaze: async (input, ctx) => {
371
+ await ctx.cross('gist.create', { id: input.id });
372
+ return Result.ok({});
373
+ },
374
+ });
375
+ `;
376
+
377
+ const diagnostics = crossDeclarations.check(code, TEST_FILE);
378
+
379
+ // 'gist.create' called but can't prove showGist doesn't cover it
380
+ expect(diagnostics.length).toBe(1);
381
+ expect(diagnostics[0]?.severity).toBe('warn');
382
+ expect(diagnostics[0]?.message).toContain('trail object references');
383
+ });
384
+
385
+ test('mixed string and trail object references: resolved string still validated', () => {
386
+ const code = `
387
+ import { showGist } from '../gist/show';
388
+ trail('gist.fork', {
389
+ crosses: ['gist.create', showGist],
390
+ input: z.object({ id: z.string() }),
391
+ blaze: async (input, ctx) => {
392
+ await ctx.cross('gist.create', { id: input.id });
393
+ return Result.ok({});
394
+ },
395
+ });
396
+ `;
397
+
398
+ const diagnostics = crossDeclarations.check(code, TEST_FILE);
399
+
400
+ // gist.create declared and called — clean. showGist unresolved but declared, not called by string — no unused warning for unresolved entries.
401
+ expect(diagnostics.length).toBe(0);
402
+ });
403
+
404
+ test('trail object only in crosses with no string cross calls is clean', () => {
405
+ const code = `
406
+ import { showGist } from '../gist/show';
407
+ trail('gist.fork', {
408
+ crosses: [showGist],
409
+ input: z.object({ id: z.string() }),
410
+ blaze: async (input, ctx) => {
411
+ return Result.ok({});
412
+ },
413
+ });
414
+ `;
415
+
416
+ const diagnostics = crossDeclarations.check(code, TEST_FILE);
417
+
418
+ // No string-resolved IDs and no string cross calls — clean
419
+ expect(diagnostics.length).toBe(0);
420
+ });
421
+ });
422
+
423
+ describe('typed ctx.cross(trailObj) calls', () => {
424
+ test('typed cross call with trail object does not produce undeclared error', () => {
425
+ const code = `
426
+ import { showGist } from '../gist/show';
427
+ trail('gist.fork', {
428
+ crosses: [showGist],
429
+ input: z.object({ id: z.string() }),
430
+ blaze: async (input, ctx) => {
431
+ await ctx.cross(showGist, { id: input.id });
432
+ return Result.ok({});
433
+ },
434
+ });
435
+ `;
436
+
437
+ const diagnostics = crossDeclarations.check(code, TEST_FILE);
438
+
439
+ expect(diagnostics.length).toBe(0);
440
+ });
441
+
442
+ test('typed cross call suppresses unused-declaration warning for matching entry', () => {
443
+ const code = `
444
+ import { showGist } from '../gist/show';
445
+ trail('gist.fork', {
446
+ crosses: ['gist.create', showGist],
447
+ input: z.object({ id: z.string() }),
448
+ blaze: async (input, ctx) => {
449
+ await ctx.cross('gist.create', { id: input.id });
450
+ await ctx.cross(showGist, { id: input.id });
451
+ return Result.ok({});
452
+ },
453
+ });
454
+ `;
455
+
456
+ const diagnostics = crossDeclarations.check(code, TEST_FILE);
457
+
458
+ // showGist is unresolvable in crosses but the typed cross call covers it
459
+ expect(diagnostics.length).toBe(0);
460
+ });
461
+
462
+ test('undeclared string cross alongside typed cross still reports error (softened)', () => {
463
+ const code = `
464
+ import { showGist } from '../gist/show';
465
+ trail('gist.fork', {
466
+ crosses: [showGist],
467
+ input: z.object({ id: z.string() }),
468
+ blaze: async (input, ctx) => {
469
+ await ctx.cross(showGist, { id: input.id });
470
+ await ctx.cross('undeclared.trail', { id: input.id });
471
+ return Result.ok({});
472
+ },
473
+ });
474
+ `;
475
+
476
+ const diagnostics = crossDeclarations.check(code, TEST_FILE);
477
+
478
+ // 'undeclared.trail' not declared — softened because showGist is unresolvable
479
+ expect(diagnostics.length).toBe(1);
480
+ expect(diagnostics[0]?.severity).toBe('warn');
481
+ expect(diagnostics[0]?.message).toContain('undeclared.trail');
482
+ });
483
+ });
484
+
240
485
  describe('edge cases', () => {
241
486
  test('dynamic cross IDs are skipped', () => {
242
487
  const code = `
@@ -0,0 +1,81 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+
3
+ import { deadInternalTrail } from '../rules/dead-internal-trail.js';
4
+
5
+ const TEST_FILE = 'entity.ts';
6
+
7
+ describe('dead-internal-trail', () => {
8
+ test('warns when an internal trail is never crossed and has no on: activation', () => {
9
+ const code = `
10
+ trail('entity.sync', {
11
+ visibility: 'internal',
12
+ blaze: async () => Result.ok({}),
13
+ });
14
+ `;
15
+
16
+ const diagnostics = deadInternalTrail.check(code, TEST_FILE);
17
+
18
+ expect(diagnostics).toHaveLength(1);
19
+ expect(diagnostics[0]?.rule).toBe('dead-internal-trail');
20
+ expect(diagnostics[0]?.severity).toBe('warn');
21
+ expect(diagnostics[0]?.message).toContain('entity.sync');
22
+ });
23
+
24
+ test('stays quiet when another trail crosses the internal trail in the same file', () => {
25
+ const code = `
26
+ trail('entity.public', {
27
+ crosses: ['entity.sync'],
28
+ blaze: async (_input, ctx) => ctx.cross('entity.sync', {}),
29
+ });
30
+
31
+ trail('entity.sync', {
32
+ visibility: 'internal',
33
+ blaze: async () => Result.ok({}),
34
+ });
35
+ `;
36
+
37
+ expect(deadInternalTrail.check(code, TEST_FILE)).toEqual([]);
38
+ });
39
+
40
+ test('stays quiet when project context marks the trail as crossed elsewhere', () => {
41
+ const code = `
42
+ trail('entity.sync', {
43
+ visibility: 'internal',
44
+ blaze: async () => Result.ok({}),
45
+ });
46
+ `;
47
+
48
+ const diagnostics = deadInternalTrail.checkWithContext(code, TEST_FILE, {
49
+ crossTargetTrailIds: new Set(['entity.sync']),
50
+ knownTrailIds: new Set(['entity.public', 'entity.sync']),
51
+ });
52
+
53
+ expect(diagnostics).toEqual([]);
54
+ });
55
+
56
+ test('stays quiet when the internal trail has on: activation', () => {
57
+ const code = `
58
+ trail('entity.audit', {
59
+ visibility: 'internal',
60
+ on: ['entity.created'],
61
+ blaze: async () => Result.ok({}),
62
+ });
63
+ `;
64
+
65
+ expect(deadInternalTrail.check(code, TEST_FILE)).toEqual([]);
66
+ });
67
+
68
+ test('stays quiet when on: is a module-level identifier reference', () => {
69
+ const code = `
70
+ const activationSignals = ['entity.created', 'entity.updated'];
71
+
72
+ trail('entity.audit', {
73
+ visibility: 'internal',
74
+ on: activationSignals,
75
+ blaze: async () => Result.ok({}),
76
+ });
77
+ `;
78
+
79
+ expect(deadInternalTrail.check(code, TEST_FILE)).toEqual([]);
80
+ });
81
+ });
@@ -0,0 +1,150 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+ import { resolve } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+
5
+ import { draftFileMarking } from '../rules/draft-file-marking.js';
6
+ import { draftVisibleDebt } from '../rules/draft-visible-debt.js';
7
+
8
+ /**
9
+ * Absolute paths to the two real framework files that define the draft-prefix
10
+ * constants. The exemption in `collectFrameworkDraftPrefixConstantOffsets` is
11
+ * keyed on absolute-path equality against these two paths.
12
+ */
13
+ const CORE_DRAFT_PATH = resolve(
14
+ fileURLToPath(new URL('../../../core/src/draft.ts', import.meta.url))
15
+ );
16
+ const WARDEN_DRAFT_PATH = resolve(
17
+ fileURLToPath(new URL('../draft.ts', import.meta.url))
18
+ );
19
+
20
+ /** Any file outside the two framework files — exemption must NOT apply here. */
21
+ const NORMAL_FILE = 'packages/example/src/ordinary.ts';
22
+
23
+ describe('draft-file-marking context-awareness', () => {
24
+ test('ignores framework DRAFT_ID_PREFIX in packages/core/src/draft.ts', () => {
25
+ const code = `export const DRAFT_ID_PREFIX = '_draft.';\n`;
26
+ expect(draftFileMarking.check(code, CORE_DRAFT_PATH)).toEqual([]);
27
+ });
28
+
29
+ test('ignores framework DRAFT_FILE_PREFIX in packages/warden/src/draft.ts', () => {
30
+ const code = `export const DRAFT_FILE_PREFIX = '_draft.';\n`;
31
+ expect(draftFileMarking.check(code, WARDEN_DRAFT_PATH)).toEqual([]);
32
+ });
33
+
34
+ test('fires when DRAFT_ID_PREFIX is reused in a non-framework file (false-negative closed)', () => {
35
+ // Before TRL-334 follow-up: identifier-name-only match silently suppressed
36
+ // this as "framework declaration". After: the path gate rejects the
37
+ // consumer file and the leak fires.
38
+ const code = `const DRAFT_ID_PREFIX = '_draft.user-leak';\n`;
39
+ const diagnostics = draftFileMarking.check(code, NORMAL_FILE);
40
+ expect(diagnostics.length).toBe(1);
41
+ expect(diagnostics[0]?.severity).toBe('error');
42
+ });
43
+
44
+ test('fires when DRAFT_FILE_PREFIX is reused in a non-framework file', () => {
45
+ const code = `const DRAFT_FILE_PREFIX = '_draft.other-leak';\n`;
46
+ const diagnostics = draftFileMarking.check(code, NORMAL_FILE);
47
+ expect(diagnostics.length).toBe(1);
48
+ expect(diagnostics[0]?.severity).toBe('error');
49
+ });
50
+
51
+ test('fires when framework file declares the constant with a non-_draft. value (value invariant)', () => {
52
+ // Same file path, same identifier, but wrong literal — exemption must
53
+ // require the exact '_draft.' value.
54
+ const code = `export const DRAFT_ID_PREFIX = '_draft.something-else';\n`;
55
+ const diagnostics = draftFileMarking.check(code, CORE_DRAFT_PATH);
56
+ expect(diagnostics.length).toBe(1);
57
+ expect(diagnostics[0]?.severity).toBe('error');
58
+ });
59
+
60
+ test('still flags draft ids in arbitrary const declarations', () => {
61
+ const code = `const something = '_draft.foo';\n`;
62
+ const diagnostics = draftFileMarking.check(code, NORMAL_FILE);
63
+ expect(diagnostics.length).toBe(1);
64
+ expect(diagnostics[0]?.severity).toBe('error');
65
+ });
66
+
67
+ test('warden-ignore-next-line pragma suppresses diagnostic', () => {
68
+ const code = `// warden-ignore-next-line\nconst x = '_draft.intentional';\n`;
69
+ expect(draftFileMarking.check(code, NORMAL_FILE)).toEqual([]);
70
+ });
71
+
72
+ test('pragma with trailing whitespace still suppresses diagnostic', () => {
73
+ const code = `// warden-ignore-next-line \nconst x = '_draft.intentional';\n`;
74
+ expect(draftFileMarking.check(code, NORMAL_FILE)).toEqual([]);
75
+ });
76
+
77
+ test('pragma with blank line between does not suppress', () => {
78
+ const code = `// warden-ignore-next-line\n\nconst x = '_draft.intentional';\n`;
79
+ const diagnostics = draftFileMarking.check(code, NORMAL_FILE);
80
+ expect(diagnostics.length).toBe(1);
81
+ });
82
+
83
+ test('does not falsely flag "marked without ids" when all draft ids are pragma-suppressed', () => {
84
+ // In a draft-marked file, pragma-suppressed draft ids still justify the
85
+ // filename marker — the user intentionally silenced them, not removed
86
+ // them. The "marked without ids" cleanup nudge must not fire.
87
+ const code = `// warden-ignore-next-line\nconst x = '_draft.intentional';\n`;
88
+ const diagnostics = draftFileMarking.check(
89
+ code,
90
+ 'packages/example/src/thing._draft.ts'
91
+ );
92
+ expect(diagnostics).toEqual([]);
93
+ });
94
+ });
95
+
96
+ describe('draft-visible-debt context-awareness', () => {
97
+ test('ignores framework DRAFT_ID_PREFIX in packages/core/src/draft.ts', () => {
98
+ const code = `export const DRAFT_ID_PREFIX = '_draft.';\n`;
99
+ expect(draftVisibleDebt.check(code, CORE_DRAFT_PATH)).toEqual([]);
100
+ });
101
+
102
+ test('ignores framework DRAFT_FILE_PREFIX in packages/warden/src/draft.ts', () => {
103
+ const code = `export const DRAFT_FILE_PREFIX = '_draft.';\n`;
104
+ expect(draftVisibleDebt.check(code, WARDEN_DRAFT_PATH)).toEqual([]);
105
+ });
106
+
107
+ test('fires when DRAFT_ID_PREFIX is reused in a non-framework file (false-negative closed)', () => {
108
+ const code = `const DRAFT_ID_PREFIX = '_draft.user-leak';\n`;
109
+ const diagnostics = draftVisibleDebt.check(code, '_draft.something.ts');
110
+ expect(diagnostics.length).toBe(1);
111
+ expect(diagnostics[0]?.severity).toBe('warn');
112
+ });
113
+
114
+ test('fires when DRAFT_FILE_PREFIX is reused in a non-framework file', () => {
115
+ const code = `const DRAFT_FILE_PREFIX = '_draft.other-leak';\n`;
116
+ const diagnostics = draftVisibleDebt.check(code, '_draft.something.ts');
117
+ expect(diagnostics.length).toBe(1);
118
+ expect(diagnostics[0]?.severity).toBe('warn');
119
+ });
120
+
121
+ test('fires when framework file declares the constant with a non-_draft. value (value invariant)', () => {
122
+ const code = `export const DRAFT_ID_PREFIX = '_draft.something-else';\n`;
123
+ const diagnostics = draftVisibleDebt.check(code, CORE_DRAFT_PATH);
124
+ expect(diagnostics.length).toBe(1);
125
+ expect(diagnostics[0]?.severity).toBe('warn');
126
+ });
127
+
128
+ test('still flags draft ids in arbitrary const declarations', () => {
129
+ const code = `const something = '_draft.foo';\n`;
130
+ const diagnostics = draftVisibleDebt.check(code, '_draft.something.ts');
131
+ expect(diagnostics.length).toBe(1);
132
+ expect(diagnostics[0]?.severity).toBe('warn');
133
+ });
134
+
135
+ test('warden-ignore-next-line pragma suppresses diagnostic', () => {
136
+ const code = `// warden-ignore-next-line\nconst x = '_draft.intentional';\n`;
137
+ expect(draftVisibleDebt.check(code, '_draft.something.ts')).toEqual([]);
138
+ });
139
+
140
+ test('pragma with trailing whitespace still suppresses diagnostic', () => {
141
+ const code = `// warden-ignore-next-line \nconst x = '_draft.intentional';\n`;
142
+ expect(draftVisibleDebt.check(code, '_draft.something.ts')).toEqual([]);
143
+ });
144
+
145
+ test('pragma with blank line between does not suppress', () => {
146
+ const code = `// warden-ignore-next-line\n\nconst x = '_draft.intentional';\n`;
147
+ const diagnostics = draftVisibleDebt.check(code, '_draft.something.ts');
148
+ expect(diagnostics.length).toBe(1);
149
+ });
150
+ });