@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
package/src/cli.ts CHANGED
@@ -8,20 +8,29 @@
8
8
  import { resolve } from 'node:path';
9
9
 
10
10
  import type { Topo } from '@ontrails/core';
11
+ import { getContourReferences } from '@ontrails/core';
11
12
 
12
13
  import type { DriftResult } from './drift.js';
13
14
  import { checkDrift } from './drift.js';
14
15
  import {
15
- collectProvisionDefinitionIds,
16
- findConfigProperty,
16
+ collectContourDefinitionIds,
17
+ collectContourReferenceTargetsByName,
18
+ collectCrudTableIds as collectCrudTableIdsFromAst,
19
+ collectCrossTargetTrailIds,
20
+ collectOnTargetSignalIds as collectOnTargetSignalIdsFromAst,
21
+ collectReconcileTableIds as collectReconcileTableIdsFromAst,
22
+ collectResourceDefinitionIds,
23
+ collectSignalDefinitionIds,
24
+ collectTrailIntentsById,
17
25
  findTrailDefinitions,
18
26
  parse,
19
- walk,
20
27
  } from './rules/ast.js';
21
- import { wardenRules } from './rules/index.js';
28
+ import { collectFileCrudCoverage } from './rules/incomplete-crud.js';
29
+ import { wardenRules, wardenTopoRules } from './rules/index.js';
22
30
  import type {
23
31
  ProjectAwareWardenRule,
24
32
  ProjectContext,
33
+ TopoAwareWardenRule,
25
34
  WardenDiagnostic,
26
35
  WardenRule,
27
36
  } from './rules/types.js';
@@ -36,8 +45,25 @@ export interface WardenOptions {
36
45
  readonly lintOnly?: boolean | undefined;
37
46
  /** Only run drift detection, skip lint rules */
38
47
  readonly driftOnly?: boolean | undefined;
39
- /** App topology for drift detection. When provided, enables real trailhead lock comparison. */
48
+ /**
49
+ * App topology for drift detection. When provided, enables real trailhead
50
+ * lock comparison and unlocks the topo-aware rule dispatch path.
51
+ *
52
+ * @remarks
53
+ * Topo-aware rules (both built-in `wardenTopoRules` and `extraTopoRules`)
54
+ * only fire when a `Topo` is supplied. Runs without a topo silently skip
55
+ * topo-aware dispatch — callers that depend on a topo-aware rule firing
56
+ * must pass `topo` explicitly.
57
+ */
40
58
  readonly topo?: Topo | undefined;
59
+ /**
60
+ * Extra topo-aware rules to run in addition to the built-in registry.
61
+ *
62
+ * Primarily a test hook — production callers should register rules via
63
+ * `wardenTopoRules` in `rules/index.ts`. These rules are only invoked
64
+ * when `topo` is also supplied (see `topo` remarks).
65
+ */
66
+ readonly extraTopoRules?: readonly TopoAwareWardenRule[] | undefined;
41
67
  }
42
68
 
