@ontrails/trails 1.0.0-beta.4 → 1.0.0-beta.42
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.
- package/CHANGELOG.md +1106 -4
- package/README.md +27 -0
- package/package.json +31 -7
- package/src/app.ts +147 -3
- package/src/cli.ts +345 -11
- package/src/completions.ts +240 -0
- package/src/lifecycle-source-io.ts +33 -0
- package/src/load-app-mirror.ts +202 -0
- package/src/local-state-io.ts +173 -0
- package/src/mcp-app.ts +42 -0
- package/src/mcp-options.ts +91 -0
- package/src/mcp.ts +8 -0
- package/src/project-writes.ts +377 -0
- package/src/regrade/config.ts +152 -0
- package/src/regrade/history.ts +430 -0
- package/src/regrade/live-api-preserve.ts +8 -0
- package/src/regrade/plan-artifact.ts +285 -0
- package/src/release/bindings.ts +39 -0
- package/src/release/check.ts +844 -0
- package/src/release/config.ts +63 -0
- package/src/release/contract-facts.ts +425 -0
- package/src/release/index.ts +142 -0
- package/src/release/lock-roundtrip-smoke.ts +230 -0
- package/src/release/native-bun-publish.ts +651 -0
- package/src/release/native-bun-registry.ts +765 -0
- package/src/release/notes-cli.ts +171 -0
- package/src/release/notes.ts +390 -0
- package/src/release/pack-coherence.ts +455 -0
- package/src/release/packed-artifacts-smoke.ts +236 -0
- package/src/release/policy.ts +1687 -0
- package/src/release/semver.ts +104 -0
- package/src/release/smoke.ts +56 -0
- package/src/release/wayfinder-dogfood-smoke.ts +760 -0
- package/src/retired-topo-command.ts +36 -0
- package/src/run-adapter-check.ts +76 -0
- package/src/run-collision.ts +126 -0
- package/src/run-completions-install.ts +179 -0
- package/src/run-example.ts +149 -0
- package/src/run-examples.ts +148 -0
- package/src/run-quiet.ts +75 -0
- package/src/run-release-check.ts +74 -0
- package/src/run-schema.ts +74 -0
- package/src/run-trace.ts +273 -0
- package/src/run-warden.ts +39 -0
- package/src/run-watch.ts +432 -0
- package/src/run-wayfind-outline.ts +170 -0
- package/src/scaffold-version-sync.ts +183 -0
- package/src/scaffold-versions.generated.ts +12 -0
- package/src/trails/adapter-check.ts +244 -0
- package/src/trails/add-surface.ts +99 -45
- package/src/trails/add-trail.ts +84 -37
- package/src/trails/add-verify.ts +100 -30
- package/src/trails/compile.ts +58 -0
- package/src/trails/completions-complete.ts +165 -0
- package/src/trails/completions.ts +47 -0
- package/src/trails/create-adapter.ts +785 -0
- package/src/trails/create-scaffold.ts +401 -106
- package/src/trails/create-versions.ts +62 -0
- package/src/trails/create.ts +186 -72
- package/src/trails/deprecate.ts +59 -0
- package/src/trails/dev-clean.ts +82 -0
- package/src/trails/dev-reset.ts +50 -0
- package/src/trails/dev-stats.ts +72 -0
- package/src/trails/dev-support.ts +360 -0
- package/src/trails/doctor.ts +77 -0
- package/src/trails/draft-promote.ts +949 -0
- package/src/trails/guide.ts +70 -68
- package/src/trails/load-app.ts +1123 -15
- package/src/trails/operator-context.ts +66 -0
- package/src/trails/project.ts +17 -3
- package/src/trails/regrade.ts +3091 -0
- package/src/trails/release-check.ts +105 -0
- package/src/trails/release-smoke.ts +49 -0
- package/src/trails/revise.ts +53 -0
- package/src/trails/root-dir.ts +21 -0
- package/src/trails/run-example.ts +475 -0
- package/src/trails/run-examples.ts +129 -0
- package/src/trails/run.ts +434 -0
- package/src/trails/scaffold-json.ts +58 -0
- package/src/trails/survey.ts +877 -214
- package/src/trails/topo-activation.ts +14 -0
- package/src/trails/topo-constants.ts +2 -0
- package/src/trails/topo-history.ts +47 -0
- package/src/trails/topo-output-schemas.ts +259 -0
- package/src/trails/topo-pin.ts +38 -0
- package/src/trails/topo-read-support.ts +368 -0
- package/src/trails/topo-reports.ts +809 -0
- package/src/trails/topo-store-support.ts +325 -0
- package/src/trails/topo-support.ts +220 -0
- package/src/trails/topo-unpin.ts +61 -0
- package/src/trails/topo.ts +92 -0
- package/src/trails/validate.ts +27 -0
- package/src/trails/version-lifecycle-support.ts +936 -0
- package/src/trails/warden-guide.ts +134 -0
- package/src/trails/warden.ts +198 -58
- package/src/trails/wayfind-outline.ts +876 -0
- package/src/trails/wayfind.ts +1053 -0
- package/src/versions.ts +31 -0
- package/.turbo/turbo-build.log +0 -1
- package/.turbo/turbo-lint.log +0 -3
- package/.turbo/turbo-typecheck.log +0 -1
- package/__tests__/examples.test.ts +0 -6
- package/dist/bin/trails.d.ts +0 -3
- package/dist/bin/trails.d.ts.map +0 -1
- package/dist/bin/trails.js +0 -4
- package/dist/bin/trails.js.map +0 -1
- package/dist/src/app.d.ts +0 -2
- package/dist/src/app.d.ts.map +0 -1
- package/dist/src/app.js +0 -11
- package/dist/src/app.js.map +0 -1
- package/dist/src/clack.d.ts +0 -9
- package/dist/src/clack.d.ts.map +0 -1
- package/dist/src/clack.js +0 -84
- package/dist/src/clack.js.map +0 -1
- package/dist/src/cli.d.ts +0 -2
- package/dist/src/cli.d.ts.map +0 -1
- package/dist/src/cli.js +0 -13
- package/dist/src/cli.js.map +0 -1
- package/dist/src/trails/add-surface.d.ts +0 -13
- package/dist/src/trails/add-surface.d.ts.map +0 -1
- package/dist/src/trails/add-surface.js +0 -88
- package/dist/src/trails/add-surface.js.map +0 -1
- package/dist/src/trails/add-trail.d.ts +0 -10
- package/dist/src/trails/add-trail.d.ts.map +0 -1
- package/dist/src/trails/add-trail.js +0 -77
- package/dist/src/trails/add-trail.js.map +0 -1
- package/dist/src/trails/add-verify.d.ts +0 -10
- package/dist/src/trails/add-verify.d.ts.map +0 -1
- package/dist/src/trails/add-verify.js +0 -67
- package/dist/src/trails/add-verify.js.map +0 -1
- package/dist/src/trails/create-scaffold.d.ts +0 -15
- package/dist/src/trails/create-scaffold.d.ts.map +0 -1
- package/dist/src/trails/create-scaffold.js +0 -288
- package/dist/src/trails/create-scaffold.js.map +0 -1
- package/dist/src/trails/create.d.ts +0 -22
- package/dist/src/trails/create.d.ts.map +0 -1
- package/dist/src/trails/create.js +0 -121
- package/dist/src/trails/create.js.map +0 -1
- package/dist/src/trails/guide.d.ts +0 -11
- package/dist/src/trails/guide.d.ts.map +0 -1
- package/dist/src/trails/guide.js +0 -80
- package/dist/src/trails/guide.js.map +0 -1
- package/dist/src/trails/load-app.d.ts +0 -4
- package/dist/src/trails/load-app.d.ts.map +0 -1
- package/dist/src/trails/load-app.js +0 -24
- package/dist/src/trails/load-app.js.map +0 -1
- package/dist/src/trails/project.d.ts +0 -8
- package/dist/src/trails/project.d.ts.map +0 -1
- package/dist/src/trails/project.js +0 -43
- package/dist/src/trails/project.js.map +0 -1
- package/dist/src/trails/survey.d.ts +0 -31
- package/dist/src/trails/survey.d.ts.map +0 -1
- package/dist/src/trails/survey.js +0 -221
- package/dist/src/trails/survey.js.map +0 -1
- package/dist/src/trails/warden.d.ts +0 -19
- package/dist/src/trails/warden.d.ts.map +0 -1
- package/dist/src/trails/warden.js +0 -88
- package/dist/src/trails/warden.js.map +0 -1
- package/dist/tsconfig.tsbuildinfo +0 -1
- package/src/__tests__/create.test.ts +0 -349
- package/src/__tests__/guide.test.ts +0 -91
- package/src/__tests__/load-app.test.ts +0 -15
- package/src/__tests__/survey.test.ts +0 -159
- package/src/__tests__/warden.test.ts +0 -74
- package/tsconfig.json +0 -9
|
@@ -0,0 +1,876 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { relative, resolve } from 'node:path';
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
DerivationError,
|
|
6
|
+
NotFoundError,
|
|
7
|
+
Result,
|
|
8
|
+
ValidationError,
|
|
9
|
+
securePath,
|
|
10
|
+
trail,
|
|
11
|
+
} from '@ontrails/core';
|
|
12
|
+
import type { TrailsError } from '@ontrails/core';
|
|
13
|
+
import { loadWayfinderArtifacts } from '@ontrails/topography';
|
|
14
|
+
import type {
|
|
15
|
+
TopoGraph,
|
|
16
|
+
TopoGraphEntry,
|
|
17
|
+
WayfinderArtifactLoad,
|
|
18
|
+
} from '@ontrails/topography';
|
|
19
|
+
import {
|
|
20
|
+
findTrailDefinitions,
|
|
21
|
+
getStringValue,
|
|
22
|
+
identifierName,
|
|
23
|
+
isCallExpression,
|
|
24
|
+
isClassMember,
|
|
25
|
+
isDeclarationWithId,
|
|
26
|
+
isExportAllDeclaration,
|
|
27
|
+
isExportDeclaration,
|
|
28
|
+
isExportDefaultDeclaration,
|
|
29
|
+
isExportNamedDeclaration,
|
|
30
|
+
isExportSpecifier,
|
|
31
|
+
isIdentifier,
|
|
32
|
+
isImportDeclaration,
|
|
33
|
+
isImportSpecifier,
|
|
34
|
+
isMemberExpression,
|
|
35
|
+
isProgram,
|
|
36
|
+
isVariableDeclaration,
|
|
37
|
+
isVariableDeclarator,
|
|
38
|
+
offsetToLineColumn,
|
|
39
|
+
parseWithDiagnostics,
|
|
40
|
+
walkWithParents,
|
|
41
|
+
} from '@ontrails/source';
|
|
42
|
+
import type { AstNode, AstParentContext } from '@ontrails/source';
|
|
43
|
+
import { z } from 'zod';
|
|
44
|
+
|
|
45
|
+
const outlineFeatureNames = [
|
|
46
|
+
'source',
|
|
47
|
+
'trails',
|
|
48
|
+
'apps',
|
|
49
|
+
'contracts',
|
|
50
|
+
'surfaces',
|
|
51
|
+
'graph',
|
|
52
|
+
'diagnostics',
|
|
53
|
+
] as const;
|
|
54
|
+
|
|
55
|
+
const defaultOutlineFeatures = [
|
|
56
|
+
'trails',
|
|
57
|
+
'apps',
|
|
58
|
+
'surfaces',
|
|
59
|
+
'graph',
|
|
60
|
+
'diagnostics',
|
|
61
|
+
] as const;
|
|
62
|
+
|
|
63
|
+
const outlineViewFeatures = {
|
|
64
|
+
all: outlineFeatureNames,
|
|
65
|
+
contracts: ['trails', 'contracts', 'graph', 'diagnostics'],
|
|
66
|
+
default: defaultOutlineFeatures,
|
|
67
|
+
review: ['source', 'trails', 'contracts', 'graph', 'diagnostics'],
|
|
68
|
+
source: ['source', 'diagnostics'],
|
|
69
|
+
surfaces: ['trails', 'apps', 'surfaces', 'graph', 'diagnostics'],
|
|
70
|
+
} as const satisfies Record<string, readonly OutlineFeature[]>;
|
|
71
|
+
|
|
72
|
+
const outlineFeatureSchema = z.enum(outlineFeatureNames);
|
|
73
|
+
const outlineViewSchema = z.enum([
|
|
74
|
+
'all',
|
|
75
|
+
'contracts',
|
|
76
|
+
'custom',
|
|
77
|
+
'default',
|
|
78
|
+
'review',
|
|
79
|
+
'source',
|
|
80
|
+
'surfaces',
|
|
81
|
+
]);
|
|
82
|
+
|
|
83
|
+
export type OutlineFeature = z.infer<typeof outlineFeatureSchema>;
|
|
84
|
+
export type OutlineView = z.infer<typeof outlineViewSchema>;
|
|
85
|
+
|
|
86
|
+
export const outlineInputSchema = z
|
|
87
|
+
.object({
|
|
88
|
+
all: z
|
|
89
|
+
.boolean()
|
|
90
|
+
.default(false)
|
|
91
|
+
.describe('Show every outline feature family'),
|
|
92
|
+
contracts: z
|
|
93
|
+
.boolean()
|
|
94
|
+
.default(false)
|
|
95
|
+
.describe('Show trail contract and schema facts'),
|
|
96
|
+
features: z
|
|
97
|
+
.string()
|
|
98
|
+
.optional()
|
|
99
|
+
.describe('Comma-separated feature families to include'),
|
|
100
|
+
file: z.string().min(1).describe('Source file to outline'),
|
|
101
|
+
review: z
|
|
102
|
+
.boolean()
|
|
103
|
+
.default(false)
|
|
104
|
+
.describe('Show the source and contract facts most useful for review'),
|
|
105
|
+
rootDir: z.string().optional().describe('Workspace root directory'),
|
|
106
|
+
source: z
|
|
107
|
+
.boolean()
|
|
108
|
+
.default(false)
|
|
109
|
+
.describe('Show source-level imports, exports, and declarations'),
|
|
110
|
+
surfaces: z
|
|
111
|
+
.boolean()
|
|
112
|
+
.default(false)
|
|
113
|
+
.describe('Show surface and app membership facts'),
|
|
114
|
+
})
|
|
115
|
+
.strict();
|
|
116
|
+
|
|
117
|
+
export type OutlineInput = z.output<typeof outlineInputSchema>;
|
|
118
|
+
|
|
119
|
+
const sourceImportSchema = z.object({
|
|
120
|
+
names: z.array(z.string()).readonly(),
|
|
121
|
+
source: z.string(),
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
const sourceExportSchema = z.object({
|
|
125
|
+
line: z.number().int().positive(),
|
|
126
|
+
names: z.array(z.string()).readonly(),
|
|
127
|
+
source: z.string().optional(),
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
const sourceDeclarationSchema = z.object({
|
|
131
|
+
kind: z.enum([
|
|
132
|
+
'class',
|
|
133
|
+
'class-member',
|
|
134
|
+
'const',
|
|
135
|
+
'function',
|
|
136
|
+
'interface',
|
|
137
|
+
'type',
|
|
138
|
+
'variable',
|
|
139
|
+
]),
|
|
140
|
+
line: z.number().int().positive(),
|
|
141
|
+
name: z.string(),
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
const sourceAppSchema = z.object({
|
|
145
|
+
callee: z.string(),
|
|
146
|
+
line: z.number().int().positive(),
|
|
147
|
+
name: z.string(),
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
const sourceOutlineSchema = z.object({
|
|
151
|
+
declarations: z.array(sourceDeclarationSchema).readonly(),
|
|
152
|
+
exports: z.array(sourceExportSchema).readonly(),
|
|
153
|
+
imports: z.array(sourceImportSchema).readonly(),
|
|
154
|
+
lineCount: z.number().int().nonnegative(),
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
const trailOutlineSchema = z.object({
|
|
158
|
+
contracts: z
|
|
159
|
+
.object({
|
|
160
|
+
input: z.boolean(),
|
|
161
|
+
output: z.boolean(),
|
|
162
|
+
})
|
|
163
|
+
.optional(),
|
|
164
|
+
graph: z
|
|
165
|
+
.object({
|
|
166
|
+
exampleCount: z.number().int().nonnegative(),
|
|
167
|
+
intent: z.enum(['destroy', 'read', 'write']),
|
|
168
|
+
surfaces: z.array(z.string()).readonly(),
|
|
169
|
+
})
|
|
170
|
+
.optional(),
|
|
171
|
+
id: z.string(),
|
|
172
|
+
line: z.number().int().positive(),
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
const graphOutlineSchema = z.object({
|
|
176
|
+
matchedTrailIds: z.array(z.string()).readonly(),
|
|
177
|
+
source: z
|
|
178
|
+
.object({
|
|
179
|
+
artifactStatus: z.string(),
|
|
180
|
+
kind: z.string(),
|
|
181
|
+
path: z.string().optional(),
|
|
182
|
+
})
|
|
183
|
+
.nullable(),
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
const outlineDiagnosticSchema = z.object({
|
|
187
|
+
code: z.string(),
|
|
188
|
+
line: z.number().int().positive().optional(),
|
|
189
|
+
message: z.string(),
|
|
190
|
+
severity: z.enum(['error', 'info', 'warn']),
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
const outlineCountsSchema = z.object({
|
|
194
|
+
apps: z.number().int().nonnegative(),
|
|
195
|
+
declarations: z.number().int().nonnegative(),
|
|
196
|
+
diagnostics: z.number().int().nonnegative(),
|
|
197
|
+
graphMatches: z.number().int().nonnegative().optional(),
|
|
198
|
+
trails: z.number().int().nonnegative(),
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
export const outlineOutputSchema = z.object({
|
|
202
|
+
apps: z.array(sourceAppSchema).readonly().optional(),
|
|
203
|
+
counts: outlineCountsSchema,
|
|
204
|
+
diagnostics: z.array(outlineDiagnosticSchema).readonly().optional(),
|
|
205
|
+
features: z.object({
|
|
206
|
+
included: z.array(outlineFeatureSchema).readonly(),
|
|
207
|
+
omitted: z.array(outlineFeatureSchema).readonly(),
|
|
208
|
+
view: outlineViewSchema,
|
|
209
|
+
}),
|
|
210
|
+
file: z.string(),
|
|
211
|
+
graph: graphOutlineSchema.optional(),
|
|
212
|
+
rootDir: z.string(),
|
|
213
|
+
source: sourceOutlineSchema.optional(),
|
|
214
|
+
surfaces: z.array(z.string()).readonly().optional(),
|
|
215
|
+
trails: z.array(trailOutlineSchema).readonly().optional(),
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
export type OutlineOutput = z.output<typeof outlineOutputSchema>;
|
|
219
|
+
|
|
220
|
+
interface OutlineDiagnostic {
|
|
221
|
+
readonly code: string;
|
|
222
|
+
readonly line?: number | undefined;
|
|
223
|
+
readonly message: string;
|
|
224
|
+
readonly severity: 'error' | 'info' | 'warn';
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
interface SourceImport {
|
|
228
|
+
readonly names: readonly string[];
|
|
229
|
+
readonly source: string;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
interface SourceExport {
|
|
233
|
+
readonly line: number;
|
|
234
|
+
readonly names: readonly string[];
|
|
235
|
+
readonly source?: string | undefined;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
interface SourceDeclaration {
|
|
239
|
+
readonly kind:
|
|
240
|
+
| 'class'
|
|
241
|
+
| 'class-member'
|
|
242
|
+
| 'const'
|
|
243
|
+
| 'function'
|
|
244
|
+
| 'interface'
|
|
245
|
+
| 'type'
|
|
246
|
+
| 'variable';
|
|
247
|
+
readonly line: number;
|
|
248
|
+
readonly name: string;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
interface SourceTrail {
|
|
252
|
+
readonly id: string;
|
|
253
|
+
readonly line: number;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
interface SourceApp {
|
|
257
|
+
readonly callee: string;
|
|
258
|
+
readonly line: number;
|
|
259
|
+
readonly name: string;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
interface ParsedSourceOutline {
|
|
263
|
+
readonly apps: readonly SourceApp[];
|
|
264
|
+
readonly declarations: readonly SourceDeclaration[];
|
|
265
|
+
readonly diagnostics: readonly OutlineDiagnostic[];
|
|
266
|
+
readonly exports: readonly SourceExport[];
|
|
267
|
+
readonly imports: readonly SourceImport[];
|
|
268
|
+
readonly lineCount: number;
|
|
269
|
+
readonly trails: readonly SourceTrail[];
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const uniqueSorted = (values: readonly string[]): readonly string[] =>
|
|
273
|
+
[...new Set(values)].toSorted();
|
|
274
|
+
|
|
275
|
+
const lineFor = (sourceCode: string, node: AstNode): number =>
|
|
276
|
+
offsetToLineColumn(sourceCode, node.start).line;
|
|
277
|
+
|
|
278
|
+
const stringLiteralValue = (node: AstNode | undefined): string | undefined => {
|
|
279
|
+
if (!node) {
|
|
280
|
+
return undefined;
|
|
281
|
+
}
|
|
282
|
+
return getStringValue(node) ?? undefined;
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
const propertyName = (node: AstNode | undefined): string | undefined => {
|
|
286
|
+
if (!node) {
|
|
287
|
+
return undefined;
|
|
288
|
+
}
|
|
289
|
+
if (isIdentifier(node)) {
|
|
290
|
+
return identifierName(node) ?? undefined;
|
|
291
|
+
}
|
|
292
|
+
return stringLiteralValue(node);
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
const staticCalleeName = (node: AstNode | undefined): string | undefined => {
|
|
296
|
+
if (!node) {
|
|
297
|
+
return undefined;
|
|
298
|
+
}
|
|
299
|
+
if (isIdentifier(node)) {
|
|
300
|
+
return identifierName(node) ?? undefined;
|
|
301
|
+
}
|
|
302
|
+
if (isMemberExpression(node)) {
|
|
303
|
+
const receiver = identifierName(node.object);
|
|
304
|
+
const member = propertyName(node.property);
|
|
305
|
+
return receiver && member ? `${receiver}.${member}` : member;
|
|
306
|
+
}
|
|
307
|
+
return undefined;
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
const localImportName = (specifier: AstNode): string | undefined => {
|
|
311
|
+
if (!isImportSpecifier(specifier)) {
|
|
312
|
+
return undefined;
|
|
313
|
+
}
|
|
314
|
+
const { imported, local } = specifier;
|
|
315
|
+
if (specifier.type === 'ImportSpecifier') {
|
|
316
|
+
return (
|
|
317
|
+
identifierName(local) ??
|
|
318
|
+
identifierName(imported) ??
|
|
319
|
+
stringLiteralValue(imported) ??
|
|
320
|
+
undefined
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
return identifierName(local) ?? undefined;
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
const collectImports = (ast: AstNode): readonly SourceImport[] => {
|
|
327
|
+
const imports: SourceImport[] = [];
|
|
328
|
+
for (const node of isProgram(ast) ? (ast.body ?? []) : []) {
|
|
329
|
+
if (!isImportDeclaration(node)) {
|
|
330
|
+
continue;
|
|
331
|
+
}
|
|
332
|
+
const source = stringLiteralValue(node.source);
|
|
333
|
+
if (source === undefined) {
|
|
334
|
+
continue;
|
|
335
|
+
}
|
|
336
|
+
imports.push({
|
|
337
|
+
names: uniqueSorted(
|
|
338
|
+
(node.specifiers ?? []).flatMap((specifier) => {
|
|
339
|
+
const name = localImportName(specifier);
|
|
340
|
+
return name === undefined ? [] : [name];
|
|
341
|
+
})
|
|
342
|
+
),
|
|
343
|
+
source,
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
return imports;
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
const declarationName = (node: AstNode | undefined): string | undefined => {
|
|
350
|
+
if (!isDeclarationWithId(node)) {
|
|
351
|
+
return undefined;
|
|
352
|
+
}
|
|
353
|
+
return identifierName(node.id) ?? undefined;
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
const variableKind = (declaration: AstNode): SourceDeclaration['kind'] =>
|
|
357
|
+
isVariableDeclaration(declaration) && declaration.kind === 'const'
|
|
358
|
+
? 'const'
|
|
359
|
+
: 'variable';
|
|
360
|
+
|
|
361
|
+
const collectVariableDeclarations = (
|
|
362
|
+
node: AstNode,
|
|
363
|
+
kind: SourceDeclaration['kind'],
|
|
364
|
+
sourceCode: string
|
|
365
|
+
): SourceDeclaration[] => {
|
|
366
|
+
if (!isVariableDeclaration(node)) {
|
|
367
|
+
return [];
|
|
368
|
+
}
|
|
369
|
+
return (node.declarations ?? []).flatMap((declarator) => {
|
|
370
|
+
if (!isVariableDeclarator(declarator)) {
|
|
371
|
+
return [];
|
|
372
|
+
}
|
|
373
|
+
const name = identifierName(declarator.id);
|
|
374
|
+
return name === null
|
|
375
|
+
? []
|
|
376
|
+
: [{ kind, line: lineFor(sourceCode, declarator), name }];
|
|
377
|
+
});
|
|
378
|
+
};
|
|
379
|
+
|
|
380
|
+
const isTopLevelStatement = (context: AstParentContext): boolean => {
|
|
381
|
+
if (isProgram(context.parent)) {
|
|
382
|
+
return true;
|
|
383
|
+
}
|
|
384
|
+
if (
|
|
385
|
+
!isExportNamedDeclaration(context.parent) &&
|
|
386
|
+
!isExportDefaultDeclaration(context.parent)
|
|
387
|
+
) {
|
|
388
|
+
return false;
|
|
389
|
+
}
|
|
390
|
+
return (
|
|
391
|
+
context.parent.parent === undefined || isProgram(context.parent.parent)
|
|
392
|
+
);
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
const collectDeclarations = (
|
|
396
|
+
ast: AstNode,
|
|
397
|
+
sourceCode: string
|
|
398
|
+
): readonly SourceDeclaration[] => {
|
|
399
|
+
const declarations: SourceDeclaration[] = [];
|
|
400
|
+
|
|
401
|
+
walkWithParents(ast, (node, context) => {
|
|
402
|
+
if (isClassMember(node)) {
|
|
403
|
+
const name = propertyName(node.key);
|
|
404
|
+
if (name !== undefined) {
|
|
405
|
+
declarations.push({
|
|
406
|
+
kind: 'class-member',
|
|
407
|
+
line: lineFor(sourceCode, node),
|
|
408
|
+
name,
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
if (!isTopLevelStatement(context)) {
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
if (isDeclarationWithId(node) && node.type === 'FunctionDeclaration') {
|
|
419
|
+
const name = declarationName(node);
|
|
420
|
+
if (name !== undefined) {
|
|
421
|
+
declarations.push({
|
|
422
|
+
kind: 'function',
|
|
423
|
+
line: lineFor(sourceCode, node),
|
|
424
|
+
name,
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
if (isDeclarationWithId(node) && node.type === 'ClassDeclaration') {
|
|
431
|
+
const name = declarationName(node);
|
|
432
|
+
if (name !== undefined) {
|
|
433
|
+
declarations.push({
|
|
434
|
+
kind: 'class',
|
|
435
|
+
line: lineFor(sourceCode, node),
|
|
436
|
+
name,
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
if (
|
|
443
|
+
isDeclarationWithId(node) &&
|
|
444
|
+
(node.type === 'TSInterfaceDeclaration' ||
|
|
445
|
+
node.type === 'InterfaceDeclaration')
|
|
446
|
+
) {
|
|
447
|
+
const name = declarationName(node);
|
|
448
|
+
if (name !== undefined) {
|
|
449
|
+
declarations.push({
|
|
450
|
+
kind: 'interface',
|
|
451
|
+
line: lineFor(sourceCode, node),
|
|
452
|
+
name,
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
if (isDeclarationWithId(node) && node.type === 'TSTypeAliasDeclaration') {
|
|
459
|
+
const name = declarationName(node);
|
|
460
|
+
if (name !== undefined) {
|
|
461
|
+
declarations.push({
|
|
462
|
+
kind: 'type',
|
|
463
|
+
line: lineFor(sourceCode, node),
|
|
464
|
+
name,
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
if (isVariableDeclaration(node)) {
|
|
471
|
+
declarations.push(
|
|
472
|
+
...collectVariableDeclarations(node, variableKind(node), sourceCode)
|
|
473
|
+
);
|
|
474
|
+
}
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
return declarations.toSorted(
|
|
478
|
+
(a, b) => a.line - b.line || a.name.localeCompare(b.name)
|
|
479
|
+
);
|
|
480
|
+
};
|
|
481
|
+
|
|
482
|
+
const exportNamesFromDeclaration = (
|
|
483
|
+
declaration: AstNode
|
|
484
|
+
): readonly string[] => {
|
|
485
|
+
if (isVariableDeclaration(declaration)) {
|
|
486
|
+
return collectVariableDeclarations(declaration, 'variable', '').map(
|
|
487
|
+
(entry) => entry.name
|
|
488
|
+
);
|
|
489
|
+
}
|
|
490
|
+
const name = declarationName(declaration);
|
|
491
|
+
return name === undefined ? [] : [name];
|
|
492
|
+
};
|
|
493
|
+
|
|
494
|
+
const collectExports = (
|
|
495
|
+
ast: AstNode,
|
|
496
|
+
sourceCode: string
|
|
497
|
+
): readonly SourceExport[] => {
|
|
498
|
+
const exports: SourceExport[] = [];
|
|
499
|
+
for (const node of isProgram(ast) ? (ast.body ?? []) : []) {
|
|
500
|
+
if (!isExportDeclaration(node)) {
|
|
501
|
+
continue;
|
|
502
|
+
}
|
|
503
|
+
const exportSource = stringLiteralValue(node.source);
|
|
504
|
+
const names = uniqueSorted([
|
|
505
|
+
...(node.declaration === undefined
|
|
506
|
+
? []
|
|
507
|
+
: exportNamesFromDeclaration(node.declaration)),
|
|
508
|
+
...(node.specifiers ?? []).flatMap((specifier) => {
|
|
509
|
+
if (!isExportSpecifier(specifier)) {
|
|
510
|
+
return [];
|
|
511
|
+
}
|
|
512
|
+
const { exported, local } = specifier;
|
|
513
|
+
return [propertyName(exported) ?? propertyName(local)].filter(
|
|
514
|
+
(name): name is string => name !== undefined
|
|
515
|
+
);
|
|
516
|
+
}),
|
|
517
|
+
...(isExportDefaultDeclaration(node) ? ['default'] : []),
|
|
518
|
+
...(isExportAllDeclaration(node) ? ['*'] : []),
|
|
519
|
+
]);
|
|
520
|
+
exports.push({
|
|
521
|
+
line: lineFor(sourceCode, node),
|
|
522
|
+
names,
|
|
523
|
+
...(exportSource === undefined ? {} : { source: exportSource }),
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
return exports;
|
|
527
|
+
};
|
|
528
|
+
|
|
529
|
+
const collectApps = (
|
|
530
|
+
ast: AstNode,
|
|
531
|
+
sourceCode: string
|
|
532
|
+
): readonly SourceApp[] => {
|
|
533
|
+
const apps: SourceApp[] = [];
|
|
534
|
+
walkWithParents(ast, (node, context) => {
|
|
535
|
+
if (!isVariableDeclarator(node) || !isCallExpression(node.init)) {
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
const callee = staticCalleeName(node.init.callee);
|
|
539
|
+
if (callee !== 'topo' && callee !== 'createTrailsApp') {
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
const name = identifierName(node.id);
|
|
543
|
+
if (name === null) {
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
const lineSource = context.parent ?? node;
|
|
547
|
+
apps.push({ callee, line: lineFor(sourceCode, lineSource), name });
|
|
548
|
+
});
|
|
549
|
+
return apps;
|
|
550
|
+
};
|
|
551
|
+
|
|
552
|
+
const parseSourceOutline = (
|
|
553
|
+
filePath: string,
|
|
554
|
+
sourceCode: string
|
|
555
|
+
): ParsedSourceOutline | null => {
|
|
556
|
+
const parsed = parseWithDiagnostics(filePath, sourceCode);
|
|
557
|
+
if (parsed.ast === null) {
|
|
558
|
+
return null;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
return {
|
|
562
|
+
apps: collectApps(parsed.ast, sourceCode),
|
|
563
|
+
declarations: collectDeclarations(parsed.ast, sourceCode),
|
|
564
|
+
diagnostics: parsed.diagnostics.map((diagnostic) => {
|
|
565
|
+
const [label] = diagnostic.labels;
|
|
566
|
+
return {
|
|
567
|
+
code: 'source.parse',
|
|
568
|
+
...(label === undefined
|
|
569
|
+
? {}
|
|
570
|
+
: { line: offsetToLineColumn(sourceCode, label.start).line }),
|
|
571
|
+
message: diagnostic.message,
|
|
572
|
+
severity:
|
|
573
|
+
diagnostic.severity.toLowerCase() === 'error' ? 'error' : 'warn',
|
|
574
|
+
};
|
|
575
|
+
}),
|
|
576
|
+
exports: collectExports(parsed.ast, sourceCode),
|
|
577
|
+
imports: collectImports(parsed.ast),
|
|
578
|
+
lineCount: sourceCode.length === 0 ? 0 : sourceCode.split('\n').length,
|
|
579
|
+
trails: findTrailDefinitions(parsed.ast).map((definition) => ({
|
|
580
|
+
id: definition.id,
|
|
581
|
+
line: offsetToLineColumn(sourceCode, definition.start).line,
|
|
582
|
+
})),
|
|
583
|
+
};
|
|
584
|
+
};
|
|
585
|
+
|
|
586
|
+
const selectedView = (
|
|
587
|
+
input: OutlineInput
|
|
588
|
+
): Result<
|
|
589
|
+
{ readonly features: readonly OutlineFeature[]; readonly view: OutlineView },
|
|
590
|
+
ValidationError
|
|
591
|
+
> => {
|
|
592
|
+
const selected = [
|
|
593
|
+
input.all ? 'all' : undefined,
|
|
594
|
+
input.contracts ? 'contracts' : undefined,
|
|
595
|
+
input.review ? 'review' : undefined,
|
|
596
|
+
input.source ? 'source' : undefined,
|
|
597
|
+
input.surfaces ? 'surfaces' : undefined,
|
|
598
|
+
input.features === undefined ? undefined : 'custom',
|
|
599
|
+
].filter((value): value is OutlineView => value !== undefined);
|
|
600
|
+
|
|
601
|
+
if (selected.length > 1) {
|
|
602
|
+
return Result.err(
|
|
603
|
+
new ValidationError(
|
|
604
|
+
'Choose only one outline view: --review, --source, --contracts, --surfaces, --all, or --features.'
|
|
605
|
+
)
|
|
606
|
+
);
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
const view = selected[0] ?? 'default';
|
|
610
|
+
if (view !== 'custom') {
|
|
611
|
+
return Result.ok({ features: outlineViewFeatures[view], view });
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
const values = (input.features ?? '')
|
|
615
|
+
.split(',')
|
|
616
|
+
.map((value) => value.trim())
|
|
617
|
+
.filter((value) => value.length > 0);
|
|
618
|
+
const parsed = z.array(outlineFeatureSchema).safeParse(values);
|
|
619
|
+
if (!parsed.success || values.length === 0) {
|
|
620
|
+
return Result.err(
|
|
621
|
+
new ValidationError(
|
|
622
|
+
`--features must be a comma-separated list drawn from: ${outlineFeatureNames.join(', ')}.`
|
|
623
|
+
)
|
|
624
|
+
);
|
|
625
|
+
}
|
|
626
|
+
return Result.ok({
|
|
627
|
+
features: uniqueSorted(parsed.data) as OutlineFeature[],
|
|
628
|
+
view,
|
|
629
|
+
});
|
|
630
|
+
};
|
|
631
|
+
|
|
632
|
+
const hasFeature = (
|
|
633
|
+
features: readonly OutlineFeature[],
|
|
634
|
+
feature: OutlineFeature
|
|
635
|
+
): boolean => features.includes(feature);
|
|
636
|
+
|
|
637
|
+
const loadGraphSoft = async (
|
|
638
|
+
rootDir: string
|
|
639
|
+
): Promise<{
|
|
640
|
+
readonly diagnostics: readonly OutlineDiagnostic[];
|
|
641
|
+
readonly graph: TopoGraph | null;
|
|
642
|
+
readonly load: WayfinderArtifactLoad | null;
|
|
643
|
+
}> => {
|
|
644
|
+
try {
|
|
645
|
+
const load = await loadWayfinderArtifacts({ rootDir });
|
|
646
|
+
if (load.topoGraph === null) {
|
|
647
|
+
return {
|
|
648
|
+
diagnostics: [
|
|
649
|
+
{
|
|
650
|
+
code: 'graph.missing',
|
|
651
|
+
message:
|
|
652
|
+
'No saved Wayfinder TopoGraph artifact found. Source outline is still available. To add graph reconciliation, run `trails compile --module <app-module> --root-dir <workspace-root> --permit \'{"id":"operator","scopes":["topo:write"]}\'` with a permit authorized for topo:write.',
|
|
653
|
+
severity: 'warn',
|
|
654
|
+
},
|
|
655
|
+
],
|
|
656
|
+
graph: null,
|
|
657
|
+
load,
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
return {
|
|
661
|
+
diagnostics:
|
|
662
|
+
load.artifactStatus.status === 'fresh'
|
|
663
|
+
? []
|
|
664
|
+
: [
|
|
665
|
+
{
|
|
666
|
+
code: 'graph.artifact-status',
|
|
667
|
+
message: `Saved Wayfinder artifacts have ${load.artifactStatus.status} artifact status.`,
|
|
668
|
+
severity: 'warn',
|
|
669
|
+
},
|
|
670
|
+
],
|
|
671
|
+
graph: load.topoGraph,
|
|
672
|
+
load,
|
|
673
|
+
};
|
|
674
|
+
} catch (error) {
|
|
675
|
+
return {
|
|
676
|
+
diagnostics: [
|
|
677
|
+
{
|
|
678
|
+
code: 'graph.load',
|
|
679
|
+
message:
|
|
680
|
+
error instanceof Error
|
|
681
|
+
? error.message
|
|
682
|
+
: 'Unable to load Wayfinder artifacts.',
|
|
683
|
+
severity: 'warn',
|
|
684
|
+
},
|
|
685
|
+
],
|
|
686
|
+
graph: null,
|
|
687
|
+
load: null,
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
};
|
|
691
|
+
|
|
692
|
+
const trailGraphFacts = (
|
|
693
|
+
graph: TopoGraph | null,
|
|
694
|
+
id: string
|
|
695
|
+
):
|
|
696
|
+
| {
|
|
697
|
+
readonly exampleCount: number;
|
|
698
|
+
readonly intent: 'destroy' | 'read' | 'write';
|
|
699
|
+
readonly surfaces: readonly string[];
|
|
700
|
+
}
|
|
701
|
+
| undefined => {
|
|
702
|
+
const entry = graph?.entries.find(
|
|
703
|
+
(candidate): candidate is TopoGraphEntry =>
|
|
704
|
+
candidate.kind === 'trail' && candidate.id === id
|
|
705
|
+
);
|
|
706
|
+
if (entry === undefined) {
|
|
707
|
+
return undefined;
|
|
708
|
+
}
|
|
709
|
+
return {
|
|
710
|
+
exampleCount: entry.exampleCount,
|
|
711
|
+
intent: entry.intent ?? 'write',
|
|
712
|
+
surfaces: entry.surfaces,
|
|
713
|
+
};
|
|
714
|
+
};
|
|
715
|
+
|
|
716
|
+
const contractFacts = (
|
|
717
|
+
graph: TopoGraph | null,
|
|
718
|
+
id: string
|
|
719
|
+
): { readonly input: boolean; readonly output: boolean } | undefined => {
|
|
720
|
+
const entry = graph?.entries.find(
|
|
721
|
+
(candidate) => candidate.kind === 'trail' && candidate.id === id
|
|
722
|
+
);
|
|
723
|
+
return entry === undefined
|
|
724
|
+
? undefined
|
|
725
|
+
: { input: entry.input !== undefined, output: entry.output !== undefined };
|
|
726
|
+
};
|
|
727
|
+
|
|
728
|
+
const graphSourcePath = (rootDir: string): string =>
|
|
729
|
+
resolve(rootDir, 'trails.lock');
|
|
730
|
+
|
|
731
|
+
const graphOutline = (
|
|
732
|
+
rootDir: string,
|
|
733
|
+
graph: TopoGraph | null,
|
|
734
|
+
load: WayfinderArtifactLoad | null,
|
|
735
|
+
sourceTrails: readonly SourceTrail[]
|
|
736
|
+
): z.output<typeof graphOutlineSchema> => ({
|
|
737
|
+
matchedTrailIds: sourceTrails
|
|
738
|
+
.map((sourceTrail) => sourceTrail.id)
|
|
739
|
+
.filter((id) =>
|
|
740
|
+
graph?.entries.some((entry) => entry.kind === 'trail' && entry.id === id)
|
|
741
|
+
)
|
|
742
|
+
.toSorted(),
|
|
743
|
+
source:
|
|
744
|
+
graph === null || load === null
|
|
745
|
+
? null
|
|
746
|
+
: {
|
|
747
|
+
artifactStatus: load.artifactStatus.status,
|
|
748
|
+
kind: 'topoGraph',
|
|
749
|
+
path: graphSourcePath(rootDir),
|
|
750
|
+
},
|
|
751
|
+
});
|
|
752
|
+
|
|
753
|
+
const buildOutline = async (
|
|
754
|
+
input: OutlineInput,
|
|
755
|
+
cwd: string | undefined
|
|
756
|
+
): Promise<Result<OutlineOutput, TrailsError>> => {
|
|
757
|
+
const view = selectedView(input);
|
|
758
|
+
if (view.isErr()) {
|
|
759
|
+
return view;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
const rootDir = resolve(input.rootDir ?? cwd ?? process.cwd());
|
|
763
|
+
const safeFile = securePath(rootDir, input.file);
|
|
764
|
+
if (safeFile.isErr()) {
|
|
765
|
+
return safeFile;
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
let sourceCode: string;
|
|
769
|
+
try {
|
|
770
|
+
sourceCode = await readFile(safeFile.value, 'utf8');
|
|
771
|
+
} catch (error) {
|
|
772
|
+
const cause = error instanceof Error ? error : new Error(String(error));
|
|
773
|
+
return Result.err(
|
|
774
|
+
new NotFoundError(`Unable to read source file "${input.file}".`, {
|
|
775
|
+
cause,
|
|
776
|
+
context: { file: safeFile.value },
|
|
777
|
+
})
|
|
778
|
+
);
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
const parsed = parseSourceOutline(safeFile.value, sourceCode);
|
|
782
|
+
if (parsed === null) {
|
|
783
|
+
return Result.err(
|
|
784
|
+
new DerivationError(`Unable to parse source file "${input.file}".`, {
|
|
785
|
+
context: { file: safeFile.value },
|
|
786
|
+
})
|
|
787
|
+
);
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
const graphLoad = await loadGraphSoft(rootDir);
|
|
791
|
+
const diagnostics = [...parsed.diagnostics, ...graphLoad.diagnostics];
|
|
792
|
+
const { features } = view.value;
|
|
793
|
+
const omitted = outlineFeatureNames.filter(
|
|
794
|
+
(feature) => !features.includes(feature)
|
|
795
|
+
);
|
|
796
|
+
const relativeFile = relative(rootDir, safeFile.value) || input.file;
|
|
797
|
+
const trails = parsed.trails.map((sourceTrail) => ({
|
|
798
|
+
...(hasFeature(features, 'contracts')
|
|
799
|
+
? { contracts: contractFacts(graphLoad.graph, sourceTrail.id) }
|
|
800
|
+
: {}),
|
|
801
|
+
id: sourceTrail.id,
|
|
802
|
+
line: sourceTrail.line,
|
|
803
|
+
...(hasFeature(features, 'graph')
|
|
804
|
+
? { graph: trailGraphFacts(graphLoad.graph, sourceTrail.id) }
|
|
805
|
+
: {}),
|
|
806
|
+
}));
|
|
807
|
+
const surfaces = uniqueSorted(
|
|
808
|
+
parsed.trails.flatMap(
|
|
809
|
+
(sourceTrail) =>
|
|
810
|
+
trailGraphFacts(graphLoad.graph, sourceTrail.id)?.surfaces ?? []
|
|
811
|
+
)
|
|
812
|
+
);
|
|
813
|
+
const graphMatches = parsed.trails.filter((sourceTrail) =>
|
|
814
|
+
graphLoad.graph?.entries.some(
|
|
815
|
+
(entry) => entry.kind === 'trail' && entry.id === sourceTrail.id
|
|
816
|
+
)
|
|
817
|
+
).length;
|
|
818
|
+
|
|
819
|
+
return Result.ok({
|
|
820
|
+
...(hasFeature(features, 'apps') ? { apps: parsed.apps } : {}),
|
|
821
|
+
counts: {
|
|
822
|
+
apps: parsed.apps.length,
|
|
823
|
+
declarations: parsed.declarations.length,
|
|
824
|
+
diagnostics: diagnostics.length,
|
|
825
|
+
...(hasFeature(features, 'graph') ? { graphMatches } : {}),
|
|
826
|
+
trails: parsed.trails.length,
|
|
827
|
+
},
|
|
828
|
+
...(hasFeature(features, 'diagnostics') ? { diagnostics } : {}),
|
|
829
|
+
features: {
|
|
830
|
+
included: features,
|
|
831
|
+
omitted,
|
|
832
|
+
view: view.value.view,
|
|
833
|
+
},
|
|
834
|
+
file: relativeFile,
|
|
835
|
+
...(hasFeature(features, 'graph')
|
|
836
|
+
? {
|
|
837
|
+
graph: graphOutline(
|
|
838
|
+
rootDir,
|
|
839
|
+
graphLoad.graph,
|
|
840
|
+
graphLoad.load,
|
|
841
|
+
parsed.trails
|
|
842
|
+
),
|
|
843
|
+
}
|
|
844
|
+
: {}),
|
|
845
|
+
rootDir,
|
|
846
|
+
...(hasFeature(features, 'source')
|
|
847
|
+
? {
|
|
848
|
+
source: {
|
|
849
|
+
declarations: parsed.declarations,
|
|
850
|
+
exports: parsed.exports,
|
|
851
|
+
imports: parsed.imports,
|
|
852
|
+
lineCount: parsed.lineCount,
|
|
853
|
+
},
|
|
854
|
+
}
|
|
855
|
+
: {}),
|
|
856
|
+
...(hasFeature(features, 'surfaces') ? { surfaces } : {}),
|
|
857
|
+
...(hasFeature(features, 'trails') ? { trails } : {}),
|
|
858
|
+
});
|
|
859
|
+
};
|
|
860
|
+
|
|
861
|
+
export const wayfindOutlineTrail = trail('wayfind.outline', {
|
|
862
|
+
args: ['file'],
|
|
863
|
+
description:
|
|
864
|
+
'Outline one source file and connect source structure to saved Trails graph facts',
|
|
865
|
+
examples: [
|
|
866
|
+
{
|
|
867
|
+
input: { file: 'apps/trails/src/app.ts', rootDir: '.' },
|
|
868
|
+
name: 'Outline a Trails source file',
|
|
869
|
+
},
|
|
870
|
+
],
|
|
871
|
+
implementation: async (input, ctx) => buildOutline(input, ctx.cwd),
|
|
872
|
+
input: outlineInputSchema,
|
|
873
|
+
intent: 'read',
|
|
874
|
+
output: outlineOutputSchema,
|
|
875
|
+
visibility: 'internal',
|
|
876
|
+
});
|