@ontrails/warden 1.0.0-beta.3 → 1.0.0-beta.32

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 (260) hide show
  1. package/CHANGELOG.md +674 -9
  2. package/README.md +133 -26
  3. package/bin/warden.ts +51 -0
  4. package/package.json +28 -5
  5. package/src/adapter-check.ts +136 -0
  6. package/src/ast.ts +137 -0
  7. package/src/cli.ts +1451 -104
  8. package/src/command.ts +966 -0
  9. package/src/config.ts +193 -0
  10. package/src/draft.ts +22 -0
  11. package/src/drift.ts +122 -22
  12. package/src/fix.ts +120 -0
  13. package/src/formatters.ts +79 -9
  14. package/src/guide.ts +245 -0
  15. package/src/index.ts +217 -14
  16. package/src/project-context.ts +192 -0
  17. package/src/project-rules.ts +290 -0
  18. package/src/resolve.ts +531 -0
  19. package/src/rules/activation-orphan.ts +97 -0
  20. package/src/rules/ast.ts +4008 -86
  21. package/src/rules/circular-refs.ts +154 -0
  22. package/src/rules/cli-command-route-coherence.ts +177 -0
  23. package/src/rules/composes-declarations.ts +837 -0
  24. package/src/rules/context-no-surface-types.ts +78 -15
  25. package/src/rules/contour-exists.ts +251 -0
  26. package/src/rules/contour-ids.ts +15 -0
  27. package/src/rules/dead-internal-trail.ts +161 -0
  28. package/src/rules/dead-public-trail.ts +258 -0
  29. package/src/rules/draft-file-marking.ts +160 -0
  30. package/src/rules/draft-visible-debt.ts +87 -0
  31. package/src/rules/duplicate-public-contract.ts +91 -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 +735 -0
  35. package/src/rules/implementation-returns-result.ts +1409 -166
  36. package/src/rules/incomplete-accessor-for-standard-op.ts +272 -0
  37. package/src/rules/incomplete-crud.ts +581 -0
  38. package/src/rules/index.ts +235 -18
  39. package/src/rules/intent-propagation.ts +127 -0
  40. package/src/rules/layer-field-name-drift.ts +102 -0
  41. package/src/rules/library-projection-coherence.ts +100 -0
  42. package/src/rules/metadata.ts +755 -0
  43. package/src/rules/missing-reconcile.ts +98 -0
  44. package/src/rules/missing-visibility.ts +110 -0
  45. package/src/rules/no-destructured-compose.ts +194 -0
  46. package/src/rules/no-dev-permit-in-source.ts +99 -0
  47. package/src/rules/no-direct-implementation-call.ts +7 -7
  48. package/src/rules/no-legacy-layer-imports.ts +211 -0
  49. package/src/rules/no-native-error-result.ts +118 -0
  50. package/src/rules/no-redundant-result-error-wrap.ts +384 -0
  51. package/src/rules/no-retired-cross-vocabulary.ts +194 -0
  52. package/src/rules/no-sync-result-assumption.ts +1135 -98
  53. package/src/rules/no-throw-in-detour-recover.ts +225 -0
  54. package/src/rules/no-throw-in-implementation.ts +10 -9
  55. package/src/rules/no-top-level-surface.ts +371 -0
  56. package/src/rules/on-references-exist.ts +194 -0
  57. package/src/rules/orphaned-signal.ts +150 -0
  58. package/src/rules/owner-projection-parity.ts +143 -0
  59. package/src/rules/permit-governance.ts +25 -0
  60. package/src/rules/public-export-example-coverage.ts +562 -0
  61. package/src/rules/public-internal-deep-imports.ts +457 -0
  62. package/src/rules/public-output-schema.ts +29 -0
  63. package/src/rules/public-union-output-discriminants.ts +150 -0
  64. package/src/rules/read-intent-fires.ts +187 -0
  65. package/src/rules/reference-exists.ts +98 -0
  66. package/src/rules/registry-names.ts +155 -0
  67. package/src/rules/resolved-import-boundary.ts +146 -0
  68. package/src/rules/resource-declarations.ts +693 -0
  69. package/src/rules/resource-exists.ts +179 -0
  70. package/src/rules/resource-id-grammar.ts +65 -0
  71. package/src/rules/resource-mock-coverage.ts +115 -0
  72. package/src/rules/scan.ts +38 -25
  73. package/src/rules/scheduled-destroy-intent.ts +44 -0
  74. package/src/rules/signal-graph-coaching.ts +191 -0
  75. package/src/rules/specs.ts +9 -5
  76. package/src/rules/static-resource-accessor-preference.ts +649 -0
  77. package/src/rules/surface-facet-coherence.ts +362 -0
  78. package/src/rules/trail-fork-coaching.ts +616 -0
  79. package/src/rules/trail-versioning-source.ts +1072 -0
  80. package/src/rules/trail-versioning-topo.ts +172 -0
  81. package/src/rules/types.ts +297 -8
  82. package/src/rules/unmaterialized-activation-source.ts +84 -0
  83. package/src/rules/unreachable-detour-shadowing.ts +339 -0
  84. package/src/rules/valid-describe-refs.ts +162 -32
  85. package/src/rules/valid-detour-contract.ts +78 -0
  86. package/src/rules/warden-export-symmetry.ts +540 -0
  87. package/src/rules/warden-rules-use-ast.ts +1114 -0
  88. package/src/rules/webhook-route-collision.ts +243 -0
  89. package/src/trails/activation-orphan.trail.ts +84 -0
  90. package/src/trails/circular-refs.trail.ts +29 -0
  91. package/src/trails/cli-command-route-coherence.trail.ts +47 -0
  92. package/src/trails/composes-declarations.trail.ts +22 -0
  93. package/src/trails/context-no-surface-types.trail.ts +21 -0
  94. package/src/trails/contour-exists.trail.ts +21 -0
  95. package/src/trails/dead-internal-trail.trail.ts +26 -0
  96. package/src/trails/dead-public-trail.trail.ts +31 -0
  97. package/src/trails/deprecation-without-guidance.trail.ts +21 -0
  98. package/src/trails/draft-file-marking.trail.ts +16 -0
  99. package/src/trails/draft-visible-debt.trail.ts +16 -0
  100. package/src/trails/duplicate-public-contract.trail.ts +47 -0
  101. package/src/trails/error-mapping-completeness.trail.ts +30 -0
  102. package/src/trails/example-valid.trail.ts +25 -0
  103. package/src/trails/fires-declarations.trail.ts +23 -0
  104. package/src/trails/fork-without-preserved-blaze.trail.ts +31 -0
  105. package/src/trails/implementation-returns-result.trail.ts +20 -0
  106. package/src/trails/incomplete-accessor-for-standard-op.trail.ts +76 -0
  107. package/src/trails/incomplete-crud.trail.ts +39 -0
  108. package/src/trails/index.ts +83 -0
  109. package/src/trails/intent-propagation.trail.ts +30 -0
  110. package/src/trails/layer-field-name-drift.trail.ts +39 -0
  111. package/src/trails/library-projection-coherence.trail.ts +43 -0
  112. package/src/trails/marker-schema-unsupported.trail.ts +23 -0
  113. package/src/trails/missing-reconcile.trail.ts +33 -0
  114. package/src/trails/missing-visibility.trail.ts +22 -0
  115. package/src/trails/no-destructured-compose.trail.ts +44 -0
  116. package/src/trails/no-dev-permit-in-source.trail.ts +16 -0
  117. package/src/trails/no-direct-implementation-call.trail.ts +16 -0
  118. package/src/trails/no-legacy-layer-imports.trail.ts +41 -0
  119. package/src/trails/no-native-error-result.trail.ts +18 -0
  120. package/src/trails/no-redundant-result-error-wrap.trail.ts +55 -0
  121. package/src/trails/no-retired-cross-vocabulary.trail.ts +42 -0
  122. package/src/trails/no-sync-result-assumption.trail.ts +19 -0
  123. package/src/trails/no-throw-in-detour-recover.trail.ts +24 -0
  124. package/src/trails/no-throw-in-implementation.trail.ts +20 -0
  125. package/src/trails/no-top-level-surface.trail.ts +43 -0
  126. package/src/trails/on-references-exist.trail.ts +21 -0
  127. package/src/trails/orphaned-signal.trail.ts +36 -0
  128. package/src/trails/owner-projection-parity.trail.ts +26 -0
  129. package/src/trails/pending-force.trail.ts +21 -0
  130. package/src/trails/permit-governance.trail.ts +51 -0
  131. package/src/trails/prefer-schema-inference.trail.ts +21 -0
  132. package/src/trails/public-export-example-coverage.trail.ts +16 -0
  133. package/src/trails/public-internal-deep-imports.trail.ts +94 -0
  134. package/src/trails/public-output-schema.trail.ts +55 -0
  135. package/src/trails/public-union-output-discriminants.trail.ts +33 -0
  136. package/src/trails/read-intent-fires.trail.ts +20 -0
  137. package/src/trails/reference-exists.trail.ts +25 -0
  138. package/src/trails/resolved-import-boundary.trail.ts +109 -0
  139. package/src/trails/resource-declarations.trail.ts +25 -0
  140. package/src/trails/resource-exists.trail.ts +27 -0
  141. package/src/trails/resource-id-grammar.trail.ts +39 -0
  142. package/src/trails/resource-mock-coverage.trail.ts +40 -0
  143. package/src/trails/run.ts +162 -0
  144. package/src/trails/scheduled-destroy-intent.trail.ts +56 -0
  145. package/src/trails/schema.ts +198 -0
  146. package/src/trails/signal-graph-coaching.trail.ts +77 -0
  147. package/src/trails/static-resource-accessor-preference.trail.ts +25 -0
  148. package/src/trails/surface-facet-coherence.trail.ts +25 -0
  149. package/src/trails/topo.ts +6 -0
  150. package/src/trails/trail-fork-coaching.trail.ts +42 -0
  151. package/src/trails/unmaterialized-activation-source.trail.ts +72 -0
  152. package/src/trails/unreachable-detour-shadowing.trail.ts +45 -0
  153. package/src/trails/valid-describe-refs.trail.ts +18 -0
  154. package/src/trails/valid-detour-contract.trail.ts +71 -0
  155. package/src/trails/version-gap.trail.ts +35 -0
  156. package/src/trails/version-pinned-compose.trail.ts +23 -0
  157. package/src/trails/version-without-examples.trail.ts +38 -0
  158. package/src/trails/warden-export-symmetry.trail.ts +16 -0
  159. package/src/trails/warden-rules-use-ast.trail.ts +64 -0
  160. package/src/trails/webhook-route-collision.trail.ts +50 -0
  161. package/src/trails/wrap-rule.ts +214 -0
  162. package/src/workspaces.ts +199 -0
  163. package/.turbo/turbo-build.log +0 -1
  164. package/.turbo/turbo-lint.log +0 -3
  165. package/.turbo/turbo-typecheck.log +0 -1
  166. package/dist/cli.d.ts +0 -46
  167. package/dist/cli.d.ts.map +0 -1
  168. package/dist/cli.js +0 -221
  169. package/dist/cli.js.map +0 -1
  170. package/dist/drift.d.ts +0 -26
  171. package/dist/drift.d.ts.map +0 -1
  172. package/dist/drift.js +0 -27
  173. package/dist/drift.js.map +0 -1
  174. package/dist/formatters.d.ts +0 -29
  175. package/dist/formatters.d.ts.map +0 -1
  176. package/dist/formatters.js +0 -87
  177. package/dist/formatters.js.map +0 -1
  178. package/dist/index.d.ts +0 -26
  179. package/dist/index.d.ts.map +0 -1
  180. package/dist/index.js +0 -26
  181. package/dist/index.js.map +0 -1
  182. package/dist/rules/ast.d.ts +0 -41
  183. package/dist/rules/ast.d.ts.map +0 -1
  184. package/dist/rules/ast.js +0 -163
  185. package/dist/rules/ast.js.map +0 -1
  186. package/dist/rules/context-no-surface-types.d.ts +0 -12
  187. package/dist/rules/context-no-surface-types.d.ts.map +0 -1
  188. package/dist/rules/context-no-surface-types.js +0 -96
  189. package/dist/rules/context-no-surface-types.js.map +0 -1
  190. package/dist/rules/implementation-returns-result.d.ts +0 -13
  191. package/dist/rules/implementation-returns-result.d.ts.map +0 -1
  192. package/dist/rules/implementation-returns-result.js +0 -277
  193. package/dist/rules/implementation-returns-result.js.map +0 -1
  194. package/dist/rules/index.d.ts +0 -22
  195. package/dist/rules/index.d.ts.map +0 -1
  196. package/dist/rules/index.js +0 -41
  197. package/dist/rules/index.js.map +0 -1
  198. package/dist/rules/no-direct-impl-in-route.d.ts +0 -12
  199. package/dist/rules/no-direct-impl-in-route.d.ts.map +0 -1
  200. package/dist/rules/no-direct-impl-in-route.js +0 -46
  201. package/dist/rules/no-direct-impl-in-route.js.map +0 -1
  202. package/dist/rules/no-direct-implementation-call.d.ts +0 -12
  203. package/dist/rules/no-direct-implementation-call.d.ts.map +0 -1
  204. package/dist/rules/no-direct-implementation-call.js +0 -39
  205. package/dist/rules/no-direct-implementation-call.js.map +0 -1
  206. package/dist/rules/no-sync-result-assumption.d.ts +0 -6
  207. package/dist/rules/no-sync-result-assumption.d.ts.map +0 -1
  208. package/dist/rules/no-sync-result-assumption.js +0 -98
  209. package/dist/rules/no-sync-result-assumption.js.map +0 -1
  210. package/dist/rules/no-throw-in-detour-target.d.ts +0 -12
  211. package/dist/rules/no-throw-in-detour-target.d.ts.map +0 -1
  212. package/dist/rules/no-throw-in-detour-target.js +0 -87
  213. package/dist/rules/no-throw-in-detour-target.js.map +0 -1
  214. package/dist/rules/no-throw-in-implementation.d.ts +0 -9
  215. package/dist/rules/no-throw-in-implementation.d.ts.map +0 -1
  216. package/dist/rules/no-throw-in-implementation.js +0 -34
  217. package/dist/rules/no-throw-in-implementation.js.map +0 -1
  218. package/dist/rules/prefer-schema-inference.d.ts +0 -7
  219. package/dist/rules/prefer-schema-inference.d.ts.map +0 -1
  220. package/dist/rules/prefer-schema-inference.js +0 -86
  221. package/dist/rules/prefer-schema-inference.js.map +0 -1
  222. package/dist/rules/scan.d.ts +0 -8
  223. package/dist/rules/scan.d.ts.map +0 -1
  224. package/dist/rules/scan.js +0 -32
  225. package/dist/rules/scan.js.map +0 -1
  226. package/dist/rules/specs.d.ts +0 -29
  227. package/dist/rules/specs.d.ts.map +0 -1
  228. package/dist/rules/specs.js +0 -192
  229. package/dist/rules/specs.js.map +0 -1
  230. package/dist/rules/structure.d.ts +0 -13
  231. package/dist/rules/structure.d.ts.map +0 -1
  232. package/dist/rules/structure.js +0 -142
  233. package/dist/rules/structure.js.map +0 -1
  234. package/dist/rules/types.d.ts +0 -52
  235. package/dist/rules/types.d.ts.map +0 -1
  236. package/dist/rules/types.js +0 -2
  237. package/dist/rules/types.js.map +0 -1
  238. package/dist/rules/valid-describe-refs.d.ts +0 -7
  239. package/dist/rules/valid-describe-refs.d.ts.map +0 -1
  240. package/dist/rules/valid-describe-refs.js +0 -51
  241. package/dist/rules/valid-describe-refs.js.map +0 -1
  242. package/dist/rules/valid-detour-refs.d.ts +0 -6
  243. package/dist/rules/valid-detour-refs.d.ts.map +0 -1
  244. package/dist/rules/valid-detour-refs.js +0 -116
  245. package/dist/rules/valid-detour-refs.js.map +0 -1
  246. package/src/__tests__/cli.test.ts +0 -198
  247. package/src/__tests__/drift.test.ts +0 -74
  248. package/src/__tests__/formatters.test.ts +0 -157
  249. package/src/__tests__/implementation-returns-result.test.ts +0 -128
  250. package/src/__tests__/no-direct-implementation-call.test.ts +0 -83
  251. package/src/__tests__/no-sync-result-assumption.test.ts +0 -85
  252. package/src/__tests__/no-throw-in-detour-target.test.ts +0 -78
  253. package/src/__tests__/prefer-schema-inference.test.ts +0 -84
  254. package/src/__tests__/rules.test.ts +0 -215
  255. package/src/__tests__/valid-describe-refs.test.ts +0 -60
  256. package/src/rules/no-direct-impl-in-route.ts +0 -77
  257. package/src/rules/no-throw-in-detour-target.ts +0 -150
  258. package/src/rules/valid-detour-refs.ts +0 -187
  259. package/tsconfig.json +0 -9
  260. package/tsconfig.tsbuildinfo +0 -1
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,107 @@
9
9
 
