@ontrails/warden 1.0.0-beta.4 → 1.0.0-beta.42

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 (279) hide show
  1. package/CHANGELOG.md +837 -13
  2. package/README.md +133 -26
  3. package/bin/warden.ts +51 -0
  4. package/package.json +27 -6
  5. package/src/adapter-check.ts +136 -0
  6. package/src/cli.ts +1570 -105
  7. package/src/command.ts +986 -0
  8. package/src/config.ts +193 -0
  9. package/src/draft.ts +22 -0
  10. package/src/drift.ts +233 -23
  11. package/src/fix.ts +126 -0
  12. package/src/formatters.ts +78 -13
  13. package/src/guide.ts +245 -0
  14. package/src/index.ts +247 -15
  15. package/src/project-context.ts +446 -0
  16. package/src/project-rules.ts +290 -0
  17. package/src/resolve.ts +531 -0
  18. package/src/rules/activation-orphan.ts +97 -0
  19. package/src/rules/captured-kernel.ts +375 -0
  20. package/src/rules/circular-refs.ts +150 -0
  21. package/src/rules/cli-command-route-coherence.ts +177 -0
  22. package/src/rules/composes-declarations.ts +839 -0
  23. package/src/rules/context-no-surface-types.ts +79 -15
  24. package/src/rules/dead-internal-trail.ts +161 -0
  25. package/src/rules/dead-public-trail.ts +258 -0
  26. package/src/rules/draft-file-marking.ts +155 -0
  27. package/src/rules/draft-visible-debt.ts +82 -0
  28. package/src/rules/duplicate-exported-symbol.ts +211 -0
  29. package/src/rules/duplicate-public-contract.ts +137 -0
  30. package/src/rules/entity-exists.ts +254 -0
  31. package/src/rules/entity-ids.ts +15 -0
  32. package/src/rules/error-mapping-completeness.ts +290 -0
  33. package/src/rules/example-valid.ts +395 -0
  34. package/src/rules/fires-declarations.ts +740 -0
  35. package/src/rules/governed-symbol-residue.ts +438 -0
  36. package/src/rules/implementation-returns-result.ts +1409 -166
  37. package/src/rules/incomplete-accessor-for-standard-op.ts +272 -0
  38. package/src/rules/incomplete-crud.ts +583 -0
  39. package/src/rules/index.ts +277 -10
  40. package/src/rules/intent-propagation.ts +125 -0
  41. package/src/rules/layer-field-name-drift.ts +102 -0
  42. package/src/rules/library-projection-coherence.ts +100 -0
  43. package/src/rules/metadata.ts +871 -0
  44. package/src/rules/missing-reconcile.ts +97 -0
  45. package/src/rules/missing-visibility.ts +111 -0
  46. package/src/rules/no-destructured-compose.ts +196 -0
  47. package/src/rules/no-dev-permit-in-source.ts +99 -0
  48. package/src/rules/no-direct-implementation-call.ts +12 -7
  49. package/src/rules/no-legacy-cli-alias-export.ts +247 -0
  50. package/src/rules/no-legacy-layer-imports.ts +211 -0
  51. package/src/rules/no-native-error-result.ts +118 -0
  52. package/src/rules/no-redundant-result-error-wrap.ts +384 -0
  53. package/src/rules/no-retired-cross-vocabulary.ts +204 -0
  54. package/src/rules/no-sync-result-assumption.ts +1141 -98
  55. package/src/rules/no-throw-in-detour-recover.ts +225 -0
  56. package/src/rules/no-throw-in-implementation.ts +15 -8
  57. package/src/rules/no-top-level-surface.ts +371 -0
  58. package/src/rules/on-references-exist.ts +194 -0
  59. package/src/rules/orphaned-signal.ts +149 -0
  60. package/src/rules/owner-projection-parity.ts +143 -0
  61. package/src/rules/permit-governance.ts +25 -0
  62. package/src/rules/public-export-example-coverage.ts +573 -0
  63. package/src/rules/public-internal-deep-imports.ts +456 -0
  64. package/src/rules/public-output-schema.ts +29 -0
  65. package/src/rules/public-union-output-discriminants.ts +150 -0
  66. package/src/rules/read-intent-fires.ts +188 -0
  67. package/src/rules/reference-exists.ts +97 -0
  68. package/src/rules/registry-names.ts +167 -0
  69. package/src/rules/resolved-import-boundary.ts +146 -0
  70. package/src/rules/resource-declarations.ts +697 -0
  71. package/src/rules/resource-exists.ts +181 -0
  72. package/src/rules/resource-id-grammar.ts +65 -0
  73. package/src/rules/resource-mock-coverage.ts +115 -0
  74. package/src/rules/retired-vocabulary.ts +633 -0
  75. package/src/rules/scan.ts +38 -25
  76. package/src/rules/scheduled-destroy-intent.ts +44 -0
  77. package/src/rules/signal-graph-coaching.ts +220 -0
  78. package/src/rules/source/composition.ts +165 -0
  79. package/src/rules/source/drafts.ts +164 -0
  80. package/src/rules/source/entities.ts +618 -0
  81. package/src/rules/source/pragmas.ts +45 -0
  82. package/src/rules/source/resources.ts +64 -0
  83. package/src/rules/source/signals.ts +397 -0
  84. package/src/rules/source/stores.ts +310 -0
  85. package/src/rules/specs.ts +9 -5
  86. package/src/rules/static-resource-accessor-preference.ts +653 -0
  87. package/src/rules/surface-overlay-coherence.ts +262 -0
  88. package/src/rules/surface-trailhead-coherence.ts +366 -0
  89. package/src/rules/trail-fork-coaching.ts +625 -0
  90. package/src/rules/trail-versioning-source.ts +1076 -0
  91. package/src/rules/trail-versioning-topo.ts +172 -0
  92. package/src/rules/trailhead-override-divergence.ts +356 -0
  93. package/src/rules/types.ts +354 -8
  94. package/src/rules/unmaterialized-activation-source.ts +85 -0
  95. package/src/rules/unreachable-detour-shadowing.ts +339 -0
  96. package/src/rules/valid-describe-refs.ts +162 -32
  97. package/src/rules/valid-detour-contract.ts +78 -0
  98. package/src/rules/warden-export-symmetry.ts +540 -0
  99. package/src/rules/warden-rules-use-ast.ts +1109 -0
  100. package/src/rules/webhook-route-collision.ts +306 -0
  101. package/src/trails/activation-orphan.trail.ts +84 -0
  102. package/src/trails/captured-kernel.trail.ts +108 -0
  103. package/src/trails/circular-refs.trail.ts +29 -0
  104. package/src/trails/cli-command-route-coherence.trail.ts +47 -0
  105. package/src/trails/composes-declarations.trail.ts +22 -0
  106. package/src/trails/context-no-surface-types.trail.ts +21 -0
  107. package/src/trails/dead-internal-trail.trail.ts +26 -0
  108. package/src/trails/dead-public-trail.trail.ts +31 -0
  109. package/src/trails/deprecation-without-guidance.trail.ts +21 -0
  110. package/src/trails/draft-file-marking.trail.ts +16 -0
  111. package/src/trails/draft-visible-debt.trail.ts +16 -0
  112. package/src/trails/duplicate-exported-symbol.trail.ts +48 -0
  113. package/src/trails/duplicate-public-contract.trail.ts +47 -0
  114. package/src/trails/entity-exists.trail.ts +21 -0
  115. package/src/trails/error-mapping-completeness.trail.ts +30 -0
  116. package/src/trails/example-valid.trail.ts +25 -0
  117. package/src/trails/fires-declarations.trail.ts +23 -0
  118. package/src/trails/fork-without-preserved-implementation.trail.ts +31 -0
  119. package/src/trails/governed-symbol-residue.trail.ts +24 -0
  120. package/src/trails/implementation-returns-result.trail.ts +20 -0
  121. package/src/trails/incomplete-accessor-for-standard-op.trail.ts +76 -0
  122. package/src/trails/incomplete-crud.trail.ts +39 -0
  123. package/src/trails/index.ts +89 -0
  124. package/src/trails/intent-propagation.trail.ts +30 -0
  125. package/src/trails/layer-field-name-drift.trail.ts +39 -0
  126. package/src/trails/library-projection-coherence.trail.ts +43 -0
  127. package/src/trails/marker-schema-unsupported.trail.ts +23 -0
  128. package/src/trails/missing-reconcile.trail.ts +33 -0
  129. package/src/trails/missing-visibility.trail.ts +22 -0
  130. package/src/trails/no-destructured-compose.trail.ts +44 -0
  131. package/src/trails/no-dev-permit-in-source.trail.ts +16 -0
  132. package/src/trails/no-direct-implementation-call.trail.ts +16 -0
  133. package/src/trails/no-legacy-cli-alias-export.trail.ts +41 -0
  134. package/src/trails/no-legacy-layer-imports.trail.ts +41 -0
  135. package/src/trails/no-native-error-result.trail.ts +18 -0
  136. package/src/trails/no-redundant-result-error-wrap.trail.ts +55 -0
  137. package/src/trails/no-retired-cross-vocabulary.trail.ts +42 -0
  138. package/src/trails/no-sync-result-assumption.trail.ts +19 -0
  139. package/src/trails/no-throw-in-detour-recover.trail.ts +24 -0
  140. package/src/trails/no-throw-in-implementation.trail.ts +20 -0
  141. package/src/trails/no-top-level-surface.trail.ts +43 -0
  142. package/src/trails/on-references-exist.trail.ts +21 -0
  143. package/src/trails/orphaned-signal.trail.ts +36 -0
  144. package/src/trails/owner-projection-parity.trail.ts +26 -0
  145. package/src/trails/pending-force.trail.ts +21 -0
  146. package/src/trails/permit-governance.trail.ts +51 -0
  147. package/src/trails/prefer-schema-inference.trail.ts +21 -0
  148. package/src/trails/public-export-example-coverage.trail.ts +16 -0
  149. package/src/trails/public-internal-deep-imports.trail.ts +94 -0
  150. package/src/trails/public-output-schema.trail.ts +55 -0
  151. package/src/trails/public-union-output-discriminants.trail.ts +33 -0
  152. package/src/trails/read-intent-fires.trail.ts +20 -0
  153. package/src/trails/reference-exists.trail.ts +25 -0
  154. package/src/trails/resolved-import-boundary.trail.ts +109 -0
  155. package/src/trails/resource-declarations.trail.ts +25 -0
  156. package/src/trails/resource-exists.trail.ts +27 -0
  157. package/src/trails/resource-id-grammar.trail.ts +39 -0
  158. package/src/trails/resource-mock-coverage.trail.ts +40 -0
  159. package/src/trails/run.ts +160 -0
  160. package/src/trails/scheduled-destroy-intent.trail.ts +56 -0
  161. package/src/trails/schema.ts +237 -0
  162. package/src/trails/signal-graph-coaching.trail.ts +77 -0
  163. package/src/trails/static-resource-accessor-preference.trail.ts +25 -0
  164. package/src/trails/surface-overlay-coherence.trail.ts +24 -0
  165. package/src/trails/surface-trailhead-coherence.trail.ts +25 -0
  166. package/src/trails/topo.ts +6 -0
  167. package/src/trails/trail-fork-coaching.trail.ts +42 -0
  168. package/src/trails/trailhead-override-divergence.trail.ts +47 -0
  169. package/src/trails/unmaterialized-activation-source.trail.ts +72 -0
  170. package/src/trails/unreachable-detour-shadowing.trail.ts +45 -0
  171. package/src/trails/valid-describe-refs.trail.ts +18 -0
  172. package/src/trails/valid-detour-contract.trail.ts +71 -0
  173. package/src/trails/version-gap.trail.ts +35 -0
  174. package/src/trails/version-pinned-compose.trail.ts +23 -0
  175. package/src/trails/version-without-examples.trail.ts +38 -0
  176. package/src/trails/warden-export-symmetry.trail.ts +16 -0
  177. package/src/trails/warden-rules-use-ast.trail.ts +64 -0
  178. package/src/trails/webhook-route-collision.trail.ts +50 -0
  179. package/src/trails/wrap-rule.ts +224 -0
  180. package/src/workspaces.ts +199 -0
  181. package/.turbo/turbo-build.log +0 -1
  182. package/.turbo/turbo-lint.log +0 -3
  183. package/.turbo/turbo-typecheck.log +0 -1
  184. package/dist/cli.d.ts +0 -46
  185. package/dist/cli.d.ts.map +0 -1
  186. package/dist/cli.js +0 -218
  187. package/dist/cli.js.map +0 -1
  188. package/dist/drift.d.ts +0 -26
  189. package/dist/drift.d.ts.map +0 -1
  190. package/dist/drift.js +0 -27
  191. package/dist/drift.js.map +0 -1
  192. package/dist/formatters.d.ts +0 -29
  193. package/dist/formatters.d.ts.map +0 -1
  194. package/dist/formatters.js +0 -87
  195. package/dist/formatters.js.map +0 -1
  196. package/dist/index.d.ts +0 -26
  197. package/dist/index.d.ts.map +0 -1
  198. package/dist/index.js +0 -26
  199. package/dist/index.js.map +0 -1
  200. package/dist/rules/ast.d.ts +0 -41
  201. package/dist/rules/ast.d.ts.map +0 -1
  202. package/dist/rules/ast.js +0 -161
  203. package/dist/rules/ast.js.map +0 -1
  204. package/dist/rules/context-no-surface-types.d.ts +0 -12
  205. package/dist/rules/context-no-surface-types.d.ts.map +0 -1
  206. package/dist/rules/context-no-surface-types.js +0 -96
  207. package/dist/rules/context-no-surface-types.js.map +0 -1
  208. package/dist/rules/implementation-returns-result.d.ts +0 -13
  209. package/dist/rules/implementation-returns-result.d.ts.map +0 -1
  210. package/dist/rules/implementation-returns-result.js +0 -277
  211. package/dist/rules/implementation-returns-result.js.map +0 -1
  212. package/dist/rules/index.d.ts +0 -15
  213. package/dist/rules/index.d.ts.map +0 -1
  214. package/dist/rules/index.js +0 -34
  215. package/dist/rules/index.js.map +0 -1
  216. package/dist/rules/no-direct-impl-in-route.d.ts +0 -12
  217. package/dist/rules/no-direct-impl-in-route.d.ts.map +0 -1
  218. package/dist/rules/no-direct-impl-in-route.js +0 -47
  219. package/dist/rules/no-direct-impl-in-route.js.map +0 -1
  220. package/dist/rules/no-direct-implementation-call.d.ts +0 -12
  221. package/dist/rules/no-direct-implementation-call.d.ts.map +0 -1
  222. package/dist/rules/no-direct-implementation-call.js +0 -39
  223. package/dist/rules/no-direct-implementation-call.js.map +0 -1
  224. package/dist/rules/no-sync-result-assumption.d.ts +0 -6
  225. package/dist/rules/no-sync-result-assumption.d.ts.map +0 -1
  226. package/dist/rules/no-sync-result-assumption.js +0 -98
  227. package/dist/rules/no-sync-result-assumption.js.map +0 -1
  228. package/dist/rules/no-throw-in-detour-target.d.ts +0 -12
  229. package/dist/rules/no-throw-in-detour-target.d.ts.map +0 -1
  230. package/dist/rules/no-throw-in-detour-target.js +0 -87
  231. package/dist/rules/no-throw-in-detour-target.js.map +0 -1
  232. package/dist/rules/no-throw-in-implementation.d.ts +0 -9
  233. package/dist/rules/no-throw-in-implementation.d.ts.map +0 -1
  234. package/dist/rules/no-throw-in-implementation.js +0 -34
  235. package/dist/rules/no-throw-in-implementation.js.map +0 -1
  236. package/dist/rules/prefer-schema-inference.d.ts +0 -7
  237. package/dist/rules/prefer-schema-inference.d.ts.map +0 -1
  238. package/dist/rules/prefer-schema-inference.js +0 -86
  239. package/dist/rules/prefer-schema-inference.js.map +0 -1
  240. package/dist/rules/scan.d.ts +0 -8
  241. package/dist/rules/scan.d.ts.map +0 -1
  242. package/dist/rules/scan.js +0 -32
  243. package/dist/rules/scan.js.map +0 -1
  244. package/dist/rules/specs.d.ts +0 -29
  245. package/dist/rules/specs.d.ts.map +0 -1
  246. package/dist/rules/specs.js +0 -192
  247. package/dist/rules/specs.js.map +0 -1
  248. package/dist/rules/structure.d.ts +0 -13
  249. package/dist/rules/structure.d.ts.map +0 -1
  250. package/dist/rules/structure.js +0 -142
  251. package/dist/rules/structure.js.map +0 -1
  252. package/dist/rules/types.d.ts +0 -52
  253. package/dist/rules/types.d.ts.map +0 -1
  254. package/dist/rules/types.js +0 -2
  255. package/dist/rules/types.js.map +0 -1
  256. package/dist/rules/valid-describe-refs.d.ts +0 -7
  257. package/dist/rules/valid-describe-refs.d.ts.map +0 -1
  258. package/dist/rules/valid-describe-refs.js +0 -51
  259. package/dist/rules/valid-describe-refs.js.map +0 -1
  260. package/dist/rules/valid-detour-refs.d.ts +0 -6
  261. package/dist/rules/valid-detour-refs.d.ts.map +0 -1
  262. package/dist/rules/valid-detour-refs.js +0 -116
  263. package/dist/rules/valid-detour-refs.js.map +0 -1
  264. package/src/__tests__/cli.test.ts +0 -198
  265. package/src/__tests__/drift.test.ts +0 -74
  266. package/src/__tests__/formatters.test.ts +0 -157
  267. package/src/__tests__/implementation-returns-result.test.ts +0 -129
  268. package/src/__tests__/no-direct-implementation-call.test.ts +0 -83
  269. package/src/__tests__/no-sync-result-assumption.test.ts +0 -85
  270. package/src/__tests__/no-throw-in-detour-target.test.ts +0 -78
  271. package/src/__tests__/prefer-schema-inference.test.ts +0 -84
  272. package/src/__tests__/rules.test.ts +0 -227
  273. package/src/__tests__/valid-describe-refs.test.ts +0 -60
  274. package/src/rules/ast.ts +0 -213
  275. package/src/rules/no-direct-impl-in-route.ts +0 -81
  276. package/src/rules/no-throw-in-detour-target.ts +0 -150
  277. package/src/rules/valid-detour-refs.ts +0 -187
  278. package/tsconfig.json +0 -9
  279. package/tsconfig.tsbuildinfo +0 -1
