@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,154 @@
1
+ import {
2
+ collectContourReferenceTargetsByName,
3
+ findContourDefinitions,
4
+ offsetToLine,
5
+ parse,
6
+ } from './ast.js';
7
+ import type { AstNode } from './ast.js';
8
+ import { isTestFile } from './scan.js';
9
+ import type {
10
+ ProjectAwareWardenRule,
11
+ ProjectContext,
12
+ WardenDiagnostic,
13
+ } from './types.js';
14
+
15
+ const mergeReferenceGraphs = (
16
+ localGraph: ReadonlyMap<string, readonly string[]>,
17
+ contextGraph?: ReadonlyMap<string, readonly string[]>
18
+ ): ReadonlyMap<string, readonly string[]> => {
19
+ const merged = new Map<string, Set<string>>();
20
+
21
+ const addTargets = (source: string, targets: readonly string[]): void => {
22
+ const existing = merged.get(source);
23
+ if (existing) {
24
+ for (const target of targets) {
25
+ existing.add(target);
26
+ }
27
+ return;
28
+ }
29
+
30
+ merged.set(source, new Set(targets));
31
+ };
32
+
33
+ for (const [source, targets] of contextGraph ?? []) {
34
+ addTargets(source, targets);
35
+ }
36
+
37
+ for (const [source, targets] of localGraph) {
38
+ addTargets(source, targets);
39
+ }
40
+
41
+ return new Map(
42
+ [...merged.entries()].map(([source, targets]) => [source, [...targets]])
43
+ );
44
+ };
45
+
46
+ const findCyclePath = (
47
+ start: string,
48
+ graph: ReadonlyMap<string, readonly string[]>,
49
+ current = start,
50
+ path: readonly string[] = [start],
51
+ active: ReadonlySet<string> = new Set([start])
52
+ ): readonly string[] | null => {
53
+ for (const target of graph.get(current) ?? []) {
54
+ if (target === start) {
55
+ return [...path, target];
56
+ }
57
+
58
+ if (active.has(target)) {
59
+ continue;
60
+ }
61
+
62
+ const cycle = findCyclePath(
63
+ start,
64
+ graph,
65
+ target,
66
+ [...path, target],
67
+ new Set([...active, target])
68
+ );
69
+ if (cycle) {
70
+ return cycle;
71
+ }
72
+ }
73
+
74
+ return null;
75
+ };
76
+
77
+ const buildCircularReferenceDiagnostic = (
78
+ contourName: string,
79
+ cyclePath: readonly string[],
80
+ filePath: string,
81
+ line: number
82
+ ): WardenDiagnostic => ({
83
+ filePath,
84
+ line,
85
+ message: `Contour "${contourName}" participates in circular contour references: ${cyclePath.join(' -> ')}.`,
86
+ rule: 'circular-refs',
87
+ severity: 'warn',
88
+ });
89
+
90
+ const checkCircularReferences = (
91
+ ast: AstNode,
92
+ sourceCode: string,
93
+ filePath: string,
94
+ graph: ReadonlyMap<string, readonly string[]>
95
+ ): readonly WardenDiagnostic[] => {
96
+ if (isTestFile(filePath)) {
97
+ return [];
98
+ }
99
+
100
+ return findContourDefinitions(ast).flatMap((definition) => {
101
+ const cyclePath = findCyclePath(definition.name, graph);
102
+ if (!cyclePath) {
103
+ return [];
104
+ }
105
+
106
+ return [
107
+ buildCircularReferenceDiagnostic(
108
+ definition.name,
109
+ cyclePath,
110
+ filePath,
111
+ offsetToLine(sourceCode, definition.start)
112
+ ),
113
+ ];
114
+ });
115
+ };
116
+
117
+ /**
118
+ * Warns when contour references form a direct or transitive cycle.
119
+ */
120
+ export const circularRefs: ProjectAwareWardenRule = {
121
+ check(sourceCode: string, filePath: string): readonly WardenDiagnostic[] {
122
+ const ast = parse(filePath, sourceCode);
123
+ if (!ast) {
124
+ return [];
125
+ }
126
+
127
+ const graph = collectContourReferenceTargetsByName(ast);
128
+ return checkCircularReferences(ast, sourceCode, filePath, graph);
129
+ },
130
+ checkWithContext(
131
+ sourceCode: string,
132
+ filePath: string,
133
+ context: ProjectContext
134
+ ): readonly WardenDiagnostic[] {
135
+ const ast = parse(filePath, sourceCode);
136
+ if (!ast) {
137
+ return [];
138
+ }
139
+
140
+ const localGraph = collectContourReferenceTargetsByName(
141
+ ast,
142
+ context.knownContourIds
143
+ );
144
+ return checkCircularReferences(
145
+ ast,
146
+ sourceCode,
147
+ filePath,
148
+ mergeReferenceGraphs(localGraph, context.contourReferencesByName)
149
+ );
150
+ },
151
+ description: 'Warn when contour references form direct or transitive cycles.',
152
+ name: 'circular-refs',
153
+ severity: 'warn',
154
+ };
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Detects imports of trailhead-specific modules and types in trail files.
2
+ * Detects imports of surface-specific modules and types in trail files.
3
3
  *
