@ontrails/trails 1.0.0-beta.12 → 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 (150) hide show
  1. package/.turbo/turbo-lint.log +1 -1
  2. package/CHANGELOG.md +40 -12
  3. package/__tests__/examples.test.ts +14 -0
  4. package/dist/src/app.d.ts.map +1 -1
  5. package/dist/src/app.js +13 -2
  6. package/dist/src/app.js.map +1 -1
  7. package/dist/src/clack.d.ts +1 -1
  8. package/dist/src/clack.js +1 -1
  9. package/dist/src/cli.js +2 -2
  10. package/dist/src/cli.js.map +1 -1
  11. package/dist/src/trails/add-trail.js +13 -13
  12. package/dist/src/trails/add-trail.js.map +1 -1
  13. package/dist/src/trails/add-trailhead.d.ts +13 -0
  14. package/dist/src/trails/add-trailhead.d.ts.map +1 -0
  15. package/dist/src/trails/add-trailhead.js +88 -0
  16. package/dist/src/trails/add-trailhead.js.map +1 -0
  17. package/dist/src/trails/add-verify.js +10 -10
  18. package/dist/src/trails/add-verify.js.map +1 -1
  19. package/dist/src/trails/create-scaffold.js +26 -26
  20. package/dist/src/trails/create-scaffold.js.map +1 -1
  21. package/dist/src/trails/create.d.ts +6 -6
  22. package/dist/src/trails/create.d.ts.map +1 -1
  23. package/dist/src/trails/create.js +29 -29
  24. package/dist/src/trails/create.js.map +1 -1
  25. package/dist/src/trails/dev-clean.d.ts +9 -0
  26. package/dist/src/trails/dev-clean.d.ts.map +1 -0
  27. package/dist/src/trails/dev-clean.js +65 -0
  28. package/dist/src/trails/dev-clean.js.map +1 -0
  29. package/dist/src/trails/dev-reset.d.ts +6 -0
  30. package/dist/src/trails/dev-reset.d.ts.map +1 -0
  31. package/dist/src/trails/dev-reset.js +38 -0
  32. package/dist/src/trails/dev-reset.js.map +1 -0
  33. package/dist/src/trails/dev-stats.d.ts +7 -0
  34. package/dist/src/trails/dev-stats.d.ts.map +1 -0
  35. package/dist/src/trails/dev-stats.js +61 -0
  36. package/dist/src/trails/dev-stats.js.map +1 -0
  37. package/dist/src/trails/dev-support.d.ts +64 -0
  38. package/dist/src/trails/dev-support.d.ts.map +1 -0
  39. package/dist/src/trails/dev-support.js +178 -0
  40. package/dist/src/trails/dev-support.js.map +1 -0
  41. package/dist/src/trails/draft-promote.d.ts +18 -0
  42. package/dist/src/trails/draft-promote.d.ts.map +1 -0
  43. package/dist/src/trails/draft-promote.js +386 -0
  44. package/dist/src/trails/draft-promote.js.map +1 -0
  45. package/dist/src/trails/guide.d.ts +13 -3
  46. package/dist/src/trails/guide.d.ts.map +1 -1
  47. package/dist/src/trails/guide.js +21 -37
  48. package/dist/src/trails/guide.js.map +1 -1
  49. package/dist/src/trails/load-app.d.ts +3 -1
  50. package/dist/src/trails/load-app.d.ts.map +1 -1
  51. package/dist/src/trails/load-app.js +53 -10
  52. package/dist/src/trails/load-app.js.map +1 -1
  53. package/dist/src/trails/project.d.ts.map +1 -1
  54. package/dist/src/trails/project.js +14 -3
  55. package/dist/src/trails/project.js.map +1 -1
  56. package/dist/src/trails/survey.d.ts +4 -58
  57. package/dist/src/trails/survey.d.ts.map +1 -1
  58. package/dist/src/trails/survey.js +52 -173
  59. package/dist/src/trails/survey.js.map +1 -1
  60. package/dist/src/trails/topo-constants.d.ts +3 -0
  61. package/dist/src/trails/topo-constants.d.ts.map +1 -0
  62. package/dist/src/trails/topo-constants.js +3 -0
  63. package/dist/src/trails/topo-constants.js.map +1 -0
  64. package/dist/src/trails/topo-export.d.ts +18 -0
  65. package/dist/src/trails/topo-export.d.ts.map +1 -0
  66. package/dist/src/trails/topo-export.js +34 -0
  67. package/dist/src/trails/topo-export.js.map +1 -0
  68. package/dist/src/trails/topo-history.d.ts +24 -0
  69. package/dist/src/trails/topo-history.d.ts.map +1 -0
  70. package/dist/src/trails/topo-history.js +33 -0
  71. package/dist/src/trails/topo-history.js.map +1 -0
  72. package/dist/src/trails/topo-pin.d.ts +21 -0
  73. package/dist/src/trails/topo-pin.d.ts.map +1 -0
  74. package/dist/src/trails/topo-pin.js +35 -0
  75. package/dist/src/trails/topo-pin.js.map +1 -0
  76. package/dist/src/trails/topo-read-support.d.ts +54 -0
  77. package/dist/src/trails/topo-read-support.d.ts.map +1 -0
  78. package/dist/src/trails/topo-read-support.js +178 -0
  79. package/dist/src/trails/topo-read-support.js.map +1 -0
  80. package/dist/src/trails/topo-reports.d.ts +50 -0
  81. package/dist/src/trails/topo-reports.d.ts.map +1 -0
  82. package/dist/src/trails/topo-reports.js +122 -0
  83. package/dist/src/trails/topo-reports.js.map +1 -0
  84. package/dist/src/trails/topo-show.d.ts +23 -0
  85. package/dist/src/trails/topo-show.d.ts.map +1 -0
  86. package/dist/src/trails/topo-show.js +53 -0
  87. package/dist/src/trails/topo-show.js.map +1 -0
  88. package/dist/src/trails/topo-store-support.d.ts +13 -0
  89. package/dist/src/trails/topo-store-support.d.ts.map +1 -0
  90. package/dist/src/trails/topo-store-support.js +55 -0
  91. package/dist/src/trails/topo-store-support.js.map +1 -0
  92. package/dist/src/trails/topo-support.d.ts +87 -0
  93. package/dist/src/trails/topo-support.d.ts.map +1 -0
  94. package/dist/src/trails/topo-support.js +165 -0
  95. package/dist/src/trails/topo-support.js.map +1 -0
  96. package/dist/src/trails/topo-unpin.d.ts +15 -0
  97. package/dist/src/trails/topo-unpin.d.ts.map +1 -0
  98. package/dist/src/trails/topo-unpin.js +39 -0
  99. package/dist/src/trails/topo-unpin.js.map +1 -0
  100. package/dist/src/trails/topo-verify.d.ts +5 -0
  101. package/dist/src/trails/topo-verify.d.ts.map +1 -0
  102. package/dist/src/trails/topo-verify.js +28 -0
  103. package/dist/src/trails/topo-verify.js.map +1 -0
  104. package/dist/src/trails/topo.d.ts +5 -0
  105. package/dist/src/trails/topo.d.ts.map +1 -0
  106. package/dist/src/trails/topo.js +67 -0
  107. package/dist/src/trails/topo.js.map +1 -0
  108. package/dist/src/trails/warden.d.ts +1 -1
  109. package/dist/src/trails/warden.d.ts.map +1 -1
  110. package/dist/src/trails/warden.js +28 -27
  111. package/dist/src/trails/warden.js.map +1 -1
  112. package/dist/tsconfig.tsbuildinfo +1 -1
  113. package/package.json +8 -7
  114. package/src/__tests__/create.test.ts +35 -33
  115. package/src/__tests__/draft-promote.test.ts +144 -0
  116. package/src/__tests__/guide.test.ts +4 -4
  117. package/src/__tests__/load-app.test.ts +43 -0
  118. package/src/__tests__/survey.test.ts +140 -55
  119. package/src/__tests__/topo-dev.test.ts +424 -0
  120. package/src/__tests__/warden.test.ts +2 -2
  121. package/src/app.ts +24 -2
  122. package/src/clack.ts +1 -1
  123. package/src/cli.ts +2 -2
  124. package/src/trails/add-trail.ts +13 -13
  125. package/src/trails/{add-surface.ts → add-trailhead.ts} +39 -37
  126. package/src/trails/add-verify.ts +10 -10
  127. package/src/trails/create-scaffold.ts +28 -28
  128. package/src/trails/create.ts +42 -42
  129. package/src/trails/dev-clean.ts +73 -0
  130. package/src/trails/dev-reset.ts +44 -0
  131. package/src/trails/dev-stats.ts +64 -0
  132. package/src/trails/dev-support.ts +326 -0
  133. package/src/trails/draft-promote.ts +704 -0
  134. package/src/trails/guide.ts +29 -44
  135. package/src/trails/load-app.ts +76 -13
  136. package/src/trails/project.ts +17 -3
  137. package/src/trails/survey.ts +80 -279
  138. package/src/trails/topo-constants.ts +2 -0
  139. package/src/trails/topo-export.ts +39 -0
  140. package/src/trails/topo-history.ts +40 -0
  141. package/src/trails/topo-pin.ts +42 -0
  142. package/src/trails/topo-read-support.ts +332 -0
  143. package/src/trails/topo-reports.ts +221 -0
  144. package/src/trails/topo-show.ts +58 -0
  145. package/src/trails/topo-store-support.ts +96 -0
  146. package/src/trails/topo-support.ts +274 -0
  147. package/src/trails/topo-unpin.ts +51 -0
  148. package/src/trails/topo-verify.ts +29 -0
  149. package/src/trails/topo.ts +73 -0
  150. package/src/trails/warden.ts +33 -32
