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

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 (201) hide show
  1. package/CHANGELOG.md +197 -2
  2. package/README.md +27 -0
  3. package/package.json +19 -8
  4. package/src/app.ts +15 -5
  5. package/src/cli.ts +303 -10
  6. package/src/completions.ts +240 -0
  7. package/src/load-app-mirror.ts +160 -0
  8. package/src/local-state-io.ts +153 -0
  9. package/src/project-writes.ts +320 -0
  10. package/src/run-collision.ts +125 -0
  11. package/src/run-completions-install.ts +179 -0
  12. package/src/run-example.ts +149 -0
  13. package/src/run-examples.ts +148 -0
  14. package/src/run-quiet.ts +75 -0
  15. package/src/run-trace.ts +273 -0
  16. package/src/run-warden.ts +39 -0
  17. package/src/run-watch.ts +432 -0
  18. package/src/scaffold-versions.generated.ts +12 -0
  19. package/src/trails/add-surface.ts +45 -23
  20. package/src/trails/add-trail.ts +27 -17
  21. package/src/trails/add-verify.ts +57 -17
  22. package/src/trails/completions-complete.ts +165 -0
  23. package/src/trails/completions.ts +47 -0
  24. package/src/trails/create-scaffold.ts +86 -33
  25. package/src/trails/create.ts +11 -3
  26. package/src/trails/dev-clean.ts +6 -1
  27. package/src/trails/dev-reset.ts +6 -1
  28. package/src/trails/dev-stats.ts +6 -1
  29. package/src/trails/dev-support.ts +29 -17
  30. package/src/trails/draft-promote.ts +289 -80
  31. package/src/trails/guide.ts +54 -34
  32. package/src/trails/load-app.ts +251 -56
  33. package/src/trails/root-dir.ts +21 -0
  34. package/src/trails/run-example.ts +482 -0
  35. package/src/trails/run-examples.ts +141 -0
  36. package/src/trails/run.ts +403 -0
  37. package/src/trails/survey.ts +506 -200
  38. package/src/trails/topo-activation.ts +385 -0
  39. package/src/trails/topo-compile.ts +55 -0
  40. package/src/trails/topo-history.ts +6 -1
  41. package/src/trails/topo-output-schemas.ts +175 -0
  42. package/src/trails/topo-pin.ts +19 -6
  43. package/src/trails/topo-read-support.ts +171 -228
  44. package/src/trails/topo-reports.ts +400 -25
  45. package/src/trails/topo-store-support.ts +43 -19
  46. package/src/trails/topo-support.ts +18 -28
  47. package/src/trails/topo-unpin.ts +6 -1
  48. package/src/trails/topo-verify.ts +18 -5
  49. package/src/trails/topo.ts +60 -23
  50. package/src/trails/warden-guide.ts +121 -0
  51. package/src/trails/warden.ts +137 -56
  52. package/src/versions.ts +3 -18
  53. package/.turbo/turbo-build.log +0 -1
  54. package/.turbo/turbo-lint.log +0 -3
  55. package/.turbo/turbo-typecheck.log +0 -1
  56. package/__tests__/examples.test.ts +0 -45
  57. package/dist/bin/trails.d.ts +0 -3
  58. package/dist/bin/trails.d.ts.map +0 -1
  59. package/dist/bin/trails.js +0 -4
  60. package/dist/bin/trails.js.map +0 -1
  61. package/dist/src/app.d.ts +0 -2
  62. package/dist/src/app.d.ts.map +0 -1
  63. package/dist/src/app.js +0 -22
  64. package/dist/src/app.js.map +0 -1
  65. package/dist/src/clack.d.ts +0 -9
  66. package/dist/src/clack.d.ts.map +0 -1
  67. package/dist/src/clack.js +0 -84
  68. package/dist/src/clack.js.map +0 -1
  69. package/dist/src/cli.d.ts +0 -2
  70. package/dist/src/cli.d.ts.map +0 -1
  71. package/dist/src/cli.js +0 -14
  72. package/dist/src/cli.js.map +0 -1
  73. package/dist/src/trails/add-surface.d.ts +0 -13
  74. package/dist/src/trails/add-surface.d.ts.map +0 -1
  75. package/dist/src/trails/add-surface.js +0 -110
  76. package/dist/src/trails/add-surface.js.map +0 -1
  77. package/dist/src/trails/add-trail.d.ts +0 -12
  78. package/dist/src/trails/add-trail.d.ts.map +0 -1
  79. package/dist/src/trails/add-trail.js +0 -104
  80. package/dist/src/trails/add-trail.js.map +0 -1
  81. package/dist/src/trails/add-trailhead.d.ts +0 -13
  82. package/dist/src/trails/add-trailhead.d.ts.map +0 -1
  83. package/dist/src/trails/add-trailhead.js +0 -88
  84. package/dist/src/trails/add-trailhead.js.map +0 -1
  85. package/dist/src/trails/add-verify.d.ts +0 -10
  86. package/dist/src/trails/add-verify.d.ts.map +0 -1
  87. package/dist/src/trails/add-verify.js +0 -68
  88. package/dist/src/trails/add-verify.js.map +0 -1
  89. package/dist/src/trails/create-scaffold.d.ts +0 -15
  90. package/dist/src/trails/create-scaffold.d.ts.map +0 -1
  91. package/dist/src/trails/create-scaffold.js +0 -295
  92. package/dist/src/trails/create-scaffold.js.map +0 -1
  93. package/dist/src/trails/create.d.ts +0 -18
  94. package/dist/src/trails/create.d.ts.map +0 -1
  95. package/dist/src/trails/create.js +0 -126
  96. package/dist/src/trails/create.js.map +0 -1
  97. package/dist/src/trails/dev-clean.d.ts +0 -9
  98. package/dist/src/trails/dev-clean.d.ts.map +0 -1
  99. package/dist/src/trails/dev-clean.js +0 -66
  100. package/dist/src/trails/dev-clean.js.map +0 -1
  101. package/dist/src/trails/dev-reset.d.ts +0 -6
  102. package/dist/src/trails/dev-reset.d.ts.map +0 -1
  103. package/dist/src/trails/dev-reset.js +0 -39
  104. package/dist/src/trails/dev-reset.js.map +0 -1
  105. package/dist/src/trails/dev-stats.d.ts +0 -7
  106. package/dist/src/trails/dev-stats.d.ts.map +0 -1
  107. package/dist/src/trails/dev-stats.js +0 -61
  108. package/dist/src/trails/dev-stats.js.map +0 -1
  109. package/dist/src/trails/dev-support.d.ts +0 -64
  110. package/dist/src/trails/dev-support.d.ts.map +0 -1
  111. package/dist/src/trails/dev-support.js +0 -181
  112. package/dist/src/trails/dev-support.js.map +0 -1
  113. package/dist/src/trails/draft-promote.d.ts +0 -18
  114. package/dist/src/trails/draft-promote.d.ts.map +0 -1
  115. package/dist/src/trails/draft-promote.js +0 -400
  116. package/dist/src/trails/draft-promote.js.map +0 -1
  117. package/dist/src/trails/guide.d.ts +0 -21
  118. package/dist/src/trails/guide.d.ts.map +0 -1
  119. package/dist/src/trails/guide.js +0 -61
  120. package/dist/src/trails/guide.js.map +0 -1
  121. package/dist/src/trails/load-app.d.ts +0 -12
  122. package/dist/src/trails/load-app.d.ts.map +0 -1
  123. package/dist/src/trails/load-app.js +0 -415
  124. package/dist/src/trails/load-app.js.map +0 -1
  125. package/dist/src/trails/project.d.ts +0 -8
  126. package/dist/src/trails/project.d.ts.map +0 -1
  127. package/dist/src/trails/project.js +0 -54
  128. package/dist/src/trails/project.js.map +0 -1
  129. package/dist/src/trails/survey.d.ts +0 -18
  130. package/dist/src/trails/survey.d.ts.map +0 -1
  131. package/dist/src/trails/survey.js +0 -234
  132. package/dist/src/trails/survey.js.map +0 -1
  133. package/dist/src/trails/topo-constants.d.ts +0 -3
  134. package/dist/src/trails/topo-constants.d.ts.map +0 -1
  135. package/dist/src/trails/topo-constants.js +0 -3
  136. package/dist/src/trails/topo-constants.js.map +0 -1
  137. package/dist/src/trails/topo-export.d.ts +0 -19
  138. package/dist/src/trails/topo-export.d.ts.map +0 -1
  139. package/dist/src/trails/topo-export.js +0 -31
  140. package/dist/src/trails/topo-export.js.map +0 -1
  141. package/dist/src/trails/topo-history.d.ts +0 -20
  142. package/dist/src/trails/topo-history.d.ts.map +0 -1
  143. package/dist/src/trails/topo-history.js +0 -32
  144. package/dist/src/trails/topo-history.js.map +0 -1
  145. package/dist/src/trails/topo-pin.d.ts +0 -17
  146. package/dist/src/trails/topo-pin.d.ts.map +0 -1
  147. package/dist/src/trails/topo-pin.js +0 -31
  148. package/dist/src/trails/topo-pin.js.map +0 -1
  149. package/dist/src/trails/topo-read-support.d.ts +0 -58
  150. package/dist/src/trails/topo-read-support.d.ts.map +0 -1
  151. package/dist/src/trails/topo-read-support.js +0 -167
  152. package/dist/src/trails/topo-read-support.js.map +0 -1
  153. package/dist/src/trails/topo-reports.d.ts +0 -54
  154. package/dist/src/trails/topo-reports.d.ts.map +0 -1
  155. package/dist/src/trails/topo-reports.js +0 -128
  156. package/dist/src/trails/topo-reports.js.map +0 -1
  157. package/dist/src/trails/topo-show.d.ts +0 -23
  158. package/dist/src/trails/topo-show.d.ts.map +0 -1
  159. package/dist/src/trails/topo-show.js +0 -49
  160. package/dist/src/trails/topo-show.js.map +0 -1
  161. package/dist/src/trails/topo-store-support.d.ts +0 -13
  162. package/dist/src/trails/topo-store-support.d.ts.map +0 -1
  163. package/dist/src/trails/topo-store-support.js +0 -55
  164. package/dist/src/trails/topo-store-support.js.map +0 -1
  165. package/dist/src/trails/topo-support.d.ts +0 -76
  166. package/dist/src/trails/topo-support.d.ts.map +0 -1
  167. package/dist/src/trails/topo-support.js +0 -132
  168. package/dist/src/trails/topo-support.js.map +0 -1
  169. package/dist/src/trails/topo-unpin.d.ts +0 -20
  170. package/dist/src/trails/topo-unpin.d.ts.map +0 -1
  171. package/dist/src/trails/topo-unpin.js +0 -44
  172. package/dist/src/trails/topo-unpin.js.map +0 -1
  173. package/dist/src/trails/topo-verify.d.ts +0 -5
  174. package/dist/src/trails/topo-verify.d.ts.map +0 -1
  175. package/dist/src/trails/topo-verify.js +0 -24
  176. package/dist/src/trails/topo-verify.js.map +0 -1
  177. package/dist/src/trails/topo.d.ts +0 -5
  178. package/dist/src/trails/topo.d.ts.map +0 -1
  179. package/dist/src/trails/topo.js +0 -63
  180. package/dist/src/trails/topo.js.map +0 -1
  181. package/dist/src/trails/warden.d.ts +0 -20
  182. package/dist/src/trails/warden.d.ts.map +0 -1
  183. package/dist/src/trails/warden.js +0 -98
  184. package/dist/src/trails/warden.js.map +0 -1
  185. package/dist/src/versions.d.ts +0 -12
  186. package/dist/src/versions.d.ts.map +0 -1
  187. package/dist/src/versions.js +0 -23
  188. package/dist/src/versions.js.map +0 -1
  189. package/dist/tsconfig.tsbuildinfo +0 -1
  190. package/src/__tests__/add-trail.test.ts +0 -97
  191. package/src/__tests__/create.test.ts +0 -415
  192. package/src/__tests__/draft-promote.test.ts +0 -144
  193. package/src/__tests__/guide.test.ts +0 -96
  194. package/src/__tests__/load-app.test.ts +0 -419
  195. package/src/__tests__/survey.test.ts +0 -377
  196. package/src/__tests__/topo-dev.test.ts +0 -426
  197. package/src/__tests__/warden.test.ts +0 -74
  198. package/src/trails/topo-export.ts +0 -35
  199. package/src/trails/topo-show.ts +0 -54
  200. package/tsconfig.json +0 -9
  201. package/tsconfig.tests.json +0 -10