4
4
  * Uses AST parsing for accurate detection — no false positives from
5
5
  * imports in comments or strings.
@@ -40,6 +40,67 @@ interface ImportSpecifier {
40
40
  readonly imported?: { readonly name?: string };
41
41
  }
42
42
 
43
+ const isBareTrailCallee = (callee: AstNode): boolean => {
44
+ if (callee.type !== 'Identifier') {
45
+ return false;
46
+ }
47
+ return (callee as unknown as { name?: string }).name === 'trail';
48
+ };
49
+
50
+ const isNamespacedTrailCallee = (callee: AstNode): boolean => {
51
+ if (
52
+ callee.type !== 'MemberExpression' &&
53
+ callee.type !== 'StaticMemberExpression'
54
+ ) {
55
+ return false;
56
+ }
57
+ // Skip computed access like `ns[trail]()` — the bracketed expression may
58
+ // resolve to any runtime value, not the `trail` primitive, even when it
59
+ // happens to be an identifier literally named `trail`.
60
+ if ((callee as unknown as { computed?: boolean }).computed === true) {
61
+ return false;
62
+ }
63
+ const prop = (callee as unknown as { property?: AstNode }).property;
64
+ if (prop?.type !== 'Identifier') {
65
+ return false;
66
+ }
67
+ return (prop as unknown as { name?: string }).name === 'trail';
68
+ };
69
+
70
+ /**
71
+ * True when `ast` contains a `trail(...)` call expression — i.e. this file
72
+ * looks like a trail definition. AST-based replacement for the legacy
73
+ * `/\btrail\s*\(/.test(sourceCode)` gate, which fired on string literals,
74
+ * comments, and docstrings.
75
+ *
76
+ * @remarks
77
+ * Both bare-identifier `trail(...)` and namespaced `ns.trail(...)` callees
78
+ * are recognized, so files using either `import { trail }` or
79
+ * `import * as ns from '@ontrails/core'` are detected as trail definitions.
80
+ *
81
+ * The inner `if (found)` guard skips further work in each callback invocation,
82
+ * but the shared `walk` helper in `./ast.ts` exposes no abort mechanism, so
83
+ * the full tree is still traversed once a match is seen. Acceptable: `walk`
84
+ * is cheap and this rule only runs on files that already matched a path
85
+ * filter upstream.
86
+ */
87
+ const hasTrailCall = (ast: AstNode): boolean => {
88
+ let found = false;
89
+ walk(ast, (node) => {
90
+ if (found || node.type !== 'CallExpression') {
91
+ return;
92
+ }
93
+ const { callee } = node as unknown as { callee?: AstNode };
94
+ if (!callee) {
95
+ return;
96
+ }
97
+ if (isBareTrailCallee(callee) || isNamespacedTrailCallee(callee)) {
98
+ found = true;
99
+ }
100
+ });
101
+ return found;
102
+ };
103
+
43
104
  const makeDiag = (
44
105
  filePath: string,
45
106
  sourceCode: string,
@@ -49,7 +110,7 @@ const makeDiag = (
49
110
  filePath,
50
111
  line: offsetToLine(sourceCode, node.start),
51
112
  message,
52
- rule: 'context-no-trailhead-types',
113
+ rule: 'context-no-surface-types',
53
114
  severity: 'error',
54
115
  });