43
69
  /**
@@ -92,6 +118,103 @@ interface SourceFile {
92
118
  readonly sourceCode: string;
93
119
  }
94
120
 
121
+ interface MutableProjectContext {
122
+ contourReferencesByName: Map<string, Set<string>>;
123
+ crudTableIds: Set<string>;
124
+ crossTargetTrailIds: Set<string>;
125
+ crudCoverageByEntity: Map<string, Set<string>>;
126
+ knownContourIds: Set<string>;
127
+ knownResourceIds: Set<string>;
128
+ knownSignalIds: Set<string>;
129
+ knownTrailIds: Set<string>;
130
+ onTargetSignalIds: Set<string>;
131
+ reconcileTableIds: Set<string>;
132
+ trailIntentsById: Map<string, 'destroy' | 'read' | 'write'>;
133
+ }
134
+
135
+ const createMutableProjectContext = (): MutableProjectContext => ({
136
+ contourReferencesByName: new Map<string, Set<string>>(),
137
+ crossTargetTrailIds: new Set<string>(),
138
+ crudCoverageByEntity: new Map<string, Set<string>>(),
139
+ crudTableIds: new Set<string>(),
140
+ knownContourIds: new Set<string>(),
141
+ knownResourceIds: new Set<string>(),
142
+ knownSignalIds: new Set<string>(),
143
+ knownTrailIds: new Set<string>(),
144
+ onTargetSignalIds: new Set<string>(),
145
+ reconcileTableIds: new Set<string>(),
146
+ trailIntentsById: new Map<string, 'destroy' | 'read' | 'write'>(),
147
+ });
148
+
149
+ const addContourReferenceTargets = (
150
+ context: MutableProjectContext,
151
+ contourName: string,
152
+ targets: readonly string[]
153
+ ): void => {
154
+ const existing = context.contourReferencesByName.get(contourName);
155
+ if (existing) {
156
+ for (const target of targets) {
157
+ existing.add(target);
158
+ }
159
+ return;
160
+ }
161
+
162
+ context.contourReferencesByName.set(contourName, new Set(targets));
163
+ };
164
+
165
+ const toProjectContext = (context: MutableProjectContext): ProjectContext => ({
166
+ ...(context.contourReferencesByName.size > 0
167
+ ? {
168
+ contourReferencesByName: new Map(
169
+ [...context.contourReferencesByName.entries()].map(
170
+ ([name, targets]) => [name, [...targets]]
171
+ )
172
+ ),
173
+ }
174
+ : {}),
175
+ ...(context.crudTableIds.size > 0
176
+ ? { crudTableIds: context.crudTableIds }
177
+ : {}),
178
+ ...(context.crudCoverageByEntity.size > 0
179
+ ? {
180
+ crudCoverageByEntity: new Map(
181
+ [...context.crudCoverageByEntity.entries()].map(
182
+ ([entityId, operations]) => [
183
+ entityId,
184
+ new Set(operations) as ReadonlySet<string>,
185
+ ]
186
+ )
187
+ ),
188
+ }
189
+ : {}),
190
+ crossTargetTrailIds: context.crossTargetTrailIds,
191
+ knownContourIds: context.knownContourIds,
192
+ knownResourceIds: context.knownResourceIds,
193
+ knownSignalIds: context.knownSignalIds,
194
+ knownTrailIds: context.knownTrailIds,
195
+ ...(context.onTargetSignalIds.size > 0
196
+ ? { onTargetSignalIds: context.onTargetSignalIds }
197
+ : {}),
198
+ ...(context.reconcileTableIds.size > 0
199
+ ? { reconcileTableIds: context.reconcileTableIds }
200
+ : {}),
201
+ trailIntentsById: context.trailIntentsById,
202
+ });
203
+
204
+ const collectKnownContourIds = (
205
+ sourceCode: string,
206
+ filePath: string,
207
+ knownContourIds: Set<string>
208
+ ): void => {
209
+ const ast = parse(filePath, sourceCode);
210
+ if (!ast) {
211
+ return;
212
+ }
213
+ for (const id of collectContourDefinitionIds(ast)) {
214
+ knownContourIds.add(id);
215
+ }
216
+ };
217
+
95
218
  const collectKnownTrailIds = (
96
219
  sourceCode: string,
97
220
  filePath: string,
@@ -106,44 +229,122 @@ const collectKnownTrailIds = (
106
229
  }
107
230
  };
108
231
 
109
- const collectDetourTargetTrailIds = (
232
+ const collectCrossedTrailIds = (
110
233
  sourceCode: string,
111
234
  filePath: string,
112
- detourTargetTrailIds: Set<string>
235
+ crossTargetTrailIds: Set<string>
113
236
  ): void => {
114
237
  const ast = parse(filePath, sourceCode);
115
238
  if (!ast) {
116
239
  return;
117
240
  }
118
- for (const def of findTrailDefinitions(ast)) {
119
- const detoursProp = findConfigProperty(def.config, 'detours');
120
- if (!detoursProp) {
121
- continue;
241
+ for (const id of collectCrossTargetTrailIds(ast, sourceCode)) {
242
+ crossTargetTrailIds.add(id);
243
+ }
244
+ };
245
+
246
+ const collectKnownResourceIds = (
247
+ sourceCode: string,
248
+ filePath: string,
249
+ knownResourceIds: Set<string>
250
+ ): void => {
251
+ const ast = parse(filePath, sourceCode);
252
+ if (!ast) {
253
+ return;
254
+ }
255
+ for (const id of collectResourceDefinitionIds(ast)) {
256
+ knownResourceIds.add(id);
257
+ }
258
+ };
259
+
260
+ const collectKnownSignalIds = (
261
+ sourceCode: string,
262
+ filePath: string,
263
+ knownSignalIds: Set<string>
264
+ ): void => {
265
+ const ast = parse(filePath, sourceCode);
266
+ if (!ast) {
267
+ return;
268
+ }
269
+ for (const id of collectSignalDefinitionIds(ast)) {
270
+ knownSignalIds.add(id);
271
+ }
272
+ };
273
+
274
+ const collectTrailIntents = (
275
+ sourceCode: string,
276
+ filePath: string,
277
+ trailIntentsById: Map<string, 'destroy' | 'read' | 'write'>
278
+ ): void => {
279
+ const ast = parse(filePath, sourceCode);
280
+ if (!ast) {
281
+ return;
282
+ }
283
+ for (const [id, intent] of collectTrailIntentsById(ast)) {
284
+ trailIntentsById.set(id, intent);
285
+ }
286
+ };
287
+
288
+ const collectCrudTableIds = (
289
+ sourceCode: string,
290
+ filePath: string,
291
+ crudTableIds: Set<string>
292
+ ): void => {
293
+ const ast = parse(filePath, sourceCode);
294
+ if (!ast) {
295
+ return;
296
+ }
297
+ for (const id of collectCrudTableIdsFromAst(ast)) {
298
+ crudTableIds.add(id);
299
+ }
300
+ };
301
+
302
+ const collectOnTargetSignalIds = (
303
+ sourceCode: string,
304
+ filePath: string,
305
+ onTargetSignalIds: Set<string>
306
+ ): void => {
307
+ const ast = parse(filePath, sourceCode);
308
+ if (!ast) {
309
+ return;
310
+ }
311
+ for (const id of collectOnTargetSignalIdsFromAst(ast, sourceCode)) {
312
+ onTargetSignalIds.add(id);
313
+ }
314
+ };
315
+
316
+ const collectCrudCoverageByEntity = (
317
+ sourceCode: string,
318
+ filePath: string,
319
+ coverageByEntity: Map<string, Set<string>>
320
+ ): void => {
321
+ const ast = parse(filePath, sourceCode);
322
+ if (!ast) {
323
+ return;
324
+ }
325
+ for (const [entityId, operations] of collectFileCrudCoverage(
326
+ ast,
327
+ sourceCode
328
+ )) {
329
+ const bucket = coverageByEntity.get(entityId) ?? new Set<string>();
330
+ for (const operation of operations) {
331
+ bucket.add(operation);
122
332
  }
123
- // Walk the detours value for string literals that look like trail IDs
124
- walk(detoursProp, (node) => {
125
- if (node.type !== 'Literal') {
126
- return;
127
- }
128
- const val = (node as unknown as { value?: string }).value;
129
- if (val && val.includes('.')) {
130
- detourTargetTrailIds.add(val);
131
- }
132
- });
333
+ coverageByEntity.set(entityId, bucket);
133
334
  }
134
335
  };
135
336
 
136
- const collectKnownProvisionIds = (
337
+ const collectReconcileTableIds = (
137
338
  sourceCode: string,
138
339
  filePath: string,
139
- knownProvisionIds: Set<string>
340
+ reconcileTableIds: Set<string>
140
341
  ): void => {
141
342
  const ast = parse(filePath, sourceCode);
142
343
  if (!ast) {
143
344
  return;
144
345
  }
145
- for (const id of collectProvisionDefinitionIds(ast)) {
146
- knownProvisionIds.add(id);
346
+ for (const id of collectReconcileTableIdsFromAst(ast)) {
347
+ reconcileTableIds.add(id);
147
348
  }
148
349
  };
149
350
 
@@ -166,92 +367,231 @@ const loadSourceFiles = async (
166
367
  return sourceFiles;
167
368
  };
168
369
 
169
- const collectTopoDetourTargetTrailIds = (
170
- appTopo: Topo
171
- ): ReadonlySet<string> => {
172
- const detourTargetTrailIds = new Set<string>();
370
+ const collectTopoKnownIds = (
371
+ appTopo: Topo,
372
+ context: MutableProjectContext
373
+ ): void => {
374
+ for (const name of appTopo.contours.keys()) {
375
+ context.knownContourIds.add(name);
376
+ }
173
377
 
378
+ for (const id of appTopo.trails.keys()) {
379
+ context.knownTrailIds.add(id);
380
+ }
381
+
382
+ for (const id of appTopo.resources.keys()) {
383
+ context.knownResourceIds.add(id);
384
+ }
385
+
386
+ for (const id of appTopo.signals.keys()) {
387
+ context.knownSignalIds.add(id);
388
+ }
389
+ };
390
+
391
+ const collectTopoCrossesAndIntents = (
392
+ appTopo: Topo,
393
+ context: MutableProjectContext
394
+ ): void => {
174
395
  for (const trail of appTopo.trails.values()) {
175
- const detours = (trail as unknown as Record<string, unknown>)['detours'] as
176
- | Readonly<Record<string, readonly string[]>>
177
- | undefined;
178
- if (!detours) {
179
- continue;
180
- }
181
- for (const targets of Object.values(detours)) {
182
- for (const id of targets) {
183
- detourTargetTrailIds.add(id);
184
- }
396
+ context.trailIntentsById.set(trail.id, trail.intent);
397
+ for (const crossedTrailId of trail.crosses) {
398
+ context.crossTargetTrailIds.add(crossedTrailId);
185
399
  }
186
400
  }
401
+ };
402
+
403
+ const collectTopoContourReferences = (
404
+ appTopo: Topo,
405
+ context: MutableProjectContext
406
+ ): void => {
407
+ for (const contour of appTopo.listContours()) {
408
+ addContourReferenceTargets(
409
+ context,
410
+ contour.name,
411
+ getContourReferences(contour).map((reference) => reference.contour)
412
+ );
413
+ }
414
+ };
187
415
 
188
- return detourTargetTrailIds;
416
+ const collectTopoTrailContext = (
417
+ appTopo: Topo,
418
+ context: MutableProjectContext
419
+ ): void => {
420
+ collectTopoKnownIds(appTopo, context);
421
+ collectTopoCrossesAndIntents(appTopo, context);
422
+ collectTopoContourReferences(appTopo, context);
189
423
  };
190
424
 
191
- const buildProjectContextFromTopo = (appTopo: Topo): ProjectContext => {
192
- const knownTrailIds = new Set<string>(appTopo.trails.keys());
193
- const knownProvisionIds = new Set<string>(appTopo.provisions.keys());
194
- const detourTargetTrailIds = collectTopoDetourTargetTrailIds(appTopo);
425
+ const collectFileKnownIds = (
426
+ sourceFile: SourceFile,
427
+ context: MutableProjectContext
428
+ ): void => {
429
+ collectKnownContourIds(
430
+ sourceFile.sourceCode,
431
+ sourceFile.filePath,
432
+ context.knownContourIds
433
+ );
434
+ collectKnownTrailIds(
435
+ sourceFile.sourceCode,
436
+ sourceFile.filePath,
437
+ context.knownTrailIds
438
+ );
439
+ collectKnownResourceIds(
440
+ sourceFile.sourceCode,
441
+ sourceFile.filePath,
442
+ context.knownResourceIds
443
+ );
444
+ collectKnownSignalIds(
445
+ sourceFile.sourceCode,
446
+ sourceFile.filePath,
447
+ context.knownSignalIds
448
+ );
449
+ };
195
450
 
196
- return {
197
- detourTargetTrailIds,
198
- knownProvisionIds,
199
- knownTrailIds,
200
- };
451
+ const collectFileTrailRelationships = (
452
+ sourceFile: SourceFile,
453
+ context: MutableProjectContext
454
+ ): void => {
455
+ collectCrossedTrailIds(
456
+ sourceFile.sourceCode,
457
+ sourceFile.filePath,
458
+ context.crossTargetTrailIds
459
+ );
460
+ collectTrailIntents(
461
+ sourceFile.sourceCode,
462
+ sourceFile.filePath,
463
+ context.trailIntentsById
464
+ );
465
+ };
466
+
467
+ const collectFileSupplementalProjectContext = (
468
+ sourceFile: SourceFile,
469
+ context: MutableProjectContext
470
+ ): void => {
471
+ collectCrudTableIds(
472
+ sourceFile.sourceCode,
473
+ sourceFile.filePath,
474
+ context.crudTableIds
475
+ );
476
+ collectOnTargetSignalIds(
477
+ sourceFile.sourceCode,
478
+ sourceFile.filePath,
479
+ context.onTargetSignalIds
480
+ );
481
+ collectReconcileTableIds(
482
+ sourceFile.sourceCode,
483
+ sourceFile.filePath,
484
+ context.reconcileTableIds
485
+ );
486
+ collectCrudCoverageByEntity(
487
+ sourceFile.sourceCode,
488
+ sourceFile.filePath,
489
+ context.crudCoverageByEntity
490
+ );
201
491
  };
202
492
 
203
- const buildProjectContextFromFiles = (
204
- sourceFiles: readonly SourceFile[]
493
+ const collectFileProjectContext = (
494
+ sourceFile: SourceFile,
495
+ context: MutableProjectContext
496
+ ): void => {
497
+ collectFileKnownIds(sourceFile, context);
498
+ collectFileTrailRelationships(sourceFile, context);
499
+ collectFileSupplementalProjectContext(sourceFile, context);
500
+ };
501
+
502
+ const collectFileContourReferences = (
503
+ sourceFile: SourceFile,
504
+ context: MutableProjectContext
505
+ ): void => {
506
+ const ast = parse(sourceFile.filePath, sourceFile.sourceCode);
507
+ if (!ast) {
508
+ return;
509
+ }
510
+
511
+ const referencesByName = collectContourReferenceTargetsByName(
512
+ ast,
513
+ context.knownContourIds
514
+ );
515
+ for (const [contourName, targets] of referencesByName) {
516
+ addContourReferenceTargets(context, contourName, targets);
517
+ }
518
+ };
519
+
520
+ const buildProjectContext = (
521
+ sourceFiles: readonly SourceFile[],
522
+ appTopo?: Topo | undefined
205
523
  ): ProjectContext => {
206
- const knownTrailIds = new Set<string>();
207
- const knownProvisionIds = new Set<string>();
208
- const detourTargetTrailIds = new Set<string>();
524
+ const context = createMutableProjectContext();
525
+
526
+ if (appTopo) {
527
+ collectTopoTrailContext(appTopo, context);
528
+ for (const sourceFile of sourceFiles) {
529
+ collectFileSupplementalProjectContext(sourceFile, context);
530
+ }
531
+ } else {
532
+ for (const sourceFile of sourceFiles) {
533
+ collectFileProjectContext(sourceFile, context);
534
+ }
535
+ }
209
536
 
210
537
  for (const sourceFile of sourceFiles) {
211
- collectKnownTrailIds(
212
- sourceFile.sourceCode,
213
- sourceFile.filePath,
214
- knownTrailIds
215
- );
216
- collectKnownProvisionIds(
217
- sourceFile.sourceCode,
218
- sourceFile.filePath,
219
- knownProvisionIds
220
- );
221
- collectDetourTargetTrailIds(
222
- sourceFile.sourceCode,
223
- sourceFile.filePath,
224
- detourTargetTrailIds
225
- );
538
+ collectFileContourReferences(sourceFile, context);
226
539
  }
227
540
 
228
- return {
229
- detourTargetTrailIds,
230
- knownProvisionIds,
231
- knownTrailIds,
232
- };
541
+ return toProjectContext(context);
233
542
  };
234
543
 
235
544
  const isProjectAwareRule = (rule: WardenRule): rule is ProjectAwareWardenRule =>
236
545
  'checkWithContext' in rule;
237
546
 
547
+ const topoRuleFailureDiagnostic = (
548
+ rule: TopoAwareWardenRule,
549
+ error: unknown
550
+ ): WardenDiagnostic => {
551
+ const cause = error instanceof Error ? error : new Error(String(error));
552
+ return {
553
+ filePath: '<topo>',
554
+ line: 1,
555
+ message: `Topo-aware rule "${rule.name}" threw: ${cause.message}`,
556
+ rule: rule.name,
557
+ severity: 'error',
558
+ };
559
+ };
560
+
238
561
  /**
239
- * Lint all files against all warden rules.
562
+ * Run all registered topo-aware rules against the resolved topo.
563
+ *
564
+ * Topo-aware rules fire exactly once per run (not per file) because they
565
+ * inspect the compiled trail graph, not source text.
240
566
  */