package/src/formatters.ts CHANGED
@@ -7,7 +7,8 @@
7
7
  */
8
8
 
9
9
  import type { WardenReport } from './cli.js';
10
- import type { WardenSeverity } from './rules/types.js';
10
+ import { staleDriftMessage } from './drift.js';
11
+ import type { WardenGuidanceLink, WardenSeverity } from './rules/types.js';
11
12
 
12
13
  /** Map warden severity to GitHub Actions annotation level. */
13
14
  const ghLevel: Record<WardenSeverity, string> = {
@@ -19,7 +20,8 @@ const ghLevel: Record<WardenSeverity, string> = {
19
20
  * Produce GitHub Actions workflow command annotations, one per diagnostic.
20
21
  *
21
22
  * Severity mapping: `error` to `::error`, `warn` to `::warning`.
22
- * Drift staleness is emitted as a single `::error` annotation when detected.
23
+ * Drift staleness or established-export blocking is emitted as a single
24
+ * `::error` annotation when detected.
23
25
  */
24
26
  export const formatGitHubAnnotations = (report: WardenReport): string => {
25
27
  const lines: string[] = [];
@@ -31,10 +33,10 @@ export const formatGitHubAnnotations = (report: WardenReport): string => {
31
33
  );
32
34
  }
33
35
 
34
- if (report.drift?.stale) {
35
- lines.push(
36
- '::error::drift: surface.lock is stale (regenerate with `trails survey generate`)'
37
- );
36
+ if (report.drift?.blockedReason !== undefined) {
37
+ lines.push(`::error::drift: ${report.drift.blockedReason}`);
38
+ } else if (report.drift?.stale) {
39
+ lines.push(`::error::drift: ${staleDriftMessage(report.drift)}`);
38
40
  }
39
41
 
40
42
  return lines.join('\n');
@@ -57,6 +59,7 @@ export const formatJson = (report: WardenReport): string => {
57
59
  {
58
60
  diagnostics: report.diagnostics,
59
61
  drift: report.drift,
62
+ fixes: report.fixes,
60
63
  passed: report.passed,
61
64
  summary,
62
65
  },
@@ -65,9 +68,56 @@ export const formatJson = (report: WardenReport): string => {
65
68
  );
66
69
  };
67
70
 
68
- /** Format a diagnostic as a markdown list item. */
69
- const diagnosticLine = (d: WardenReport['diagnostics'][number]): string =>
70
- `- \`${d.filePath}:${String(d.line)}\` — ${d.rule}: ${d.message}`;
71
+ const formatGuidanceLink = (link: WardenGuidanceLink): string => {
72
+ if (link.path !== undefined) {
73
+ return `[${link.label}](${link.path})`;
74
+ }
75
+ if (link.url !== undefined) {
76
+ return `[${link.label}](${link.url})`;
77
+ }
78
+ return link.label;
79
+ };
80
+
81
+ /** Format diagnostic guidance as indented markdown lines. */
82
+ const diagnosticGuidanceLines = (
83
+ d: WardenReport['diagnostics'][number]
84
+ ): readonly string[] => {
85
+ const { guidance } = d;
86
+ if (guidance === undefined) {
87
+ return [];
88
+ }
89
+
90
+ const lines = [` - Next: ${guidance.summary}`];
91
+ if (guidance.steps !== undefined && guidance.steps.length > 0) {
92
+ lines.push(
93
+ ` - Steps: ${guidance.steps
94
+ .map((step, index) => `${String(index + 1)}. ${step}`)
95
+ .join(' ')}`
96
+ );
97
+ }
98
+ if (guidance.docs !== undefined && guidance.docs.length > 0) {
99
+ lines.push(` - Docs: ${guidance.docs.map(formatGuidanceLink).join(', ')}`);
100
+ }
101
+ if (guidance.commands !== undefined && guidance.commands.length > 0) {
102
+ lines.push(
103
+ ` - Commands: ${guidance.commands.map((cmd) => `\`${cmd}\``).join(', ')}`
104
+ );
105
+ }
106
+ if (guidance.relatedRules !== undefined && guidance.relatedRules.length > 0) {
107
+ lines.push(
108
+ ` - Related: ${guidance.relatedRules.map((rule) => `\`${rule}\``).join(', ')}`
109
+ );
110
+ }
111
+ return lines;
112
+ };
113
+
114
+ /** Format a diagnostic as markdown lines. */
115
+ const diagnosticLines = (
116
+ d: WardenReport['diagnostics'][number]
117
+ ): readonly string[] => [
118
+ `- \`${d.filePath}:${String(d.line)}\` — ${d.rule}: ${d.message}`,
119
+ ...diagnosticGuidanceLines(d),
120
+ ];
71
121
 
72
122
  /** Render a severity group as a headed markdown section, or empty array. */
73
123
  const severitySection = (
@@ -77,18 +127,32 @@ const severitySection = (
77
127
  if (diagnostics.length === 0) {
78
128
  return [];
79
129
  }
80
- return ['', `### ${heading}`, ...diagnostics.map(diagnosticLine)];
130
+ return ['', `### ${heading}`, ...diagnostics.flatMap(diagnosticLines)];
81
131
  };
82
132
 
83
133
  /** Render a drift section if stale, otherwise empty array. */
84
134
  const driftSection = (drift: WardenReport['drift']): readonly string[] => {
135
+ if (drift?.blockedReason !== undefined) {
136
+ return [
137
+ '',
138
+ '### Drift',
139
+ `- established exports are blocked: ${drift.blockedReason}`,
140
+ ];
141
+ }
142
+
85
143
  if (!drift?.stale) {
86
144
  return [];
87
145
  }
146
+ return ['', '### Drift', `- ${staleDriftMessage(drift)}`];
147
+ };
148
+
149
+ /** Render safe-fix counts when a fix pass was requested. */
150
+ const fixSummaryLine = (fixes: WardenReport['fixes']): readonly string[] => {
151
+ if (fixes === undefined) {
152
+ return [];
153
+ }
88
154
  return [
89
- '',
90
- '### Drift',
91
- '- surface.lock is stale (regenerate with `trails survey generate`)',
155
+ `**Fixes:** ${String(fixes.applied)} applied, ${String(fixes.filesChanged)} files changed, ${String(fixes.skipped)} skipped`,
92
156
  ];
93
157
  };
94
158
 
@@ -106,6 +170,7 @@ export const formatSummary = (report: WardenReport): string => {
106
170
  '## Warden Report',
107
171
  '',
108
172
  `**Result: ${result}** | ${String(report.errorCount)} errors, ${String(report.warnCount)} warnings`,
173
+ ...fixSummaryLine(report.fixes),
109
174
  ...severitySection('Errors', errors),
110
175
  ...severitySection('Warnings', warnings),
111
176
  ...driftSection(report.drift),
package/src/guide.ts ADDED
@@ -0,0 +1,245 @@
1
+ import type {
2
+ WardenFixCapability,
3
+ WardenGuidance,
4
+ WardenGuidanceLink,
5
+ WardenRuleConcern,
6
+ WardenRuleLifecycle,
7
+ WardenRuleScope,
8
+ WardenRuleTier,
9
+ WardenSeverity,
10
+ } from './rules/index.js';
11
+ import {
12
+ listWardenRuleMetadata,
13
+ wardenRules,
14
+ wardenTopoRules,
15
+ } from './rules/index.js';
16
+ import type { WardenDepth } from './config.js';
17
+
18
+ export const wardenGuideFormatValues = [
19
+ 'markdown',
20
+ 'agent-json',
21
+ 'manifest',
22
+ ] as const;
23
+
24
+ export type WardenGuideFormat = (typeof wardenGuideFormatValues)[number];
25
+
26
+ export interface WardenRuleGuideEntry {
27
+ readonly concern: WardenRuleConcern;
28
+ readonly depth: WardenDepth;
29
+ readonly description: string;
30
+ readonly docs: readonly WardenGuidanceLink[];
31
+ readonly fix?: WardenFixCapability | undefined;
32
+ readonly guidance?: WardenGuidance | undefined;
33
+ readonly id: string;
34
+ readonly invariant: string;
35
+ readonly lifecycle: WardenRuleLifecycle;
36
+ readonly scope: WardenRuleScope;
37
+ readonly severity: WardenSeverity;
38
+ readonly tier: WardenRuleTier;
39
+ }
40
+
41
+ export interface WardenGuideManifest {
42
+ readonly generatedFrom: {
43
+ readonly package: '@ontrails/warden';
44
+ readonly registries: readonly ['wardenRules', 'wardenTopoRules'];
45
+ readonly source: 'builtin-rule-metadata';
46
+ };
47
+ readonly kind: 'trails-warden-guide-manifest';
48
+ readonly ruleCount: number;
49
+ readonly rules: readonly WardenRuleGuideEntry[];
50
+ readonly version: 1;
51
+ }
52
+
53
+ interface WardenAgentRuleGuide {
54
+ readonly appliesAt: {
55
+ readonly depth: WardenDepth;
56
+ readonly scope: WardenRuleScope;
57
+ readonly tier: WardenRuleTier;
58
+ };
59
+ readonly concern: WardenRuleConcern;
60
+ readonly fix?: WardenFixCapability | undefined;
61
+ readonly guidance?: WardenGuidance | undefined;
62
+ readonly id: string;
63
+ readonly invariant: string;
64
+ readonly severity: WardenSeverity;
65
+ }
66
+
67
+ interface WardenAgentGuide {
68
+ readonly instructions: readonly string[];
69
+ readonly kind: 'trails-warden-agent-guide';
70
+ readonly rules: readonly WardenAgentRuleGuide[];
71
+ readonly version: 1;
72
+ }
73
+
74
+ const lookupRule = (id: string) =>
75
+ wardenRules.get(id) ?? wardenTopoRules.get(id);
76
+
77
+ const compareRuleEntries = (
78
+ [left]: readonly [string, unknown],
79
+ [right]: readonly [string, unknown]
80
+ ): number => left.localeCompare(right);
81
+
82
+ export const buildWardenGuideManifest = (): WardenGuideManifest => {
83
+ const rules = listWardenRuleMetadata()
84
+ .toSorted(compareRuleEntries)
85
+ .map(([id, metadata]) => {
86
+ const rule = lookupRule(id);
87
+ const docs = metadata.guidance?.docs ?? [];
88
+ return {
89
+ concern: metadata.concern,
90
+ depth: metadata.depth,
91
+ description: rule?.description ?? '',
92
+ docs,
93
+ fix: metadata.fix,
94
+ guidance: metadata.guidance,
95
+ id,
96
+ invariant: metadata.invariant,
97
+ lifecycle: metadata.lifecycle,
98
+ scope: metadata.scope,
99
+ severity: rule?.severity ?? 'warn',
100
+ tier: metadata.tier,
101
+ } satisfies WardenRuleGuideEntry;
102
+ });
103
+
104
+ return {
105
+ generatedFrom: {
106
+ package: '@ontrails/warden',
107
+ registries: ['wardenRules', 'wardenTopoRules'],
108
+ source: 'builtin-rule-metadata',
109
+ },
110
+ kind: 'trails-warden-guide-manifest',
111
+ ruleCount: rules.length,
112
+ rules,
113
+ version: 1,
114
+ };
115
+ };
116
+
117
+ const formatGuideLink = (link: WardenGuidanceLink): string => {
118
+ if (link.path) {
119
+ return `[${link.label}](${link.path})`;
120
+ }
121
+ if (link.url) {
122
+ return `[${link.label}](${link.url})`;
123
+ }
124
+ return link.label;
125
+ };
126
+
127
+ const renderOptionalList = (
128
+ lines: string[],
129
+ label: string,
130
+ items: readonly string[] | undefined
131
+ ): void => {
132
+ if (items === undefined || items.length === 0) {
133
+ return;
134
+ }
135
+
136
+ lines.push(`- ${label}:`);
137
+ for (const [index, item] of items.entries()) {
138
+ lines.push(` ${index + 1}. ${item}`);
139
+ }
140
+ };
141
+
142
+ const renderRuleMarkdown = (rule: WardenRuleGuideEntry): readonly string[] => {
143
+ const lines = [
144
+ `### \`${rule.id}\``,
145
+ '',
146
+ `- Severity: \`${rule.severity}\``,
147
+ `- Concern: \`${rule.concern}\``,
148
+ `- Depth: \`${rule.depth}\``,
149
+ `- Tier: \`${rule.tier}\``,
150
+ `- Scope: \`${rule.scope}\``,
151
+ `- Lifecycle: \`${rule.lifecycle.state}\``,
152
+ `- Invariant: ${rule.invariant}`,
153
+ `- Description: ${rule.description}`,
154
+ ];
155
+
156
+ if (rule.lifecycle.retireWhen) {
157
+ lines.push(`- Retire when: ${rule.lifecycle.retireWhen}`);
158
+ }
159
+
160
+ if (rule.fix) {
161
+ lines.push(
162
+ `- Fix: \`${rule.fix.class}\` (${rule.fix.safety === 'safe' ? 'safe, applied by `warden --fix`' : 'review-required'})`
163
+ );
164
+ }
165
+
166
+ if (rule.guidance) {
167
+ lines.push('', `Guidance: ${rule.guidance.summary}`);
168
+ renderOptionalList(lines, 'Steps', rule.guidance.steps);
169
+ renderOptionalList(lines, 'Commands', rule.guidance.commands);
170
+ if (rule.docs.length > 0) {
171
+ lines.push(`- Docs: ${rule.docs.map(formatGuideLink).join(', ')}`);
172
+ }
173
+ if (rule.guidance.relatedRules && rule.guidance.relatedRules.length > 0) {
174
+ lines.push(
175
+ `- Related rules: ${rule.guidance.relatedRules.map((id) => `\`${id}\``).join(', ')}`
176
+ );
177
+ }
178
+ }
179
+
180
+ return lines;
181
+ };
182
+
183
+ export const formatWardenGuideMarkdown = (
184
+ manifest: WardenGuideManifest
185
+ ): string => {
186
+ const lines = [
187
+ '# Trails Warden Guide',
188
+ '',
189
+ 'Generated from `@ontrails/warden` built-in rule metadata and live rule registries.',
190
+ '',
191
+ '## Summary',
192
+ '',
193
+ `- Rules: ${manifest.ruleCount}`,
194
+ `- Guided rules: ${manifest.rules.filter((rule) => rule.guidance).length}`,
195
+ '',
196
+ '## Rules',
197
+ '',
198
+ ];
199
+
200
+ for (const rule of manifest.rules) {
201
+ lines.push(...renderRuleMarkdown(rule), '');
202
+ }
203
+
204
+ return lines.join('\n').trimEnd();
205
+ };
206
+
207
+ export const buildWardenAgentGuide = (
208
+ manifest: WardenGuideManifest
209
+ ): WardenAgentGuide => ({
210
+ instructions: [
211
+ 'Treat Warden rules as enforceable Trails doctrine when working in this repository.',
212
+ 'Prefer the rule guidance summary and ordered steps over diagnostic prose when deciding how to remediate a finding.',
213
+ 'When guidance is absent, use the invariant, concern, tier, and scope as classification metadata rather than inventing a rule-specific fix.',
214
+ ],
215
+ kind: 'trails-warden-agent-guide',
216
+ rules: manifest.rules.map((rule) => ({
217
+ appliesAt: {
218
+ depth: rule.depth,
219
+ scope: rule.scope,
220
+ tier: rule.tier,
221
+ },
222
+ concern: rule.concern,
223
+ fix: rule.fix,
224
+ guidance: rule.guidance,
225
+ id: rule.id,
226
+ invariant: rule.invariant,
227
+ severity: rule.severity,
228
+ })),
229
+ version: 1,
230
+ });
231
+
232
+ export const formatWardenGuide = (
233
+ manifest: WardenGuideManifest,
234
+ format: WardenGuideFormat
235
+ ): string => {
236
+ if (format === 'markdown') {
237
+ return formatWardenGuideMarkdown(manifest);
238
+ }
239
+
240
+ if (format === 'agent-json') {
241
+ return JSON.stringify(buildWardenAgentGuide(manifest), null, 2);
242
+ }
243
+
244
+ return JSON.stringify(manifest, null, 2);
245
+ };
package/src/index.ts CHANGED
@@ -9,32 +9,130 @@
9
9
 
10
10
  // Rule types
11
11
  export type {
12
+ BuiltinWardenRuleName,
12
13
  ProjectAwareWardenRule,
13
14
  ProjectContext,
15
+ TopoAwareWardenRule,
14
16
  WardenDiagnostic,
17
+ WardenExportedSymbolDefinition,
18
+ WardenFix,
19
+ WardenFixCapability,
20
+ WardenFixClass,
21
+ WardenFixEdit,
22
+ WardenFixScanTargets,
23
+ WardenFixSafety,
24
+ WardenGuidance,
25
+ WardenGuidanceLink,
15
26
  WardenRule,
27
+ WardenRuleConcern,
28
+ WardenRuleLifecycle,
29
+ WardenRuleLifecycleState,
30
+ WardenRuleMetadata,
31
+ WardenRuleScope,
32
+ WardenRuleTier,
16
33
  WardenSeverity,
17
34
  } from './rules/index.js';
18
35
 
19
- // Individual rules
20
- export { noThrowInImplementation } from './rules/no-throw-in-implementation.js';
21
- export { contextNoSurfaceTypes } from './rules/context-no-surface-types.js';
22
- export { validDetourRefs } from './rules/valid-detour-refs.js';
23
- export { noDirectImplInRoute } from './rules/no-direct-impl-in-route.js';
24
- export { noDirectImplementationCall } from './rules/no-direct-implementation-call.js';
25
- export { noSyncResultAssumption } from './rules/no-sync-result-assumption.js';
26
- export { implementationReturnsResult } from './rules/implementation-returns-result.js';
27
- export { noThrowInDetourTarget } from './rules/no-throw-in-detour-target.js';
28
- export { preferSchemaInference } from './rules/prefer-schema-inference.js';
29
- export { validDescribeRefs } from './rules/valid-describe-refs.js';
30
-
31
36
  // Rule registry
32
- export { wardenRules } from './rules/index.js';
37
+ export {
38
+ builtinWardenRuleMetadata,
39
+ formatGovernedVocabularyTransitionGuide,
40
+ getGovernedVocabularyTransition,
41
+ governedVocabularyLiteralRenameSchema,
42
+ governedVocabularyPreserveRuleSchema,
43
+ governedVocabularyRegistrySchema,
44
+ governedVocabularyScopeSchema,
45
+ governedVocabularySymbolRenameSchema,
46
+ governedVocabularyTargetSchema,
47
+ governedVocabularyTransitionSchema,
48
+ governedVocabularyTransitionStatuses,
49
+ governedVocabularyTransitions,
50
+ getWardenRuleMetadata,
51
+ listWardenRuleMetadata,
52
+ listGovernedVocabularyTransitions,
53
+ requireGovernedVocabularyTransition,
54
+ wardenFixClasses,
55
+ wardenFixSafeties,
56
+ wardenRuleConcerns,
57
+ wardenRuleLifecycleStates,
58
+ wardenRuleScopes,
59
+ wardenRuleTiers,
60
+ wardenRules,
61
+ wardenTopoRules,
62
+ } from './rules/index.js';
63
+ export type {
64
+ GovernedVocabularyLiteralRename,
65
+ GovernedVocabularyPreserveRule,
66
+ GovernedVocabularyScope,
67
+ GovernedVocabularySymbolRename,
68
+ GovernedVocabularyTarget,
69
+ GovernedVocabularyTransition,
70
+ GovernedVocabularyTransitionInput,
71
+ } from './rules/index.js';
72
+
73
+ // Rule-scoped cache controls for long-lived consumers (watch mode, LSPs).
74
+ export { clearImplementationReturnsResultCache } from './rules/implementation-returns-result.js';
75
+ export {
76
+ isWardenDevPermitTestScanTarget,
77
+ isWardenInfrastructureScanTarget,
78
+ isWardenSourceScanTarget,
79
+ isWardenTestScanTarget,
80
+ } from './rules/scan.js';
33
81
 
34
82
  // CLI runner
35
- export type { WardenOptions, WardenReport } from './cli.js';
83
+ export type {
84
+ WardenOptions,
85
+ WardenReport,
86
+ WardenRunOptions,
87
+ WardenTopoTarget,
88
+ } from './cli.js';
36
89
  export { formatWardenReport, runWarden } from './cli.js';
37
90
 
91
+ // Adapter authoring checks
92
+ export {
93
+ adapterCheckRuleName,
94
+ runWardenAdapterChecks,
95
+ } from './adapter-check.js';
96
+
97
+ // CLI command surface
98
+ export type {
99
+ ParsedWardenCommand,
100
+ RunWardenCommandOptions,
101
+ WardenCommandResult,
102
+ } from './command.js';
103
+ export {
104
+ formatWardenCommandOutput,
105
+ loadWardenConfig,
106
+ parseWardenCommandArgs,
107
+ resolveWardenTopoTargets,
108
+ runWardenCommand,
109
+ } from './command.js';
110
+
111
+ // Config schema
112
+ export {
113
+ wardenConfigSchema,
114
+ wardenDepthValues,
115
+ wardenDraftsValues,
116
+ wardenFailOnValues,
117
+ wardenFormatValues,
118
+ wardenLockValues,
119
+ } from './config.js';
120
+ export type {
121
+ WardenConfig,
122
+ WardenConfigInput,
123
+ WardenDepth,
124
+ WardenDraftsMode,
125
+ WardenFailOn,
126
+ WardenFormat,
127
+ WardenScope,
128
+ WardenLockMode,
129
+ EffectiveWardenConfig,
130
+ ResolveWardenConfigOptions,
131
+ WardenConfigLayer,
132
+ WardenConfigResolution,
133
+ } from './config.js';
134
+ export { resolveWardenConfig } from './config.js';
135
+
38
136
  // CI formatters
39
137
  export {
40
138
  formatGitHubAnnotations,
@@ -43,5 +141,139 @@ export {
43
141
  } from './formatters.js';
44
142
 
45
143
  // Drift detection
46
- export type { DriftResult } from './drift.js';
144
+ export type { CheckDriftOptions, DriftResult } from './drift.js';
47
145
  export { checkDrift } from './drift.js';
146
+
147
+ // Project-local rule loading
148
+ export type { ProjectWardenRules } from './project-rules.js';
149
+ export { loadProjectWardenRules } from './project-rules.js';
150
+
151
+ // Guide projection
152
+ export type {
153
+ WardenGuideFormat,
154
+ WardenGuideManifest,
155
+ WardenRuleGuideEntry,
156
+ } from './guide.js';
157
+ export {
158
+ buildWardenAgentGuide,
159
+ buildWardenGuideManifest,
160
+ formatWardenGuide,
161
+ formatWardenGuideMarkdown,
162
+ wardenGuideFormatValues,
163
+ } from './guide.js';
164
+
165
+ // Resolver helpers
166
+ export {
167
+ collectImportResolutionsForFile,
168
+ collectImportSpecifiers,
169
+ createWardenResolver,
170
+ defaultWardenResolveOptions,
171
+ } from './resolve.js';
172
+ export type {
173
+ WardenImportResolution,
174
+ WardenImportResolutionErrorKind,
175
+ WardenImportSpecifier,
176
+ WardenProjectResolver,
177
+ WardenResolverOptions,
178
+ } from './resolve.js';
179
+
180
+ // Draft helpers
181
+ export {
182
+ DRAFT_FILE_PREFIX,
183
+ DRAFT_FILE_SEGMENT,
184
+ isDraftMarkedFile,
185
+ stripDraftFileMarkers,
186
+ } from './draft.js';
187
+
188
+ // Trail layer
189
+ export { wardenTopo } from './trails/topo.js';
190
+ export { runTopoAwareWardenTrails, runWardenTrails } from './trails/run.js';
191
+ export {
192
+ activationOrphanTrail,
193
+ capturedKernelTrail,
194
+ cliCommandRouteCoherenceTrail,
195
+ circularRefsTrail,
196
+ entityExistsTrail,
197
+ contextNoSurfaceTypesTrail,
198
+ composesDeclarationsTrail,
199
+ deadInternalTrailTrail,
200
+ deadPublicTrailTrail,
201
+ deprecationWithoutGuidanceTrail,
202
+ diagnosticSchema,
203
+ duplicateExportedSymbolTrail,
204
+ duplicatePublicContractTrail,
205
+ draftFileMarkingTrail,
206
+ draftVisibleDebtTrail,
207
+ errorMappingCompletenessTrail,
208
+ exampleValidTrail,
209
+ firesDeclarationsTrail,
210
+ forkWithoutPreservedImplementationTrail,
211
+ governedSymbolResidueTrail,
212
+ implementationReturnsResultTrail,
213
+ incompleteAccessorForStandardOpTrail,
214
+ incompleteCrudTrail,
215
+ intentPropagationTrail,
216
+ layerFieldNameDriftTrail,
217
+ libraryProjectionCoherenceTrail,
218
+ markerSchemaUnsupportedTrail,
219
+ missingVisibilityTrail,
220
+ missingReconcileTrail,
221
+ noDevPermitInSourceTrail,
222
+ noDestructuredComposeTrail,
223
+ noDirectImplementationCallTrail,
224
+ noLegacyCliAliasExportTrail,
225
+ noLegacyLayerImportsTrail,
226
+ noNativeErrorResultTrail,
227
+ noRedundantResultErrorWrapTrail,
228
+ noRetiredCrossVocabularyTrail,
229
+ noSyncResultAssumptionTrail,
230
+ noThrowInDetourRecoverTrail,
231
+ noThrowInImplementationTrail,
232
+ noTopLevelSurfaceTrail,
233
+ onReferencesExistTrail,
234
+ orphanedSignalTrail,
235
+ ownerProjectionParityTrail,
236
+ pendingForceTrail,
237
+ permitGovernanceTrail,
238
+ preferSchemaInferenceTrail,
239
+ projectAwareRuleInput,
240
+ publicExportExampleCoverageTrail,
241
+ publicInternalDeepImportsTrail,
242
+ publicOutputSchemaTrail,
243
+ publicUnionOutputDiscriminantsTrail,
244
+ readIntentFiresTrail,
245
+ referenceExistsTrail,
246
+ resolvedImportBoundaryTrail,
247
+ ruleInput,
248
+ ruleOutput,
249
+ resourceDeclarationsTrail,
250
+ resourceIdGrammarTrail,
251
+ resourceExistsTrail,
252
+ resourceMockCoverageTrail,
253
+ scheduledDestroyIntentTrail,
254
+ signalGraphCoachingTrail,
255
+ staticResourceAccessorPreferenceTrail,
256
+ surfaceOverlayCoherenceTrail,
257
+ surfaceTrailheadCoherenceTrail,
258
+ trailForkCoachingTrail,
259
+ trailheadOverrideDivergenceTrail,
260
+ unmaterializedActivationSourceTrail,
261
+ topoAwareRuleInput,
262
+ unreachableDetourShadowingTrail,
263
+ validDetourContractTrail,
264
+ validDescribeRefsTrail,
265
+ versionGapTrail,
266
+ versionPinnedComposeTrail,
267
+ versionWithoutExamplesTrail,
268
+ wardenExportSymmetryTrail,
269
+ wardenRulesUseAstTrail,
270
+ webhookRouteCollisionTrail,
271
+ wrapRule,
272
+ wrapTopoRule,
273
+ } from './trails/index.js';
274
+ export type {
275
+ ProjectAwareRuleInput,
276
+ RuleInput,
277
+ RuleOutput,
278
+ TopoAwareRuleInput,
279
+ } from './trails/index.js';