55
116
 
@@ -92,7 +153,7 @@ const checkSpecifiersForSurfaceTypes = (
92
153
  filePath,
93
154
  sourceCode,
94
155
  node,
95
- `Do not import trailhead type "${typeName}" in trail implementation files.`
156
+ `Do not import surface type "${typeName}" in trail implementation files.`
96
157
  );
97
158
  };
98
159
 
@@ -111,7 +172,7 @@ const classifyImport = (
111
172
  filePath,
112
173
  sourceCode,
113
174
  node,
114
- `Do not import from trailhead module "${moduleName}" in trail implementation files.`
175
+ `Do not import from surface module "${moduleName}" in trail implementation files.`
115
176
  );
116
177
  }
117
178
 
@@ -119,18 +180,17 @@ const classifyImport = (
119
180
  };
120
181
 
121
182
  /**
122
- * Detects imports of trailhead-specific types in trail implementation files.
183
+ * Detects imports of surface-specific types in trail implementation files.
123
184
  */
124
- export const contextNoTrailheadTypes: WardenRule = {
185
+ export const contextNoSurfaceTypes: WardenRule = {
125
186
  check(sourceCode: string, filePath: string): readonly WardenDiagnostic[] {
126
- if (!/\btrail\s*\(/.test(sourceCode)) {
127
- return [];
128
- }
129
-
130
187
  const ast = parse(filePath, sourceCode);
131
188
  if (!ast) {
132
189
  return [];
133
190
  }
191
+ if (!hasTrailCall(ast)) {
192
+ return [];
193
+ }
134
194
 
135
195
  const diagnostics: WardenDiagnostic[] = [];
136
196
  walk(ast, (node) => {
@@ -143,8 +203,8 @@ export const contextNoTrailheadTypes: WardenRule = {
143
203
  return diagnostics;
144
204
  },
145
205
  description:
146
- 'Disallow trailhead-specific type imports (Request, Response, McpSession, etc.) in trail implementation files.',
147
- name: 'context-no-trailhead-types',
206
+ 'Disallow surface-specific type imports (Request, Response, McpSession, etc.) in trail implementation files.',
207
+ name: 'context-no-surface-types',
148
208
 
149
209
  severity: 'error',
150
210
  };
@@ -0,0 +1,251 @@
1
+ import {
2
+ buildUserNamespaceContext,
3
+ collectContourDefinitionIds,
4
+ collectImportAliasMap,
5
+ collectNamedContourIds,
6
+ extractFirstStringArg,
7
+ findConfigProperty,
8
+ findTrailDefinitions,
9
+ identifierName,
10
+ isMemberAccessNonComputed,
11
+ isUserNamespaceReceiverAllowed,
12
+ offsetToLine,
13
+ parse,
14
+ deriveContourIdentifierName,
15
+ } from './ast.js';
16
+ import type { AstNode, TrailDefinition, UserNamespaceContext } from './ast.js';
17
+ import { mergeKnownContourIds } from './contour-ids.js';
18
+ import { isTestFile } from './scan.js';
19
+ import type {
20
+ ProjectAwareWardenRule,
21
+ ProjectContext,
22
+ WardenDiagnostic,
23
+ } from './types.js';
24
+
25
+ const isContourCall = (node: AstNode): boolean =>
26
+ node.type === 'CallExpression' &&
27
+ identifierName((node as unknown as { callee?: AstNode }).callee) ===
28
+ 'contour';
29
+
30
+ const getContourElements = (config: AstNode): readonly AstNode[] => {
31
+ const contoursProp = findConfigProperty(config, 'contours');
32
+ if (!contoursProp) {
33
+ return [];
34
+ }
35
+
36
+ const arrayNode = contoursProp.value;
37
+ if (!arrayNode || (arrayNode as AstNode).type !== 'ArrayExpression') {
38
+ return [];
39
+ }
40
+
41
+ const elements = (arrayNode as AstNode)['elements'] as
42
+ | readonly AstNode[]
43
+ | undefined;
44
+ return elements ?? [];
45
+ };
46
+
47
+ /**
48
+ * Resolve `contours.user` to its contour name. When `userNamespace` carries a
49
+ * scope-aware `safeMemberStarts` set, the member access must appear in it —
50
+ * rejecting cases where `contours` is shadowed by a local binding such as a
51
+ * function parameter or `const contours = ...`. Without the set, falls back
52
+ * to the bare name check for backward compatibility.
53
+ */
54
+ const resolveNamespaceMemberContourName = (
55
+ element: AstNode,
56
+ userNamespace: UserNamespaceContext
57
+ ): string | null => {
58
+ if (!isMemberAccessNonComputed(element)) {
59
+ return null;
60
+ }
61
+ const { object, property } = element as unknown as {
62
+ readonly object?: AstNode;
63
+ readonly property?: AstNode;
64
+ };
65
+ const receiver = object ? identifierName(object) : null;
66
+ if (
67
+ !receiver ||
68
+ !isUserNamespaceReceiverAllowed(receiver, element.start, userNamespace)
69
+ ) {
70
+ return null;
71
+ }
72
+ return property ? identifierName(property) : null;
73
+ };
74
+
75
+ const resolveDeclaredContourName = (
76
+ element: AstNode,
77
+ contourIdsByName: ReadonlyMap<string, string>,
78
+ knownContourIds?: ReadonlySet<string>,
79
+ importAliases?: ReadonlyMap<string, string>,
80
+ userNamespace?: UserNamespaceContext
81
+ ): string | null => {
82
+ if (element.type === 'Identifier') {
83
+ const name = identifierName(element);
84
+ return name
85
+ ? deriveContourIdentifierName(
86
+ name,
87
+ contourIdsByName,
88
+ knownContourIds,
89
+ importAliases
90
+ )
91
+ : null;
92
+ }
93
+
94
+ if (userNamespace && userNamespace.bindings.size > 0) {
95
+ const namespaceTarget = resolveNamespaceMemberContourName(
96
+ element,
97
+ userNamespace
98
+ );
99
+ if (namespaceTarget) {
100
+ return namespaceTarget;
101
+ }
102
+ }
103
+
104
+ return isContourCall(element) ? extractFirstStringArg(element) : null;
105
+ };
106
+
107
+ const extractDeclaredContourNames = (
108
+ config: AstNode,
109
+ contourIdsByName: ReadonlyMap<string, string>,
110
+ knownContourIds?: ReadonlySet<string>,
111
+ importAliases?: ReadonlyMap<string, string>,
112
+ userNamespace?: UserNamespaceContext
113
+ ): readonly string[] => [
114
+ ...new Set(
115
+ getContourElements(config).flatMap((element) => {
116
+ const contourName = resolveDeclaredContourName(
117
+ element,
118
+ contourIdsByName,
119
+ knownContourIds,
120
+ importAliases,
121
+ userNamespace
122
+ );
123
+ return contourName ? [contourName] : [];
124
+ })
125
+ ),
126
+ ];
127
+
128
+ const buildMissingContourDiagnostic = (
129
+ trailId: string,
130
+ contourName: string,
131
+ filePath: string,
132
+ line: number
133
+ ): WardenDiagnostic => ({
134
+ filePath,
135
+ line,
136
+ message: `Trail "${trailId}" declares contour "${contourName}" which is not defined in the project.`,
137
+ rule: 'contour-exists',
138
+ severity: 'error',
139
+ });
140
+
141
+ const buildDiagnosticsForDefinition = (
142
+ definition: TrailDefinition,
143
+ sourceCode: string,
144
+ filePath: string,
145
+ knownContourIds: ReadonlySet<string>,
146
+ contourIdsByName: ReadonlyMap<string, string>,
147
+ importAliases: ReadonlyMap<string, string>,
148
+ userNamespace: UserNamespaceContext
149
+ ): readonly WardenDiagnostic[] => {
150
+ if (definition.kind !== 'trail') {
151
+ return [];
152
+ }
153
+
154
+ const line = offsetToLine(sourceCode, definition.start);
155
+ return extractDeclaredContourNames(
156
+ definition.config,
157
+ contourIdsByName,
158
+ knownContourIds,
159
+ importAliases,
160
+ userNamespace
161
+ ).flatMap((contourName) =>
162
+ knownContourIds.has(contourName)
163
+ ? []
164
+ : [
165
+ buildMissingContourDiagnostic(
166
+ definition.id,
167
+ contourName,
168
+ filePath,
169
+ line
170
+ ),
171
+ ]
172
+ );
173
+ };
174
+
175
+ const buildContourDiagnostics = (
176
+ ast: AstNode,
177
+ sourceCode: string,
178
+ filePath: string,
179
+ knownContourIds: ReadonlySet<string>
180
+ ): readonly WardenDiagnostic[] => {
181
+ const contourIdsByName = collectNamedContourIds(ast);
182
+ const importAliases = collectImportAliasMap(ast);
183
+ const userNamespace = buildUserNamespaceContext(ast);
184
+
185
+ return findTrailDefinitions(ast).flatMap((definition) =>
186
+ buildDiagnosticsForDefinition(
187
+ definition,
188
+ sourceCode,
189
+ filePath,
190
+ knownContourIds,
191
+ contourIdsByName,
192
+ importAliases,
193
+ userNamespace
194
+ )
195
+ );
196
+ };
197
+
198
+ const checkContourDeclarations = (
199
+ ast: AstNode,
200
+ sourceCode: string,
201
+ filePath: string,
202
+ knownContourIds: ReadonlySet<string>
203
+ ): readonly WardenDiagnostic[] => {
204
+ if (isTestFile(filePath)) {
205
+ return [];
206
+ }
207
+
208
+ return buildContourDiagnostics(ast, sourceCode, filePath, knownContourIds);
209
+ };
210
+
211
+ /**
212
+ * Checks that every contour declared in a trail `contours` array resolves to a
213
+ * known contour definition.
214
+ */
215
+ export const contourExists: ProjectAwareWardenRule = {
216
+ check(sourceCode: string, filePath: string): readonly WardenDiagnostic[] {
217
+ const ast = parse(filePath, sourceCode);
218
+ if (!ast) {
219
+ return [];
220
+ }
221
+
222
+ return checkContourDeclarations(
223
+ ast,
224
+ sourceCode,
225
+ filePath,
226
+ collectContourDefinitionIds(ast)
227
+ );
228
+ },
229
+ checkWithContext(
230
+ sourceCode: string,
231
+ filePath: string,
232
+ context: ProjectContext
233
+ ): readonly WardenDiagnostic[] {
234
+ const ast = parse(filePath, sourceCode);
235
+ if (!ast) {
236
+ return [];
237
+ }
238
+
239
+ const localContourIds = collectContourDefinitionIds(ast);
240
+ return checkContourDeclarations(
241
+ ast,
242
+ sourceCode,
243
+ filePath,
244
+ mergeKnownContourIds(localContourIds, context.knownContourIds)
245
+ );
246
+ },
247
+ description:
248
+ 'Ensure every contour declared on a trail resolves to a known contour definition.',
249
+ name: 'contour-exists',
250
+ severity: 'error',
251
+ };
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Merge a file's locally-defined contour IDs with the project-wide set.
3
+ *
4
+ * Rules that run with a `ProjectContext` need to treat both local and
5
+ * project-wide contour definitions as "known" so that declarations and
6
+ * references resolve correctly. When no project context is available — e.g.
7
+ * single-file lint runs via `check` — the local set is returned as-is.
8
+ */
9
+ export const mergeKnownContourIds = (
10
+ localContourIds: ReadonlySet<string>,
11
+ projectContourIds?: ReadonlySet<string>
12
+ ): ReadonlySet<string> =>
13
+ projectContourIds
14
+ ? new Set([...projectContourIds, ...localContourIds])
15
+ : localContourIds;