241
- const lintFiles = async (
242
- rootDir: string,
243
- appTopo?: Topo | undefined
244
- ): Promise<WardenDiagnostic[]> => {
245
- const allDiagnostics: WardenDiagnostic[] = [];
246
- const sourceFiles = await loadSourceFiles(rootDir);
247
- const context = appTopo
248
- ? buildProjectContextFromTopo(appTopo)
249
- : buildProjectContextFromFiles(sourceFiles);
567
+ const lintTopo = async (
568
+ appTopo: Topo,
569
+ extraTopoRules: readonly TopoAwareWardenRule[]
570
+ ): Promise<readonly WardenDiagnostic[]> => {
571
+ const diagnostics: WardenDiagnostic[] = [];
572
+ const rules: readonly TopoAwareWardenRule[] = [
573
+ ...wardenTopoRules.values(),
574
+ ...extraTopoRules,
575
+ ];
576
+ for (const rule of rules) {
577
+ try {
578
+ diagnostics.push(...(await rule.checkTopo(appTopo)));
579
+ } catch (error) {
580
+ diagnostics.push(topoRuleFailureDiagnostic(rule, error));
581
+ }
582
+ }
583
+ return diagnostics;
584
+ };
250
585
 
586
+ const lintSourceFiles = (
587
+ sourceFiles: readonly SourceFile[],
588
+ context: ProjectContext
589
+ ): readonly WardenDiagnostic[] => {
590
+ const diagnostics: WardenDiagnostic[] = [];
251
591
  for (const sourceFile of sourceFiles) {
252
592
  for (const rule of wardenRules.values()) {
253
593
  if (isProjectAwareRule(rule)) {
254
- allDiagnostics.push(
594
+ diagnostics.push(
255
595
  ...rule.checkWithContext(
256
596
  sourceFile.sourceCode,
257
597
  sourceFile.filePath,
@@ -260,12 +600,31 @@ const lintFiles = async (
260
600
  );
261
601
  continue;
262
602
  }
263
-
264
- allDiagnostics.push(
603
+ diagnostics.push(
265
604
  ...rule.check(sourceFile.sourceCode, sourceFile.filePath)
266
605
  );
267
606
  }
268
607
  }
608
+ return diagnostics;
609
+ };
610
+
611
+ /**
612
+ * Lint all files against all warden rules.
613
+ */
614
+ const lintFiles = async (
615
+ rootDir: string,
616
+ appTopo?: Topo | undefined,
617
+ extraTopoRules: readonly TopoAwareWardenRule[] = []
618
+ ): Promise<WardenDiagnostic[]> => {
619
+ const sourceFiles = await loadSourceFiles(rootDir);
620
+ const context = buildProjectContext(sourceFiles, appTopo);
621
+ const allDiagnostics: WardenDiagnostic[] = [
622
+ ...lintSourceFiles(sourceFiles, context),
623
+ ];
624
+
625
+ if (appTopo) {
626
+ allDiagnostics.push(...(await lintTopo(appTopo, extraTopoRules)));
627
+ }
269
628
 
270
629
  return allDiagnostics;
271
630
  };
@@ -279,7 +638,7 @@ export const runWarden = async (
279
638
  const rootDir = resolve(options.rootDir ?? process.cwd());
280
639
  const allDiagnostics = options.driftOnly
281
640
  ? []
282
- : await lintFiles(rootDir, options.topo);
641
+ : await lintFiles(rootDir, options.topo, options.extraTopoRules ?? []);
283
642
  const drift = options.lintOnly
284
643
  ? null
285
644
  : await checkDrift(rootDir, options.topo);
@@ -293,7 +652,10 @@ export const runWarden = async (
293
652
  diagnostics: allDiagnostics,
294
653
  drift,
295
654
  errorCount,
296
- passed: errorCount === 0 && !(drift?.stale ?? false),
655
+ passed:
656
+ errorCount === 0 &&
657
+ !(drift?.stale ?? false) &&
658
+ drift?.blockedReason === undefined,
297
659
  warnCount,
298
660
  };
299
661
  };
@@ -327,8 +689,11 @@ const formatDriftSection = (drift: DriftResult | null): string[] => {
327
689
  if (drift === null) {
328
690
  return [];
329
691
  }
692
+ if (drift.blockedReason !== undefined) {
693
+ return [`Drift: blocked (${drift.blockedReason})`, ''];
694
+ }
330
695
  const label = drift.stale
331
- ? 'Drift: trailhead.lock is stale (regenerate with `trails survey generate`)'
696
+ ? 'Drift: trails.lock is stale (regenerate with `trails topo export`)'
332
697
  : 'Drift: clean';
333
698
  return [label, ''];
334
699
  };
@@ -344,7 +709,9 @@ const formatResultLine = (report: WardenReport): string => {
344
709
  if (report.errorCount > 0) {
345
710
  parts.push(`${report.errorCount} errors`);
346
711
  }
347
- if (report.drift?.stale) {
712
+ if (report.drift?.blockedReason !== undefined) {
713
+ parts.push('established exports blocked');
714
+ } else if (report.drift?.stale) {
348
715
  parts.push('drift detected');
349
716
  }
350
717
  return `Result: FAIL (${parts.join(', ')})`;
package/src/draft.ts ADDED
@@ -0,0 +1,22 @@
1
+ import { basename } from 'node:path';
2
+
3
+ export const DRAFT_FILE_PREFIX = '_draft.';
4
+ export const DRAFT_FILE_SEGMENT = '.draft.';
5
+
6
+ const DRAFT_TRAILING_SEGMENT = /\.draft(?=\.[^.]+$)/;
7
+
8
+ export const isDraftMarkedFile = (filePath: string): boolean => {
9
+ const fileName = basename(filePath);
10
+ return (
11
+ fileName.startsWith(DRAFT_FILE_PREFIX) ||
12
+ fileName.includes(DRAFT_FILE_SEGMENT)
13
+ );
14
+ };
15
+
16
+ export const stripDraftFileMarkers = (fileName: string): string => {
17
+ if (fileName.startsWith(DRAFT_FILE_PREFIX)) {
18
+ return fileName.slice(DRAFT_FILE_PREFIX.length);
19
+ }
20
+
21
+ return fileName.replace(DRAFT_TRAILING_SEGMENT, '');
22
+ };