@ontrails/warden 1.0.0-beta.13 → 1.0.0-beta.14

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 (60) hide show
  1. package/.turbo/turbo-lint.log +1 -1
  2. package/CHANGELOG.md +12 -0
  3. package/README.md +12 -10
  4. package/dist/cli.d.ts.map +1 -1
  5. package/dist/cli.js +11 -3
  6. package/dist/cli.js.map +1 -1
  7. package/dist/draft.d.ts +5 -0
  8. package/dist/draft.d.ts.map +1 -0
  9. package/dist/draft.js +16 -0
  10. package/dist/draft.js.map +1 -0
  11. package/dist/drift.d.ts +9 -6
  12. package/dist/drift.d.ts.map +1 -1
  13. package/dist/drift.js +49 -15
  14. package/dist/drift.js.map +1 -1
  15. package/dist/formatters.d.ts +2 -1
  16. package/dist/formatters.d.ts.map +1 -1
  17. package/dist/formatters.js +15 -4
  18. package/dist/formatters.js.map +1 -1
  19. package/dist/index.d.ts +5 -0
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +6 -0
  22. package/dist/index.js.map +1 -1
  23. package/dist/rules/ast.d.ts +7 -0
  24. package/dist/rules/ast.d.ts.map +1 -1
  25. package/dist/rules/ast.js +22 -0
  26. package/dist/rules/ast.js.map +1 -1
  27. package/dist/rules/draft-file-marking.d.ts +6 -0
  28. package/dist/rules/draft-file-marking.d.ts.map +1 -0
  29. package/dist/rules/draft-file-marking.js +65 -0
  30. package/dist/rules/draft-file-marking.js.map +1 -0
  31. package/dist/rules/draft-visible-debt.d.ts +12 -0
  32. package/dist/rules/draft-visible-debt.d.ts.map +1 -0
  33. package/dist/rules/draft-visible-debt.js +45 -0
  34. package/dist/rules/draft-visible-debt.js.map +1 -0
  35. package/dist/rules/index.d.ts +2 -0
  36. package/dist/rules/index.d.ts.map +1 -1
  37. package/dist/rules/index.js +6 -0
  38. package/dist/rules/index.js.map +1 -1
  39. package/dist/rules/provision-exists.d.ts.map +1 -1
  40. package/dist/rules/provision-exists.js +2 -1
  41. package/dist/rules/provision-exists.js.map +1 -1
  42. package/dist/rules/valid-detour-refs.d.ts.map +1 -1
  43. package/dist/rules/valid-detour-refs.js +3 -2
  44. package/dist/rules/valid-detour-refs.js.map +1 -1
  45. package/package.json +4 -4
  46. package/src/__tests__/cli.test.ts +114 -1
  47. package/src/__tests__/drift.test.ts +74 -4
  48. package/src/__tests__/formatters.test.ts +2 -2
  49. package/src/cli.ts +11 -3
  50. package/src/draft.ts +22 -0
  51. package/src/drift.ts +60 -18
  52. package/src/formatters.ts +15 -4
  53. package/src/index.ts +21 -0
  54. package/src/rules/ast.ts +38 -0
  55. package/src/rules/draft-file-marking.ts +112 -0
  56. package/src/rules/draft-visible-debt.ts +62 -0
  57. package/src/rules/index.ts +6 -0
  58. package/src/rules/provision-exists.ts +3 -1
  59. package/src/rules/valid-detour-refs.ts +4 -2
  60. package/tsconfig.tsbuildinfo +1 -1
package/src/drift.ts CHANGED
@@ -1,32 +1,43 @@
1
1
  /**
2
- * Trailhead lock drift detection.
2
+ * Topo lock drift detection.
3
3
  *
4
- * Compares the committed `trailhead.lock` hash against a freshly generated
4
+ * Compares the committed `trails.lock` hash against a freshly generated
5
5
  * trailhead map hash to detect when the trail topology has changed without
6
- * updating the lock file.
6
+ * updating the lock file. The committed lock may be structured JSON or the
7
+ * legacy single-line hash format.
7
8
  */
