@ontrails/warden 1.0.0-beta.39 → 1.0.0-beta.41

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 (134) hide show
  1. package/CHANGELOG.md +125 -0
  2. package/README.md +5 -5
  3. package/package.json +10 -12
  4. package/src/cli.ts +49 -44
  5. package/src/command.ts +2 -2
  6. package/src/drift.ts +4 -3
  7. package/src/index.ts +3 -2
  8. package/src/project-context.ts +3 -3
  9. package/src/resolve.ts +2 -2
  10. package/src/rules/captured-kernel.ts +375 -0
  11. package/src/rules/circular-refs.ts +12 -16
  12. package/src/rules/cli-command-route-coherence.ts +1 -1
  13. package/src/rules/composes-declarations.ts +15 -13
  14. package/src/rules/context-no-surface-types.ts +4 -3
  15. package/src/rules/dead-internal-trail.ts +3 -3
  16. package/src/rules/dead-public-trail.ts +3 -3
  17. package/src/rules/draft-file-marking.ts +4 -9
  18. package/src/rules/draft-visible-debt.ts +3 -8
  19. package/src/rules/duplicate-public-contract.ts +50 -5
  20. package/src/rules/{contour-exists.ts → entity-exists.ts} +67 -64
  21. package/src/rules/entity-ids.ts +15 -0
  22. package/src/rules/error-mapping-completeness.ts +2 -2
  23. package/src/rules/example-valid.ts +84 -84
  24. package/src/rules/fires-declarations.ts +27 -22
  25. package/src/rules/governed-symbol-residue.ts +339 -32
  26. package/src/rules/implementation-returns-result.ts +8 -8
  27. package/src/rules/incomplete-accessor-for-standard-op.ts +15 -15
  28. package/src/rules/incomplete-crud.ts +42 -40
  29. package/src/rules/index.ts +9 -6
  30. package/src/rules/intent-propagation.ts +3 -5
  31. package/src/rules/layer-field-name-drift.ts +2 -2
  32. package/src/rules/library-projection-coherence.ts +2 -2
  33. package/src/rules/metadata.ts +37 -15
  34. package/src/rules/missing-reconcile.ts +3 -4
  35. package/src/rules/missing-visibility.ts +2 -1
  36. package/src/rules/no-destructured-compose.ts +14 -12
  37. package/src/rules/no-direct-implementation-call.ts +12 -7
  38. package/src/rules/no-legacy-cli-alias-export.ts +2 -2
  39. package/src/rules/no-native-error-result.ts +2 -2
  40. package/src/rules/no-redundant-result-error-wrap.ts +10 -10
  41. package/src/rules/no-sync-result-assumption.ts +84 -78
  42. package/src/rules/no-throw-in-detour-recover.ts +2 -2
  43. package/src/rules/no-throw-in-implementation.ts +13 -7
  44. package/src/rules/no-top-level-surface.ts +2 -2
  45. package/src/rules/on-references-exist.ts +3 -3
  46. package/src/rules/orphaned-signal.ts +4 -5
  47. package/src/rules/owner-projection-parity.ts +2 -2
  48. package/src/rules/public-export-example-coverage.ts +10 -4
  49. package/src/rules/public-internal-deep-imports.ts +2 -3
  50. package/src/rules/read-intent-fires.ts +4 -3
  51. package/src/rules/reference-exists.ts +20 -21
  52. package/src/rules/registry-names.ts +6 -4
  53. package/src/rules/resolved-import-boundary.ts +1 -1
  54. package/src/rules/resource-declarations.ts +21 -17
  55. package/src/rules/resource-exists.ts +4 -2
  56. package/src/rules/resource-id-grammar.ts +2 -2
  57. package/src/rules/resource-mock-coverage.ts +2 -2
  58. package/src/rules/retired-vocabulary.ts +242 -5
  59. package/src/rules/source/composition.ts +165 -0
  60. package/src/rules/source/drafts.ts +164 -0
  61. package/src/rules/source/entities.ts +618 -0
  62. package/src/rules/source/pragmas.ts +45 -0
  63. package/src/rules/source/resources.ts +64 -0
  64. package/src/rules/source/signals.ts +397 -0
  65. package/src/rules/source/stores.ts +310 -0
  66. package/src/rules/static-resource-accessor-preference.ts +15 -11
  67. package/src/rules/surface-overlay-coherence.ts +1 -1
  68. package/src/rules/surface-trailhead-coherence.ts +2 -2
  69. package/src/rules/trail-fork-coaching.ts +27 -18
  70. package/src/rules/trail-versioning-source.ts +16 -12
  71. package/src/rules/trail-versioning-topo.ts +2 -2
  72. package/src/rules/trailhead-override-divergence.ts +2 -2
  73. package/src/rules/types.ts +7 -7
  74. package/src/rules/unmaterialized-activation-source.ts +1 -0
  75. package/src/rules/unreachable-detour-shadowing.ts +2 -2
  76. package/src/rules/valid-describe-refs.ts +2 -2
  77. package/src/rules/warden-export-symmetry.ts +3 -3
  78. package/src/rules/warden-rules-use-ast.ts +12 -17
  79. package/src/trails/activation-orphan.trail.ts +5 -5
  80. package/src/trails/captured-kernel.trail.ts +108 -0
  81. package/src/trails/circular-refs.trail.ts +6 -6
  82. package/src/trails/cli-command-route-coherence.trail.ts +2 -2
  83. package/src/trails/composes-declarations.trail.ts +1 -1
  84. package/src/trails/context-no-surface-types.trail.ts +1 -1
  85. package/src/trails/dead-internal-trail.trail.ts +2 -2
  86. package/src/trails/dead-public-trail.trail.ts +1 -1
  87. package/src/trails/deprecation-without-guidance.trail.ts +1 -1
  88. package/src/trails/duplicate-public-contract.trail.ts +3 -3
  89. package/src/trails/entity-exists.trail.ts +21 -0
  90. package/src/trails/example-valid.trail.ts +3 -3
  91. package/src/trails/fires-declarations.trail.ts +1 -1
  92. package/src/trails/{fork-without-preserved-blaze.trail.ts → fork-without-preserved-implementation.trail.ts} +4 -4
  93. package/src/trails/implementation-returns-result.trail.ts +1 -1
  94. package/src/trails/incomplete-accessor-for-standard-op.trail.ts +4 -4
  95. package/src/trails/index.ts +3 -2
  96. package/src/trails/intent-propagation.trail.ts +2 -2
  97. package/src/trails/library-projection-coherence.trail.ts +2 -2
  98. package/src/trails/marker-schema-unsupported.trail.ts +1 -1
  99. package/src/trails/missing-visibility.trail.ts +1 -1
  100. package/src/trails/no-destructured-compose.trail.ts +5 -5
  101. package/src/trails/no-direct-implementation-call.trail.ts +1 -1
  102. package/src/trails/no-redundant-result-error-wrap.trail.ts +2 -2
  103. package/src/trails/no-sync-result-assumption.trail.ts +2 -2
  104. package/src/trails/no-throw-in-detour-recover.trail.ts +1 -1
  105. package/src/trails/no-throw-in-implementation.trail.ts +1 -1
  106. package/src/trails/on-references-exist.trail.ts +1 -1
  107. package/src/trails/pending-force.trail.ts +1 -1
  108. package/src/trails/permit-governance.trail.ts +2 -2
  109. package/src/trails/prefer-schema-inference.trail.ts +1 -1
  110. package/src/trails/public-output-schema.trail.ts +2 -2
  111. package/src/trails/public-union-output-discriminants.trail.ts +1 -1
  112. package/src/trails/read-intent-fires.trail.ts +1 -1
  113. package/src/trails/reference-exists.trail.ts +5 -5
  114. package/src/trails/resource-declarations.trail.ts +1 -1
  115. package/src/trails/resource-exists.trail.ts +1 -1
  116. package/src/trails/run.ts +4 -6
  117. package/src/trails/scheduled-destroy-intent.trail.ts +2 -2
  118. package/src/trails/schema.ts +9 -9
  119. package/src/trails/signal-graph-coaching.trail.ts +2 -2
  120. package/src/trails/static-resource-accessor-preference.trail.ts +1 -1
  121. package/src/trails/surface-overlay-coherence.trail.ts +1 -1
  122. package/src/trails/trail-fork-coaching.trail.ts +1 -1
  123. package/src/trails/unmaterialized-activation-source.trail.ts +5 -5
  124. package/src/trails/valid-detour-contract.trail.ts +1 -1
  125. package/src/trails/version-gap.trail.ts +1 -1
  126. package/src/trails/version-pinned-compose.trail.ts +1 -1
  127. package/src/trails/version-without-examples.trail.ts +1 -1
  128. package/src/trails/warden-rules-use-ast.trail.ts +2 -2
  129. package/src/trails/webhook-route-collision.trail.ts +2 -2
  130. package/src/trails/wrap-rule.ts +17 -17
  131. package/src/ast.ts +0 -137
  132. package/src/rules/ast.ts +0 -4143
  133. package/src/rules/contour-ids.ts +0 -15
  134. package/src/trails/contour-exists.trail.ts +0 -21
