@ontrails/trails 1.0.0-beta.14 → 1.0.0-beta.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (155) hide show
  1. package/.turbo/turbo-lint.log +1 -1
  2. package/CHANGELOG.md +13 -0
  3. package/__tests__/examples.test.ts +29 -4
  4. package/dist/src/app.js +2 -2
  5. package/dist/src/app.js.map +1 -1
  6. package/dist/src/clack.d.ts +1 -1
  7. package/dist/src/clack.js +1 -1
  8. package/dist/src/cli.js +4 -3
  9. package/dist/src/cli.js.map +1 -1
  10. package/dist/src/trails/add-surface.d.ts +3 -3
  11. package/dist/src/trails/add-surface.d.ts.map +1 -1
  12. package/dist/src/trails/add-surface.js +46 -24
  13. package/dist/src/trails/add-surface.js.map +1 -1
  14. package/dist/src/trails/add-trail.d.ts +3 -1
  15. package/dist/src/trails/add-trail.d.ts.map +1 -1
  16. package/dist/src/trails/add-trail.js +38 -11
  17. package/dist/src/trails/add-trail.js.map +1 -1
  18. package/dist/src/trails/add-trailhead.d.ts +1 -1
  19. package/dist/src/trails/add-trailhead.d.ts.map +1 -1
  20. package/dist/src/trails/add-trailhead.js +4 -4
  21. package/dist/src/trails/add-verify.d.ts +1 -1
  22. package/dist/src/trails/add-verify.d.ts.map +1 -1
  23. package/dist/src/trails/add-verify.js +7 -6
  24. package/dist/src/trails/add-verify.js.map +1 -1
  25. package/dist/src/trails/create-scaffold.d.ts +1 -1
  26. package/dist/src/trails/create-scaffold.d.ts.map +1 -1
  27. package/dist/src/trails/create-scaffold.js +10 -3
  28. package/dist/src/trails/create-scaffold.js.map +1 -1
  29. package/dist/src/trails/create.d.ts +10 -14
  30. package/dist/src/trails/create.d.ts.map +1 -1
  31. package/dist/src/trails/create.js +42 -37
  32. package/dist/src/trails/create.js.map +1 -1
  33. package/dist/src/trails/dev-clean.d.ts +4 -4
  34. package/dist/src/trails/dev-clean.d.ts.map +1 -1
  35. package/dist/src/trails/dev-clean.js +22 -21
  36. package/dist/src/trails/dev-clean.js.map +1 -1
  37. package/dist/src/trails/dev-reset.d.ts +1 -1
  38. package/dist/src/trails/dev-reset.d.ts.map +1 -1
  39. package/dist/src/trails/dev-reset.js +3 -2
  40. package/dist/src/trails/dev-reset.js.map +1 -1
  41. package/dist/src/trails/dev-stats.d.ts +4 -4
  42. package/dist/src/trails/dev-stats.d.ts.map +1 -1
  43. package/dist/src/trails/dev-stats.js +19 -19
  44. package/dist/src/trails/dev-stats.js.map +1 -1
  45. package/dist/src/trails/dev-support.d.ts +17 -17
  46. package/dist/src/trails/dev-support.d.ts.map +1 -1
  47. package/dist/src/trails/dev-support.js +60 -57
  48. package/dist/src/trails/dev-support.js.map +1 -1
  49. package/dist/src/trails/draft-promote.d.ts +1 -1
  50. package/dist/src/trails/draft-promote.d.ts.map +1 -1
  51. package/dist/src/trails/draft-promote.js +30 -16
  52. package/dist/src/trails/draft-promote.js.map +1 -1
  53. package/dist/src/trails/guide.d.ts +2 -2
  54. package/dist/src/trails/guide.d.ts.map +1 -1
  55. package/dist/src/trails/guide.js +1 -4
  56. package/dist/src/trails/guide.js.map +1 -1
  57. package/dist/src/trails/load-app.d.ts +7 -1
  58. package/dist/src/trails/load-app.d.ts.map +1 -1
  59. package/dist/src/trails/load-app.js +374 -26
  60. package/dist/src/trails/load-app.js.map +1 -1
  61. package/dist/src/trails/survey.d.ts +5 -5
  62. package/dist/src/trails/survey.d.ts.map +1 -1
  63. package/dist/src/trails/survey.js +51 -29
  64. package/dist/src/trails/survey.js.map +1 -1
  65. package/dist/src/trails/topo-export.d.ts +5 -4
  66. package/dist/src/trails/topo-export.d.ts.map +1 -1
  67. package/dist/src/trails/topo-export.js +4 -7
  68. package/dist/src/trails/topo-export.js.map +1 -1
  69. package/dist/src/trails/topo-history.d.ts +6 -10
  70. package/dist/src/trails/topo-history.d.ts.map +1 -1
  71. package/dist/src/trails/topo-history.js +7 -8
  72. package/dist/src/trails/topo-history.js.map +1 -1
  73. package/dist/src/trails/topo-pin.d.ts +5 -9
  74. package/dist/src/trails/topo-pin.d.ts.map +1 -1
  75. package/dist/src/trails/topo-pin.js +5 -9
  76. package/dist/src/trails/topo-pin.js.map +1 -1
  77. package/dist/src/trails/topo-read-support.d.ts +12 -8
  78. package/dist/src/trails/topo-read-support.d.ts.map +1 -1
  79. package/dist/src/trails/topo-read-support.js +71 -82
  80. package/dist/src/trails/topo-read-support.js.map +1 -1
  81. package/dist/src/trails/topo-reports.d.ts +15 -11
  82. package/dist/src/trails/topo-reports.d.ts.map +1 -1
  83. package/dist/src/trails/topo-reports.js +40 -34
  84. package/dist/src/trails/topo-reports.js.map +1 -1
  85. package/dist/src/trails/topo-show.d.ts +4 -4
  86. package/dist/src/trails/topo-show.d.ts.map +1 -1
  87. package/dist/src/trails/topo-show.js +8 -12
  88. package/dist/src/trails/topo-show.js.map +1 -1
  89. package/dist/src/trails/topo-store-support.d.ts +2 -2
  90. package/dist/src/trails/topo-store-support.d.ts.map +1 -1
  91. package/dist/src/trails/topo-store-support.js +22 -22
  92. package/dist/src/trails/topo-store-support.js.map +1 -1
  93. package/dist/src/trails/topo-support.d.ts +19 -30
  94. package/dist/src/trails/topo-support.d.ts.map +1 -1
  95. package/dist/src/trails/topo-support.js +63 -96
  96. package/dist/src/trails/topo-support.js.map +1 -1
  97. package/dist/src/trails/topo-unpin.d.ts +9 -4
  98. package/dist/src/trails/topo-unpin.d.ts.map +1 -1
  99. package/dist/src/trails/topo-unpin.js +9 -4
  100. package/dist/src/trails/topo-unpin.js.map +1 -1
  101. package/dist/src/trails/topo-verify.d.ts +2 -2
  102. package/dist/src/trails/topo-verify.d.ts.map +1 -1
  103. package/dist/src/trails/topo-verify.js +1 -5
  104. package/dist/src/trails/topo-verify.js.map +1 -1
  105. package/dist/src/trails/topo.d.ts +2 -2
  106. package/dist/src/trails/topo.d.ts.map +1 -1
  107. package/dist/src/trails/topo.js +6 -10
  108. package/dist/src/trails/topo.js.map +1 -1
  109. package/dist/src/trails/warden.d.ts +3 -2
  110. package/dist/src/trails/warden.d.ts.map +1 -1
  111. package/dist/src/trails/warden.js +11 -2
  112. package/dist/src/trails/warden.js.map +1 -1
  113. package/dist/src/versions.d.ts +12 -0
  114. package/dist/src/versions.d.ts.map +1 -0
  115. package/dist/src/versions.js +23 -0
  116. package/dist/src/versions.js.map +1 -0
  117. package/dist/tsconfig.tsbuildinfo +1 -1
  118. package/package.json +8 -8
  119. package/src/__tests__/add-trail.test.ts +97 -0
  120. package/src/__tests__/create.test.ts +91 -27
  121. package/src/__tests__/guide.test.ts +10 -5
  122. package/src/__tests__/load-app.test.ts +365 -4
  123. package/src/__tests__/survey.test.ts +143 -67
  124. package/src/__tests__/topo-dev.test.ts +48 -46
  125. package/src/app.ts +2 -2
  126. package/src/clack.ts +1 -1
  127. package/src/cli.ts +4 -3
  128. package/src/trails/add-surface.ts +150 -0
  129. package/src/trails/add-trail.ts +46 -10
  130. package/src/trails/add-verify.ts +11 -6
  131. package/src/trails/create-scaffold.ts +16 -3
  132. package/src/trails/create.ts +76 -71
  133. package/src/trails/dev-clean.ts +25 -21
  134. package/src/trails/dev-reset.ts +3 -2
  135. package/src/trails/dev-stats.ts +22 -19
  136. package/src/trails/dev-support.ts +88 -86
  137. package/src/trails/draft-promote.ts +70 -35
  138. package/src/trails/guide.ts +1 -4
  139. package/src/trails/load-app.ts +511 -32
  140. package/src/trails/survey.ts +60 -35
  141. package/src/trails/topo-export.ts +5 -9
  142. package/src/trails/topo-history.ts +8 -10
  143. package/src/trails/topo-pin.ts +9 -13
  144. package/src/trails/topo-read-support.ts +103 -106
  145. package/src/trails/topo-reports.ts +54 -47
  146. package/src/trails/topo-show.ts +8 -12
  147. package/src/trails/topo-store-support.ts +36 -28
  148. package/src/trails/topo-support.ts +90 -134
  149. package/src/trails/topo-unpin.ts +11 -6
  150. package/src/trails/topo-verify.ts +1 -5
  151. package/src/trails/topo.ts +6 -10
  152. package/src/trails/warden.ts +12 -3
  153. package/src/versions.ts +43 -0
  154. package/tsconfig.tests.json +10 -0
  155. package/src/trails/add-trailhead.ts +0 -121