8
9
 
10
+ import { existsSync, statSync } from 'node:fs';
11
+
9
12
  import type { Topo } from '@ontrails/core';
13
+ import {
14
+ createTopoStore,
15
+ NotFoundError,
16
+ ValidationError,
17
+ } from '@ontrails/core';
18
+ import { resolveTrailsDir } from '@ontrails/core/internal/trails-db';
10
19
  import {
11
20
  generateTrailheadMap,
12
21
  hashTrailheadMap,
13
- readTrailheadLock,
22
+ readTrailheadLockData,
14
23
  } from '@ontrails/schema';
15
24
 
16
25
  /**
17
- * Result of a drift check comparing committed trailhead.lock against the current state.
26
+ * Result of a drift check comparing committed trails.lock against the current state.
18
27
  */
19
28
  export interface DriftResult {
29
+ /** Why drift could not be computed for the established graph, when blocked. */
30
+ readonly blockedReason?: string | undefined;
20
31
  /** Whether the committed lock is out of date */
21
32
  readonly stale: boolean;
22
- /** Hash from the committed trailhead.lock file, or null if not found */
33
+ /** Hash from the committed trails.lock file, or null if not found */
23
34
  readonly committedHash: string | null;
24
35
  /** Hash computed from the current trail topology */
25
36
  readonly currentHash: string;
26
37
  }
27
38
 
28
39
  /**
29
- * Check whether the committed trailhead.lock is stale compared to the current topology.
40
+ * Check whether the committed trails.lock is stale compared to the current topology.
30
41
  *
31
42
  * When no topo is provided, returns a clean result (no drift detectable without runtime info).
32
43
  */
@@ -34,17 +45,48 @@ export const checkDrift = async (
34
45
  rootDir: string,
35
46
  topo?: Topo | undefined
36
47
  ): Promise<DriftResult> => {
37
- if (!topo) {
38
- return { committedHash: null, currentHash: 'unknown', stale: false };
39
- }
48
+ try {
49
+ const trailsDir = resolveTrailsDir({ rootDir });
50
+ const committedLock =
51
+ existsSync(rootDir) && statSync(rootDir).isDirectory()
52
+ ? await readTrailheadLockData({ dir: trailsDir })
53
+ : null;
54
+ // Prefer the stored hash (computed by the export pipeline) to avoid
55
+ // divergence between the schema and store hash pipelines.
56
+ const storedHash = (() => {
57
+ try {
58
+ return createTopoStore({ rootDir }).exports.get()?.trailheadHash;
59
+ } catch (error) {
60
+ if (error instanceof NotFoundError) {
61
+ return;
62
+ }
63
+ throw error;
64
+ }
65
+ })();
66
+ const currentHash =
67
+ storedHash ??
68
+ (topo === undefined
69
+ ? 'unknown'
70
+ : hashTrailheadMap(generateTrailheadMap(topo)));
40
71
 
41
- const trailheadMap = generateTrailheadMap(topo);
42
- const currentHash = hashTrailheadMap(trailheadMap);
43
- const committedHash = await readTrailheadLock({ dir: rootDir });
72
+ return {
73
+ committedHash: committedLock?.hash ?? null,
74
+ currentHash,
75
+ stale:
76
+ committedLock !== null &&
77
+ currentHash !== 'unknown' &&
78
+ committedLock.hash !== currentHash,
79
+ };
80
+ } catch (error) {
81
+ if (!(error instanceof ValidationError)) {
82
+ throw error;
83
+ }
44
84
 
45
- return {
46
- committedHash,
47
- currentHash,
48
- stale: committedHash !== null && committedHash !== currentHash,
49
- };
85
+ return {
86
+ blockedReason: error.message,
87
+ committedHash: null,
88
+ currentHash: 'blocked',
89
+ stale: true,
90
+ };
91
+ }
50
92
  };