@@ -1,207 +1,91 @@
1
1
  /**
2
- * Read-only topo store consumer helpers.
2
+ * Read-only live topo consumer helpers.
3
3
  *
4
- * Extracted from topo-support.ts to isolate read-only store consumer helpers,
4
+ * Extracted from topo-support.ts to isolate read-only topo consumer helpers,
5
5
  * keeping module boundaries clean.
6
6
  */
7
7
 
8
8
  import { existsSync } from 'node:fs';
9
9
  import { join } from 'node:path';
10
10
 
11
+ import type { Topo, TrailContext } from '@ontrails/core';
11
12
  import {
12
13
  ConflictError,
13
- createTopoStore,
14
- InternalError,
15
- listTopoSnapshots,
14
+ deriveTrailsDbPath,
15
+ deriveTrailsDir,
16
16
  NotFoundError,
17
17
  Result,
18
+ SURFACE_LAYER_NAMES_KEY,
19
+ ValidationError,
18
20
  } from '@ontrails/core';
19
- import type { Topo, TopoSnapshot } from '@ontrails/core';
21
+ import { deriveTopoGraph, readLockManifest } from '@ontrails/topographer';
22
+
23
+ import type {
24
+ BriefReport,
25
+ SignalDetailReport,
26
+ SurfaceLayerNames,
27
+ SurveyListReport,
28
+ TrailDetailReport,
29
+ } from './topo-reports.js';
20
30
  import {
21
- deriveTrailsDbPath,
22
- deriveTrailsDir,
23
- } from '@ontrails/core/internal/trails-db';
24
- import { readSurfaceLockData } from '@ontrails/schema';
25
-
26
- import type { BriefReport, SurveyListReport } from './topo-reports.js';
31
+ deriveBriefReport,
32
+ deriveResourceDetail,
33
+ deriveSignalDetail,
34
+ deriveSurveyList,
35
+ deriveTrailDetail,
36
+ } from './topo-reports.js';
37
+ import type { ActivationGraphReport } from './topo-activation.js';
38
+ import { deriveActivationGraph } from './topo-activation.js';
27
39
  import type { TopoSummaryReport, TopoVerifyReport } from './topo-support.js';