@@ -2,7 +2,7 @@ import { Result, ValidationError, trail } from '@ontrails/core';
2
2
  import { z } from 'zod';
3
3
 
4
4
  import { resetDevState } from './dev-support.js';
5
- import { isolatedExampleInput } from './topo-support.js';
5
+ import { createIsolatedExampleInput } from './topo-support.js';
6
6
 
7
7
  export const devResetTrail = trail('dev.reset', {
8
8
  blaze: (input, ctx) => {
@@ -22,7 +22,7 @@ export const devResetTrail = trail('dev.reset', {
22
22
  {
23
23
  input: {
24
24
  dryRun: true,
25
- rootDir: isolatedExampleInput('dev-reset').rootDir,
25
+ rootDir: createIsolatedExampleInput('dev-reset').rootDir,
26
26
  },
27
27
  name: 'Preview local reset',
28
28
  },
@@ -41,4 +41,5 @@ export const devResetTrail = trail('dev.reset', {
41
41
  removedCount: z.number(),
42
42
  removedFiles: z.array(z.string()),
43
43
  }),
44
+ permit: { scopes: ['dev:reset'] },
44
45
  });
@@ -1,39 +1,42 @@
1
1
  import { Result, trail } from '@ontrails/core';
2
2
  import { z } from 'zod';
3
3
 
4
- import { buildDevStats, DEFAULT_TOPO_SAVE_RETENTION } from './dev-support.js';
5
- import { isolatedExampleInput } from './topo-support.js';
4
+ import {
5
+ buildDevStats,
6
+ DEFAULT_TOPO_SNAPSHOT_RETENTION,
7
+ } from './dev-support.js';
8
+ import { createIsolatedExampleInput } from './topo-support.js';
6
9
 
7
10
  export const devStatsTrail = trail('dev.stats', {
8
11
  blaze: (input, ctx) => {
9
12
  const rootDir = input.rootDir ?? ctx.cwd ?? process.cwd();
10
13
  return Result.ok(
11
14
  buildDevStats({
12
- maxAge: input.trackAgeMs,
13
- maxRecords: input.tracks,
15
+ maxAge: input.traceAgeMs,
16
+ maxRecords: input.traces,
14
17
  rootDir,
15
- saveRetention: input.saves,
18
+ snapshotRetention: input.snapshots,
16
19
  })
17
20
  );
18
21
  },
19
22
  description: 'Show local Trails workspace state and retention',
20
23
  examples: [
21
24
  {
22
- input: { rootDir: isolatedExampleInput('dev-stats').rootDir },
25
+ input: { rootDir: createIsolatedExampleInput('dev-stats').rootDir },
23
26
  name: 'Show local dev state',
24
27
  },
25
28
  ],
26
29
  input: z.object({
27
30
  rootDir: z.string().optional().describe('Workspace root directory'),
28
- saves: z
31
+ snapshots: z
29
32
  .number()
30
- .default(DEFAULT_TOPO_SAVE_RETENTION)
31
- .describe('Unpinned topo saves to retain'),
32
- trackAgeMs: z
33
+ .default(DEFAULT_TOPO_SNAPSHOT_RETENTION)
34
+ .describe('Unpinned topo snapshots to retain'),
35
+ traceAgeMs: z
33
36
  .number()
34
37
  .default(7 * 24 * 60 * 60 * 1000)
35
- .describe('Maximum retained track age in milliseconds'),
36
- tracks: z.number().default(10_000).describe('Maximum retained track count'),
38
+ .describe('Maximum retained trace age in milliseconds'),
39
+ traces: z.number().default(10_000).describe('Maximum retained trace count'),
37
40
  }),
38
41
  intent: 'read',
39
42
  output: z.object({
@@ -48,16 +51,16 @@ export const devStatsTrail = trail('dev.stats', {
48
51
  path: z.string(),
49
52
  }),
50
53
  retention: z.object({
51
- saves: z.number(),
52
- trackAgeMs: z.number(),
53
- tracks: z.number(),
54
+ snapshots: z.number(),
55
+ traceAgeMs: z.number(),
56
+ traces: z.number(),
54
57
  }),
55
58
  topo: z.object({
56
- pinCount: z.number(),
57
- prunableSaveCount: z.number(),
58
- saveCount: z.number(),
59
+ pinnedCount: z.number(),
60
+ prunableSnapshotCount: z.number(),
61
+ snapshotCount: z.number(),
59
62
  }),
60
- tracker: z.object({
63
+ tracing: z.object({
61
64
  recordCount: z.number(),
62
65
  }),
63
66
  }),
@@ -2,30 +2,28 @@ import { existsSync, rmSync, statSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
3
 
4
4
  import {
5
- countPrunableTopoSaves,
6
- countTopoPins,
7
- countTopoSaves,
8
- pruneUnpinnedTopoSaves,
9
- } from '@ontrails/core/internal/topo-saves';
5
+ countPinnedSnapshots,
6
+ countPrunableSnapshots,
7
+ countTopoSnapshots,
8
+ pruneUnpinnedSnapshots,
9
+ } from '@ontrails/core/internal/topo-snapshots';
10
10
  import {
11
11
  openReadTrailsDb,
12
12
  openWriteTrailsDb,
13
- resolveTrailsDbPath,
14
- resolveTrailsDir,
13
+ deriveTrailsDbPath,
14
+ deriveTrailsDir,
15
15
  } from '@ontrails/core/internal/trails-db';
16
16
  import {
17
17
  DEFAULT_MAX_AGE,
18
18
  DEFAULT_MAX_RECORDS,
19
- applyTrackCleanup,
20
- countTrackRecords,
21
- previewTrackCleanup,
22
- } from '@ontrails/tracker/internal/dev-state';
19
+ applyTraceCleanup,
20
+ countTraceRecords,
21
+ previewTraceCleanup,
22
+ } from '@ontrails/tracing/internal/dev-state';
23
23
 
24
- import { resolveLockPath } from './topo-support.js';
24
+ export const DEFAULT_TOPO_SNAPSHOT_RETENTION = 50;
25
25
 
26
- export const DEFAULT_TOPO_SAVE_RETENTION = 50;
27
-
28
- const resolveRootDir = (cwd?: string): string => cwd ?? process.cwd();
26
+ const deriveRootDir = (cwd?: string): string => cwd ?? process.cwd();
29
27
 
30
28
  const removeIfPresent = (filePath: string): boolean => {
31
29
  if (!existsSync(filePath)) {
@@ -47,16 +45,16 @@ export interface DevStatsReport {
47
45
  readonly path: string;
48
46
  };
49
47
  readonly retention: {
50
- readonly saves: number;
51
- readonly trackAgeMs: number;
52
- readonly tracks: number;
48
+ readonly snapshots: number;
49
+ readonly traceAgeMs: number;
50
+ readonly traces: number;
53
51
  };
54
52
  readonly topo: {
55
- readonly pinCount: number;
56
- readonly prunableSaveCount: number;
57
- readonly saveCount: number;
53
+ readonly pinnedCount: number;
54
+ readonly prunableSnapshotCount: number;
55
+ readonly snapshotCount: number;
58
56
  };
59
- readonly tracker: {
57
+ readonly tracing: {
60
58
  readonly recordCount: number;
61
59
  };
62
60
  }
@@ -64,18 +62,18 @@ export interface DevStatsReport {
64
62
  export interface DevCleanReport {
65
63
  readonly dryRun: boolean;
66
64
  readonly remaining: {
67
- readonly pinCount: number;
68
- readonly saveCount: number;
69
- readonly trackCount: number;
65
+ readonly pinnedCount: number;
66
+ readonly snapshotCount: number;
67
+ readonly traceCount: number;
70
68
  };
71
69
  readonly removed: {
72
- readonly topoSaves: number;
73
- readonly trackRecords: number;
70
+ readonly topoSnapshots: number;
71
+ readonly traceRecords: number;
74
72
  };
75
73
  readonly retention: {
76
- readonly saves: number;
77
- readonly trackAgeMs: number;
78
- readonly tracks: number;
74
+ readonly snapshots: number;
75
+ readonly traceAgeMs: number;
76
+ readonly traces: number;
79
77
  };
80
78
  }
81
79
 
@@ -89,7 +87,7 @@ interface DevRetentionOptions {
89
87
  readonly maxAge?: number;
90
88
  readonly maxRecords?: number;
91
89
  readonly rootDir?: string;
92
- readonly saveRetention?: number;
90
+ readonly snapshotRetention?: number;
93
91
  }
94
92
 
95
93
  interface DevCleanupContext {
@@ -100,9 +98,9 @@ interface DevCleanupContext {
100
98
  }
101
99
 
102
100
  const buildRetention = (options?: DevRetentionOptions) => ({
103
- saves: options?.saveRetention ?? DEFAULT_TOPO_SAVE_RETENTION,
104
- trackAgeMs: options?.maxAge ?? DEFAULT_MAX_AGE,
105
- tracks: options?.maxRecords ?? DEFAULT_MAX_RECORDS,
101
+ snapshots: options?.snapshotRetention ?? DEFAULT_TOPO_SNAPSHOT_RETENTION,
102
+ traceAgeMs: options?.maxAge ?? DEFAULT_MAX_AGE,
103
+ traces: options?.maxRecords ?? DEFAULT_MAX_RECORDS,
106
104
  });
107
105
 
108
106
  const emptyDevClean = (
@@ -111,13 +109,13 @@ const emptyDevClean = (
111
109
  ): DevCleanReport => ({
112
110
  dryRun,
113
111
  remaining: {
114
- pinCount: 0,
115
- saveCount: 0,
116
- trackCount: 0,
112
+ pinnedCount: 0,
113
+ snapshotCount: 0,
114
+ traceCount: 0,
117
115
  },
118
116
  removed: {
119
- topoSaves: 0,
120
- trackRecords: 0,
117
+ topoSnapshots: 0,
118
+ traceRecords: 0,
121
119
  },
122
120
  retention,
123
121
  });
@@ -146,17 +144,17 @@ const emptyDevStats = (
146
144
  lock: buildLockStats(lockPath),
147
145
  retention,
148
146
  topo: {
149
- pinCount: 0,
150
- prunableSaveCount: 0,
151
- saveCount: 0,
147
+ pinnedCount: 0,
148
+ prunableSnapshotCount: 0,
149
+ snapshotCount: 0,
152
150
  },
153
- tracker: {
151
+ tracing: {
154
152
  recordCount: 0,
155
153
  },
156
154
  });
157
155
 
158
156
  const liveDevStats = (
159
- db: Parameters<typeof countTopoPins>[0],
157
+ db: Parameters<typeof countPinnedSnapshots>[0],
160
158
  dbPath: string,
161
159
  lockPath: string,
162
160
  retention: DevStatsReport['retention']
@@ -165,20 +163,22 @@ const liveDevStats = (
165
163
  lock: buildLockStats(lockPath),
166
164
  retention,
167
165
  topo: {
168
- pinCount: countTopoPins(db),
169
- prunableSaveCount: countPrunableTopoSaves(db, { keep: retention.saves }),
170
- saveCount: countTopoSaves(db),
166
+ pinnedCount: countPinnedSnapshots(db),
167
+ prunableSnapshotCount: countPrunableSnapshots(db, {
168
+ keep: retention.snapshots,
169
+ }),
170
+ snapshotCount: countTopoSnapshots(db),
171
171
  },
172
- tracker: {
173
- recordCount: countTrackRecords(db),
172
+ tracing: {
173
+ recordCount: countTraceRecords(db),
174
174
  },
175
175
  });
176
176
 
177
- const resolveDevStatsContext = (options?: DevRetentionOptions) => {
178
- const rootDir = resolveRootDir(options?.rootDir);
179
- const dbPath = resolveTrailsDbPath({ rootDir });
180
- const trailsDir = resolveTrailsDir({ rootDir });
181
- const lockPath = resolveLockPath(trailsDir);
177
+ const deriveDevStatsContext = (options?: DevRetentionOptions) => {
178
+ const rootDir = deriveRootDir(options?.rootDir);
179
+ const dbPath = deriveTrailsDbPath({ rootDir });
180
+ const trailsDir = deriveTrailsDir({ rootDir });
181
+ const lockPath = join(trailsDir, 'trails.lock');
182
182
  return {
183
183
  dbExists: existsSync(dbPath),
184
184
  dbPath,
@@ -188,60 +188,62 @@ const resolveDevStatsContext = (options?: DevRetentionOptions) => {
188
188
  };
189
189
  };
190
190
 
191
- const resolveDevCleanupContext = (
191
+ const deriveDevCleanupContext = (
192
192
  options?: DevRetentionOptions & { readonly dryRun?: boolean }
193
193
  ): DevCleanupContext => {
194
- const rootDir = resolveRootDir(options?.rootDir);
194
+ const rootDir = deriveRootDir(options?.rootDir);
195
195
  return {
196
- dbPath: resolveTrailsDbPath({ rootDir }),
196
+ dbPath: deriveTrailsDbPath({ rootDir }),
197
197
  dryRun: options?.dryRun ?? false,
198
198
  retention: buildRetention(options),
199
199
  rootDir,
200
200
  };
201
201
  };
202
202
 
203
- const cleanupTracks = (
204
- db: Parameters<typeof countTopoPins>[0],
203
+ const cleanupTraces = (
204
+ db: Parameters<typeof countPinnedSnapshots>[0],
205
205
  context: DevCleanupContext
206
206
  ) =>
207
207
  context.dryRun
208
- ? previewTrackCleanup(db, {
209
- maxAge: context.retention.trackAgeMs,
210
- maxRecords: context.retention.tracks,
208
+ ? previewTraceCleanup(db, {
209
+ maxAge: context.retention.traceAgeMs,
210
+ maxRecords: context.retention.traces,
211
211
  })
212
- : applyTrackCleanup(db, {
213
- maxAge: context.retention.trackAgeMs,
214
- maxRecords: context.retention.tracks,
212
+ : applyTraceCleanup(db, {
213
+ maxAge: context.retention.traceAgeMs,
214
+ maxRecords: context.retention.traces,
215
215
  });
216
216
 
217
- const cleanupTopoSaves = (
218
- db: Parameters<typeof countTopoPins>[0],
217
+ const cleanupTopoSnapshots = (
218
+ db: Parameters<typeof countPinnedSnapshots>[0],
219
219
  context: DevCleanupContext
220
220
  ): number =>
221
221
  context.dryRun
222
- ? countPrunableTopoSaves(db, { keep: context.retention.saves })
223
- : pruneUnpinnedTopoSaves(db, { keep: context.retention.saves });
222
+ ? countPrunableSnapshots(db, { keep: context.retention.snapshots })
223
+ : pruneUnpinnedSnapshots(db, { keep: context.retention.snapshots });
224
224
 
225
225
  const buildCleanReport = (
226
- db: Parameters<typeof countTopoPins>[0],
226
+ db: Parameters<typeof countPinnedSnapshots>[0],
227
227
  context: DevCleanupContext
228
228
  ): DevCleanReport => {
229
- const trackReport = cleanupTracks(db, context);
230
- const topoRemoved = cleanupTopoSaves(db, context);
231
- const saveCount = countTopoSaves(db);
229
+ const traceReport = cleanupTraces(db, context);
230
+ const topoRemoved = cleanupTopoSnapshots(db, context);
231
+ const snapshotCount = countTopoSnapshots(db);
232
232
 
233
233
  return {
234
234
  dryRun: context.dryRun,
235
235
  remaining: {
236
- pinCount: countTopoPins(db),
237
- saveCount: context.dryRun ? saveCount - topoRemoved : saveCount,
238
- trackCount: context.dryRun
239
- ? trackReport.remaining - trackReport.removedTotal
240
- : trackReport.remaining,
236
+ pinnedCount: countPinnedSnapshots(db),
237
+ snapshotCount: context.dryRun
238
+ ? snapshotCount - topoRemoved
239
+ : snapshotCount,
240
+ traceCount: context.dryRun
241
+ ? traceReport.remaining - traceReport.removedTotal
242
+ : traceReport.remaining,
241
243
  },
242
244
  removed: {
243
- topoSaves: topoRemoved,
244
- trackRecords: trackReport.removedTotal,
245
+ topoSnapshots: topoRemoved,
246
+ traceRecords: traceReport.removedTotal,
245
247
  },
246
248
  retention: context.retention,
247
249
  };
@@ -251,9 +253,9 @@ const RESET_FILES = [
251
253
  '.trails/trails.db',
252
254
  '.trails/trails.db-shm',
253
255
  '.trails/trails.db-wal',
254
- '.trails/dev/tracker.db',
255
- '.trails/dev/tracker.db-shm',
256
- '.trails/dev/tracker.db-wal',
256
+ '.trails/dev/tracing.db',
257
+ '.trails/dev/tracing.db-shm',
258
+ '.trails/dev/tracing.db-wal',
257
259
  ] as const;
258
260
 
259
261
  const presentResetFiles = (
@@ -265,7 +267,7 @@ export const buildDevStats = (
265
267
  options?: DevRetentionOptions
266
268
  ): DevStatsReport => {
267
269
  const { dbExists, dbPath, lockPath, retention, rootDir } =
268
- resolveDevStatsContext(options);
270
+ deriveDevStatsContext(options);
269
271
 
270
272
  if (!dbExists) {
271
273
  return emptyDevStats(dbPath, lockPath, retention);
@@ -283,7 +285,7 @@ export const buildDevStats = (
283
285
  export const cleanDevState = (
284
286
  options?: DevRetentionOptions & { readonly dryRun?: boolean }
285
287
  ): DevCleanReport => {
286
- const context = resolveDevCleanupContext(options);
288
+ const context = deriveDevCleanupContext(options);
287
289
  if (!existsSync(context.dbPath)) {
288
290
  return emptyDevClean(context.retention, context.dryRun);
289
291
  }
@@ -303,7 +305,7 @@ export const resetDevState = (options?: {
303
305
  readonly dryRun?: boolean;
304
306
  readonly rootDir?: string;
305
307
  }): DevResetReport => {
306
- const rootDir = resolveRootDir(options?.rootDir);
308
+ const rootDir = deriveRootDir(options?.rootDir);
307
309
  const files = presentResetFiles(rootDir);
308
310
 
309
311
  if (options?.dryRun === true) {
@@ -4,10 +4,11 @@ import { basename, dirname, join, relative } from 'node:path';
4
4
  import {
5
5
  Result,
6
6
  ValidationError,
7
- analyzeDraftState,
7
+ deriveDraftReport,
8
8
  isDraftId,
9
9
  trail,
10
10
  } from '@ontrails/core';
11
+ import type { Topo } from '@ontrails/core';
11
12
  import {
12
13
  DRAFT_FILE_PREFIX,
13
14
  findStringLiterals,
@@ -17,7 +18,7 @@ import {
17
18
  } from '@ontrails/warden';
18
19
  import { z } from 'zod';
19
20
 
20
- import { loadApp } from './load-app.js';
21
+ import { loadFreshAppLease } from './load-app.js';
21
22
  import { findTopoPath } from './project.js';
22
23
 
23
24
  interface PromotionEdit {
@@ -164,10 +165,8 @@ const replaceLiteralValue = (
164
165
  };
165
166
  };
166
167
 
167
- const collectOutputId = (
168
- app: Awaited<ReturnType<typeof loadApp>>,
169
- id: string
170
- ) => app.get(id) ?? app.signals.get(id) ?? app.getProvision(id);
168
+ const collectOutputId = (app: Topo, id: string) =>
169
+ app.get(id) ?? app.signals.get(id) ?? app.getResource(id);
171
170
 
172
171
  const toRelativeOutputPath = (rootDir: string, filePath: string): string =>
173
172
  relative(rootDir, filePath).replaceAll('\\', '/');
@@ -185,7 +184,6 @@ interface PromotionRewriteState {
185
184
  interface PromotionLoadState {
186
185
  readonly appModule: string | null;
187
186
  readonly loadError: string | null;
188
- readonly loadedApp: Awaited<ReturnType<typeof loadApp>> | undefined;
189
187
  }
190
188
 
191
189
  const validatePromotionInput = (input: {
@@ -511,23 +509,56 @@ const resolvePromotionAppModule = async (
511
509
  );
512
510
  };
513
511
 
514
- const loadVerifiedApp = async (
515
- appModule: string | null,
512
+ /**
513
+ * Run `consume` while holding a fresh-load lease on `appModule`.
514
+ *
515
+ * @remarks
516
+ * The lease deletes its on-disk mirror on release. Any Topo consumption that
517
+ * may trigger deferred filesystem imports (for example inside a trail's
518
+ * `blaze` or a lazy relative `import()`) must run before the lease is
519
+ * released, otherwise those resolutions race the mirror teardown. Collapsing
520
+ * consumption into the leased critical section keeps that contract
521
+ * structural rather than relying on the caller to discover it.
522
+ */
523
+ type LeaseAttempt =
524
+ | {
525
+ readonly ok: true;
526
+ readonly lease: Awaited<ReturnType<typeof loadFreshAppLease>>;
527
+ }
528
+ | { readonly ok: false; readonly loadError: string };
529
+
530
+ const tryAcquireLease = async (
531
+ appModule: string,
516
532
  rootDir: string
517
- ): Promise<PromotionLoadState> => {
533
+ ): Promise<LeaseAttempt> => {
534
+ try {
535
+ return { lease: await loadFreshAppLease(appModule, rootDir), ok: true };
536
+ } catch (error) {
537
+ const loadError = error instanceof Error ? error.message : String(error);
538
+ return { loadError, ok: false };
539
+ }
540
+ };
541
+
542
+ const withVerifiedApp = async <T>(
543
+ appModule: string | null,
544
+ rootDir: string,
545
+ consume: (app: Topo) => T | Promise<T>
546
+ ): Promise<{ readonly load: PromotionLoadState; readonly value: T | null }> => {
518
547
  if (appModule === null) {
519
- return { appModule, loadError: null, loadedApp: undefined };
548
+ return { load: { appModule, loadError: null }, value: null };
520
549
  }
521
550
 
522
- let loadError: string | null = null;
523
- const loadedApp = await loadApp(appModule, rootDir, { fresh: true }).catch(
524
- (error: unknown): undefined => {
525
- loadError = error instanceof Error ? error.message : String(error);
526
- return undefined;
527
- }
528
- );
551
+ const attempt = await tryAcquireLease(appModule, rootDir);
552
+ if (!attempt.ok) {
553
+ return { load: { appModule, loadError: attempt.loadError }, value: null };
554
+ }
529
555
 
530
- return { appModule, loadError, loadedApp };
556
+ try {
557
+ const value = await consume(attempt.lease.app);
558
+ return { load: { appModule, loadError: null }, value };
559
+ } finally {
560
+ attempt.lease.release();
561
+ }
531
562
  };
532
563
 
533
564
  const toRenamedFiles = (rootDir: string, renames: readonly FileRename[]) =>
@@ -562,7 +593,7 @@ const buildUnverifiedPromotionResult = (
562
593
 
563
594
  const buildVerifiedPromotionResult = (
564
595
  rootDir: string,
565
- analysis: ReturnType<typeof analyzeDraftState>,
596
+ analysis: ReturnType<typeof deriveDraftReport>,
566
597
  promotedEstablished: boolean,
567
598
  renames: readonly FileRename[],
568
599
  updatedSourceFiles: Set<string>,
@@ -589,13 +620,13 @@ const buildVerifiedPromotionResult = (
589
620
 
590
621
  const buildVerifiedPromotionResultFromApp = (
591
622
  rootDir: string,
592
- loadedApp: Awaited<ReturnType<typeof loadApp>>,
623
+ loadedApp: Topo,
593
624
  renames: readonly FileRename[],
594
625
  updatedSourceFiles: Set<string>,
595
626
  appModule: string | null,
596
627
  toId: string
597
628
  ) => {
598
- const analysis = analyzeDraftState(loadedApp);
629
+ const analysis = deriveDraftReport(loadedApp);
599
630
  const promotedNode = collectOutputId(loadedApp, toId);
600
631
  const promotedEstablished =
601
632
  promotedNode !== undefined && !analysis.contaminatedIds.has(toId);
@@ -633,13 +664,11 @@ const promoteDraftState = async (
633
664
 
634
665
  const { renames, updatedSourceFiles } = rewriteResult.value;
635
666
  const appModule = await resolvePromotionAppModule(input, rootDirResult.value);
636
- const { loadError, loadedApp } = await loadVerifiedApp(
667
+ const { load, value } = await withVerifiedApp(
637
668
  appModule,
638
- rootDirResult.value
639
- );
640
-
641
- return loadedApp
642
- ? buildVerifiedPromotionResultFromApp(
669
+ rootDirResult.value,
670
+ (loadedApp) =>
671
+ buildVerifiedPromotionResultFromApp(
643
672
  rootDirResult.value,
644
673
  loadedApp,
645
674
  renames,
@@ -647,13 +676,18 @@ const promoteDraftState = async (
647
676
  appModule,
648
677
  input.toId
649
678
  )
650
- : buildUnverifiedPromotionResult(
651
- rootDirResult.value,
652
- loadError,
653
- renames,
654
- updatedSourceFiles,
655
- appModule
656
- );
679
+ );
680
+
681
+ return (
682
+ value ??
683
+ buildUnverifiedPromotionResult(
684
+ rootDirResult.value,
685
+ load.loadError,
686
+ renames,
687
+ updatedSourceFiles,
688
+ appModule
689
+ )
690
+ );
657
691
  };
658
692
 
659
693
  export const draftPromoteTrail = trail('draft.promote', {
@@ -664,6 +698,7 @@ export const draftPromoteTrail = trail('draft.promote', {
664
698
  {
665
699
  error: 'ValidationError',
666
700
  input: {
701
+ // warden-ignore-next-line
667
702
  fromId: '_draft.entity.prepare',
668
703
  renameFiles: true,
669
704
  rootDir: './__does_not_exist__/draft-promote-example',
@@ -62,10 +62,7 @@ export const guideTrail = trail('guide', {
62
62
  },
63
63
  ],
64
64
  input: z.object({
65
- module: z
66
- .string()
67
- .default('./src/app.ts')
68
- .describe('Path to the app module'),
65
+ module: z.string().optional().describe('Path to the app module'),
69
66
  trailId: z.string().optional().describe('Trail ID for detailed guidance'),
70
67
  }),
71
68
  intent: 'read',