package/src/formatters.ts CHANGED
@@ -19,7 +19,8 @@ const ghLevel: Record<WardenSeverity, string> = {
19
19
  * Produce GitHub Actions workflow command annotations, one per diagnostic.
20
20
  *
21
21
  * Severity mapping: `error` to `::error`, `warn` to `::warning`.
22
- * Drift staleness is emitted as a single `::error` annotation when detected.
22
+ * Drift staleness or established-export blocking is emitted as a single
23
+ * `::error` annotation when detected.
23
24
  */
24
25
  export const formatGitHubAnnotations = (report: WardenReport): string => {
25
26
  const lines: string[] = [];
@@ -31,9 +32,11 @@ export const formatGitHubAnnotations = (report: WardenReport): string => {
31
32
  );
32
33
  }
33
34
 
34
- if (report.drift?.stale) {
35
+ if (report.drift?.blockedReason !== undefined) {
36
+ lines.push(`::error::drift: ${report.drift.blockedReason}`);
37
+ } else if (report.drift?.stale) {
35
38
  lines.push(
36
- '::error::drift: trailhead.lock is stale (regenerate with `trails survey generate`)'
39
+ '::error::drift: trails.lock is stale (regenerate with `trails topo export`)'
37
40
  );
38
41
  }
39
42
 
@@ -82,13 +85,21 @@ const severitySection = (
82
85
 
83
86
  /** Render a drift section if stale, otherwise empty array. */
84
87
  const driftSection = (drift: WardenReport['drift']): readonly string[] => {
88
+ if (drift?.blockedReason !== undefined) {
89
+ return [
90
+ '',
91
+ '### Drift',
92
+ `- established exports are blocked: ${drift.blockedReason}`,
93
+ ];
94
+ }
95
+
85
96
  if (!drift?.stale) {
86
97
  return [];
87
98
  }
88
99
  return [
89
100
  '',
90
101
  '### Drift',
91
- '- trailhead.lock is stale (regenerate with `trails survey generate`)',
102
+ '- trails.lock is stale (regenerate with `trails topo export`)',
92
103
  ];
93
104
  };
94
105
 
package/src/index.ts CHANGED
@@ -19,6 +19,8 @@ export type {
19
19
  // Individual rules
20
20
  export { noThrowInImplementation } from './rules/no-throw-in-implementation.js';
21
21
  export { contextNoTrailheadTypes } from './rules/context-no-trailhead-types.js';
22
+ export { draftFileMarking } from './rules/draft-file-marking.js';
23
+ export { draftVisibleDebt } from './rules/draft-visible-debt.js';
22
24
  export { validDetourRefs } from './rules/valid-detour-refs.js';
23
25
  export { noDirectImplInRoute } from './rules/no-direct-impl-in-route.js';
24
26
  export { noDirectImplementationCall } from './rules/no-direct-implementation-call.js';
@@ -48,6 +50,25 @@ export {
48
50
  export type { DriftResult } from './drift.js';
49
51
  export { checkDrift } from './drift.js';
50
52
 
53
+ // Draft helpers
54
+ export {
55
+ DRAFT_FILE_PREFIX,
56
+ DRAFT_FILE_SEGMENT,
57
+ isDraftMarkedFile,
58
+ stripDraftFileMarkers,
59
+ } from './draft.js';
60
+
61
+ // AST helpers for repo-local tooling
62
+ export {
63
+ findStringLiterals,
64
+ getStringValue,
65
+ isStringLiteral,
66
+ offsetToLine,
67
+ parse,
68
+ walk,
69
+ } from './rules/ast.js';
70
+ export type { AstNode, StringLiteralMatch } from './rules/ast.js';
71
+
51
72
  // Trail gate
52
73
  export { wardenTopo } from './trails/topo.js';
53
74
  export { runWardenTrails } from './trails/run.js';
package/src/rules/ast.ts CHANGED
@@ -155,6 +155,44 @@ export const extractStringLiteral = (
155
155
  ): string | null =>
156
156
  node && isStringLiteral(node) ? getStringValue(node) : null;
157
157
 
158
+ export interface StringLiteralMatch {
159
+ readonly end: number;
160
+ readonly node: AstNode;
161
+ readonly start: number;
162
+ readonly value: string;
163
+ }
164
+
165
+ export const findStringLiterals = (
166
+ ast: AstNode,
167
+ predicate?: (value: string, node: AstNode) => boolean
168
+ ): StringLiteralMatch[] => {
169
+ const matches: StringLiteralMatch[] = [];
170
+
171
+ walk(ast, (node) => {
172
+ if (!isStringLiteral(node)) {
173
+ return;
174
+ }
175
+
176
+ const value = getStringValue(node);
177
+ if (value === null) {
178
+ return;
179
+ }
180
+
181
+ if (predicate && !predicate(value, node)) {
182
+ return;
183
+ }
184
+
185
+ matches.push({
186
+ end: node.end,
187
+ node,
188
+ start: node.start,
189
+ value,
190
+ });
191
+ });
192
+
193
+ return matches;
194
+ };
195
+
158
196
  /** Extract the first string argument from a CallExpression. */
159
197
  export const extractFirstStringArg = (node: AstNode): string | null => {
160
198
  if (node.type !== 'CallExpression') {
@@ -0,0 +1,112 @@
1
+ import { isDraftId } from '@ontrails/core';
2
+
3
+ import { isDraftMarkedFile } from '../draft.js';
4
+ import { findStringLiterals, offsetToLine, parse } from './ast.js';
5
+ import type { WardenDiagnostic, WardenRule } from './types.js';
6
+
7
+ const messageForMissingMarker = (draftId: string): string =>
8
+ `Draft id "${draftId}" appears in source, but the file is not draft-marked. ` +
9
+ 'Rename it with an _draft. prefix or a .draft. trailing segment.';
10
+
11
+ const makeDiagnostic = (
12
+ sourceCode: string,
13
+ filePath: string,
14
+ start: number,
15
+ message: string,
16
+ severity: WardenDiagnostic['severity']
17
+ ): WardenDiagnostic => ({
18
+ filePath,
19
+ line: offsetToLine(sourceCode, start),
20
+ message,
21
+ rule: 'draft-file-marking',
22
+ severity,
23
+ });
24
+
25
+ const draftMissingMarkerDiagnostic = (
26
+ sourceCode: string,
27
+ filePath: string,
28
+ ast: NonNullable<ReturnType<typeof parse>>
29
+ ): WardenDiagnostic | null => {
30
+ const draftMatches = findStringLiterals(ast, (value) => isDraftId(value));
31
+ if (!draftMatches.length || isDraftMarkedFile(filePath)) {
32
+ return null;
33
+ }
34
+
35
+ const [first] = draftMatches;
36
+ if (!first) {
37
+ return null;
38
+ }
39
+
40
+ return makeDiagnostic(
41
+ sourceCode,
42
+ filePath,
43
+ first.start,
44
+ messageForMissingMarker(first.value),
45
+ 'error'
46
+ );
47
+ };
48
+
49
+ const draftMarkedWithoutIdsDiagnostic = (
50
+ filePath: string,
51
+ ast: NonNullable<ReturnType<typeof parse>>
52
+ ): WardenDiagnostic | null => {
53
+ if (findStringLiterals(ast, (value) => isDraftId(value)).length > 0) {
54
+ return null;
55
+ }
56
+
57
+ if (!isDraftMarkedFile(filePath)) {
58
+ return null;
59
+ }
60
+
61
+ return {
62
+ filePath,
63
+ line: 1,
64
+ message:
65
+ 'File is draft-marked but no longer contains draft ids. Remove the draft filename marker or finish the promotion cleanup.',
66
+ rule: 'draft-file-marking',
67
+ severity: 'warn',
68
+ };
69
+ };
70
+
71
+ const collectDraftFileMarkingDiagnostics = (
72
+ sourceCode: string,
73
+ filePath: string,
74
+ ast: NonNullable<ReturnType<typeof parse>>
75
+ ): WardenDiagnostic[] => {
76
+ const missingMarkerDiagnostic = draftMissingMarkerDiagnostic(
77
+ sourceCode,
78
+ filePath,
79
+ ast
80
+ );
81
+ if (missingMarkerDiagnostic) {
82
+ return [missingMarkerDiagnostic];
83
+ }
84
+
85
+ const markedWithoutIdsDiagnostic = draftMarkedWithoutIdsDiagnostic(
86
+ filePath,
87
+ ast
88
+ );
89
+ if (markedWithoutIdsDiagnostic) {
90
+ return [markedWithoutIdsDiagnostic];
91
+ }
92
+
93
+ return [];
94
+ };
95
+
96
+ /**
97
+ * Ensures files containing draft ids are visibly marked as draft-bearing files.
98
+ */
99
+ export const draftFileMarking: WardenRule = {
100
+ check(sourceCode: string, filePath: string): readonly WardenDiagnostic[] {
101
+ const ast = parse(filePath, sourceCode);
102
+ if (!ast) {
103
+ return [];
104
+ }
105
+
106
+ return collectDraftFileMarkingDiagnostics(sourceCode, filePath, ast);
107
+ },
108
+ description:
109
+ 'Require draft-bearing files to use _draft.* or *.draft.* filename markers.',
110
+ name: 'draft-file-marking',
111
+ severity: 'error',
112
+ };
@@ -0,0 +1,62 @@
1
+ import { isDraftId } from '@ontrails/core';
2
+
3
+ import { findStringLiterals, offsetToLine, parse } from './ast.js';
4
+ import type { WardenDiagnostic, WardenRule } from './types.js';
5
+
6
+ const createDiagnostic = (
7
+ sourceCode: string,
8
+ filePath: string,
9
+ match: { start: number; value: string }
10
+ ): WardenDiagnostic => ({
11
+ filePath,
12
+ line: offsetToLine(sourceCode, match.start),
13
+ message:
14
+ `Draft id "${match.value}" is still visible debt. ` +
15
+ 'Established trailheads, lock export, and OpenAPI generation will reject it until it is promoted.',
16
+ rule: 'draft-visible-debt',
17
+ severity: 'warn',
18
+ });
19
+
20
+ const collectDraftVisibleDebtDiagnostics = (
21
+ sourceCode: string,
22
+ filePath: string,
23
+ ast: NonNullable<ReturnType<typeof parse>>
24
+ ): WardenDiagnostic[] => {
25
+ const seen = new Set<string>();
26
+ const diagnostics: WardenDiagnostic[] = [];
27
+
28
+ for (const match of findStringLiterals(ast, (value) => isDraftId(value))) {
29
+ const key = `${match.value}:${String(match.start)}`;
30
+ if (seen.has(key)) {
31
+ continue;
32
+ }
33
+ seen.add(key);
34
+ diagnostics.push(createDiagnostic(sourceCode, filePath, match));
35
+ }
36
+
37
+ return diagnostics;
38
+ };
39
+
40
+ /**
41
+ * Warns when draft ids are still present so the debt stays visible during
42
+ * review even when the file is correctly marked.
43
+ *
44
+ * Severity is intentionally `warn`, not `error`. The hard rejection gate for
45
+ * draft state leaking into established outputs is `validateEstablishedTopo` at
46
+ * runtime — it blocks topo export, trailhead projection, and lockfile writes.
47
+ * This rule surfaces the debt for human reviewers without duplicating that gate.
48
+ */
49
+ export const draftVisibleDebt: WardenRule = {
50
+ check(sourceCode: string, filePath: string): readonly WardenDiagnostic[] {
51
+ const ast = parse(filePath, sourceCode);
52
+ if (!ast) {
53
+ return [];
54
+ }
55
+
56
+ return collectDraftVisibleDebtDiagnostics(sourceCode, filePath, ast);
57
+ },
58
+ description:
59
+ 'Warn when draft ids remain in source so the debt stays visible during review.',
60
+ name: 'draft-visible-debt',
61
+ severity: 'warn',
62
+ };
@@ -1,5 +1,7 @@
1
1
  import { contextNoTrailheadTypes } from './context-no-trailhead-types.js';
2
2
  import { crossDeclarations } from './cross-declarations.js';
3
+ import { draftFileMarking } from './draft-file-marking.js';
4
+ import { draftVisibleDebt } from './draft-visible-debt.js';
3
5
  import { implementationReturnsResult } from './implementation-returns-result.js';
4
6
  import { noDirectImplInRoute } from './no-direct-impl-in-route.js';
5
7
  import { noDirectImplementationCall } from './no-direct-implementation-call.js';
@@ -24,6 +26,8 @@ export type {
24
26
  export { noThrowInImplementation } from './no-throw-in-implementation.js';
25
27
  export { contextNoTrailheadTypes } from './context-no-trailhead-types.js';
26
28
  export { crossDeclarations } from './cross-declarations.js';
29
+ export { draftFileMarking } from './draft-file-marking.js';
30
+ export { draftVisibleDebt } from './draft-visible-debt.js';
27
31
  export { validDetourRefs } from './valid-detour-refs.js';
28
32
  export { noDirectImplInRoute } from './no-direct-impl-in-route.js';
29
33
  export { noDirectImplementationCall } from './no-direct-implementation-call.js';
@@ -43,6 +47,8 @@ export const wardenRules: ReadonlyMap<string, WardenRule> = new Map<
43
47
  [noThrowInImplementation.name, noThrowInImplementation],
44
48
  [contextNoTrailheadTypes.name, contextNoTrailheadTypes],
45
49
  [crossDeclarations.name, crossDeclarations],
50
+ [draftFileMarking.name, draftFileMarking],
51
+ [draftVisibleDebt.name, draftVisibleDebt],
46
52
  [provisionDeclarations.name, provisionDeclarations],
47
53
  [provisionExists.name, provisionExists],
48
54
  [preferSchemaInference.name, preferSchemaInference],
@@ -1,3 +1,5 @@
1
+ import { isDraftId } from '@ontrails/core';
2
+
1
3
  import {
2
4
  collectNamedProvisionIds,
3
5
  collectProvisionDefinitionIds,
@@ -94,7 +96,7 @@ const reportMissingProvisions = (
94
96
  def.config,
95
97
  provisionIdsByName
96
98
  )) {
97
- if (!knownProvisionIds.has(provisionId)) {
99
+ if (!knownProvisionIds.has(provisionId) && !isDraftId(provisionId)) {
98
100
  diagnostics.push(
99
101
  buildMissingProvisionDiagnostic(def.id, provisionId, filePath, line)
100
102
  );
@@ -1,3 +1,5 @@
1
+ import { isDraftId } from '@ontrails/core';
2
+
1
3
  import { collectTrailIds } from './specs.js';
2
4
  import type {
3
5
  ProjectAwareWardenRule,
@@ -44,7 +46,7 @@ const findMissingDetourTargets = (
44
46
  const missing: string[] = [];
45
47
  for (const m of text.matchAll(/target\s*:\s*["'`]([^"'`]+)["'`]/g)) {
46
48
  const [, id] = m;
47
- if (id && !knownIds.has(id)) {
49
+ if (id && !knownIds.has(id) && !isDraftId(id)) {
48
50
  missing.push(id);
49
51
  }
50
52
  }
@@ -59,7 +61,7 @@ const findMissingPlainDetours = (
59
61
  const cleaned = text.replaceAll(/target\s*:\s*["'`][^"'`]+["'`]/g, '');
60
62
  for (const m of cleaned.matchAll(/["'`]([^"'`]+)["'`]/g)) {
61
63
  const [, id] = m;
62
- if (id && id.includes('.') && !knownIds.has(id)) {
64
+ if (id && id.includes('.') && !knownIds.has(id) && !isDraftId(id)) {
63
65
  missing.push(id);
64
66
  }
65
67
  }
@@ -1 +1 @@
1
- {"root":["./src/cli.ts","./src/drift.ts","./src/formatters.ts","./src/index.ts","./src/rules/ast.ts","./src/rules/context-no-trailhead-types.ts","./src/rules/cross-declarations.ts","./src/rules/implementation-returns-result.ts","./src/rules/index.ts","./src/rules/no-direct-impl-in-route.ts","./src/rules/no-direct-implementation-call.ts","./src/rules/no-sync-result-assumption.ts","./src/rules/no-throw-in-detour-target.ts","./src/rules/no-throw-in-implementation.ts","./src/rules/prefer-schema-inference.ts","./src/rules/provision-declarations.ts","./src/rules/provision-exists.ts","./src/rules/scan.ts","./src/rules/specs.ts","./src/rules/structure.ts","./src/rules/types.ts","./src/rules/valid-describe-refs.ts","./src/rules/valid-detour-refs.ts","./src/trails/context-no-trailhead-types.trail.ts","./src/trails/cross-declarations.trail.ts","./src/trails/implementation-returns-result.trail.ts","./src/trails/index.ts","./src/trails/no-direct-impl-in-route.trail.ts","./src/trails/no-direct-implementation-call.trail.ts","./src/trails/no-sync-result-assumption.trail.ts","./src/trails/no-throw-in-detour-target.trail.ts","./src/trails/no-throw-in-implementation.trail.ts","./src/trails/prefer-schema-inference.trail.ts","./src/trails/provision-declarations.trail.ts","./src/trails/provision-exists.trail.ts","./src/trails/run.ts","./src/trails/schema.ts","./src/trails/topo.ts","./src/trails/valid-describe-refs.trail.ts","./src/trails/valid-detour-refs.trail.ts","./src/trails/wrap-rule.ts"],"version":"5.9.3"}
1
+ {"root":["./src/cli.ts","./src/draft.ts","./src/drift.ts","./src/formatters.ts","./src/index.ts","./src/rules/ast.ts","./src/rules/context-no-trailhead-types.ts","./src/rules/cross-declarations.ts","./src/rules/draft-file-marking.ts","./src/rules/draft-visible-debt.ts","./src/rules/implementation-returns-result.ts","./src/rules/index.ts","./src/rules/no-direct-impl-in-route.ts","./src/rules/no-direct-implementation-call.ts","./src/rules/no-sync-result-assumption.ts","./src/rules/no-throw-in-detour-target.ts","./src/rules/no-throw-in-implementation.ts","./src/rules/prefer-schema-inference.ts","./src/rules/provision-declarations.ts","./src/rules/provision-exists.ts","./src/rules/scan.ts","./src/rules/specs.ts","./src/rules/structure.ts","./src/rules/types.ts","./src/rules/valid-describe-refs.ts","./src/rules/valid-detour-refs.ts","./src/trails/context-no-trailhead-types.trail.ts","./src/trails/cross-declarations.trail.ts","./src/trails/implementation-returns-result.trail.ts","./src/trails/index.ts","./src/trails/no-direct-impl-in-route.trail.ts","./src/trails/no-direct-implementation-call.trail.ts","./src/trails/no-sync-result-assumption.trail.ts","./src/trails/no-throw-in-detour-target.trail.ts","./src/trails/no-throw-in-implementation.trail.ts","./src/trails/prefer-schema-inference.trail.ts","./src/trails/provision-declarations.trail.ts","./src/trails/provision-exists.trail.ts","./src/trails/run.ts","./src/trails/schema.ts","./src/trails/topo.ts","./src/trails/valid-describe-refs.trail.ts","./src/trails/valid-detour-refs.trail.ts","./src/trails/wrap-rule.ts"],"version":"5.9.3"}