@@ -0,0 +1,375 @@
1
+ import { realpathSync } from 'node:fs';
2
+ import { resolve } from 'node:path';
3
+
4
+ import {
5
+ extractStringLiteral,
6
+ getNodeDeclaration,
7
+ getNodeImportKind,
8
+ getNodeLocal,
9
+ getNodeSource,
10
+ getNodeSpecifiers,
11
+ identifierName,
12
+ isExportDefaultDeclaration,
13
+ isExportNamedDeclaration,
14
+ isImportDeclaration,
15
+ offsetToLine,
16
+ parse,
17
+ walk,
18
+ } from '@ontrails/source';
19
+ import type { WardenImportResolution } from '../resolve.js';
20
+ import type { WardenPublicWorkspace } from '../workspaces.js';
21
+ import { hasIgnoreCommentOnLine, splitSourceLines } from './source/pragmas.js';
22
+ import { isTestFile } from './scan.js';
23
+ import type {
24
+ ProjectAwareWardenRule,
25
+ ProjectContext,
26
+ WardenDiagnostic,
27
+ } from './types.js';
28
+
29
+ const RULE_NAME = 'captured-kernel';
30
+
31
+ interface InternalReExportSite {
32
+ readonly importSource: string;
33
+ readonly line: number;
34
+ readonly resolutionLine: number;
35
+ }
36
+
37
+ interface ImportedBindingSite {
38
+ readonly importSource: string;
39
+ readonly resolutionLine: number;
40
+ }
41
+
42
+ interface ExportTarget {
43
+ readonly specifier: string;
44
+ readonly workspace: WardenPublicWorkspace;
45
+ }
46
+
47
+ const normalizePath = (path: string): string => path.replaceAll('\\', '/');
48
+
49
+ const normalizeRealPath = (path: string): string => {
50
+ try {
51
+ return normalizePath(realpathSync(path));
52
+ } catch {
53
+ return normalizePath(resolve(path));
54
+ }
55
+ };
56
+
57
+ const pathIsInside = (filePath: string, rootDir: string): boolean => {
58
+ const absoluteFilePath = normalizeRealPath(filePath);
59
+ const absoluteRootDir = normalizeRealPath(rootDir);
60
+ return (
61
+ absoluteFilePath === absoluteRootDir ||
62
+ absoluteFilePath.startsWith(`${absoluteRootDir}/`)
63
+ );
64
+ };
65
+
66
+ const sourcePackageNameForFile = (
67
+ filePath: string,
68
+ workspaces: ReadonlyMap<string, WardenPublicWorkspace>
69
+ ): string | undefined => {
70
+ for (const workspace of workspaces.values()) {
71
+ if (pathIsInside(filePath, workspace.rootDir)) {
72
+ return workspace.name;
73
+ }
74
+ }
75
+ return undefined;
76
+ };
77
+
78
+ const exportTargetsForFile = (
79
+ filePath: string,
80
+ workspaces: ReadonlyMap<string, WardenPublicWorkspace>
81
+ ): readonly ExportTarget[] => {
82
+ const normalizedFilePath = normalizeRealPath(filePath);
83
+ const targets: ExportTarget[] = [];
84
+ for (const workspace of workspaces.values()) {
85
+ for (const [specifier, target] of Object.entries(
86
+ workspace.exportTargets ?? {}
87
+ )) {
88
+ if (specifier === workspace.name) {
89
+ continue;
90
+ }
91
+ if (normalizeRealPath(target) === normalizedFilePath) {
92
+ targets.push({ specifier, workspace });
93
+ }
94
+ }
95
+ }
96
+ return targets.toSorted((left, right) =>
97
+ left.specifier.localeCompare(right.specifier)
98
+ );
99
+ };
100
+
101
+ const collectReExportSites = (
102
+ sourceCode: string,
103
+ filePath: string
104
+ ): readonly InternalReExportSite[] => {
105
+ const ast = parse(filePath, sourceCode);
106
+ if (!ast) {
107
+ return [];
108
+ }
109
+
110
+ const sites: InternalReExportSite[] = [];
111
+ const importedBindings = new Map<string, ImportedBindingSite>();
112
+ walk(ast, (node) => {
113
+ if (!isImportDeclaration(node)) {
114
+ return;
115
+ }
116
+ if (getNodeImportKind(node) === 'type') {
117
+ return;
118
+ }
119
+ const value = extractStringLiteral(getNodeSource(node));
120
+ if (typeof value !== 'string') {
121
+ return;
122
+ }
123
+ const resolutionLine = offsetToLine(sourceCode, node.start);
124
+ for (const specifier of getNodeSpecifiers(node) ?? []) {
125
+ if (getNodeImportKind(specifier) === 'type') {
126
+ continue;
127
+ }
128
+ const localName = identifierName(getNodeLocal(specifier));
129
+ if (localName !== null) {
130
+ importedBindings.set(localName, {
131
+ importSource: value,
132
+ resolutionLine,
133
+ });
134
+ }
135
+ }
136
+ });
137
+
138
+ const siteKeys = new Set<string>();
139
+ const addSite = (site: InternalReExportSite): void => {
140
+ const key = `${site.importSource}:${site.line}:${site.resolutionLine}`;
141
+ if (!siteKeys.has(key)) {
142
+ siteKeys.add(key);
143
+ sites.push(site);
144
+ }
145
+ };
146
+ walk(ast, (node) => {
147
+ if (isExportDefaultDeclaration(node)) {
148
+ const localName = identifierName(getNodeDeclaration(node));
149
+ if (localName === null) {
150
+ return;
151
+ }
152
+ const imported = importedBindings.get(localName);
153
+ if (imported !== undefined) {
154
+ addSite({
155
+ ...imported,
156
+ line: offsetToLine(sourceCode, node.start),
157
+ });
158
+ }
159
+ return;
160
+ }
161
+
162
+ if (
163
+ node.type !== 'ExportNamedDeclaration' &&
164
+ node.type !== 'ExportAllDeclaration'
165
+ ) {
166
+ return;
167
+ }
168
+
169
+ const line = offsetToLine(sourceCode, node.start);
170
+ const value = extractStringLiteral(getNodeSource(node));
171
+ if (typeof value === 'string') {
172
+ addSite({ importSource: value, line, resolutionLine: line });
173
+ return;
174
+ }
175
+
176
+ if (!isExportNamedDeclaration(node)) {
177
+ return;
178
+ }
179
+ for (const specifier of getNodeSpecifiers(node) ?? []) {
180
+ const localName = identifierName(getNodeLocal(specifier));
181
+ if (localName === null) {
182
+ continue;
183
+ }
184
+ const imported = importedBindings.get(localName);
185
+ if (imported !== undefined) {
186
+ addSite({ ...imported, line });
187
+ }
188
+ }
189
+ });
190
+ return sites.toSorted((left, right) => left.line - right.line);
191
+ };
192
+
193
+ const importResolutionsForFile = (
194
+ context: ProjectContext,
195
+ filePath: string
196
+ ): readonly WardenImportResolution[] =>
197
+ context.importResolutionsByFile?.get(filePath) ?? [];
198
+
199
+ const reExportResolution = (
200
+ resolutions: readonly WardenImportResolution[],
201
+ site: InternalReExportSite
202
+ ): WardenImportResolution | undefined =>
203
+ resolutions.find(
204
+ (resolution) =>
205
+ resolution.importSource === site.importSource &&
206
+ resolution.line === site.resolutionLine
207
+ );
208
+
209
+ const isOwnedInternalReExport = (
210
+ workspace: WardenPublicWorkspace,
211
+ resolution: WardenImportResolution | undefined
212
+ ): boolean => {
213
+ if (!resolution?.isInternalTarget) {
214
+ return false;
215
+ }
216
+ if (resolution.packageName === workspace.name) {
217
+ return true;
218
+ }
219
+ if (
220
+ resolution.packageRoot &&
221
+ normalizeRealPath(resolution.packageRoot) ===
222
+ normalizeRealPath(workspace.rootDir)
223
+ ) {
224
+ return true;
225
+ }
226
+ return resolution.resolvedPath
227
+ ? pathIsInside(resolution.resolvedPath, workspace.rootDir)
228
+ : false;
229
+ };
230
+
231
+ const isNonProductionEvidenceFile = (filePath: string): boolean => {
232
+ const normalized = normalizePath(filePath);
233
+ return (
234
+ isTestFile(filePath) ||
235
+ /(?:^|\/)(?:__fixtures__|fixtures?|migrations?|historical|changelogs?|changesets?|agent-notes?)(?:\/|$)/.test(
236
+ normalized
237
+ ) ||
238
+ /(?:^|\/)\.(?:changeset|agents)(?:\/|$)/.test(normalized) ||
239
+ /(?:^|\/)(?:CHANGELOG|changeset)\.md$/i.test(normalized)
240
+ );
241
+ };
242
+
243
+ const externalProductionConsumerPackages = ({
244
+ context,
245
+ exportSpecifier,
246
+ hostPackage,
247
+ workspaces,
248
+ }: {
249
+ readonly context: ProjectContext;
250
+ readonly exportSpecifier: string;
251
+ readonly hostPackage: string;
252
+ readonly workspaces: ReadonlyMap<string, WardenPublicWorkspace>;
253
+ }): readonly string[] => {
254
+ const consumers = new Set<string>();
255
+ for (const resolutions of context.importResolutionsByFile?.values() ?? []) {
256
+ for (const resolution of resolutions) {
257
+ if (resolution.importSource !== exportSpecifier) {
258
+ continue;
259
+ }
260
+ if (resolution.errorKind || !resolution.usesPublicExport) {
261
+ continue;
262
+ }
263
+ if (!resolution.crossesPackageBoundary) {
264
+ continue;
265
+ }
266
+ if (resolution.packageName !== hostPackage) {
267
+ continue;
268
+ }
269
+ if (isNonProductionEvidenceFile(resolution.importerPath)) {
270
+ continue;
271
+ }
272
+
273
+ const sourcePackageName = sourcePackageNameForFile(
274
+ resolution.importerPath,
275
+ workspaces
276
+ );
277
+ if (!sourcePackageName || sourcePackageName === hostPackage) {
278
+ continue;
279
+ }
280
+ consumers.add(sourcePackageName);
281
+ }
282
+ }
283
+ return [...consumers].toSorted();
284
+ };
285
+
286
+ const diagnosticMessage = ({
287
+ consumers,
288
+ exportSpecifier,
289
+ hostPackage,
290
+ importSource,
291
+ }: {
292
+ readonly consumers: readonly string[];
293
+ readonly exportSpecifier: string;
294
+ readonly hostPackage: string;
295
+ readonly importSource: string;
296
+ }): string =>
297
+ `${hostPackage} export target "${exportSpecifier}" re-exports internal target "${importSource}" and is consumed by external production packages ${consumers.join(
298
+ ', '
299
+ )}. Review ownership of the exported subpath and its captured kernel before it becomes a durable public seam.`;
300
+
301
+ export const capturedKernel: ProjectAwareWardenRule = {
302
+ check(): readonly WardenDiagnostic[] {
303
+ return [];
304
+ },
305
+ checkWithContext(
306
+ sourceCode: string,
307
+ filePath: string,
308
+ context: ProjectContext
309
+ ): readonly WardenDiagnostic[] {
310
+ const workspaces = context.publicWorkspaces;
311
+ if (!workspaces || workspaces.size === 0) {
312
+ return [];
313
+ }
314
+
315
+ const exportTargets = exportTargetsForFile(filePath, workspaces);
316
+ if (exportTargets.length === 0) {
317
+ return [];
318
+ }
319
+
320
+ const lines = splitSourceLines(sourceCode);
321
+ const resolutions = importResolutionsForFile(context, filePath);
322
+ const diagnostics: WardenDiagnostic[] = [];
323
+
324
+ for (const exportTarget of exportTargets) {
325
+ const consumers = externalProductionConsumerPackages({
326
+ context,
327
+ exportSpecifier: exportTarget.specifier,
328
+ hostPackage: exportTarget.workspace.name,
329
+ workspaces,
330
+ });
331
+ if (consumers.length < 2) {
332
+ continue;
333
+ }
334
+
335
+ for (const site of collectReExportSites(sourceCode, filePath)) {
336
+ if (hasIgnoreCommentOnLine(lines, site.line)) {
337
+ continue;
338
+ }
339
+
340
+ const resolution = reExportResolution(resolutions, site);
341
+ if (!isOwnedInternalReExport(exportTarget.workspace, resolution)) {
342
+ continue;
343
+ }
344
+
345
+ diagnostics.push({
346
+ filePath,
347
+ guidance: {
348
+ steps: [
349
+ 'Review whether the public subpath should become an owned package surface, move back behind the package root, or be split into a better-owned package.',
350
+ 'If the imported capability is reusable source-code machinery, serves at least two independently owned toolchain capabilities, exposes one genuinely shared contract, and owns no verdict, migration plan, graph query, or surface rendering, consider relocating it to @ontrails/source.',
351
+ 'Otherwise, preserve the current owner or choose another doctrinal owner.',
352
+ ],
353
+ summary:
354
+ 'Review ownership before an internal re-exported kernel hardens into a public package seam.',
355
+ },
356
+ line: site.line,
357
+ message: diagnosticMessage({
358
+ consumers,
359
+ exportSpecifier: exportTarget.specifier,
360
+ hostPackage: exportTarget.workspace.name,
361
+ importSource: site.importSource,
362
+ }),
363
+ rule: RULE_NAME,
364
+ severity: 'warn',
365
+ });
366
+ }
367
+ }
368
+
369
+ return diagnostics;
370
+ },
371
+ description:
372
+ 'Flag public subpath exports that capture internal kernels after multiple production packages consume them.',
373
+ name: RULE_NAME,
374
+ severity: 'warn',
375
+ };
@@ -1,10 +1,6 @@
1
- import {
2
- collectContourReferenceTargetsByName,
3
- findContourDefinitions,
4
- offsetToLine,
5
- parse,
6
- } from './ast.js';
7
- import type { AstNode } from './ast.js';
1
+ import { collectEntityReferenceTargetsByName } from './source/entities.js';
2
+ import { findEntityDefinitions, offsetToLine, parse } from '@ontrails/source';
3
+ import type { AstNode } from '@ontrails/source';
8
4
  import { isTestFile } from './scan.js';
