@ontrails/trails 0.2.0

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 (121) hide show
  1. package/CHANGELOG.md +1906 -0
  2. package/README.md +48 -0
  3. package/bin/trails.ts +3 -0
  4. package/package.json +57 -0
  5. package/src/app.ts +167 -0
  6. package/src/clack.ts +111 -0
  7. package/src/cli.ts +308 -0
  8. package/src/completions.ts +431 -0
  9. package/src/lifecycle-source-io.ts +33 -0
  10. package/src/load-app-mirror.ts +202 -0
  11. package/src/local-state-io.ts +129 -0
  12. package/src/mcp-app.ts +42 -0
  13. package/src/mcp-options.ts +92 -0
  14. package/src/mcp.ts +8 -0
  15. package/src/project-writes.ts +377 -0
  16. package/src/regrade/audit.ts +571 -0
  17. package/src/regrade/config.ts +152 -0
  18. package/src/regrade/history.ts +636 -0
  19. package/src/regrade/lifecycle.ts +76 -0
  20. package/src/regrade/live-api-preserve.ts +123 -0
  21. package/src/regrade/plan-artifact.ts +515 -0
  22. package/src/regrade/plan-derivation.ts +301 -0
  23. package/src/regrade/prepared-run.ts +259 -0
  24. package/src/regrade/receipt-history.ts +446 -0
  25. package/src/regrade/source-transaction.ts +185 -0
  26. package/src/release/bindings.ts +58 -0
  27. package/src/release/changeset-packages.ts +99 -0
  28. package/src/release/check.ts +1191 -0
  29. package/src/release/cli-bundle.ts +575 -0
  30. package/src/release/config.ts +73 -0
  31. package/src/release/contract-facts.ts +425 -0
  32. package/src/release/homebrew.ts +221 -0
  33. package/src/release/index.ts +180 -0
  34. package/src/release/lock-roundtrip-smoke.ts +255 -0
  35. package/src/release/lock-roundtrip-workspace.ts +107 -0
  36. package/src/release/native-bun-publish.ts +964 -0
  37. package/src/release/native-bun-registry.ts +848 -0
  38. package/src/release/notes-cli.ts +171 -0
  39. package/src/release/notes.ts +390 -0
  40. package/src/release/pack-coherence.ts +455 -0
  41. package/src/release/package-route-facts.ts +146 -0
  42. package/src/release/packed-artifacts-smoke.ts +236 -0
  43. package/src/release/policy.ts +1780 -0
  44. package/src/release/semver.ts +104 -0
  45. package/src/release/smoke.ts +56 -0
  46. package/src/release/stable-version-release.ts +80 -0
  47. package/src/release/wayfinder-dogfood-smoke.ts +762 -0
  48. package/src/release/zero-line-transition.ts +68 -0
  49. package/src/retired-topo-command.ts +36 -0
  50. package/src/run-adapter-check.ts +76 -0
  51. package/src/run-argv.ts +133 -0
  52. package/src/run-collision.ts +126 -0
  53. package/src/run-completions-install.ts +179 -0
  54. package/src/run-example.ts +149 -0
  55. package/src/run-examples.ts +148 -0
  56. package/src/run-quiet.ts +75 -0
  57. package/src/run-regrade-progress.ts +47 -0
  58. package/src/run-release-check.ts +74 -0
  59. package/src/run-schema.ts +74 -0
  60. package/src/run-trace.ts +273 -0
  61. package/src/run-warden.ts +39 -0
  62. package/src/run-watch-project.ts +52 -0
  63. package/src/run-watch.ts +381 -0
  64. package/src/run-wayfind-outline.ts +170 -0
  65. package/src/scaffold-version-sync.ts +183 -0
  66. package/src/scaffold-versions.generated.ts +12 -0
  67. package/src/trails/adapter-check.ts +244 -0
  68. package/src/trails/add-surface.ts +816 -0
  69. package/src/trails/add-trail.ts +141 -0
  70. package/src/trails/add-verify.ts +252 -0
  71. package/src/trails/compile.ts +118 -0
  72. package/src/trails/completions-complete.ts +236 -0
  73. package/src/trails/completions.ts +47 -0
  74. package/src/trails/config-explain.ts +43 -0
  75. package/src/trails/create-adapter.ts +785 -0
  76. package/src/trails/create-scaffold.ts +1215 -0
  77. package/src/trails/create-versions.ts +62 -0
  78. package/src/trails/create.ts +652 -0
  79. package/src/trails/deprecate.ts +59 -0
  80. package/src/trails/dev-clean.ts +80 -0
  81. package/src/trails/dev-reset.ts +48 -0
  82. package/src/trails/dev-stats.ts +71 -0
  83. package/src/trails/dev-support.ts +360 -0
  84. package/src/trails/doctor.ts +77 -0
  85. package/src/trails/draft-promote.ts +949 -0
  86. package/src/trails/guide.ts +106 -0
  87. package/src/trails/load-app.ts +1145 -0
  88. package/src/trails/operator-context.ts +66 -0
  89. package/src/trails/project-context-output.ts +304 -0
  90. package/src/trails/project-context.ts +613 -0
  91. package/src/trails/project.ts +65 -0
  92. package/src/trails/regrade.ts +4951 -0
  93. package/src/trails/release-check.ts +113 -0
  94. package/src/trails/release-smoke.ts +49 -0
  95. package/src/trails/revise.ts +53 -0
  96. package/src/trails/root-dir.ts +21 -0
  97. package/src/trails/run-example.ts +592 -0
  98. package/src/trails/run-examples.ts +149 -0
  99. package/src/trails/run.ts +496 -0
  100. package/src/trails/scaffold-json.ts +60 -0
  101. package/src/trails/scaffold-topo-identity.ts +479 -0
  102. package/src/trails/survey.ts +990 -0
  103. package/src/trails/topo-activation.ts +14 -0
  104. package/src/trails/topo-constants.ts +2 -0
  105. package/src/trails/topo-history.ts +47 -0
  106. package/src/trails/topo-output-schemas.ts +259 -0
  107. package/src/trails/topo-pin.ts +38 -0
  108. package/src/trails/topo-read-support.ts +368 -0
  109. package/src/trails/topo-reports.ts +809 -0
  110. package/src/trails/topo-store-support.ts +323 -0
  111. package/src/trails/topo-support.ts +247 -0
  112. package/src/trails/topo-unpin.ts +61 -0
  113. package/src/trails/topo.ts +92 -0
  114. package/src/trails/validate.ts +348 -0
  115. package/src/trails/version-lifecycle-support.ts +936 -0
  116. package/src/trails/warden-guide.ts +134 -0
  117. package/src/trails/warden.ts +598 -0
  118. package/src/trails/wayfind-diff.ts +716 -0
  119. package/src/trails/wayfind-outline.ts +876 -0
  120. package/src/trails/wayfind.ts +1319 -0
  121. package/src/versions.ts +31 -0
