@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
@@ -61,10 +61,7 @@ const getStringValue = (node: AstNode): string | null => {
61
61
  * Returns the string value if a simple `const <name> = '...'` or `"..."` is
62
62
  * found in the source. Returns null for anything more complex.
63
63
  */
64
- const resolveConstString = (
65
- name: string,
66
- sourceCode: string
67
- ): string | null => {
64
+ const deriveConstString = (name: string, sourceCode: string): string | null => {
68
65
  const pattern = new RegExp(
69
66
  `const\\s+${name}\\s*=\\s*(?:'([^']*)'|"([^"]*)")`
70
67
  );
@@ -84,11 +81,11 @@ const resolveIdentifierElement = (
84
81
  if (!name) {
85
82
  return null;
86
83
  }
87
- return resolveConstString(name, sourceCode);
84
+ return deriveConstString(name, sourceCode);
88
85
  };
89
86
 
90
87
  /** Resolve an array element to a static trail ID when possible. */
91
- const resolveCrossElementId = (
88
+ const deriveCrossElementId = (
92
89
  element: AstNode,
93
90
  sourceCode: string
94
91
  ): string | null => {
@@ -125,28 +122,62 @@ const getCrossElements = (config: AstNode): readonly AstNode[] | null => {
125
122
  return elements ?? null;
126
123
  };
127
124
 
128
- /** Collect string IDs from array elements, resolving identifiers when possible. */
129
- const collectStringIds = (
125
+ interface DeclaredCrosses {
126
+ /** Statically resolved trail IDs from string literals / const identifiers. */
127
+ readonly ids: ReadonlySet<string>;
128
+ /**
129
+ * True if any element could not be statically resolved (e.g. trail object
130
+ * reference like `crosses: [showGist]`). When true, "undeclared" diagnostics
131
+ * are softened from error to warn since the declared set is incomplete.
132
+ */
133
+ readonly hasUnresolved: boolean;
134
+ }
135
+
136
+ /**
137
+ * Collect string IDs from array elements, resolving identifiers when possible.
138
+ *
139
+ * Trail-object references (`crosses: [showGist]`) cannot be resolved at lint
140
+ * time; they're normalized at runtime by `trail()`. When any entry is
141
+ * unresolved, `hasUnresolved` is set so callers can soften diagnostics.
142
+ */
143
+ /** Classify a single element and accumulate into the id set. */
144
+ const classifyCrossElement = (
145
+ element: AstNode,
146
+ sourceCode: string,
147
+ ids: Set<string>
148
+ ): boolean => {
149
+ const resolved = deriveCrossElementId(element, sourceCode);
150
+ if (!resolved) {
151
+ // Element could not be statically resolved
152
+ return true;
153
+ }
154
+ ids.add(resolved);
155
+ return false;
156
+ };
157
+
158
+ const resolveDeclaredCrossElements = (
130
159
  elements: readonly AstNode[],
131
160
  sourceCode: string
132
- ): Set<string> => {
161
+ ): DeclaredCrosses => {
133
162
  const ids = new Set<string>();
163
+ let hasUnresolved = false;
134
164
  for (const element of elements) {
135
- const resolved = resolveCrossElementId(element, sourceCode);
136
- if (resolved) {
137
- ids.add(resolved);
165
+ if (classifyCrossElement(element, sourceCode, ids)) {
166
+ hasUnresolved = true;
138
167
  }
139
168
  }
140
- return ids;
169
+ return { hasUnresolved, ids };
141
170
  };
142
171
 
143
- /** Extract string literal elements from a `crosses: [...]` array property. */
172
+ /** Extract declared crosses from a `crosses: [...]` array. */
144
173
  const extractDeclaredCrosses = (
145
174
  config: AstNode,
146
175
  sourceCode: string
147
- ): ReadonlySet<string> => {
176
+ ): DeclaredCrosses => {
148
177
  const elements = getCrossElements(config);
149
- return elements ? collectStringIds(elements, sourceCode) : new Set();
178
+ return elements
179
+ ? resolveDeclaredCrossElements(elements, sourceCode)
180
+ : { hasUnresolved: false, ids: new Set() };
150
181
  };
151
182
 
152
183
  // ---------------------------------------------------------------------------
@@ -173,33 +204,25 @@ const extractMemberPair = (
173
204
  return objName && propName ? { objName, propName } : null;
174
205
  };
175
206
 
176
- /** Extract the first argument string from a CallExpression's arguments list. */
177
- const extractFirstStringArg = (node: AstNode): string | null => {
178
- const args = node['arguments'] as readonly AstNode[] | undefined;
179
- if (!args || args.length === 0) {
180
- return null;
181
- }
182
-
183
- const [firstArg] = args;
184
- if (!firstArg || !isStringLiteral(firstArg)) {
185
- return null;
186
- }
187
-
188
- return getStringValue(firstArg);
189
- };
190
-
191
207
  /**
192
208
  * Extract the second parameter name from a blaze function node.
193
209
  *
194
- * Handles `(input, ctx) => ...`, `async (input, context) => ...`, and
195
- * `function(input, ctx) { ... }` forms.
210
+ * Handles `(input, ctx) => ...`, `async (input, context) => ...`,
211
+ * `function(input, ctx) { ... }`, and defaulted params like
212
+ * `(input, ctx = fallback) => ...` (AssignmentPattern whose `.left` is the
213
+ * Identifier).
196
214
  */
197
215
  const extractContextParamName = (blazeBody: AstNode): string | null => {
198
216
  const params = blazeBody['params'] as readonly AstNode[] | undefined;
199
217
  if (!params || params.length < 2) {
200
218
  return null;
201
219
  }
202
- return identifierName(params[1]);
220
+ const [, param] = params;
221
+ if (param?.type === 'AssignmentPattern') {
222
+ const { left } = param as unknown as { left?: AstNode };
223
+ return identifierName(left);
224
+ }
225
+ return identifierName(param);
203
226
  };
204
227
 
205
228
  /** Check if a callee is a member-style cross call: <ctxName>.cross(...). */
@@ -211,38 +234,151 @@ const isMemberCrossCall = (
211
234
  return !!pair && ctxNames.has(pair.objName) && pair.propName === 'cross';
212
235
  };
213
236
 
214
- /**
215
- * Check if a node is a `<ctxName>.cross(...)` call and return the string trail ID.
216
- *
217
- * Also matches bare `cross(...)` calls from destructuring.
218
- */
219
- const extractCrossCallId = (
237
+ interface ExtractedCrossCall {
238
+ readonly ids: readonly string[];
239
+ readonly hasUnresolved: boolean;
240
+ }
241
+
242
+ const unresolvedCross = (): ExtractedCrossCall => ({
243
+ hasUnresolved: true,
244
+ ids: [],
245
+ });
246
+
247
+ const resolveBatchCrossTupleTarget = (
248
+ element: AstNode,
249
+ sourceCode: string
250
+ ): string | null => {
251
+ if (element.type !== 'ArrayExpression') {
252
+ return null;
253
+ }
254
+
255
+ const tupleElements = element['elements'] as readonly AstNode[] | undefined;
256
+ const [target] = tupleElements ?? [];
257
+ return target ? deriveCrossElementId(target, sourceCode) : null;
258
+ };
259
+
260
+ const collectBatchCrossId = (
261
+ element: AstNode,
262
+ sourceCode: string,
263
+ ids: string[]
264
+ ): boolean => {
265
+ const resolved = resolveBatchCrossTupleTarget(element, sourceCode);
266
+ if (!resolved) {
267
+ return true;
268
+ }
269
+ ids.push(resolved);
270
+ return false;
271
+ };
272
+
273
+ /** Extract statically-resolved trail IDs from `ctx.cross([[trail, input], ...])`. */
274
+ const extractBatchCrossIds = (
275
+ firstArg: AstNode | undefined,
276
+ sourceCode: string
277
+ ): ExtractedCrossCall | null => {
278
+ if (firstArg?.type !== 'ArrayExpression') {
279
+ return null;
280
+ }
281
+
282
+ const elements = firstArg['elements'] as readonly AstNode[] | undefined;
283
+ const ids: string[] = [];
284
+ let hasUnresolved = false;
285
+
286
+ for (const element of elements ?? []) {
287
+ if (collectBatchCrossId(element, sourceCode, ids)) {
288
+ hasUnresolved = true;
289
+ }
290
+ }
291
+
292
+ return { hasUnresolved, ids };
293
+ };
294
+
295
+ const extractDirectCrossIds = (
296
+ firstArg: AstNode | undefined
297
+ ): ExtractedCrossCall | null => {
298
+ if (!firstArg || !isStringLiteral(firstArg)) {
299
+ return null;
300
+ }
301
+
302
+ const value = getStringValue(firstArg);
303
+ return value ? { hasUnresolved: false, ids: [value] } : unresolvedCross();
304
+ };
305
+
306
+ const isCrossCallExpression = (
307
+ callee: AstNode,
308
+ ctxNames: ReadonlySet<string>
309
+ ): boolean =>
310
+ isMemberCrossCall(callee, ctxNames) || identifierName(callee) === 'cross';
311
+
312
+ const extractCrossFirstArg = (node: AstNode): AstNode | undefined => {
313
+ const args = node['arguments'] as readonly AstNode[] | undefined;
314
+ return args?.[0];
315
+ };
316
+
317
+ const resolveCrossCallNode = (
220
318
  node: AstNode,
221
319
  ctxNames: ReadonlySet<string>
222
- ): string | null => {
320
+ ): AstNode | null => {
223
321
  if (node.type !== 'CallExpression') {
224
322
  return null;
225
323
  }
226
324
 
227
325
  const callee = node['callee'] as AstNode | undefined;
228
- if (!callee) {
326
+ if (!callee || !isCrossCallExpression(callee, ctxNames)) {
229
327
  return null;
230
328
  }
231
329
 
232
- if (isMemberCrossCall(callee, ctxNames)) {
233
- return extractFirstStringArg(node);
330
+ return node;
331
+ };
332
+
333
+ const resolveCrossCallTargets = (
334
+ firstArg: AstNode | undefined,
335
+ sourceCode: string
336
+ ): ExtractedCrossCall => {
337
+ const direct = extractDirectCrossIds(firstArg);
338
+ if (direct) {
339
+ return direct;
234
340
  }
235
341
 
236
- if (identifierName(callee) === 'cross') {
237
- return extractFirstStringArg(node);
342
+ const batch = extractBatchCrossIds(firstArg, sourceCode);
343
+ return batch ?? unresolvedCross();
344
+ };
345
+
346
+ /**
347
+ * Check if a node is a `<ctxName>.cross(...)` call and return any statically
348
+ * resolvable target IDs.
349
+ *
350
+ * Also matches bare `cross(...)` calls from destructuring. When the first
351
+ * argument is a non-string expression (e.g. a trail object identifier like
352
+ * `ctx.cross(showGist, input)`), marks the call as unresolved so callers can
353
+ * track that a cross call exists but its target cannot be statically resolved.
354
+ */
355
+ const extractCrossCall = (
356
+ node: AstNode,
357
+ ctxNames: ReadonlySet<string>,
358
+ sourceCode: string
359
+ ): ExtractedCrossCall | null => {
360
+ const crossCall = resolveCrossCallNode(node, ctxNames);
361
+ if (!crossCall) {
362
+ return null;
238
363
  }
239
364
 
240
- return null;
365
+ return resolveCrossCallTargets(extractCrossFirstArg(crossCall), sourceCode);
241
366
  };
242
367
 
243
- /** Build the set of context parameter names to match against. */
368
+ /**
369
+ * Build the set of context parameter names to match against.
370
+ *
371
+ * Returns ONLY the actual second-parameter name from the blaze signature.
372
+ * No seeded defaults: if the blaze has no second parameter, the returned set
373
+ * is empty and no `ctx.cross(...)` / `context.cross(...)` calls are tracked
374
+ * for that blaze. An unrelated closure-scoped `ctx` identifier is not the
375
+ * trail context and must not be treated as one.
376
+ *
377
+ * Mirrors `fires-declarations.ts` and `resource-declarations.ts` for the same
378
+ * reason.
379
+ */
244
380
  const buildCtxNames = (body: AstNode): ReadonlySet<string> => {
245
- const ctxNames = new Set(['ctx', 'context']);
381
+ const ctxNames = new Set<string>();
246
382
  const paramName = extractContextParamName(body);
247
383
  if (paramName) {
248
384
  ctxNames.add(paramName);
@@ -250,22 +386,59 @@ const buildCtxNames = (body: AstNode): ReadonlySet<string> => {
250
386
  return ctxNames;
251
387
  };
252
388
 
389
+ interface CalledCrosses {
390
+ /** Statically resolved trail IDs from string literal arguments. */
391
+ readonly ids: ReadonlySet<string>;
392
+ /**
393
+ * True if any `ctx.cross()` call used a non-string first argument (e.g.
394
+ * `ctx.cross(showGist, input)`). When true, "unused declaration"
395
+ * diagnostics are softened since the call may target a declared entry.
396
+ */
397
+ readonly hasUnresolved: boolean;
398
+ }
399
+
400
+ /** Collect cross call results from a single blaze body. */
401
+ const collectCrossCallsFromBody = (
402
+ body: AstNode,
403
+ ids: Set<string>,
404
+ sourceCode: string
405
+ ): boolean => {
406
+ const ctxNames = buildCtxNames(body);
407
+ let foundUnresolved = false;
408
+
409
+ walk(body, (node) => {
410
+ const extracted = extractCrossCall(node, ctxNames, sourceCode);
411
+ if (!extracted) {
412
+ return;
413
+ }
414
+
415
+ if (extracted.hasUnresolved) {
416
+ foundUnresolved = true;
417
+ }
418
+
419
+ for (const id of extracted.ids) {
420
+ ids.add(id);
421
+ }
422
+ });
423
+
424
+ return foundUnresolved;
425
+ };
426
+
253
427
  /** Walk blaze bodies and collect all statically resolvable ctx.cross() trail IDs. */
254
- const extractCalledCrosses = (config: AstNode): ReadonlySet<string> => {
428
+ const extractCalledCrosses = (
429
+ config: AstNode,
430
+ sourceCode: string
431
+ ): CalledCrosses => {
255
432
  const ids = new Set<string>();
433
+ let hasUnresolved = false;
256
434
 
257
435
  for (const body of findBlazeBodies(config)) {
258
- const ctxNames = buildCtxNames(body);
259
-
260
- walk(body, (node) => {
261
- const id = extractCrossCallId(node, ctxNames);
262
- if (id) {
263
- ids.add(id);
264
- }
265
- });
436
+ if (collectCrossCallsFromBody(body, ids, sourceCode)) {
437
+ hasUnresolved = true;
438
+ }
266
439
  }
267
440
 
268
- return ids;
441
+ return { hasUnresolved, ids };
269
442
  };
270
443
 
271
444
  // ---------------------------------------------------------------------------
@@ -276,13 +449,16 @@ const buildUndeclaredDiagnostic = (
276
449
  trailId: string,
277
450
  crossedId: string,
278
451
  filePath: string,
279
- line: number
452
+ line: number,
453
+ softened = false
280
454
  ): WardenDiagnostic => ({
281
455
  filePath,
282
456
  line,
283
- message: `Trail "${trailId}": ctx.cross('${crossedId}') called but '${crossedId}' is not declared in crosses`,
457
+ message: softened
458
+ ? `Trail "${trailId}": ctx.cross('${crossedId}') called but '${crossedId}' is not declared in crosses (may be declared via trail object references)`
459
+ : `Trail "${trailId}": ctx.cross('${crossedId}') called but '${crossedId}' is not declared in crosses`,
284
460
  rule: 'cross-declarations',
285
- severity: 'error',
461
+ severity: softened ? 'warn' : 'error',
286
462
  });
287
463
 
288
464
  const buildUnusedDiagnostic = (
@@ -306,13 +482,24 @@ const buildUnusedDiagnostic = (
306
482
  const reportUndeclared = (
307
483
  called: ReadonlySet<string>,
308
484
  declared: ReadonlySet<string>,
309
- ctx: { trailId: string; filePath: string; line: number },
485
+ ctx: {
486
+ trailId: string;
487
+ filePath: string;
488
+ line: number;
489
+ softened?: boolean;
490
+ },
310
491
  diagnostics: WardenDiagnostic[]
311
492
  ): void => {
312
493
  for (const id of called) {
313
494
  if (!declared.has(id)) {
314
495
  diagnostics.push(
315
- buildUndeclaredDiagnostic(ctx.trailId, id, ctx.filePath, ctx.line)
496
+ buildUndeclaredDiagnostic(
497
+ ctx.trailId,
498
+ id,
499
+ ctx.filePath,
500
+ ctx.line,
501
+ ctx.softened
502
+ )
316
503
  );
317
504
  }
318
505
  }
@@ -341,17 +528,38 @@ const checkTrailDefinition = (
341
528
  diagnostics: WardenDiagnostic[]
342
529
  ): void => {
343
530
  const declared = extractDeclaredCrosses(def.config, sourceCode);
344
- const called = extractCalledCrosses(def.config);
345
-
346
- if (declared.size === 0 && called.size === 0) {
531
+ const called = extractCalledCrosses(def.config, sourceCode);
532
+
533
+ if (
534
+ declared.ids.size === 0 &&
535
+ !declared.hasUnresolved &&
536
+ called.ids.size === 0 &&
537
+ !called.hasUnresolved
538
+ ) {
347
539
  return;
348
540
  }
349
541
 
350
542
  const line = offsetToLine(sourceCode, def.start);
351
543
  const ctx = { filePath, line, trailId: def.id };
352
544
 
353
- reportUndeclared(called, declared, ctx, diagnostics);
354
- reportUnused(declared, called, ctx, diagnostics);
545
+ // When the declared array contains trail object references we can't resolve,
546
+ // downgrade "undeclared" diagnostics from error to warn. The developer still
547
+ // sees genuinely undeclared calls, but we can't statically prove the call
548
+ // isn't covered by a trail object entry the runtime will normalize.
549
+ reportUndeclared(
550
+ called.ids,
551
+ declared.ids,
552
+ { ...ctx, softened: declared.hasUnresolved },
553
+ diagnostics
554
+ );
555
+
556
+ // When all ctx.cross() calls are statically resolved, report unused
557
+ // declarations. When some calls use trail object references (unresolved),
558
+ // skip — a declared string like 'gist.show' might be the target of an
559
+ // unresolved `ctx.cross(showGist)` call, producing false positives.
560
+ if (!called.hasUnresolved) {
561
+ reportUnused(declared.ids, called.ids, ctx, diagnostics);
562
+ }
355
563
  };
356
564
 
357
565
  // ---------------------------------------------------------------------------
@@ -0,0 +1,141 @@
1
+ import {
2
+ collectCrossTargetTrailIds,
3
+ findConfigProperty,
4
+ findTrailDefinitions,
5
+ getStringValue,
6
+ isStringLiteral,
7
+ offsetToLine,
8
+ parse,
9
+ } from './ast.js';
10
+ import type { AstNode } from './ast.js';
11
+ import { isTestFile } from './scan.js';
12
+ import type {
13
+ ProjectAwareWardenRule,
14
+ ProjectContext,
15
+ WardenDiagnostic,
16
+ } from './types.js';
17
+
18
+ const isNonEmptyActivationValue = (onValue: AstNode): boolean => {
19
+ // Identifier reference (e.g. `on: signalsArray`) — conservatively treat as
20
+ // having activation to avoid false positives. We can't cheaply resolve what
21
+ // the identifier binds to, so assume it's a non-empty activation.
22
+ if (onValue.type === 'Identifier') {
23
+ return true;
24
+ }
25
+ if (onValue.type !== 'ArrayExpression') {
26
+ return false;
27
+ }
28
+ const elements = onValue['elements'] as readonly AstNode[] | undefined;
29
+ return (elements?.length ?? 0) > 0;
30
+ };
31
+
32
+ const hasOnActivation = (config: AstNode): boolean => {
33
+ const onProp = findConfigProperty(config, 'on');
34
+ const onValue = onProp?.value as AstNode | undefined;
35
+ return onValue ? isNonEmptyActivationValue(onValue) : false;
36
+ };
37
+
38
+ const hasExplicitInternalVisibility = (config: AstNode): boolean => {
39
+ const visibilityProp = findConfigProperty(config, 'visibility');
40
+ const visibilityValue = visibilityProp?.value as AstNode | undefined;
41
+ return (
42
+ !!visibilityValue &&
43
+ isStringLiteral(visibilityValue) &&
44
+ getStringValue(visibilityValue) === 'internal'
45
+ );
46
+ };
47
+
48
+ /** Check legacy `meta: { internal: true }` convention (mirrors runtime effectiveVisibility). */
49
+ const hasLegacyMetaInternal = (config: AstNode): boolean => {
50
+ const metaProp = findConfigProperty(config, 'meta');
51
+ const metaValue = metaProp?.value as AstNode | undefined;
52
+ if (!metaValue || metaValue.type !== 'ObjectExpression') {
53
+ return false;
54
+ }
55
+ const internalProp = findConfigProperty(metaValue, 'internal');
56
+ const internalValue = internalProp?.value as AstNode | undefined;
57
+ return (
58
+ internalValue?.type === 'BooleanLiteral' &&
59
+ (internalValue as unknown as { value: boolean }).value === true
60
+ );
61
+ };
62
+
63
+ const isInternalTrail = (config: AstNode): boolean =>
64
+ hasExplicitInternalVisibility(config) || hasLegacyMetaInternal(config);
65
+
66
+ const buildDeadInternalTrailDiagnostic = (
67
+ trailId: string,
68
+ filePath: string,
69
+ line: number
70
+ ): WardenDiagnostic => ({
71
+ filePath,
72
+ line,
73
+ message: `Trail "${trailId}" is marked visibility: 'internal' but nothing crosses it and it has no on: activation. Internal trails should stay reachable through ctx.cross() or reactive activation.`,
74
+ rule: 'dead-internal-trail',
75
+ severity: 'warn',
76
+ });
77
+
78
+ const checkDeadInternalTrails = (
79
+ ast: AstNode | null,
80
+ sourceCode: string,
81
+ filePath: string,
82
+ crossedTrailIds: ReadonlySet<string>
83
+ ): readonly WardenDiagnostic[] => {
84
+ if (isTestFile(filePath) || !ast) {
85
+ return [];
86
+ }
87
+
88
+ const diagnostics: WardenDiagnostic[] = [];
89
+
90
+ for (const def of findTrailDefinitions(ast)) {
91
+ if (def.kind !== 'trail' || !isInternalTrail(def.config)) {
92
+ continue;
93
+ }
94
+
95
+ if (hasOnActivation(def.config) || crossedTrailIds.has(def.id)) {
96
+ continue;
97
+ }
98
+
99
+ diagnostics.push(
100
+ buildDeadInternalTrailDiagnostic(
101
+ def.id,
102
+ filePath,
103
+ offsetToLine(sourceCode, def.start)
104
+ )
105
+ );
106
+ }
107
+
108
+ return diagnostics;
109
+ };
110
+
111
+ export const deadInternalTrail: ProjectAwareWardenRule = {
112
+ check(sourceCode: string, filePath: string): readonly WardenDiagnostic[] {
113
+ const ast = parse(filePath, sourceCode);
114
+ return checkDeadInternalTrails(
115
+ ast,
116
+ sourceCode,
117
+ filePath,
118
+ ast ? collectCrossTargetTrailIds(ast, sourceCode) : new Set<string>()
119
+ );
120
+ },
121
+ checkWithContext(
122
+ sourceCode: string,
123
+ filePath: string,
124
+ context: ProjectContext
125
+ ): readonly WardenDiagnostic[] {
126
+ const ast = parse(filePath, sourceCode);
127
+ const localCrossTargetTrailIds = ast
128
+ ? collectCrossTargetTrailIds(ast, sourceCode)
129
+ : new Set<string>();
130
+ return checkDeadInternalTrails(
131
+ ast,
132
+ sourceCode,
133
+ filePath,
134
+ context.crossTargetTrailIds ?? localCrossTargetTrailIds
135
+ );
136
+ },
137
+ description:
138
+ 'Warn when an internal trail has no crossings anywhere in the project and no on: activation.',
139
+ name: 'dead-internal-trail',
140
+ severity: 'warn',
141
+ };