9
5
  import type {
10
6
  ProjectAwareWardenRule,
@@ -75,14 +71,14 @@ const findCyclePath = (
75
71
  };
76
72
 
77
73
  const buildCircularReferenceDiagnostic = (
78
- contourName: string,
74
+ entityName: string,
79
75
  cyclePath: readonly string[],
80
76
  filePath: string,
81
77
  line: number
82
78
  ): WardenDiagnostic => ({
83
79
  filePath,
84
80
  line,
85
- message: `Contour "${contourName}" participates in circular contour references: ${cyclePath.join(' -> ')}. Break the cycle by removing one contour reference, or extract the shared shape into a new contour neither side depends on.`,
81
+ message: `Entity "${entityName}" participates in circular entity references: ${cyclePath.join(' -> ')}. Break the cycle by removing one entity reference, or extract the shared shape into a new entity neither side depends on.`,
86
82
  rule: 'circular-refs',
87
83
  severity: 'warn',
88
84
  });
@@ -97,7 +93,7 @@ const checkCircularReferences = (
97
93
  return [];
98
94
  }
99
95
 
100
- return findContourDefinitions(ast).flatMap((definition) => {
96
+ return findEntityDefinitions(ast).flatMap((definition) => {
101
97
  const cyclePath = findCyclePath(definition.name, graph);
102
98
  if (!cyclePath) {
103
99
  return [];
@@ -115,7 +111,7 @@ const checkCircularReferences = (
115
111
  };
116
112
 
117
113
  /**
118
- * Warns when contour references form a direct or transitive cycle.
114
+ * Warns when entity references form a direct or transitive cycle.
119
115
  */
120
116
  export const circularRefs: ProjectAwareWardenRule = {
121
117
  check(sourceCode: string, filePath: string): readonly WardenDiagnostic[] {
@@ -124,7 +120,7 @@ export const circularRefs: ProjectAwareWardenRule = {
124
120
  return [];
125
121
  }
126
122
 
127
- const graph = collectContourReferenceTargetsByName(ast);
123
+ const graph = collectEntityReferenceTargetsByName(ast);
128
124
  return checkCircularReferences(ast, sourceCode, filePath, graph);
129
125
  },
130
126
  checkWithContext(
@@ -137,18 +133,18 @@ export const circularRefs: ProjectAwareWardenRule = {
137
133
  return [];
138
134
  }
139
135
 
140
- const localGraph = collectContourReferenceTargetsByName(
136
+ const localGraph = collectEntityReferenceTargetsByName(
141
137
  ast,
142
- context.knownContourIds
138
+ context.knownEntityIds
143
139
  );
144
140
  return checkCircularReferences(
145
141
  ast,
146
142
  sourceCode,
147
143
  filePath,
148
- mergeReferenceGraphs(localGraph, context.contourReferencesByName)
144
+ mergeReferenceGraphs(localGraph, context.entityReferencesByName)
149
145
  );
150
146
  },
151
- description: 'Warn when contour references form direct or transitive cycles.',
147
+ description: 'Warn when entity references form direct or transitive cycles.',
152
148
  name: 'circular-refs',
153
149
  severity: 'warn',
154
150
  };
@@ -1,6 +1,6 @@
1
1
  import { deriveTrailCliCommandProjection } from '@ontrails/core';
2
2
  import type { CliCommandRoute, Topo } from '@ontrails/core';
3
- import type { TopoGraph } from '@ontrails/topographer';
3
+ import type { TopoGraph } from '@ontrails/topography';
4
4
 
5
5
  import type { TopoAwareWardenRule, WardenDiagnostic } from './types.js';
6
6
 
@@ -1,14 +1,14 @@
1
1
  /**
2
2
  * Validates that `ctx.compose()` calls match the declared `composes` array.
3
3
  *
4
- * Statically analyzes trail `blaze` functions to find `ctx.compose('trailId', ...)`
4
+ * Statically analyzes trail `implementation` functions to find `ctx.compose('trailId', ...)`
5
5
  * calls and compares them against the `composes: [...]` declaration in the trail
6
6
  * config. Reports errors for undeclared compositions and warnings for unused ones.
7
7
  */
8
8
 
9
9
  import {
10
- findBlazeBodies,
11
10
  findConfigProperty,
11
+ findImplementationBodies,
12
12
  findTrailDefinitions,
13
13
  getNodeArguments,
14
14
  getNodeBodyNode,
@@ -32,8 +32,8 @@ import {
32
32
  offsetToLine,
33
33
  parse,
34
34
  walkWithScopes,
35
- } from './ast.js';
36
- import type { AstNode } from './ast.js';
35
+ } from '@ontrails/source';
36
+ import type { AstNode } from '@ontrails/source';
37
37
  import { isTestFile } from './scan.js';
38
38
  import type { WardenDiagnostic, WardenRule } from './types.js';
39
39
 
@@ -220,15 +220,17 @@ const extractMemberPair = (
220
220
  };
221
221
 
222
222
  /**
223
- * Extract the second parameter name from a blaze function node.
223
+ * Extract the second parameter name from a implementation function node.
224
224
  *
225
225
  * Handles `(input, ctx) => ...`, `async (input, context) => ...`,
226
226
  * `function(input, ctx) { ... }`, and defaulted params like
227
227
  * `(input, ctx = fallback) => ...` (AssignmentPattern whose `.left` is the
228
228
  * Identifier).
229
229
  */
230
- const extractContextParamName = (blazeBody: AstNode): string | null => {
231
- const params = blazeBody['params'] as readonly AstNode[] | undefined;
230
+ const extractContextParamName = (
231
+ implementationBody: AstNode
232
+ ): string | null => {
233
+ const params = implementationBody['params'] as readonly AstNode[] | undefined;
232
234
  if (!params || params.length < 2) {
233
235
  return null;
234
236
  }
@@ -422,10 +424,10 @@ const extractComposeCall = (
422
424
  /**
423
425
  * Build the set of context parameter names to match against.
424
426
  *
425
- * Returns ONLY the actual second-parameter name from the blaze signature.
426
- * No seeded defaults: if the blaze has no second parameter, the returned set
427
+ * Returns ONLY the actual second-parameter name from the implementation signature.
428
+ * No seeded defaults: if the implementation has no second parameter, the returned set
427
429
  * is empty and no `ctx.compose(...)` / `context.compose(...)` calls are tracked
428
- * for that blaze. An unrelated closure-scoped `ctx` identifier is not the
430
+ * for that implementation. An unrelated closure-scoped `ctx` identifier is not the
429
431
  * trail context and must not be treated as one.
430
432
  *
431
433
  * Mirrors `fires-declarations.ts` and `resource-declarations.ts` for the same
@@ -613,7 +615,7 @@ interface CalledComposes {
613
615
  readonly hasUnresolved: boolean;
614
616
  }
615
617
 
616
- /** Collect compose call results from a single blaze body. */
618
+ /** Collect compose call results from a single implementation body. */
617
619
  const collectComposeCallsFromBody = (
618
620
  body: AstNode,
619
621
  ids: Set<string>,
@@ -682,7 +684,7 @@ const collectComposeCallsFromBody = (
682
684
  return foundUnresolved;
683
685
  };
684
686
 
685
- /** Walk blaze bodies and collect all statically resolvable ctx.compose() trail IDs. */
687
+ /** Walk implementation bodies and collect all statically resolvable ctx.compose() trail IDs. */
686
688
  const extractCalledComposes = (
687
689
  config: AstNode,
688
690
  ast: AstNode,
@@ -692,7 +694,7 @@ const extractCalledComposes = (
692
694
  let hasUnresolved = false;
693
695
  const helpers = collectComposeHelpers(ast);
694
696
 
695
- for (const body of findBlazeBodies(config)) {
697
+ for (const body of findImplementationBodies(config)) {
696
698
  if (collectComposeCallsFromBody(body, ids, sourceCode, helpers)) {
697
699
  hasUnresolved = true;
698
700
  }
@@ -13,8 +13,8 @@ import {
13
13
  offsetToLine,
14
14
  parse,
15
15
  walk,
16
- } from './ast.js';
17
- import type { AstNode } from './ast.js';
16
+ } from '@ontrails/source';
17
+ import type { AstNode } from '@ontrails/source';
18
18
  import type { WardenDiagnostic, WardenRule } from './types.js';
19
19
 
20
20
  const SURFACE_MODULES = new Set([
@@ -82,7 +82,8 @@ const isNamespacedTrailCallee = (callee: AstNode): boolean => {
82
82
  * `import * as ns from '@ontrails/core'` are detected as trail definitions.
83
83
  *
84
84
  * The inner `if (found)` guard skips further work in each callback invocation,
85
- * but the shared `walk` helper in `./ast.ts` exposes no abort mechanism, so
85
+ * but the shared `walk` helper from `@ontrails/source` exposes no abort
86
+ * mechanism, so
86
87
  * the full tree is still traversed once a match is seen. Acceptable: `walk`
87
88
  * is cheap and this rule only runs on files that already matched a path
88
89
  * filter upstream.
@@ -1,5 +1,5 @@
1
+ import { collectComposeTargetTrailIds } from './source/composition.js';
1
2
  import {
2
- collectComposeTargetTrailIds,
3
3
  findConfigProperty,
4
4
  findTrailDefinitions,
5
5
  getNodeValue,
@@ -7,8 +7,8 @@ import {
7
7
  isStringLiteral,
8
8
  offsetToLine,
9
9
  parse,
10
- } from './ast.js';
11
- import type { AstNode } from './ast.js';
10
+ } from '@ontrails/source';
11
+ import type { AstNode } from '@ontrails/source';
12
12
  import { isTestFile } from './scan.js';
13
13
  import type {
14
14
  ProjectAwareWardenRule,
@@ -1,5 +1,5 @@
1
+ import { collectComposeTargetTrailIds } from './source/composition.js';
1
2
  import {
2
- collectComposeTargetTrailIds,
3
3
  findConfigProperty,
4
4
  findTrailDefinitions,
5
5
  getNodeDeclaration,
@@ -15,8 +15,8 @@ import {
15
15
  offsetToLine,
16
16
  parse,
17
17
  walkWithParents,
18
- } from './ast.js';
19
- import type { AstNode, AstParentContext } from './ast.js';
18
+ } from '@ontrails/source';
19
+ import type { AstNode, AstParentContext } from '@ontrails/source';
20
20
  import { isTestFile } from './scan.js';
21
21
  import type {
22
22
  ProjectAwareWardenRule,
@@ -1,15 +1,10 @@
1
1
  import { isDraftId } from '@ontrails/core';
2
2
 
3
3
  import { isDraftMarkedFile } from '../draft.js';
4
- import {
5
- collectFrameworkDraftPrefixConstantOffsets,
6
- findStringLiterals,
7
- hasIgnoreCommentOnLine,
8
- offsetToLine,
9
- parse,
10
- splitSourceLines,
11
- } from './ast.js';
12
- import type { StringLiteralMatch } from './ast.js';
4
+ import { collectFrameworkDraftPrefixConstantOffsets } from './source/drafts.js';
5
+ import { hasIgnoreCommentOnLine, splitSourceLines } from './source/pragmas.js';
6
+ import { findStringLiterals, offsetToLine, parse } from '@ontrails/source';
7
+ import type { StringLiteralMatch } from '@ontrails/source';
13
8
  import type { WardenDiagnostic, WardenRule } from './types.js';
14
9
 
15
10
  const messageForMissingMarker = (draftId: string): string =>
@@ -1,13 +1,8 @@
1
1
  import { isDraftId } from '@ontrails/core';
2
2
 
3
- import {
4
- collectFrameworkDraftPrefixConstantOffsets,
5
- findStringLiterals,
6
- hasIgnoreCommentOnLine,
7
- offsetToLine,
8
- parse,
9
- splitSourceLines,
10
- } from './ast.js';
3
+ import { collectFrameworkDraftPrefixConstantOffsets } from './source/drafts.js';
4
+ import { hasIgnoreCommentOnLine, splitSourceLines } from './source/pragmas.js';
5
+ import { findStringLiterals, offsetToLine, parse } from '@ontrails/source';
11
6
  import type { WardenDiagnostic, WardenRule } from './types.js';
12
7
 
13
8
  const createDiagnostic = (