10
10
  // Rule types
11
11
  export type {
12
+ BuiltinWardenRuleName,
12
13
  ProjectAwareWardenRule,
13
14
  ProjectContext,
15
+ TopoAwareWardenRule,
14
16
  WardenDiagnostic,
17
+ WardenFix,
18
+ WardenFixCapability,
19
+ WardenFixClass,
20
+ WardenFixEdit,
21
+ WardenFixScanTargets,
22
+ WardenFixSafety,
23
+ WardenGuidance,
24
+ WardenGuidanceLink,
15
25
  WardenRule,
26
+ WardenRuleConcern,
27
+ WardenRuleLifecycle,
28
+ WardenRuleLifecycleState,
29
+ WardenRuleMetadata,
30
+ WardenRuleScope,
31
+ WardenRuleTier,
16
32
  WardenSeverity,
17
33
  } from './rules/index.js';
18
34
 
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
35
  // Rule registry
32
- export { wardenRules } from './rules/index.js';
36
+ export {
37
+ builtinWardenRuleMetadata,
38
+ getWardenRuleMetadata,
39
+ listWardenRuleMetadata,
40
+ wardenFixClasses,
41
+ wardenFixSafeties,
42
+ wardenRuleConcerns,
43
+ wardenRuleLifecycleStates,
44
+ wardenRuleScopes,
45
+ wardenRuleTiers,
46
+ wardenRules,
47
+ wardenTopoRules,
48
+ } from './rules/index.js';
49
+
50
+ // Rule-scoped cache controls for long-lived consumers (watch mode, LSPs).
51
+ export { clearImplementationReturnsResultCache } from './rules/implementation-returns-result.js';
52
+ export {
53
+ isWardenDevPermitTestScanTarget,
54
+ isWardenInfrastructureScanTarget,
55
+ isWardenSourceScanTarget,
56
+ isWardenTestScanTarget,
57
+ } from './rules/scan.js';
33
58
 