@@ -1,255 +1,45 @@
1
1
  /**
2
2
  * `survey` trail -- Full topo introspection.
3
3
  *
4
- * Lists trails, shows detail for individual trails, generates surface maps,
4
+ * Lists trails, shows detail for individual trails, generates trailhead maps,
5
5
  * and diffs against previous versions.
6
6
  */
7
7
 
8
- import type { Topo, Trail } from '@ontrails/core';
9
- import { Result, trail } from '@ontrails/core';
8
+ import type { Topo } from '@ontrails/core';
9
+ import { NotFoundError, Result, trail } from '@ontrails/core';
10
10
  import type { DiffResult } from '@ontrails/schema';
11
11
  import {
12
- diffSurfaceMaps,
12
+ diffTrailheadMaps,
13
13
  generateOpenApiSpec,
14
- generateSurfaceMap,
15
- hashSurfaceMap,
16
- readSurfaceMap,
17
- writeSurfaceLock,
18
- writeSurfaceMap,
14
+ generateTrailheadMap,
15
+ readTrailheadMap,
19
16
  } from '@ontrails/schema';
20
17
  import { z } from 'zod';
21
18
 
22
19
  import { loadApp } from './load-app.js';
20
+ import {
21
+ buildCurrentTopoBrief,
22
+ buildCurrentTopoDetail,
23
+ buildCurrentTopoList,
24
+ } from './topo-read-support.js';
25
+ import { exportCurrentTopo } from './topo-store-support.js';
26
+
27
+ export {
28
+ formatProvisionDetail,
29
+ generateBriefReport,
30
+ generateSurveyList,
31
+ generateTrailDetail,
32
+ } from './topo-reports.js';
33
+ export type {
34
+ BriefReport,
35
+ SurveyListReport,
36
+ TrailDetailReport,
37
+ } from './topo-reports.js';
23
38
 