@@ -0,0 +1,141 @@
1
+ /**
2
+ * `add.trail` trail -- Scaffold a new trail file with tests.
3
+ */
4
+
5
+ import { resolve } from 'node:path';
6
+
7
+ import { Result, trail } from '@ontrails/core';
8
+ import { z } from 'zod';
9
+
10
+ import {
11
+ trailIdToExportName,
12
+ trailIdToModuleName,
13
+ TRAIL_ID_MESSAGE,
14
+ TRAIL_ID_PATTERN,
15
+ validateTrailId,
16
+ writeProjectFile,
17
+ } from '../project-writes.js';
18
+
19
+ // ---------------------------------------------------------------------------
20
+ // Helpers
21
+ // ---------------------------------------------------------------------------
22
+
23
+ const literal = (value: string): string => JSON.stringify(value);
24
+
25
+ const deriveExampleMessage = (id: string): string => `${id} completed`;
26
+
27
+ const generateTrailFile = (
28
+ id: string,
29
+ description: string,
30
+ exampleName: string,
31
+ intent: 'read' | 'write' | 'destroy'
32
+ ): string => {
33
+ const intentLine =
34
+ intent === 'write' ? '' : `\n intent: ${literal(intent)},`;
35
+ const exampleMessage = deriveExampleMessage(id);
36
+ const trailName = trailIdToExportName(id);
37
+
38
+ return `import { Result, trail } from '@ontrails/core';
39
+ import { z } from 'zod';
40
+
41
+ export const ${trailName} = trail(${literal(id)}, {
42
+ implementation: async () => {
43
+ return Result.ok({ message: ${literal(exampleMessage)} });
44
+ },
45
+ description: ${literal(description)},
46
+ examples: [
47
+ {
48
+ expected: { message: ${literal(exampleMessage)} },
49
+ input: {},
50
+ name: ${literal(exampleName)},
51
+ },
52
+ ],
53
+ input: z.object({}),${intentLine}
54
+ output: z.object({ message: z.string() }),
55
+ });
56
+ `;
57
+ };
58
+
59
+ const generateTestFile = (id: string, exampleName: string): string => {
60
+ const moduleName = trailIdToModuleName(id);
61
+ const trailName = trailIdToExportName(id);
62
+ const exampleMessage = deriveExampleMessage(id);
63
+ return `import { testTrail } from '@ontrails/testing';
64
+ import { ${trailName} } from '../src/trails/${moduleName}.js';
65
+
66
+ testTrail(${trailName}, [
67
+ {
68
+ description: ${literal(exampleName)},
69
+ expectValue: { message: ${literal(exampleMessage)} },
70
+ input: {},
71
+ },
72
+ ]);
73
+ `;
74
+ };
75
+
76
+ // ---------------------------------------------------------------------------
77
+ // Trail definition
78
+ // ---------------------------------------------------------------------------
79
+
80
+ export const addTrail = trail('add.trail', {
81
+ args: ['id'],
82
+ description: 'Scaffold a new trail with tests and examples',
83
+ implementation: async (input, ctx) => {
84
+ const { id } = input;
85
+ const validated = validateTrailId(id);
86
+ if (validated.isErr()) {
87
+ return validated;
88
+ }
89
+
90
+ const moduleName = trailIdToModuleName(validated.value);
91
+ const cwd = resolve(ctx.cwd ?? '.');
92
+
93
+ const files = new Map<string, string>([
94
+ [
95
+ `src/trails/${moduleName}.ts`,
96
+ generateTrailFile(
97
+ id,
98
+ input.description,
99
+ input.exampleName,
100
+ input.intent
101
+ ),
102
+ ],
103
+ [
104
+ `__tests__/${moduleName}.test.ts`,
105
+ generateTestFile(id, input.exampleName),
106
+ ],
107
+ ]);
108
+
109
+ for (const [relativePath, content] of files) {
110
+ const written = await writeProjectFile(cwd, relativePath, content);
111
+ if (written.isErr()) {
112
+ return written;
113
+ }
114
+ }
115
+
116
+ return Result.ok({ created: [...files.keys()] });
117
+ },
118
+ input: z.object({
119
+ description: z
120
+ .string()
121
+ .min(1, 'Trail description is required')
122
+ .describe('Trail description'),
123
+ exampleName: z
124
+ .string()
125
+ .min(1, 'Starter example name is required')
126
+ .describe('Starter example name'),
127
+ id: z
128
+ .string()
129
+ .min(1, 'Trail ID is required')
130
+ .regex(TRAIL_ID_PATTERN, TRAIL_ID_MESSAGE)
131
+ .describe('Trail ID (e.g., entity.update)'),
132
+ intent: z
133
+ .enum(['read', 'write', 'destroy'])
134
+ .default('write')
135
+ .describe('Trail intent'),
136
+ }),
137
+ output: z.object({
138
+ created: z.array(z.string()),
139
+ }),
140
+ permit: { scopes: ['project:write'] },
141
+ });
@@ -0,0 +1,252 @@
1
+ /**
2
+ * `add.verify` trail -- Add testing + warden setup to a project.
3
+ */
4
+
5
+ import { existsSync, realpathSync } from 'node:fs';
6
+ import { basename, dirname, join, resolve } from 'node:path';
7
+
8
+ import { findTrailsConfigPaths } from '@ontrails/config';
9
+ import { InternalError, Result, trail } from '@ontrails/core';
10
+ import { z } from 'zod';
11
+
12
+ import {
13
+ PROJECT_NAME_MESSAGE,
14
+ PROJECT_NAME_PATTERN,
15
+ projectPathExists,
16
+ resolveProjectDir,
17
+ resolveProjectPath,
18
+ writeProjectFile,
19
+ } from '../project-writes.js';
20
+ import {
21
+ ontrailsPackageRange,
22
+ scaffoldDependencyVersions,
23
+ } from '../versions.js';
24
+ import { resolveOperatorProjectContext } from './project-context.js';
25
+ import { stringifyScaffoldPackageJson } from './scaffold-json.js';
26
+
27
+ // ---------------------------------------------------------------------------
28
+ // Content generators
29
+ // ---------------------------------------------------------------------------
30
+
31
+ const generateTestFile = (): string =>
32
+ `import { testAllEstablished } from '@ontrails/testing/established';
33
+ import { app } from '../src/app.js';
34
+
35
+ const permitScopes = [
36
+ ...new Set(
37
+ app
38
+ .list()
39
+ .flatMap((trail) =>
40
+ trail.permit && trail.permit !== 'public' ? trail.permit.scopes : []
41
+ )
42
+ ),
43
+ ];
44
+
45
+ testAllEstablished(app, {
46
+ ctx: {
47
+ permit: { id: 'test-permit', scopes: permitScopes },
48
+ },
49
+ });
50
+ `;
51
+
52
+ const generateLefthookYml = (): string =>
53
+ `pre-push:
54
+ commands:
55
+ warden:
56
+ run: bunx trails warden
57
+ `;
58
+
59
+ /** Add testing and warden devDependencies to package.json when present. */
60
+ const patchVerifyDeps = (pkg: Record<string, unknown>): void => {
61
+ const devDeps = (pkg['devDependencies'] ?? {}) as Record<string, string>;
62
+ devDeps['@ontrails/testing'] = ontrailsPackageRange;
63
+ devDeps['@ontrails/warden'] = ontrailsPackageRange;
64
+ devDeps['lefthook'] = scaffoldDependencyVersions.lefthook;
65
+ pkg['devDependencies'] = Object.fromEntries(
66
+ Object.entries(devDeps).toSorted(([a], [b]) => a.localeCompare(b))
67
+ );
68
+ };
69
+
70
+ /** Update package.json in the target project with verify dependencies. */
71
+ const updatePackageJsonForVerify = async (
72
+ projectDir: string
73
+ ): Promise<Result<void, Error>> => {
74
+ const pkgPathResult = resolveProjectPath(projectDir, 'package.json');
75
+ if (pkgPathResult.isErr()) {
76
+ return pkgPathResult;
77
+ }
78
+
79
+ const pkgPath = pkgPathResult.value;
80
+ if (!existsSync(pkgPath)) {
81
+ return Result.ok();
82
+ }
83
+ const pkg = (await Bun.file(pkgPath).json()) as Record<string, unknown>;
84
+ patchVerifyDeps(pkg);
85
+ const written = await writeProjectFile(
86
+ projectDir,
87
+ 'package.json',
88
+ stringifyScaffoldPackageJson(pkg)
89
+ );
90
+ return written.isErr() ? Result.err(written.error) : Result.ok();
91
+ };
92
+
93
+ export const resolveCanonicalHookDir = (
94
+ projectDir: string,
95
+ appRoot: string,
96
+ workspaceRoot: string
97
+ ): Result<string, Error> => {
98
+ try {
99
+ if (realpathSync(appRoot) !== realpathSync(projectDir)) {
100
+ return Result.ok(projectDir);
101
+ }
102
+ return Result.ok(realpathSync(workspaceRoot));
103
+ } catch (error) {
104
+ return Result.err(
105
+ new InternalError('Failed to resolve add.verify hook ownership.', {
106
+ cause: error instanceof Error ? error : new Error(String(error)),
107
+ context: { appRoot, projectDir, workspaceRoot },
108
+ })
109
+ );
110
+ }
111
+ };
112
+
113
+ const canonicalizeProjectedProjectDir = (projectDir: string): string => {
114
+ const missingSegments: string[] = [];
115
+ let existingAncestor = resolve(projectDir);
116
+ while (!existsSync(existingAncestor)) {
117
+ const parent = dirname(existingAncestor);
118
+ if (parent === existingAncestor) {
119
+ return resolve(projectDir);
120
+ }
121
+ missingSegments.unshift(basename(existingAncestor));
122
+ existingAncestor = parent;
123
+ }
124
+ return join(realpathSync(existingAncestor), ...missingSegments);
125
+ };
126
+
127
+ const hasTrailsConfigAncestor = (projectDir: string): boolean => {
128
+ let current = resolve(projectDir);
129
+ while (true) {
130
+ if (findTrailsConfigPaths(current).length > 0) {
131
+ return true;
132
+ }
133
+ const parent = dirname(current);
134
+ if (parent === current) {
135
+ return false;
136
+ }
137
+ current = parent;
138
+ }
139
+ };
140
+
141
+ /** Place the hook at a Config-owned workspace root, never a package-only root. */
142
+ export const resolveVerifyHookDir = async (
143
+ projectDir: string
144
+ ): Promise<Result<string, Error>> => {
145
+ let contextDir: string;
146
+ try {
147
+ contextDir = canonicalizeProjectedProjectDir(projectDir);
148
+ } catch (error) {
149
+ return Result.err(
150
+ new InternalError('Failed to resolve add.verify project context.', {
151
+ cause: error instanceof Error ? error : new Error(String(error)),
152
+ context: { projectDir },
153
+ })
154
+ );
155
+ }
156
+ if (!hasTrailsConfigAncestor(contextDir)) {
157
+ return Result.ok(projectDir);
158
+ }
159
+ const context = await resolveOperatorProjectContext({}, { cwd: contextDir });
160
+ if (context.isErr()) {
161
+ return context;
162
+ }
163
+ if (context.value.selectedExtent !== 'configured-app') {
164
+ return Result.ok(projectDir);
165
+ }
166
+
167
+ return resolveCanonicalHookDir(
168
+ projectDir,
169
+ context.value.app.rootDir,
170
+ context.value.projectRoot
171
+ );
172
+ };
173
+
174
+ // ---------------------------------------------------------------------------
175
+ // Trail definition
176
+ // ---------------------------------------------------------------------------
177
+
178
+ export const addVerify = trail('add.verify', {
179
+ description: 'Add testing and warden verification',
180
+ implementation: async (input) => {
181
+ const projectDirResult = resolveProjectDir(input.dir ?? '.', input.name);
182
+ if (projectDirResult.isErr()) {
183
+ return projectDirResult;
184
+ }
185
+
186
+ const projectDir = projectDirResult.value;
187
+ const hookDirResult = await resolveVerifyHookDir(projectDir);
188
+ if (hookDirResult.isErr()) {
189
+ return hookDirResult;
190
+ }
191
+ const hookDir = hookDirResult.value;
192
+ const files: string[] = [];
193
+
194
+ const writeFile = async (
195
+ rootDir: string,
196
+ relativePath: string,
197
+ content: string
198
+ ): Promise<Result<void, Error>> => {
199
+ const exists = projectPathExists(rootDir, relativePath);
200
+ if (exists.isErr()) {
201
+ return exists;
202
+ }
203
+ if (exists.value) {
204
+ return Result.ok();
205
+ }
206
+
207
+ const written = await writeProjectFile(rootDir, relativePath, content);
208
+ if (written.isErr()) {
209
+ return written;
210
+ }
211
+ files.push(written.value);
212
+ return Result.ok();
213
+ };
214
+
215
+ const testFile = await writeFile(
216
+ projectDir,
217
+ '__tests__/examples.test.ts',
218
+ generateTestFile()
219
+ );
220
+ if (testFile.isErr()) {
221
+ return testFile;
222
+ }
223
+
224
+ const lefthookFile = await writeFile(
225
+ hookDir,
226
+ 'lefthook.yml',
227
+ generateLefthookYml()
228
+ );
229
+ if (lefthookFile.isErr()) {
230
+ return lefthookFile;
231
+ }
232
+
233
+ const packageResult = await updatePackageJsonForVerify(projectDir);
234
+ if (packageResult.isErr()) {
235
+ return packageResult;
236
+ }
237
+
238
+ return Result.ok({ created: files });
239
+ },
240
+ input: z.object({
241
+ dir: z.string().optional().describe('Parent directory'),
242
+ name: z
243
+ .string()
244
+ .regex(PROJECT_NAME_PATTERN, PROJECT_NAME_MESSAGE)
245
+ .describe('Project name'),
246
+ }),
247
+ output: z.object({
248
+ created: z.array(z.string()),
249
+ }),
250
+ permit: { scopes: ['project:write'] },
251
+ visibility: 'internal',
252
+ });
@@ -0,0 +1,118 @@
1
+ import { Result, trail } from '@ontrails/core';
2
+ import type { Topo } from '@ontrails/core';
3
+ import type { TopoGraphOverlayRegistration } from '@ontrails/topography';
4
+ import { z } from 'zod';
5
+
6
+ import { withFreshAppLease } from './operator-context.js';
7
+ import {
8
+ assertConfiguredAppBinding,
9
+ assertObservableProjectApps,
10
+ compileNeedsAppError,
11
+ resolveOperatorAppModuleContext,
12
+ resolveOperatorProjectContext,
13
+ } from './project-context.js';
14
+ import {
15
+ appProjectSelection,
16
+ appProjectSelectionSchema,
17
+ } from './project-context-output.js';
18
+ import { exportCurrentTopo } from './topo-store-support.js';
19
+ import type { TopoExportReport } from './topo-support.js';
20
+ import {
21
+ createCurrentAppExampleInput,
22
+ topoSnapshotOutput,
23
+ } from './topo-support.js';
24
+
25
+ export const compileCurrentTopo = async (
26
+ app: Topo,
27
+ options?: {
28
+ readonly force?: boolean | undefined;
29
+ readonly rootDir?: string;
30
+ readonly overlays?: readonly TopoGraphOverlayRegistration[] | undefined;
31
+ }
32
+ ): Promise<Result<TopoExportReport, Error>> => exportCurrentTopo(app, options);
33
+
34
+ const compileTrailInputSchema = z.object({
35
+ app: z.string().optional().describe('Configured workspace app ID'),
36
+ force: z
37
+ .boolean()
38
+ .optional()
39
+ .describe('Record graph-only force events for breaking changes'),
40
+ module: z.string().optional().describe('Path to the app module'),
41
+ rootDir: z.string().optional().describe('Workspace root directory'),
42
+ });
43
+
44
+ type CompileTrailInput = z.output<typeof compileTrailInputSchema>;
45
+
46
+ const compileTrailOutputSchema = z.object({
47
+ hash: z.string(),
48
+ lockPath: z.string(),
49
+ project: appProjectSelectionSchema,
50
+ snapshot: topoSnapshotOutput,
51
+ });
52
+
53
+ type CompileTrailOutput = z.output<typeof compileTrailOutputSchema>;
54
+
55
+ const compileSelectedProject = async (
56
+ input: CompileTrailInput,
57
+ cwd: string | undefined
58
+ ): Promise<Result<CompileTrailOutput, Error>> => {
59
+ const contextResult = await resolveOperatorProjectContext(input, { cwd });
60
+ if (contextResult.isErr()) {
61
+ return contextResult;
62
+ }
63
+ const context = contextResult.value;
64
+ if (context.selectedExtent === 'workspace') {
65
+ return Result.err(compileNeedsAppError(context));
66
+ }
67
+ const appContext = resolveOperatorAppModuleContext(context);
68
+ if (appContext.isErr()) {
69
+ return appContext;
70
+ }
71
+ const selectedContext = appContext.value;
72
+ const observable = await assertObservableProjectApps(context);
73
+ if (observable.isErr()) {
74
+ return observable;
75
+ }
76
+ return withFreshAppLease(
77
+ selectedContext.app.modulePath,
78
+ selectedContext.app.rootDir,
79
+ async (lease) => {
80
+ const binding = assertConfiguredAppBinding(
81
+ selectedContext,
82
+ lease.app.name
83
+ );
84
+ if (binding.isErr()) {
85
+ return binding;
86
+ }
87
+ const compiled = await compileCurrentTopo(lease.app, {
88
+ force: input.force,
89
+ overlays: lease.overlays,
90
+ rootDir: selectedContext.app.rootDir,
91
+ });
92
+ return compiled.isErr()
93
+ ? compiled
94
+ : Result.ok({
95
+ ...compiled.value,
96
+ project: appProjectSelection(selectedContext, lease.app.name),
97
+ });
98
+ }
99
+ );
100
+ };
101
+
102
+ export const compileTrail = trail('compile', {
103
+ description: 'Compile the current topo to trails.lock',
104
+ examples: [
105
+ {
106
+ input: createCurrentAppExampleInput({
107
+ selection: 'configured-project',
108
+ }),
109
+ name: 'Compile the current topo to trails.lock',
110
+ },
111
+ ],
112
+ implementation: async (input: CompileTrailInput, ctx) =>
113
+ compileSelectedProject(input, ctx.cwd),
114
+ input: compileTrailInputSchema,
115
+ intent: 'write',
116
+ output: compileTrailOutputSchema,
117
+ permit: { scopes: ['topo:write'] },
118
+ });