34
59
  // CLI runner
35
- export type { WardenOptions, WardenReport } from './cli.js';
60
+ export type {
61
+ WardenOptions,
62
+ WardenReport,
63
+ WardenRunOptions,
64
+ WardenTopoTarget,
65
+ } from './cli.js';
36
66
  export { formatWardenReport, runWarden } from './cli.js';
37
67
 
68
+ // Adapter authoring checks
69
+ export {
70
+ adapterCheckRuleName,
71
+ runWardenAdapterChecks,
72
+ } from './adapter-check.js';
73
+
74
+ // CLI command surface
75
+ export type {
76
+ ParsedWardenCommand,
77
+ RunWardenCommandOptions,
78
+ WardenCommandResult,
79
+ } from './command.js';
80
+ export {
81
+ formatWardenCommandOutput,
82
+ loadWardenConfig,
83
+ parseWardenCommandArgs,
84
+ resolveWardenTopoTargets,
85
+ runWardenCommand,
86
+ } from './command.js';
87
+
88
+ // Config schema
89
+ export {
90
+ wardenConfigSchema,
91
+ wardenDepthValues,
92
+ wardenDraftsValues,
93
+ wardenFailOnValues,
94
+ wardenFormatValues,
95
+ wardenLockValues,
96
+ } from './config.js';
97
+ export type {
98
+ WardenConfig,
99
+ WardenConfigInput,
100
+ WardenDepth,
101
+ WardenDraftsMode,
102
+ WardenFailOn,
103
+ WardenFormat,
104
+ WardenScope,
105
+ WardenLockMode,
106
+ EffectiveWardenConfig,
107
+ ResolveWardenConfigOptions,
108
+ WardenConfigLayer,
109
+ WardenConfigResolution,
110
+ } from './config.js';
111
+ export { resolveWardenConfig } from './config.js';
112
+
38
113
  // CI formatters