28
- import { REPORT_CONTRACT_VERSION, REPORT_VERSION } from './topo-constants.js';
29
- import {
30
- createCurrentTopoSnapshot,
31
- deriveRootDir,
32
- LOCK_PATH,
33
- } from './topo-support.js';
40
+ import { deriveRootDir, LOCK_PATH } from './topo-support.js';
41
+ import { deriveCurrentTopoExport } from './topo-store-support.js';
34
42
 
35
- // ---------------------------------------------------------------------------
36
- // Internal types
37
- // ---------------------------------------------------------------------------
43
+ export type CurrentTrailDetail = TrailDetailReport;
38
44
 
39
- interface StoredSurfaceMapEntry {
40
- readonly detours?: readonly {
41
- readonly on: string;
42
- readonly maxAttempts: number;
43
- }[];
44
- readonly kind: 'resource' | 'signal' | 'trail';
45
- }
46
-
47
- interface CurrentTrailDetail {
48
- readonly crosses: string[];
49
- readonly description: string | null;
50
- readonly detours:
51
- | readonly { readonly on: string; readonly maxAttempts: number }[]
52
- | null;
53
- readonly examples: unknown[];
54
- readonly id: string;
55
- readonly intent: 'destroy' | 'read' | 'write';
56
- readonly kind: string;
57
- readonly pattern: string | null;
58
- readonly resources: string[];
59
- readonly safety: string;
60
- }
61
-
62
- interface CurrentResourceDetail {
45
+ export interface CurrentResourceDetail {
63
46
  readonly description: string | null;
64
47
  readonly health: 'available' | 'none';
65
48
  readonly id: string;
66
49
  readonly kind: 'resource';
67
50
  readonly lifetime: 'singleton';
68
- readonly usedBy: string[];
51
+ readonly usedBy: readonly string[];
69
52
  }
70
53
 
71
- // ---------------------------------------------------------------------------
72
- // Store helpers
73
- // ---------------------------------------------------------------------------
74
-
75
- const topoStoreRef = (snapshotId: string) => ({ snapshotId }) as const;
76
-
77
- const hasCommittedLock = (trailsDir: string): boolean =>
78
- existsSync(join(trailsDir, 'trails.lock'));
79
-
80
- const readSurfaceEntries = (
81
- surfaceMapJson: string
82
- ): readonly StoredSurfaceMapEntry[] =>
83
- (
84
- JSON.parse(surfaceMapJson) as {
85
- readonly entries: readonly StoredSurfaceMapEntry[];
86
- }
87
- ).entries;
54
+ export type CurrentTopoDetail =
55
+ | CurrentResourceDetail
56
+ | CurrentTrailDetail
57
+ | SignalDetailReport;
88
58
 
89
- const buildBriefReportFromStore = (
90
- app: Topo,
91
- store: ReturnType<typeof createTopoStore>,
92
- ref: ReturnType<typeof topoStoreRef>,
93
- snapshot: TopoSnapshot
94
- ): BriefReport => {
95
- const trails = store.trails.list({ snapshot: ref });
96
- const exportRecord = store.exports.get(ref);
97
- const trailEntries =
98
- exportRecord === undefined
99
- ? []
100
- : readSurfaceEntries(exportRecord.surfaceMapJson).filter(
101
- (entry) => entry.kind === 'trail'
102
- );
59
+ export interface CurrentTopoMatch {
60
+ readonly kind: CurrentTopoDetail['kind'];
61
+ readonly detail: CurrentTopoDetail;
62
+ }
103
63
 
104
- return {
105
- contractVersion: REPORT_CONTRACT_VERSION,
106
- features: {
107
- detours: trailEntries.some((entry) => (entry.detours ?? []).length > 0),
108
- examples: trails.some((trail) => trail.hasExamples),
109
- outputSchemas: trails.some((trail) => trail.hasOutput),
110
- resources: snapshot.resourceCount > 0,
111
- signals: snapshot.signalCount > 0,
112
- },
113
- name: app.name,
114
- resources: snapshot.resourceCount,
115
- signals: snapshot.signalCount,
116
- trails: snapshot.trailCount,
117
- version: REPORT_VERSION,
118
- };
119
- };
64
+ export interface CurrentTopoReadOptions {
65
+ readonly rootDir?: string | undefined;
66
+ readonly surfaceLayerNames?: Partial<SurfaceLayerNames> | undefined;
67
+ }
120
68
 
121
- const buildSurveyListFromStore = (
122
- store: ReturnType<typeof createTopoStore>,
123
- ref: ReturnType<typeof topoStoreRef>
124
- ): SurveyListReport => {
125
- const trails = store.trails.list({ snapshot: ref });
126
- const resources = store.resources.list({ snapshot: ref });
69
+ const isStringArray = (value: unknown): value is readonly string[] =>
70
+ Array.isArray(value) && value.every((item) => typeof item === 'string');
127
71
 
72
+ export const readSurfaceLayerNamesFromContext = (
73
+ ctx: TrailContext
74
+ ): Partial<SurfaceLayerNames> => {
75
+ const value = ctx.extensions?.[SURFACE_LAYER_NAMES_KEY];
76
+ if (value === null || typeof value !== 'object' || Array.isArray(value)) {
77
+ return {};
78
+ }
79
+ const raw = value as Record<string, unknown>;
128
80
  return {
129
- count: trails.length,
130
- entries: trails.map((trail) => ({
131
- examples: trail.exampleCount,
132
- id: trail.id,
133
- kind: trail.kind,
134
- safety: trail.safety,
135
- })),
136
- resourceCount: resources.length,
137
- resources: resources.map((resource) => ({
138
- description: resource.description,
139
- health: resource.health,
140
- id: resource.id,
141
- kind: resource.kind,
142
- lifetime: resource.lifetime,
143
- usedBy: resource.usedBy,
144
- })),
81
+ ...(isStringArray(raw['cli']) ? { cli: raw['cli'] } : {}),
82
+ ...(isStringArray(raw['http']) ? { http: raw['http'] } : {}),
83
+ ...(isStringArray(raw['mcp']) ? { mcp: raw['mcp'] } : {}),
145
84
  };
146
85
  };
147
86
 
148
- const buildTrailDetailFromStore = (
149
- detail: NonNullable<
150
- ReturnType<ReturnType<typeof createTopoStore>['trails']['get']>
151
- >
152
- ): CurrentTrailDetail => ({
153
- crosses: [...detail.crosses],
154
- description: detail.description,
155
- detours: detail.detours,
156
- examples: [...detail.examples],
157
- id: detail.id,
158
- intent: detail.intent,
159
- kind: detail.kind,
160
- pattern: detail.pattern,
161
- resources: [...detail.resources],
162
- safety: detail.safety,
163
- });
164
-
165
- const buildResourceDetailFromStore = (
166
- resource: NonNullable<
167
- ReturnType<ReturnType<typeof createTopoStore>['resources']['get']>
168
- >
169
- ): CurrentResourceDetail => ({
170
- description: resource.description,
171
- health: resource.health,
172
- id: resource.id,
173
- kind: resource.kind,
174
- lifetime: resource.lifetime,
175
- usedBy: [...resource.usedBy],
176
- });
177
-
178
- // ---------------------------------------------------------------------------
179
- // withCurrentTopoStore
180
- // ---------------------------------------------------------------------------
181
-
182
- /**
183
- * Run a read callback against the latest topo store state.
184
- *
185
- * Uses the most recent existing snapshot when available, only creating a new
186
- * snapshot when no prior snapshot exists. This avoids unbounded snapshot
187
- * accumulation from
188
- * read-only operations like survey, guide, and show.
189
- */
190
- const withCurrentTopoStore = <T>(
191
- app: Topo,
192
- rootDir: string,
193
- read: (
194
- store: ReturnType<typeof createTopoStore>,
195
- ref: ReturnType<typeof topoStoreRef>,
196
- snapshot: TopoSnapshot
197
- ) => T
198
- ): T => {
199
- const [existingSnapshot] = listTopoSnapshots({ limit: 1, rootDir });
200
- const snapshot =
201
- existingSnapshot ?? createCurrentTopoSnapshot(app, { rootDir });
202
- const store = createTopoStore({ rootDir });
203
- return read(store, topoStoreRef(snapshot.id), snapshot);
204
- };
87
+ const hasCommittedLock = (trailsDir: string): boolean =>
88
+ existsSync(join(trailsDir, 'trails.lock'));
205
89
 
206
90
  // ---------------------------------------------------------------------------
207
91
  // Public read-only consumers
@@ -213,72 +97,116 @@ export const buildTopoSummary = (
213
97
  ): TopoSummaryReport => {
214
98
  const rootDir = deriveRootDir(options?.rootDir);
215
99
  const trailsDir = deriveTrailsDir({ rootDir });
216
- return withCurrentTopoStore(app, rootDir, (store, ref, snapshot) => ({
217
- app: buildBriefReportFromStore(app, store, ref, snapshot),
100
+ return {
101
+ app: deriveBriefReport(app),
218
102
  dbPath: deriveTrailsDbPath({ rootDir }),
219
- list: buildSurveyListFromStore(store, ref),
103
+ list: deriveSurveyList(app),
220
104
  lockExists: hasCommittedLock(trailsDir),
221
105
  lockPath: LOCK_PATH,
222
- }));
106
+ };
223
107
  };
224
108
 
225
109
  export const buildCurrentTopoBrief = (
226
110
  app: Topo,
227
- options?: { readonly rootDir?: string }
228
- ): BriefReport => {
229
- const rootDir = deriveRootDir(options?.rootDir);
230
- return withCurrentTopoStore(app, rootDir, (store, ref, snapshot) =>
231
- buildBriefReportFromStore(app, store, ref, snapshot)
232
- );
233
- };
111
+ _options?: { readonly rootDir?: string }
112
+ ): BriefReport => deriveBriefReport(app);
234
113
 
235
114
  export const buildCurrentTopoList = (
236
115
  app: Topo,
237
- options?: { readonly rootDir?: string }
238
- ): SurveyListReport => {
239
- const rootDir = deriveRootDir(options?.rootDir);
240
- return withCurrentTopoStore(app, rootDir, (store, ref) =>
241
- buildSurveyListFromStore(store, ref)
242
- );
243
- };
116
+ _options?: { readonly rootDir?: string }
117
+ ): SurveyListReport => deriveSurveyList(app);
244
118
 