24
39
  // ---------------------------------------------------------------------------
25
40
  // Brief report (formerly scout)
26
41
  // ---------------------------------------------------------------------------
27
42
 
28
- export interface BriefReport {
29
- readonly name: string;
30
- readonly version: string;
31
- readonly contractVersion: string;
32
- readonly features: {
33
- readonly services: boolean;
34
- readonly outputSchemas: boolean;
35
- readonly examples: boolean;
36
- readonly detours: boolean;
37
- readonly events: boolean;
38
- };
39
- readonly trails: number;
40
- readonly events: number;
41
- readonly services: number;
42
- }
43
-
44
- export interface SurveyListReport {
45
- readonly count: number;
46
- readonly entries: readonly {
47
- readonly examples: number;
48
- readonly id: string;
49
- readonly kind: string;
50
- readonly safety: string;
51
- }[];
52
- readonly serviceCount: number;
53
- readonly services: readonly {
54
- readonly description: string | null;
55
- readonly health: 'available' | 'none';
56
- readonly id: string;
57
- readonly kind: 'service';
58
- readonly lifetime: 'singleton';
59
- readonly usedBy: readonly string[];
60
- }[];
61
- }
62
-
63
- export interface TrailDetailReport {
64
- readonly description: string | null;
65
- readonly detours: Trail<unknown, unknown>['detours'] | null;
66
- readonly examples: readonly unknown[];
67
- readonly follow: readonly string[];
68
- readonly id: string;
69
- readonly intent: 'read' | 'write' | 'destroy';
70
- readonly kind: string;
71
- readonly safety: string;
72
- readonly services: readonly string[];
73
- }
74
-
75
- /** Check if a trail has a specific feature. */
76
- const trailHas = (raw: Record<string, unknown>, key: string): boolean => {
77
- if (key === 'examples') {
78
- return Array.isArray(raw[key]) && (raw[key] as unknown[]).length > 0;
79
- }
80
- return Boolean(raw[key]);
81
- };
82
-
83
- /** Detect which features are used across trails. */
84
- const detectFeatures = (
85
- app: Topo
86
- ): {
87
- hasDetours: boolean;
88
- hasExamples: boolean;
89
- hasOutputSchemas: boolean;
90
- hasServices: boolean;
91
- } => {
92
- const trails = [...app.trails.values()].map(
93
- (item) => item as unknown as Record<string, unknown>
94
- );
95
- return {
96
- hasDetours: trails.some((r) => trailHas(r, 'detours')),
97
- hasExamples: trails.some((r) => trailHas(r, 'examples')),
98
- hasOutputSchemas: trails.some((r) => trailHas(r, 'output')),
99
- hasServices: trails.some(
100
- (r) =>
101
- Array.isArray(r['services']) && (r['services'] as unknown[]).length > 0
102
- ),
103
- };
104
- };
105
-
106
- /** Generate a compact capability report for the given topo. */
107
- export const generateBriefReport = (app: Topo): BriefReport => {
108
- const { hasDetours, hasExamples, hasOutputSchemas, hasServices } =
109
- detectFeatures(app);
110
-
111
- return {
112
- contractVersion: '2026-03',
113
- events: app.events.size,
114
- features: {
115
- detours: hasDetours,
116
- events: app.events.size > 0,
117
- examples: hasExamples,
118
- outputSchemas: hasOutputSchemas,
119
- services: hasServices,
120
- },
121
- name: app.name,
122
- services: app.services.size,
123
- trails: app.trails.size,
124
- version: '0.1.0',
125
- };
126
- };
127
-
128
- // ---------------------------------------------------------------------------
129
- // Formatting helpers
130
- // ---------------------------------------------------------------------------
131
-
132
- const safetyLabel = (entry: {
133
- intent?: 'read' | 'write' | 'destroy';
134
- }): string => {
135
- if (entry.intent === 'destroy') {
136
- return 'destroy';
137
- }
138
- if (entry.intent === 'read') {
139
- return 'read';
140
- }
141
- return '-';
142
- };
143
-
144
- const buildServiceUsage = (
145
- app: Topo
146
- ): ReadonlyMap<string, readonly string[]> => {
147
- const usage = new Map<string, string[]>();
148
-
149
- for (const trailDef of app.list()) {
150
- for (const declaredService of trailDef.services) {
151
- const users = usage.get(declaredService.id) ?? [];
152
- users.push(trailDef.id);
153
- usage.set(declaredService.id, users);
154
- }
155
- }
156
-
157
- return new Map(
158
- [...usage.entries()].map(([id, users]) => [id, users.toSorted()] as const)
159
- );
160
- };
161
-
162
- const serviceHealthStatus = (service: {
163
- health?: unknown;
164
- }): 'available' | 'none' =>
165
- service.health === undefined ? 'none' : 'available';
166
-
167
- const formatServiceList = (app: Topo): SurveyListReport['services'] => {
168
- const usage = buildServiceUsage(app);
169
- return app
170
- .listServices()
171
- .map((service) => ({
172
- description: service.description ?? null,
173
- health: serviceHealthStatus(service),
174
- id: service.id,
175
- kind: service.kind,
176
- lifetime: 'singleton' as const,
177
- usedBy: usage.get(service.id) ?? [],
178
- }))
179
- .toSorted((a, b) => a.id.localeCompare(b.id));
180
- };
181
-
182
- export const generateSurveyList = (app: Topo): SurveyListReport => {
183
- const items = app.list();
184
- const entries = items.map((item) => {
185
- const safety = safetyLabel(
186
- item as unknown as { intent?: 'read' | 'write' | 'destroy' }
187
- );
188
- const examples = Array.isArray(
189
- (item as unknown as { examples?: unknown[] }).examples
190
- )
191
- ? (item as unknown as { examples: unknown[] }).examples.length
192
- : 0;
193
-
194
- return {
195
- examples,
196
- id: item.id,
197
- kind: item.kind,
198
- safety,
199
- };
200
- });
201
-
202
- const services = formatServiceList(app);
203
-
204
- return {
205
- count: items.length,
206
- entries,
207
- serviceCount: services.length,
208
- services,
209
- };
210
- };
211
-
212
- /**
213
- * Build a human-readable detail view for a single trail.
214
- *
215
- * Overlaps with `trailToEntry` in `@ontrails/schema` which builds the
216
- * surface-map entry. The two serve different audiences (human display vs
217
- * machine-diffable surface map) so they are kept separate.
218
- */
219
- export const generateTrailDetail = (
220
- item: Trail<unknown, unknown>
221
- ): TrailDetailReport => {
222
- const safety = safetyLabel(
223
- item as unknown as { intent?: 'read' | 'write' | 'destroy' }
224
- );
225
-
226
- return {
227
- description: item.description ?? null,
228
- detours: item.detours ?? null,
229
- examples: item.examples ?? [],
230
- follow: item.follow.toSorted(),
231
- id: item.id,
232
- intent: item.intent,
233
- kind: item.kind,
234
- safety,
235
- services: item.services.map((service) => service.id).toSorted(),
236
- };
237
- };
238
-
239
- const formatServiceDetail = (app: Topo, serviceId: string): object => {
240
- const item = app.getService(serviceId);
241
- const usedBy = buildServiceUsage(app).get(serviceId) ?? [];
242
-
243
- return {
244
- description: item?.description ?? null,
245
- health: item ? serviceHealthStatus(item) : 'none',
246
- id: serviceId,
247
- kind: 'service',
248
- lifetime: 'singleton',
249
- usedBy,
250
- };
251
- };
252
-
253
43
  const formatDiff = (diff: DiffResult): object => ({
254
44
  breaking: diff.breaking,
255
45
  hasBreaking: diff.hasBreaking,
@@ -261,17 +51,17 @@ const buildSurveyDiff = async (
261
51
  app: Topo,
262
52
  breakingOnly: boolean
263
53
  ): Promise<Result<object, Error>> => {
264
- const currentMap = generateSurfaceMap(app);
265
- const previousMap = await readSurfaceMap();
54
+ const currentMap = generateTrailheadMap(app);
55
+ const previousMap = await readTrailheadMap();
266
56
  if (!previousMap) {
267
57
  return Result.err(
268
- new Error(
269
- 'No previous surface map found. Run `trails survey generate` first.'
58
+ new NotFoundError(
59
+ 'No previous trailhead map found. Run `trails topo export` first.'
270
60
  )
271
61
  );
272
62
  }
273
63
 
274
- const diff = diffSurfaceMaps(previousMap, currentMap);
64
+ const diff = diffTrailheadMaps(previousMap, currentMap);
275
65
  return Result.ok(
276
66
  breakingOnly
277
67
  ? formatDiff({
@@ -286,26 +76,31 @@ const buildSurveyDiff = async (
286
76
 
287
77
  const buildSurveyDetail = (
288
78
  app: Topo,
289
- trailId: string
79
+ trailId: string,
80
+ rootDir: string
290
81
  ): Result<object, Error> => {
291
- const item = app.get(trailId);
292
- if (item) {
293
- return Result.ok(generateTrailDetail(item as Trail<unknown, unknown>));
82
+ const detail = buildCurrentTopoDetail(app, trailId, { rootDir });
83
+ if (detail !== undefined) {
84
+ return Result.ok(detail);
294
85
  }
295
- if (app.getService(trailId)) {
296
- return Result.ok(formatServiceDetail(app, trailId));
297
- }
298
- return Result.err(new Error(`Trail or service not found: ${trailId}`));
86
+ return Result.err(
87
+ new NotFoundError(`Trail or provision not found: ${trailId}`)
88
+ );
299
89
  };
300
90
 
301
91
  const buildSurveyGenerate = async (
302
- app: Topo
92
+ app: Topo,
93
+ rootDir: string
303
94
  ): Promise<Result<object, Error>> => {
304
- const surfaceMap = generateSurfaceMap(app);
305
- const mapPath = await writeSurfaceMap(surfaceMap);
306
- const hash = hashSurfaceMap(surfaceMap);
307
- const lockPath = await writeSurfaceLock(hash);
308
- return Result.ok({ hash, lockPath, mapPath });
95
+ const exported = await exportCurrentTopo(app, { rootDir });
96
+ if (exported.isErr()) {
97
+ return exported;
98
+ }
99
+ return Result.ok({
100
+ hash: exported.value.hash,
101
+ lockPath: exported.value.lockPath,
102
+ mapPath: exported.value.mapPath,
103
+ });
309
104
  };
310
105
 
311
106
  interface SurveyInput {
@@ -334,27 +129,32 @@ const resolveSurveyMode = (input: SurveyInput): SurveyMode =>
334
129
 
335
130
  type SurveyHandler = (
336
131
  app: Topo,
337
- input: SurveyInput
132
+ input: SurveyInput,
133
+ rootDir: string
338
134
  ) => Result<object, Error> | Promise<Result<object, Error>>;
339
135
 
340
136
  /** Handlers keyed by survey mode. */
341
137
  const surveyHandlers: Record<SurveyMode, SurveyHandler> = {
342
- brief: (app) => Result.ok(generateBriefReport(app)),
343
- detail: (app, input) => buildSurveyDetail(app, input.trailId ?? ''),
138
+ brief: (app, _input, rootDir) =>
139
+ Result.ok(buildCurrentTopoBrief(app, { rootDir })),
140
+ detail: (app, input, rootDir) =>
141
+ buildSurveyDetail(app, input.trailId ?? '', rootDir),
344
142
  diff: (app, input) => buildSurveyDiff(app, input.breakingOnly),
345
- generate: (app) => buildSurveyGenerate(app),
346
- list: (app) => Result.ok(generateSurveyList(app)),
143
+ generate: (app, _input, rootDir) => buildSurveyGenerate(app, rootDir),
144
+ list: (app, _input, rootDir) =>
145
+ Result.ok(buildCurrentTopoList(app, { rootDir })),
347
146
  openapi: (app) => Result.ok(generateOpenApiSpec(app)),
348
147
  };
349
148
 
350
149
  /** Dispatch to the appropriate survey sub-command based on input flags. */
351
150
  const dispatchSurvey = (
352
151
  app: Topo,
353
- input: SurveyInput
152
+ input: SurveyInput,
153
+ rootDir: string
354
154
  ): Result<object, Error> | Promise<Result<object, Error>> => {
355
155
  const mode = resolveSurveyMode(input);
356
156
  const handler = surveyHandlers[mode];
357
- return handler(app, input);
157
+ return handler(app, input, rootDir);
358
158
  };
359
159
 
360
160
  // ---------------------------------------------------------------------------
@@ -362,21 +162,26 @@ const dispatchSurvey = (
362
162
  // ---------------------------------------------------------------------------
363
163
 
364
164
  export const surveyTrail = trail('survey', {
165
+ blaze: async (input, ctx) => {
166
+ const rootDir = ctx.cwd ?? '.';
167
+ const app = await loadApp(input.module, rootDir);
168
+ return dispatchSurvey(app, input, rootDir);
169
+ },
365
170
  description: 'Full topo introspection',
366
171
  examples: [
367
172
  {
368
- description: 'Lists all registered trails with safety and surface info',
369
- input: {},
173
+ description: 'Lists all registered trails with safety and trailhead info',
174
+ input: { module: './src/app.ts' },
370
175
  name: 'List all trails',
371
176
  },
372
177
  {
373
178
  description: 'Quick capability summary with counts and feature flags',
374
- input: { brief: true },
179
+ input: { brief: true, module: './src/app.ts' },
375
180
  name: 'Brief capability report',
376
181
  },
377
182
  {
378
183
  description: 'Generate an OpenAPI 3.1 specification for the topo',
379
- input: { openapi: true },
184
+ input: { module: './src/app.ts', openapi: true },
380
185
  name: 'OpenAPI spec',
381
186
  },
382
187
  ],
@@ -390,7 +195,7 @@ export const surveyTrail = trail('survey', {
390
195
  generate: z
391
196
  .boolean()
392
197
  .default(false)
393
- .describe('Generate surface map and lock file'),
198
+ .describe('Generate trailhead map and lock file'),
394
199
  module: z
395
200
  .string()
396
201
  .default('./src/app.ts')
@@ -410,13 +215,13 @@ export const surveyTrail = trail('survey', {
410
215
  safety: z.string(),
411
216
  })
412
217
  ),
413
- serviceCount: z.number(),
414
- services: z.array(
218
+ provisionCount: z.number(),
219
+ provisions: z.array(
415
220
  z.object({
416
221
  description: z.string().nullable(),
417
222
  health: z.enum(['available', 'none']),
418
223
  id: z.string(),
419
- kind: z.literal('service'),
224
+ kind: z.literal('provision'),
420
225
  lifetime: z.literal('singleton'),
421
226
  usedBy: z.array(z.string()),
422
227
  })
@@ -424,16 +229,16 @@ export const surveyTrail = trail('survey', {
424
229
  }),
425
230
  z.object({
426
231
  contractVersion: z.string(),
427
- events: z.number(),
428
232
  features: z.object({
429
233
  detours: z.boolean(),
430
- events: z.boolean(),
431
234
  examples: z.boolean(),
432
235
  outputSchemas: z.boolean(),
433
- services: z.boolean(),
236
+ provisions: z.boolean(),
237
+ signals: z.boolean(),
434
238
  }),
435
239
  name: z.string(),
436
- services: z.number(),
240
+ provisions: z.number(),
241
+ signals: z.number(),
437
242
  trails: z.number(),
438
243
  version: z.string(),
439
244
  }),
@@ -444,21 +249,21 @@ export const surveyTrail = trail('survey', {
444
249
  warnings: z.array(z.unknown()),
445
250
  }),
446
251
  z.object({
252
+ crosses: z.array(z.string()),
447
253
  description: z.unknown().nullable(),
448
254
  detours: z.unknown().nullable(),
449
255
  examples: z.array(z.unknown()),
450
- follow: z.array(z.string()),
451
256
  id: z.string(),
452
257
  intent: z.enum(['read', 'write', 'destroy']),
453
258
  kind: z.string(),
259
+ provisions: z.array(z.string()),
454
260
  safety: z.string(),
455
- services: z.array(z.string()),
456
261
  }),
457
262
  z.object({
458
263
  description: z.string().nullable(),
459
264
  health: z.enum(['available', 'none']),
460
265
  id: z.string(),
461
- kind: z.literal('service'),
266
+ kind: z.literal('provision'),
462
267
  lifetime: z.literal('singleton'),
463
268
  usedBy: z.array(z.string()),
464
269
  }),
@@ -488,8 +293,4 @@ export const surveyTrail = trail('survey', {
488
293
  .optional(),
489
294
  }),
490
295
  ]),
491
- run: async (input, ctx) => {
492
- const app = await loadApp(input.module, ctx.cwd ?? '.');
493
- return dispatchSurvey(app, input);
494
- },
495
296
  });
@@ -0,0 +1,2 @@
1
+ export const REPORT_CONTRACT_VERSION = '2026-03';
2
+ export const REPORT_VERSION = '0.1.0';
@@ -0,0 +1,39 @@
1
+ import { trail } from '@ontrails/core';
2
+ import { z } from 'zod';
3
+
4
+ import { loadApp } from './load-app.js';
5
+ import { exportCurrentTopo } from './topo-store-support.js';
6
+ import {
7
+ DEFAULT_APP_MODULE,
8
+ isolatedExampleInput,
9
+ topoSaveOutput,
10
+ } from './topo-support.js';
11
+
12
+ export const topoExportTrail = trail('topo.export', {
13
+ blaze: async (input, ctx) => {
14
+ const rootDir = input.rootDir ?? ctx.cwd ?? process.cwd();
15
+ const app = await loadApp(input.module, rootDir);
16
+ return exportCurrentTopo(app, { rootDir });
17
+ },
18
+ description: 'Export the current topo to .trails artifacts',
19
+ examples: [
20
+ {
21
+ input: isolatedExampleInput('topo-export'),
22
+ name: 'Write the current topo export',
23
+ },
24
+ ],
25
+ input: z.object({
26
+ module: z
27
+ .string()
28
+ .default(DEFAULT_APP_MODULE)
29
+ .describe('Path to the app module'),
30
+ rootDir: z.string().optional().describe('Workspace root directory'),
31
+ }),
32
+ intent: 'write',
33
+ output: z.object({
34
+ hash: z.string(),
35
+ lockPath: z.string(),
36
+ mapPath: z.string(),
37
+ save: topoSaveOutput,
38
+ }),
39
+ });
@@ -0,0 +1,40 @@
1
+ import { Result, trail } from '@ontrails/core';
2
+ import { z } from 'zod';
3
+
4
+ import {
5
+ DEFAULT_TOPO_HISTORY_LIMIT,
6
+ isolatedExampleInput,
7
+ listTopoHistory,
8
+ topoPinOutput,
9
+ topoSaveOutput,
10
+ } from './topo-support.js';
11
+
12
+ export const topoHistoryTrail = trail('topo.history', {
13
+ blaze: (input, ctx) => {
14
+ const rootDir = input.rootDir ?? ctx.cwd ?? process.cwd();
15
+ return Result.ok(listTopoHistory({ limit: input.limit, rootDir }));
16
+ },
17
+ description: 'List saved topo metadata, including pins and recent autosaves',
18
+ examples: [
19
+ {
20
+ input: isolatedExampleInput('topo-history'),
21
+ name: 'Show topo history',
22
+ },
23
+ ],
24
+ input: z.object({
25
+ limit: z
26
+ .number()
27
+ .default(DEFAULT_TOPO_HISTORY_LIMIT)
28
+ .describe('Maximum number of autosaves to return'),
29
+ rootDir: z.string().optional().describe('Workspace root directory'),
30
+ }),
31
+ intent: 'read',
32
+ output: z.object({
33
+ dbPath: z.string(),
34
+ limit: z.number(),
35
+ pinCount: z.number(),
36
+ pins: z.array(topoPinOutput),
37
+ saveCount: z.number(),
38
+ saves: z.array(topoSaveOutput),
39
+ }),
40
+ });
@@ -0,0 +1,42 @@
1
+ import { Result, trail } from '@ontrails/core';
2
+ import { z } from 'zod';
3
+
4
+ import { loadApp } from './load-app.js';
5
+ import {
6
+ DEFAULT_APP_MODULE,
7
+ isolatedExampleInput,
8
+ pinCurrentTopo,
9
+ topoPinOutput,
10
+ topoSaveOutput,
11
+ } from './topo-support.js';
12
+
13
+ export const topoPinTrail = trail('topo.pin', {
14
+ blaze: async (input, ctx) => {
15
+ const rootDir = input.rootDir ?? ctx.cwd ?? process.cwd();
16
+ const app = await loadApp(input.module, rootDir);
17
+ return Result.ok(pinCurrentTopo(app, { name: input.name, rootDir }));
18
+ },
19
+ description: 'Pin the current topo under a durable name',
20
+ examples: [
21
+ {
22
+ input: {
23
+ ...isolatedExampleInput('topo-pin'),
24
+ name: 'before-auth-refactor',
25
+ },
26
+ name: 'Pin the current topo',
27
+ },
28
+ ],
29
+ input: z.object({
30
+ module: z
31
+ .string()
32
+ .default(DEFAULT_APP_MODULE)
33
+ .describe('Path to the app module'),
34
+ name: z.string().describe('Pin name'),
35
+ rootDir: z.string().optional().describe('Workspace root directory'),
36
+ }),
37
+ intent: 'write',
38
+ output: z.object({
39
+ pin: topoPinOutput,
40
+ save: topoSaveOutput,
41
+ }),
42
+ });