39
114
  export {
40
115
  formatGitHubAnnotations,
@@ -45,3 +120,131 @@ export {
45
120
  // Drift detection
46
121
  export type { DriftResult } from './drift.js';
47
122
  export { checkDrift } from './drift.js';
123
+
124
+ // Project-local rule loading
125
+ export type { ProjectWardenRules } from './project-rules.js';
126
+ export { loadProjectWardenRules } from './project-rules.js';
127
+
128
+ // Guide projection
129
+ export type {
130
+ WardenGuideFormat,
131
+ WardenGuideManifest,
132
+ WardenRuleGuideEntry,
133
+ } from './guide.js';
134
+ export {
135
+ buildWardenAgentGuide,
136
+ buildWardenGuideManifest,
137
+ formatWardenGuide,
138
+ formatWardenGuideMarkdown,
139
+ wardenGuideFormatValues,
140
+ } from './guide.js';
141
+
142
+ // Resolver helpers
143
+ export {
144
+ collectImportResolutionsForFile,
145
+ collectImportSpecifiers,
146
+ createWardenResolver,
147
+ defaultWardenResolveOptions,
148
+ } from './resolve.js';
149
+ export type {
150
+ WardenImportResolution,
151
+ WardenImportResolutionErrorKind,
152
+ WardenImportSpecifier,
153
+ WardenProjectResolver,
154
+ WardenResolverOptions,
155
+ } from './resolve.js';
156
+
157
+ // Draft helpers
158
+ export {
159
+ DRAFT_FILE_PREFIX,
160
+ DRAFT_FILE_SEGMENT,
161
+ isDraftMarkedFile,
162
+ stripDraftFileMarkers,
163
+ } from './draft.js';
164
+
165
+ // Trail layer
166
+ export { wardenTopo } from './trails/topo.js';
167
+ export { runTopoAwareWardenTrails, runWardenTrails } from './trails/run.js';
168
+ export {
169
+ activationOrphanTrail,
170
+ cliCommandRouteCoherenceTrail,
171
+ circularRefsTrail,
172
+ contourExistsTrail,
173
+ contextNoSurfaceTypesTrail,
174
+ composesDeclarationsTrail,
175
+ deadInternalTrailTrail,
176
+ deadPublicTrailTrail,
177
+ deprecationWithoutGuidanceTrail,
178
+ diagnosticSchema,
179
+ duplicatePublicContractTrail,
180
+ draftFileMarkingTrail,
181
+ draftVisibleDebtTrail,
182
+ errorMappingCompletenessTrail,
183
+ exampleValidTrail,
184
+ firesDeclarationsTrail,
185
+ forkWithoutPreservedBlazeTrail,
186
+ implementationReturnsResultTrail,
187
+ incompleteAccessorForStandardOpTrail,
188
+ incompleteCrudTrail,
189
+ intentPropagationTrail,
190
+ layerFieldNameDriftTrail,
191
+ libraryProjectionCoherenceTrail,
192
+ markerSchemaUnsupportedTrail,
193
+ missingVisibilityTrail,
194
+ missingReconcileTrail,
195
+ noDevPermitInSourceTrail,
196
+ noDestructuredComposeTrail,
197
+ noDirectImplementationCallTrail,
198
+ noLegacyLayerImportsTrail,
199
+ noNativeErrorResultTrail,
200
+ noRedundantResultErrorWrapTrail,
201
+ noRetiredCrossVocabularyTrail,
202
+ noSyncResultAssumptionTrail,
203
+ noThrowInDetourRecoverTrail,
204
+ noThrowInImplementationTrail,
205
+ noTopLevelSurfaceTrail,
206
+ onReferencesExistTrail,
207
+ orphanedSignalTrail,
208
+ ownerProjectionParityTrail,
209
+ pendingForceTrail,
210
+ permitGovernanceTrail,
211
+ preferSchemaInferenceTrail,
212
+ projectAwareRuleInput,
213
+ publicExportExampleCoverageTrail,
214
+ publicInternalDeepImportsTrail,
215
+ publicOutputSchemaTrail,
216
+ publicUnionOutputDiscriminantsTrail,
217
+ readIntentFiresTrail,
218
+ referenceExistsTrail,
219
+ resolvedImportBoundaryTrail,
220
+ ruleInput,
221
+ ruleOutput,
222
+ resourceDeclarationsTrail,
223
+ resourceIdGrammarTrail,
224
+ resourceExistsTrail,
225
+ resourceMockCoverageTrail,
226
+ scheduledDestroyIntentTrail,
227
+ signalGraphCoachingTrail,
228
+ staticResourceAccessorPreferenceTrail,
229
+ surfaceFacetCoherenceTrail,
230
+ trailForkCoachingTrail,
231
+ unmaterializedActivationSourceTrail,
232
+ topoAwareRuleInput,
233
+ unreachableDetourShadowingTrail,
234
+ validDetourContractTrail,
235
+ validDescribeRefsTrail,
236
+ versionGapTrail,
237
+ versionPinnedComposeTrail,
238
+ versionWithoutExamplesTrail,
239
+ wardenExportSymmetryTrail,
240
+ wardenRulesUseAstTrail,
241
+ webhookRouteCollisionTrail,
242
+ wrapRule,
243
+ wrapTopoRule,
244
+ } from './trails/index.js';
245
+ export type {
246
+ ProjectAwareRuleInput,
247
+ RuleInput,
248
+ RuleOutput,
249
+ TopoAwareRuleInput,
250
+ } from './trails/index.js';
@@ -0,0 +1,192 @@
1
+ /**
2
+ * Project-context helpers shared by the Warden runner and resolver-backed
3
+ * rules.
4
+ */
5
+
6
+ import { realpathSync } from 'node:fs';
7
+ import { resolve } from 'node:path';
8
+
9
+ import {
10
+ collectImportResolutionsForFile,
11
+ createWardenResolver,
12
+ normalizePath,
13
+ } from './resolve.js';
14
+ import type {
15
+ WardenImportResolution,
16
+ WardenResolverOptions,
17
+ } from './resolve.js';
18
+ import { offsetToLine } from './rules/ast.js';
19
+ import { collectPublicWorkspaces } from './workspaces.js';
20
+ import type { WardenPublicWorkspace } from './workspaces.js';
21
+
22
+ const ONTRAILS_DOCUMENTATION_SPECIFIER_PATTERN =
23
+ /@ontrails\/[a-z0-9-]+(?:\/[A-Za-z0-9._~-]+)+/g;
24
+
25
+ const normalizeRealPath = (path: string): string => {
26
+ try {
27
+ return normalizePath(realpathSync(path));
28
+ } catch {
29
+ return normalizePath(resolve(path));
30
+ }
31
+ };
32
+
33
+ const setResolutionsForFile = (
34
+ resolutionsByFile: Map<string, readonly WardenImportResolution[]>,
35
+ sourceFilePath: string,
36
+ resolutions: readonly WardenImportResolution[]
37
+ ): void => {
38
+ const normalizedFilePath =
39
+ resolutions[0]?.importerPath ?? normalizeRealPath(sourceFilePath);
40
+ resolutionsByFile.set(normalizedFilePath, resolutions);
41
+ if (normalizedFilePath !== sourceFilePath) {
42
+ resolutionsByFile.set(sourceFilePath, resolutions);
43
+ }
44
+ };
45
+
46
+ export interface WardenProjectContextSourceFile {
47
+ readonly filePath: string;
48
+ readonly kind: 'documentation' | 'text' | 'typescript';
49
+ readonly sourceCode: string;
50
+ }
51
+
52
+ const collectDocumentationImportSpecifiers = (
53
+ sourceCode: string
54
+ ): readonly { readonly importSource: string; readonly line: number }[] => {
55
+ const specifiers: { importSource: string; line: number }[] = [];
56
+ for (const match of sourceCode.matchAll(
57
+ ONTRAILS_DOCUMENTATION_SPECIFIER_PATTERN
58
+ )) {
59
+ if (match.index === undefined) {
60
+ continue;
61
+ }
62
+ specifiers.push({
63
+ importSource: match[0],
64
+ line: offsetToLine(sourceCode, match.index),
65
+ });
66
+ }
67
+ return specifiers;
68
+ };
69
+
70
+ const exportAliasesForWorkspaces = (
71
+ workspaces: ReadonlyMap<string, WardenPublicWorkspace>
72
+ ): Record<string, string[]> => {
73
+ const aliases: Record<string, string[]> = {};
74
+ for (const workspace of workspaces.values()) {
75
+ for (const [specifier, target] of Object.entries(
76
+ workspace.exportTargets ?? {}
77
+ )) {
78
+ aliases[`${specifier}$`] = [target];
79
+ }
80
+ }
81
+ return aliases;
82
+ };
83
+
84
+ const resolveOptionsWithWorkspaceAliases = (
85
+ publicWorkspaces: ReadonlyMap<string, WardenPublicWorkspace> | undefined,
86
+ resolveOptions: WardenResolverOptions['resolveOptions'] | undefined
87
+ ): WardenResolverOptions['resolveOptions'] => {
88
+ if (!publicWorkspaces) {
89
+ return resolveOptions;
90
+ }
91
+
92
+ const workspaceAliases = exportAliasesForWorkspaces(publicWorkspaces);
93
+ return {
94
+ ...resolveOptions,
95
+ alias: {
96
+ ...workspaceAliases,
97
+ ...resolveOptions?.alias,
98
+ },
99
+ };
100
+ };
101
+
102
+ export const collectProjectImportResolutions = ({
103
+ publicWorkspaces,
104
+ resolveOptions,
105
+ rootDir,
106
+ sourceFiles,
107
+ }: {
108
+ readonly publicWorkspaces?: ReadonlyMap<string, WardenPublicWorkspace>;
109
+ readonly resolveOptions?: WardenResolverOptions['resolveOptions'];
110
+ readonly rootDir: string;
111
+ readonly sourceFiles: readonly WardenProjectContextSourceFile[];
112
+ }): ReadonlyMap<string, readonly WardenImportResolution[]> => {
113
+ const resolver = createWardenResolver({
114
+ resolveOptions: resolveOptionsWithWorkspaceAliases(
115
+ publicWorkspaces,
116
+ resolveOptions
117
+ ),
118
+ rootDir,
119
+ });
120
+ const resolutionsByFile = new Map<
121
+ string,
122
+ readonly WardenImportResolution[]
123
+ >();
124
+
125
+ for (const sourceFile of sourceFiles) {
126
+ if (sourceFile.kind !== 'typescript') {
127
+ continue;
128
+ }
129
+ const resolutions = collectImportResolutionsForFile({
130
+ filePath: sourceFile.filePath,
131
+ resolver,
132
+ sourceCode: sourceFile.sourceCode,
133
+ });
134
+ if (resolutions.length > 0) {
135
+ setResolutionsForFile(
136
+ resolutionsByFile,
137
+ sourceFile.filePath,
138
+ resolutions
139
+ );
140
+ }
141
+ }
142
+
143
+ return resolutionsByFile;
144
+ };
145
+
146
+ export const collectProjectDocumentationImportResolutions = ({
147
+ publicWorkspaces: providedPublicWorkspaces,
148
+ rootDir,
149
+ sourceFiles,
150
+ }: {
151
+ readonly publicWorkspaces?: ReadonlyMap<string, WardenPublicWorkspace>;
152
+ readonly rootDir: string;
153
+ readonly sourceFiles: readonly WardenProjectContextSourceFile[];
154
+ }): ReadonlyMap<string, readonly WardenImportResolution[]> => {
155
+ const publicWorkspaces =
156
+ providedPublicWorkspaces ?? collectPublicWorkspaces(rootDir);
157
+ const resolver = createWardenResolver({
158
+ resolveOptions: { alias: exportAliasesForWorkspaces(publicWorkspaces) },
159
+ rootDir,
160
+ });
161
+ const resolutionsByFile = new Map<
162
+ string,
163
+ readonly WardenImportResolution[]
164
+ >();
165
+
166
+ for (const sourceFile of sourceFiles) {
167
+ if (sourceFile.kind !== 'documentation') {
168
+ continue;
169
+ }
170
+ const resolutions = collectDocumentationImportSpecifiers(
171
+ sourceFile.sourceCode
172
+ ).map((specifier) =>
173
+ resolver.resolveImport(
174
+ sourceFile.filePath,
175
+ specifier.importSource,
176
+ specifier.line
177
+ )
178
+ );
179
+ if (resolutions.length > 0) {
180
+ setResolutionsForFile(
181
+ resolutionsByFile,
182
+ sourceFile.filePath,
183
+ resolutions
184
+ );
185
+ }
186
+ }
187
+
188
+ return resolutionsByFile;
189
+ };
190
+
191
+ export { collectPublicWorkspaces };
192
+ export type { WardenPublicWorkspace } from './workspaces.js';