245
119
  export const buildCurrentGuideEntries = (
246
120
  app: Topo,
247
- options?: { readonly rootDir?: string }
121
+ _options?: { readonly rootDir?: string }
248
122
  ): readonly {
249
123
  readonly description: string;
250
124
  readonly exampleCount: number;
251
125
  readonly id: string;
252
- readonly kind: string;
253
- }[] => {
254
- const rootDir = deriveRootDir(options?.rootDir);
255
- return withCurrentTopoStore(app, rootDir, (store, ref) =>
256
- store.trails.list({ snapshot: ref }).map((trail) => ({
126
+ readonly kind: 'trail';
127
+ }[] =>
128
+ app
129
+ .list()
130
+ .map((trail) => ({
257
131
  description: trail.description ?? '(no description)',
258
- exampleCount: trail.exampleCount,
132
+ exampleCount: trail.examples?.length ?? 0,
259
133
  id: trail.id,
260
- kind: trail.kind,
134
+ kind: 'trail' as const,
261
135
  }))
262
- );
136
+ .toSorted((a, b) => a.id.localeCompare(b.id));
137
+
138
+ export const buildCurrentTrailDetail = (
139
+ app: Topo,
140
+ id: string,
141
+ options?: CurrentTopoReadOptions
142
+ ): CurrentTrailDetail | undefined => {
143
+ const trail = app.get(id);
144
+ return trail === undefined
145
+ ? undefined
146
+ : deriveTrailDetail(trail, app, undefined, {
147
+ surfaceLayerNames: options?.surfaceLayerNames,
148
+ });
263
149
  };
264
150
 
151
+ export const buildCurrentResourceDetail = (
152
+ app: Topo,
153
+ id: string,
154
+ _options?: { readonly rootDir?: string }
155
+ ): CurrentResourceDetail | undefined =>
156
+ app.getResource(id) === undefined
157
+ ? undefined
158
+ : (deriveResourceDetail(app, id) as CurrentResourceDetail);
159
+
160
+ export const buildCurrentSignalDetail = (
161
+ app: Topo,
162
+ id: string,
163
+ _options?: { readonly rootDir?: string }
164
+ ): SignalDetailReport | undefined => deriveSignalDetail(app, id);
165
+
265
166
  export const buildCurrentTopoDetail = (
266
167
  app: Topo,
267
168
  id: string,
268
- options?: { readonly rootDir?: string }
269
- ): CurrentResourceDetail | CurrentTrailDetail | undefined => {
270
- const rootDir = deriveRootDir(options?.rootDir);
271
- return withCurrentTopoStore(app, rootDir, (store, ref) => {
272
- const trail = store.trails.get(id, { snapshot: ref });
273
- if (trail !== undefined) {
274
- return buildTrailDetailFromStore(trail);
275
- }
169
+ options?: CurrentTopoReadOptions
170
+ ): CurrentTopoDetail | undefined =>
171
+ buildCurrentTrailDetail(app, id, options) ??
172
+ buildCurrentResourceDetail(app, id) ??
173
+ buildCurrentSignalDetail(app, id);
276
174
 
277
- const resource = store.resources.get(id, { snapshot: ref });
278
- return resource === undefined
279
- ? undefined
280
- : buildResourceDetailFromStore(resource);
281
- });
175
+ export const buildCurrentTopoMatches = (
176
+ app: Topo,
177
+ id: string,
178
+ options?: CurrentTopoReadOptions
179
+ ): readonly CurrentTopoMatch[] => {
180
+ const matches: CurrentTopoMatch[] = [];
181
+ let activationGraph: ActivationGraphReport | undefined;
182
+ const getActivationGraph = (): ActivationGraphReport =>
183
+ (activationGraph ??= deriveActivationGraph(app));
184
+ let topoGraph: ReturnType<typeof deriveTopoGraph> | undefined;
185
+ const getTopoGraph = (): ReturnType<typeof deriveTopoGraph> =>
186
+ (topoGraph ??= deriveTopoGraph(app));
187
+
188
+ const trail = app.get(id);
189
+ if (trail !== undefined) {
190
+ matches.push({
191
+ detail: deriveTrailDetail(trail, app, getActivationGraph(), {
192
+ surfaceLayerNames: options?.surfaceLayerNames,
193
+ topoGraph: getTopoGraph(),
194
+ }),
195
+ kind: 'trail',
196
+ });
197
+ }
198
+
199
+ const resource = buildCurrentResourceDetail(app, id);
200
+ if (resource !== undefined) {
201
+ matches.push({ detail: resource, kind: 'resource' });
202
+ }
203
+
204
+ const signal = deriveSignalDetail(app, id, activationGraph);
205
+ if (signal !== undefined) {
206
+ matches.push({ detail: signal, kind: 'signal' });
207
+ }
208
+
209
+ return matches;
282
210
  };
283
211
 
284
212
  export const verifyCurrentTopo = async (
@@ -286,42 +214,57 @@ export const verifyCurrentTopo = async (
286
214
  options?: { readonly rootDir?: string }
287
215
  ): Promise<Result<TopoVerifyReport, Error>> => {
288
216
  const rootDir = deriveRootDir(options?.rootDir);
289
- const committedLock = await readSurfaceLockData({
290
- dir: deriveTrailsDir({ rootDir }),
291
- });
217
+ let lockManifest: Awaited<ReturnType<typeof readLockManifest>>;
218
+ try {
219
+ lockManifest = await readLockManifest({
220
+ dir: deriveTrailsDir({ rootDir }),
221
+ });
222
+ } catch (error) {
223
+ const message =
224
+ error instanceof Error
225
+ ? error.message
226
+ : 'Unable to read committed trails.lock manifest.';
227
+ return Result.err(
228
+ error instanceof Error
229
+ ? new ValidationError(message, { cause: error })
230
+ : new ValidationError(message)
231
+ );
232
+ }
292
233
 
293
- if (committedLock === null) {
234
+ if (lockManifest === null) {
294
235
  return Result.err(
295
236
  new NotFoundError(
296
- 'No committed trails.lock found. Run `trails topo export` first.'
237
+ 'No committed trails.lock found. Run `trails topo compile` first.'
297
238
  )
298
239
  );
299
240
  }
300
241
 
301
- const currentHash = withCurrentTopoStore(
302
- app,
303
- rootDir,
304
- (store, ref) => store.exports.get(ref)?.surfaceHash
242
+ const currentExport = deriveCurrentTopoExport(app, { rootDir });
243
+ if (currentExport.isErr()) {
244
+ return currentExport;
245
+ }
246
+ const currentHash = currentExport.value.topoGraphHash;
247
+ const topoArtifact = lockManifest.artifacts.find(
248
+ (artifact) => artifact.role === 'topo' && artifact.path === 'topo.lock'
305
249
  );
306
-
307
- if (currentHash === undefined) {
250
+ if (topoArtifact === undefined) {
308
251
  return Result.err(
309
- new InternalError(
310
- 'No stored topo export found for the current topo snapshot'
252
+ new NotFoundError(
253
+ 'No topo.lock artifact found in trails.lock. Run `trails topo compile` first.'
311
254
  )
312
255
  );
313
256
  }
314
257
 
315
- if (committedLock.hash !== currentHash) {
258
+ if (topoArtifact.sha256 !== currentHash) {
316
259
  return Result.err(
317
260
  new ConflictError(
318
- 'trails.lock is stale. Run `trails topo export` to refresh it.'
261
+ 'trails.lock is stale. Run `trails topo compile` to refresh it.'
319
262
  )
320
263
  );
321
264
  }
322
265
 
323
266
  return Result.ok({
324
- committedHash: committedLock.hash,
267
+ committedHash: topoArtifact.sha256,
325
268
  currentHash,
326
269
  lockPath: LOCK_PATH,
327
270
  stale: false,