@ontrails/trails 1.0.0-beta.2 → 1.0.0-beta.22

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/CHANGELOG.md +647 -0
  2. package/README.md +26 -0
  3. package/package.json +28 -7
  4. package/src/app.ts +86 -2
  5. package/src/clack.ts +22 -0
  6. package/src/cli.ts +330 -11
  7. package/src/completions.ts +240 -0
  8. package/src/lifecycle-source-io.ts +33 -0
  9. package/src/load-app-mirror.ts +202 -0
  10. package/src/local-state-io.ts +153 -0
  11. package/src/mcp-app.ts +30 -0
  12. package/src/mcp-options.ts +77 -0
  13. package/src/mcp.ts +8 -0
  14. package/src/project-writes.ts +377 -0
  15. package/src/release/bindings.ts +39 -0
  16. package/src/release/check.ts +818 -0
  17. package/src/release/config.ts +63 -0
  18. package/src/release/contract-facts.ts +425 -0
  19. package/src/release/index.ts +85 -0
  20. package/src/release/native-bun-publish.ts +651 -0
  21. package/src/release/native-bun-registry.ts +350 -0
  22. package/src/release/packed-artifacts-smoke.ts +236 -0
  23. package/src/release/smoke.ts +46 -0
  24. package/src/release/wayfinder-dogfood-smoke.ts +226 -0
  25. package/src/retired-topo-command.ts +36 -0
  26. package/src/run-adapter-check.ts +76 -0
  27. package/src/run-collision.ts +126 -0
  28. package/src/run-completions-install.ts +179 -0
  29. package/src/run-example.ts +149 -0
  30. package/src/run-examples.ts +148 -0
  31. package/src/run-quiet.ts +75 -0
  32. package/src/run-release-check.ts +74 -0
  33. package/src/run-trace.ts +273 -0
  34. package/src/run-warden.ts +39 -0
  35. package/src/run-watch.ts +432 -0
  36. package/src/scaffold-version-sync.ts +183 -0
  37. package/src/scaffold-versions.generated.ts +12 -0
  38. package/src/trails/adapter-check.ts +244 -0
  39. package/src/trails/add-surface.ts +94 -40
  40. package/src/trails/add-trail.ts +79 -41
  41. package/src/trails/add-verify.ts +95 -25
  42. package/src/trails/compile.ts +67 -0
  43. package/src/trails/completions-complete.ts +165 -0
  44. package/src/trails/completions.ts +47 -0
  45. package/src/trails/create-adapter.ts +1084 -0
  46. package/src/trails/create-scaffold.ts +399 -104
  47. package/src/trails/create-versions.ts +62 -0
  48. package/src/trails/create.ts +185 -71
  49. package/src/trails/deprecate.ts +59 -0
  50. package/src/trails/dev-clean.ts +82 -0
  51. package/src/trails/dev-reset.ts +50 -0
  52. package/src/trails/dev-stats.ts +72 -0
  53. package/src/trails/dev-support.ts +340 -0
  54. package/src/trails/doctor.ts +56 -0
  55. package/src/trails/draft-promote.ts +949 -0
  56. package/src/trails/guide.ts +74 -68
  57. package/src/trails/load-app.ts +1143 -15
  58. package/src/trails/project.ts +17 -3
  59. package/src/trails/release-check.ts +104 -0
  60. package/src/trails/release-smoke.ts +48 -0
  61. package/src/trails/revise.ts +53 -0
  62. package/src/trails/root-dir.ts +21 -0
  63. package/src/trails/run-example.ts +491 -0
  64. package/src/trails/run-examples.ts +145 -0
  65. package/src/trails/run.ts +410 -0
  66. package/src/trails/scaffold-json.ts +58 -0
  67. package/src/trails/survey.ts +881 -226
  68. package/src/trails/topo-activation.ts +385 -0
  69. package/src/trails/topo-constants.ts +2 -0
  70. package/src/trails/topo-history.ts +47 -0
  71. package/src/trails/topo-output-schemas.ts +248 -0
  72. package/src/trails/topo-pin.ts +52 -0
  73. package/src/trails/topo-read-support.ts +313 -0
  74. package/src/trails/topo-reports.ts +807 -0
  75. package/src/trails/topo-store-support.ts +174 -0
  76. package/src/trails/topo-support.ts +220 -0
  77. package/src/trails/topo-unpin.ts +61 -0
  78. package/src/trails/topo.ts +106 -0
  79. package/src/trails/validate.ts +38 -0
  80. package/src/trails/version-lifecycle-support.ts +945 -0
  81. package/src/trails/warden-guide.ts +129 -0
  82. package/src/trails/warden.ts +165 -58
  83. package/src/versions.ts +31 -0
  84. package/.turbo/turbo-build.log +0 -1
  85. package/.turbo/turbo-lint.log +0 -3
  86. package/.turbo/turbo-typecheck.log +0 -1
  87. package/__tests__/examples.test.ts +0 -6
  88. package/dist/bin/trails.d.ts +0 -3
  89. package/dist/bin/trails.d.ts.map +0 -1
  90. package/dist/bin/trails.js +0 -4
  91. package/dist/bin/trails.js.map +0 -1
  92. package/dist/src/app.d.ts +0 -2
  93. package/dist/src/app.d.ts.map +0 -1
  94. package/dist/src/app.js +0 -11
  95. package/dist/src/app.js.map +0 -1
  96. package/dist/src/clack.d.ts +0 -9
  97. package/dist/src/clack.d.ts.map +0 -1
  98. package/dist/src/clack.js +0 -62
  99. package/dist/src/clack.js.map +0 -1
  100. package/dist/src/cli.d.ts +0 -2
  101. package/dist/src/cli.d.ts.map +0 -1
  102. package/dist/src/cli.js +0 -13
  103. package/dist/src/cli.js.map +0 -1
  104. package/dist/src/trails/add-surface.d.ts +0 -13
  105. package/dist/src/trails/add-surface.d.ts.map +0 -1
  106. package/dist/src/trails/add-surface.js +0 -88
  107. package/dist/src/trails/add-surface.js.map +0 -1
  108. package/dist/src/trails/add-trail.d.ts +0 -11
  109. package/dist/src/trails/add-trail.d.ts.map +0 -1
  110. package/dist/src/trails/add-trail.js +0 -85
  111. package/dist/src/trails/add-trail.js.map +0 -1
  112. package/dist/src/trails/add-verify.d.ts +0 -10
  113. package/dist/src/trails/add-verify.d.ts.map +0 -1
  114. package/dist/src/trails/add-verify.js +0 -67
  115. package/dist/src/trails/add-verify.js.map +0 -1
  116. package/dist/src/trails/create-scaffold.d.ts +0 -15
  117. package/dist/src/trails/create-scaffold.d.ts.map +0 -1
  118. package/dist/src/trails/create-scaffold.js +0 -288
  119. package/dist/src/trails/create-scaffold.js.map +0 -1
  120. package/dist/src/trails/create.d.ts +0 -22
  121. package/dist/src/trails/create.d.ts.map +0 -1
  122. package/dist/src/trails/create.js +0 -121
  123. package/dist/src/trails/create.js.map +0 -1
  124. package/dist/src/trails/guide.d.ts +0 -11
  125. package/dist/src/trails/guide.d.ts.map +0 -1
  126. package/dist/src/trails/guide.js +0 -80
  127. package/dist/src/trails/guide.js.map +0 -1
  128. package/dist/src/trails/load-app.d.ts +0 -4
  129. package/dist/src/trails/load-app.d.ts.map +0 -1
  130. package/dist/src/trails/load-app.js +0 -24
  131. package/dist/src/trails/load-app.js.map +0 -1
  132. package/dist/src/trails/project.d.ts +0 -8
  133. package/dist/src/trails/project.d.ts.map +0 -1
  134. package/dist/src/trails/project.js +0 -43
  135. package/dist/src/trails/project.js.map +0 -1
  136. package/dist/src/trails/survey.d.ts +0 -33
  137. package/dist/src/trails/survey.d.ts.map +0 -1
  138. package/dist/src/trails/survey.js +0 -225
  139. package/dist/src/trails/survey.js.map +0 -1
  140. package/dist/src/trails/warden.d.ts +0 -19
  141. package/dist/src/trails/warden.d.ts.map +0 -1
  142. package/dist/src/trails/warden.js +0 -88
  143. package/dist/src/trails/warden.js.map +0 -1
  144. package/dist/tsconfig.tsbuildinfo +0 -1
  145. package/src/__tests__/create.test.ts +0 -349
  146. package/src/__tests__/guide.test.ts +0 -91
  147. package/src/__tests__/load-app.test.ts +0 -15
  148. package/src/__tests__/survey.test.ts +0 -161
  149. package/src/__tests__/warden.test.ts +0 -74
  150. package/tsconfig.json +0 -9
@@ -0,0 +1,62 @@
1
+ /**
2
+ * `create.versions` trail -- Sync generated scaffold dependency versions.
3
+ *
4
+ * Derives `apps/trails/src/scaffold-versions.generated.ts` from the root
5
+ * `package.json` catalog and devDependencies. Graduated from
6
+ * `scripts/sync-scaffold-versions.ts`.
7
+ */
8
+
9
+ import { Result, trail, ValidationError } from '@ontrails/core';
10
+ import { z } from 'zod';
11
+
12
+ import { syncScaffoldVersions } from '../scaffold-version-sync.js';
13
+ import { resolveTrailRootDir } from './root-dir.js';
14
+
15
+ const createVersionsInputSchema = z.object({
16
+ check: z
17
+ .boolean()
18
+ .default(false)
19
+ .describe('Verify the generated file is current instead of writing'),
20
+ rootDir: z.string().optional().describe('Workspace root directory'),
21
+ });
22
+
23
+ const createVersionsOutputSchema = z.object({
24
+ generatedPath: z.string(),
25
+ mode: z.enum(['check', 'write']),
26
+ written: z.boolean(),
27
+ });
28
+
29
+ export const createVersionsTrail = trail('create.versions', {
30
+ blaze: async (input, ctx) => {
31
+ const rootDirResult = resolveTrailRootDir(input.rootDir, ctx.cwd);
32
+ if (rootDirResult.isErr()) {
33
+ return rootDirResult;
34
+ }
35
+
36
+ try {
37
+ return Result.ok(
38
+ await syncScaffoldVersions({
39
+ check: input.check,
40
+ rootDir: rootDirResult.value,
41
+ })
42
+ );
43
+ } catch (error) {
44
+ return Result.err(
45
+ new ValidationError(
46
+ error instanceof Error ? error.message : String(error)
47
+ )
48
+ );
49
+ }
50
+ },
51
+ description: 'Sync generated scaffold dependency versions',
52
+ examples: [
53
+ {
54
+ input: { check: true },
55
+ name: 'Verify generated scaffold versions are current',
56
+ },
57
+ ],
58
+ input: createVersionsInputSchema,
59
+ intent: 'write',
60
+ output: createVersionsOutputSchema,
61
+ permit: { scopes: ['project:write'] },
62
+ });
@@ -1,22 +1,29 @@
1
1
  /**
2
- * `create` route -- Create a new Trails project.
2
+ * `create` trail -- Create a new Trails project.
3
3
  *
4
4
  * Composes create.scaffold, add.surface, and add.verify sub-trails
5
- * via ctx.follow().
5
+ * via ctx.compose.
6
6
  */
7
7
 
8
- import type { FollowFn } from '@ontrails/core';
9
- import { Result, hike } from '@ontrails/core';
8
+ import { InternalError, Result, trail } from '@ontrails/core';
9
+ import type { TrailContext } from '@ontrails/core';
10
10
  import { z } from 'zod';
11
11
 
12
+ import {
13
+ PROJECT_NAME_MESSAGE,
14
+ PROJECT_NAME_PATTERN,
15
+ projectPathExists,
16
+ writeProjectFile,
17
+ } from '../project-writes.js';
18
+
12
19
  // ---------------------------------------------------------------------------
13
20
  // Helpers
14
21
  // ---------------------------------------------------------------------------
15
22
 
16
23
  type Starter = 'empty' | 'entity' | 'hello';
17
- type Surface = 'cli' | 'mcp';
24
+ type Surface = 'cli' | 'http' | 'mcp';
18
25
 
19
- interface BlazeInput {
26
+ interface CreateInput {
20
27
  readonly dir?: string | undefined;
21
28
  readonly name: string;
22
29
  readonly starter: Starter;
@@ -42,6 +49,18 @@ interface ScaffoldedProject {
42
49
  readonly name: string;
43
50
  }
44
51
 
52
+ interface SurfaceResult {
53
+ readonly created: string | null;
54
+ readonly dependency: string;
55
+ }
56
+
57
+ type TrailContextWithCompose = TrailContext & {
58
+ readonly compose: NonNullable<TrailContext['compose']>;
59
+ };
60
+
61
+ const hasCompose = (ctx: TrailContext): ctx is TrailContextWithCompose =>
62
+ Boolean(ctx.compose);
63
+
45
64
  const buildScaffoldInput = (input: ScaffoldRequest) => ({
46
65
  ...(input.dir === undefined ? {} : { dir: input.dir }),
47
66
  name: input.name,
@@ -58,45 +77,34 @@ const buildVerifyInput = (input: VerifyRequest) => ({
58
77
  name: input.name,
59
78
  });
60
79
 
61
- const scaffoldProject = (
62
- follow: FollowFn,
63
- input: ScaffoldRequest
64
- ): Promise<Result<ScaffoldedProject, Error>> =>
65
- follow('create.scaffold', buildScaffoldInput(input));
66
-
67
- const addSurfaceFiles = async (
68
- follow: FollowFn,
69
- dir: string,
70
- surfaces: readonly string[]
80
+ const collectSurfaceFiles = async (
81
+ surfaces: readonly string[],
82
+ addSurface: (surface: string) => Promise<Result<SurfaceResult, Error>>
71
83
  ): Promise<Result<string[], Error>> => {
72
84
  const created: string[] = [];
73
85
 
74
86
  for (const surface of surfaces) {
75
- const result = await follow<{ created: string; dependency: string }>(
76
- 'add.surface',
77
- buildSurfaceInput(dir, surface)
78
- );
87
+ const result = await addSurface(surface);
79
88
  if (result.isErr()) {
80
89
  return Result.err(result.error);
81
90
  }
82
- created.push(result.value.created);
91
+ if (result.value.created !== null) {
92
+ created.push(result.value.created);
93
+ }
83
94
  }
84
95
 
85
96
  return Result.ok(created);
86
97
  };
87
98
 
88
99
  const collectVerifyFiles = async (
89
- follow: FollowFn,
90
- input: VerifyRequest
100
+ shouldVerify: boolean,
101
+ addVerify: () => Promise<Result<{ created: string[] }, Error>>
91
102
  ): Promise<Result<string[], Error>> => {
92
- if (!input.verify) {
103
+ if (!shouldVerify) {
93
104
  return Result.ok([]);
94
105
  }
95
106
 
96
- const result = await follow<{ created: string[] }>(
97
- 'add.verify',
98
- buildVerifyInput(input)
99
- );
107
+ const result = await addVerify();
100
108
  return result.isErr()
101
109
  ? Result.err(result.error)
102
110
  : Result.ok(result.value.created);
@@ -105,48 +113,151 @@ const collectVerifyFiles = async (
105
113
  const collectCreatedFiles = (
106
114
  scaffolded: readonly string[],
107
115
  surfaces: readonly string[],
108
- verify: readonly string[]
109
- ): string[] => [...scaffolded, ...surfaces, ...verify];
110
-
111
- const runCreate = async (
112
- follow: FollowFn,
113
- input: BlazeInput
114
- ): Promise<Result<{ created: string[]; dir: string; name: string }, Error>> => {
115
- const scaffolded = await scaffoldProject(follow, input);
116
- if (scaffolded.isErr()) {
117
- return Result.err(scaffolded.error);
118
- }
116
+ verify: readonly string[],
117
+ readme: string | null
118
+ ): string[] =>
119
+ readme === null
120
+ ? [...scaffolded, ...surfaces, ...verify]
121
+ : [...scaffolded, ...surfaces, ...verify, readme];
119
122
 
120
- const surfaceResults = await addSurfaceFiles(
121
- follow,
122
- scaffolded.value.dir,
123
- input.surfaces
124
- );
125
- if (surfaceResults.isErr()) {
126
- return Result.err(surfaceResults.error);
127
- }
123
+ const surfaceReadmeLines = {
124
+ cli: '- `src/cli.ts` - CLI surface entry point',
125
+ http: '- `src/http.ts` - HTTP surface entry point',
126
+ mcp: '- `src/mcp.ts` - MCP surface entry point',
127
+ } satisfies Record<Surface, string>;
128
+
129
+ const starterReadmeLines = {
130
+ empty:
131
+ 'Starts with an empty `src/trails/` directory for authoring from scratch.',
132
+ entity:
133
+ 'Includes sample entity trails, a signal, and an in-memory store for exploration.',
134
+ hello: 'Includes a `hello` trail with examples for the first happy path.',
135
+ } satisfies Record<Starter, string>;
136
+
137
+ const generateReadme = (input: CreateInput): string => {
138
+ const surfaceLines = input.surfaces
139
+ .map((surface) => surfaceReadmeLines[surface])
140
+ .join('\n');
141
+ const verificationCommand = input.verify ? 'bun test\n' : '';
142
+ const verificationStructure = input.verify
143
+ ? '- `__tests__/examples.test.ts` - examples-as-tests harness\n'
144
+ : '- Verification files were not generated for this project\n';
145
+
146
+ return `# ${input.name}
147
+
148
+ A Trails project. Trails is an agent-native, contract-first TypeScript framework: author a trail once with typed input, Result output, examples, intent, and meta; surface it through CLI, MCP, HTTP, or future WebSocket.
149
+
150
+ ## Getting Started
151
+
152
+ \`\`\`bash
153
+ bun install
154
+ ${verificationCommand}bun run warden
155
+ bun run survey
156
+ bun run guide
157
+ \`\`\`
158
+
159
+ ## Project Structure
160
+
161
+ - \`src/app.ts\` - the topo that collects this project's trails
162
+ - \`src/trails/\` - trail definitions
163
+ ${surfaceLines}
164
+ ${verificationStructure}- \`AGENTS.md\` - project guidance for agents working in this app
128
165
 
129
- const verifyFiles = await collectVerifyFiles(follow, input);
130
- if (verifyFiles.isErr()) {
131
- return Result.err(verifyFiles.error);
166
+ ## Starter
167
+
168
+ ${starterReadmeLines[input.starter]}
169
+
170
+ ## Next Steps
171
+
172
+ - Add a trail with \`bun run add\`
173
+ - Run \`bun run warden\` before review
174
+ - Read \`AGENTS.md\` for Trails vocabulary and conventions
175
+ `;
176
+ };
177
+
178
+ const writeReadme = async (
179
+ input: CreateInput,
180
+ dir: string
181
+ ): Promise<Result<string | null, Error>> => {
182
+ const exists = projectPathExists(dir, 'README.md');
183
+ if (exists.isErr()) {
184
+ return Result.err(exists.error);
185
+ }
186
+ if (exists.value) {
187
+ return Result.ok(null);
132
188
  }
133
189
 
134
- return Result.ok({
135
- created: collectCreatedFiles(
136
- scaffolded.value.created,
137
- surfaceResults.value,
138
- verifyFiles.value
139
- ),
140
- dir: scaffolded.value.dir,
141
- name: input.name,
142
- });
190
+ const written = await writeProjectFile(
191
+ dir,
192
+ 'README.md',
193
+ generateReadme(input)
194
+ );
195
+ return written.isErr() ? Result.err(written.error) : Result.ok('README.md');
143
196
  };
144
197
 
145
198
  // ---------------------------------------------------------------------------
146
- // Route definition
199
+ // Trail definition
147
200
  // ---------------------------------------------------------------------------
148
201
 
149
- export const createRoute = hike('create', {
202
+ export const createTrail = trail('create', {
203
+ blaze: async (input: CreateInput, ctx) => {
204
+ if (!hasCompose(ctx)) {
205
+ return Result.err(new InternalError('create trail requires ctx.compose'));
206
+ }
207
+
208
+ const scaffolded = await ctx.compose<ScaffoldedProject>(
209
+ 'create.scaffold',
210
+ buildScaffoldInput(input)
211
+ );
212
+ if (scaffolded.isErr()) {
213
+ return scaffolded;
214
+ }
215
+
216
+ const finishCreate = async (): Promise<
217
+ Result<{ created: string[]; dir: string; name: string }, Error>
218
+ > => {
219
+ const surfaceFiles = await collectSurfaceFiles(
220
+ input.surfaces,
221
+ (surface) =>
222
+ ctx.compose<SurfaceResult>(
223
+ 'add.surface',
224
+ buildSurfaceInput(scaffolded.value.dir, surface)
225
+ )
226
+ );
227
+ if (surfaceFiles.isErr()) {
228
+ return Result.err(surfaceFiles.error);
229
+ }
230
+
231
+ const verifyFiles = await collectVerifyFiles(input.verify, () =>
232
+ ctx.compose<{ created: string[] }>(
233
+ 'add.verify',
234
+ buildVerifyInput(input)
235
+ )
236
+ );
237
+ if (verifyFiles.isErr()) {
238
+ return Result.err(verifyFiles.error);
239
+ }
240
+
241
+ const readmeFile = await writeReadme(input, scaffolded.value.dir);
242
+ if (readmeFile.isErr()) {
243
+ return Result.err(readmeFile.error);
244
+ }
245
+
246
+ return Result.ok({
247
+ created: collectCreatedFiles(
248
+ scaffolded.value.created,
249
+ surfaceFiles.value,
250
+ verifyFiles.value,
251
+ readmeFile.value
252
+ ),
253
+ dir: scaffolded.value.dir,
254
+ name: input.name,
255
+ });
256
+ };
257
+
258
+ return finishCreate();
259
+ },
260
+ composes: ['create.scaffold', 'add.surface', 'add.verify'],
150
261
  description: 'Create a new Trails project',
151
262
  fields: {
152
263
  starter: {
@@ -157,7 +268,7 @@ export const createRoute = hike('create', {
157
268
  value: 'hello',
158
269
  },
159
270
  {
160
- hint: '4 trails, hike, event, store',
271
+ hint: '4 trails, signal, store',
161
272
  label: 'Entity CRUD',
162
273
  value: 'entity',
163
274
  },
@@ -172,25 +283,27 @@ export const createRoute = hike('create', {
172
283
  label: 'MCP',
173
284
  value: 'mcp',
174
285
  },
286
+ {
287
+ hint: 'Hono-powered HTTP endpoints',
288
+ label: 'HTTP',
289
+ value: 'http',
290
+ },
175
291
  ],
176
292
  },
177
293
  },
178
- follows: ['create.scaffold', 'add.surface', 'add.verify'],
179
- implementation: async (input: BlazeInput, ctx) => {
180
- if (!ctx.follow) {
181
- return Result.err(new Error('create route requires ctx.follow'));
182
- }
183
- return await runCreate(ctx.follow, input);
184
- },
185
294
  input: z.object({
186
295
  dir: z.string().optional().describe('Parent directory'),
187
- name: z.string().describe('Project name'),
296
+ name: z
297
+ .string()
298
+ .regex(PROJECT_NAME_PATTERN, PROJECT_NAME_MESSAGE)
299
+ .describe('Project name'),
188
300
  starter: z
189
301
  .enum(['hello', 'entity', 'empty'])
190
302
  .default('hello')
191
303
  .describe('Starter trail'),
192
304
  surfaces: z
193
- .array(z.enum(['cli', 'mcp']))
305
+ .array(z.enum(['cli', 'http', 'mcp']))
306
+ .min(1)
194
307
  .default(['cli'])
195
308
  .describe('Surfaces'),
196
309
  verify: z.boolean().default(true).describe('Include testing + warden'),
@@ -200,4 +313,5 @@ export const createRoute = hike('create', {
200
313
  dir: z.string(),
201
314
  name: z.string(),
202
315
  }),
316
+ permit: { scopes: ['project:write'] },
203
317
  });
@@ -0,0 +1,59 @@
1
+ import { trail } from '@ontrails/core';
2
+ import { z } from 'zod';
3
+
4
+ import {
5
+ parseLifecycleTarget,
6
+ setVersionStatusSource,
7
+ withLifecycleApp,
8
+ } from './version-lifecycle-support.js';
9
+
10
+ export const deprecateTrail = trail('deprecate', {
11
+ args: ['target'],
12
+ blaze: async (input, ctx) =>
13
+ withLifecycleApp(input, ctx.cwd, async (_app, rootDir) => {
14
+ const target = parseLifecycleTarget(input.target);
15
+ if (target.isErr()) {
16
+ return target;
17
+ }
18
+ return setVersionStatusSource(
19
+ rootDir,
20
+ target.value,
21
+ input.archive ? 'archived' : 'deprecated',
22
+ {
23
+ migration: input.migration,
24
+ note: input.note,
25
+ reason: input.reason,
26
+ successor: input.successor,
27
+ }
28
+ );
29
+ }),
30
+ description: 'Mark a historical trail version deprecated or archived',
31
+ input: z.object({
32
+ archive: z
33
+ .boolean()
34
+ .default(false)
35
+ .describe('Archive instead of deprecate'),
36
+ migration: z
37
+ .array(z.string())
38
+ .default([])
39
+ .describe('Migration guidance entries'),
40
+ module: z.string().optional().describe('Path to the app module'),
41
+ note: z.string().optional().describe('Deprecation note'),
42
+ reason: z.string().optional().describe('Archive reason'),
43
+ rootDir: z.string().optional().describe('Workspace root directory'),
44
+ successor: z
45
+ .number()
46
+ .int()
47
+ .positive()
48
+ .optional()
49
+ .describe('Successor version'),
50
+ target: z.string().min(1).describe('Historical version target trail.id@N'),
51
+ }),
52
+ intent: 'write',
53
+ output: z.object({
54
+ file: z.string(),
55
+ trailId: z.string(),
56
+ updated: z.boolean(),
57
+ }),
58
+ permit: { scopes: ['version:write'] },
59
+ });
@@ -0,0 +1,82 @@
1
+ import { Result, ValidationError, trail } from '@ontrails/core';
2
+ import { z } from 'zod';
3
+
4
+ import {
5
+ cleanDevState,
6
+ DEFAULT_TOPO_SNAPSHOT_RETENTION,
7
+ } from './dev-support.js';
8
+ import { resolveTrailRootDir } from './root-dir.js';
9
+ import { createIsolatedExampleInput } from './topo-support.js';
10
+
11
+ export const devCleanTrail = trail('dev.clean', {
12
+ blaze: (input, ctx) => {
13
+ if (input.dryRun !== true && input.yes !== true) {
14
+ return Result.err(
15
+ new ValidationError(
16
+ 'Refusing to clean local state without `--yes` or `--dry-run`.'
17
+ )
18
+ );
19
+ }
20
+
21
+ const rootDirResult = resolveTrailRootDir(input.rootDir, ctx.cwd);
22
+ if (rootDirResult.isErr()) {
23
+ return rootDirResult;
24
+ }
25
+ const rootDir = rootDirResult.value;
26
+ return Result.ok(
27
+ cleanDevState({
28
+ dryRun: input.dryRun,
29
+ maxAge: input.traceAgeMs,
30
+ maxRecords: input.traces,
31
+ rootDir,
32
+ snapshotRetention: input.snapshots,
33
+ })
34
+ );
35
+ },
36
+ description: 'Prune unpinned topo snapshots and old trace records',
37
+ examples: [
38
+ {
39
+ input: {
40
+ dryRun: true,
41
+ rootDir: createIsolatedExampleInput('dev-clean').rootDir,
42
+ },
43
+ name: 'Preview local cleanup',
44
+ },
45
+ ],
46
+ input: z.object({
47
+ dryRun: z
48
+ .boolean()
49
+ .default(true)
50
+ .describe('Preview cleanup without changing state'),
51
+ rootDir: z.string().optional().describe('Workspace root directory'),
52
+ snapshots: z
53
+ .number()
54
+ .default(DEFAULT_TOPO_SNAPSHOT_RETENTION)
55
+ .describe('Unpinned topo snapshots to retain'),
56
+ traceAgeMs: z
57
+ .number()
58
+ .default(7 * 24 * 60 * 60 * 1000)
59
+ .describe('Maximum retained trace age in milliseconds'),
60
+ traces: z.number().default(10_000).describe('Maximum retained trace count'),
61
+ yes: z.boolean().default(false).describe('Confirm destructive changes'),
62
+ }),
63
+ intent: 'destroy',
64
+ output: z.object({
65
+ dryRun: z.boolean(),
66
+ remaining: z.object({
67
+ pinnedCount: z.number(),
68
+ snapshotCount: z.number(),
69
+ traceCount: z.number(),
70
+ }),
71
+ removed: z.object({
72
+ topoSnapshots: z.number(),
73
+ traceRecords: z.number(),
74
+ }),
75
+ retention: z.object({
76
+ snapshots: z.number(),
77
+ traceAgeMs: z.number(),
78
+ traces: z.number(),
79
+ }),
80
+ }),
81
+ permit: { scopes: ['dev:write'] },
82
+ });
@@ -0,0 +1,50 @@
1
+ import { Result, ValidationError, trail } from '@ontrails/core';
2
+ import { z } from 'zod';
3
+
4
+ import { resetDevState } from './dev-support.js';
5
+ import { resolveTrailRootDir } from './root-dir.js';
6
+ import { createIsolatedExampleInput } from './topo-support.js';
7
+
8
+ export const devResetTrail = trail('dev.reset', {
9
+ blaze: (input, ctx) => {
10
+ if (input.dryRun !== true && input.yes !== true) {
11
+ return Result.err(
12
+ new ValidationError(
13
+ 'Refusing to reset local state without `--yes` or `--dry-run`.'
14
+ )
15
+ );
16
+ }
17
+
18
+ const rootDirResult = resolveTrailRootDir(input.rootDir, ctx.cwd);
19
+ if (rootDirResult.isErr()) {
20
+ return rootDirResult;
21
+ }
22
+ const rootDir = rootDirResult.value;
23
+ return Result.ok(resetDevState({ dryRun: input.dryRun, rootDir }));
24
+ },
25
+ description: 'Remove local Trails database artifacts',
26
+ examples: [
27
+ {
28
+ input: {
29
+ dryRun: true,
30
+ rootDir: createIsolatedExampleInput('dev-reset').rootDir,
31
+ },
32
+ name: 'Preview local reset',
33
+ },
34
+ ],
35
+ input: z.object({
36
+ dryRun: z
37
+ .boolean()
38
+ .default(true)
39
+ .describe('Preview reset without changing state'),
40
+ rootDir: z.string().optional().describe('Workspace root directory'),
41
+ yes: z.boolean().default(false).describe('Confirm destructive changes'),
42
+ }),
43
+ intent: 'destroy',
44
+ output: z.object({
45
+ dryRun: z.boolean(),
46
+ removedCount: z.number(),
47
+ removedFiles: z.array(z.string()),
48
+ }),
49
+ permit: { scopes: ['dev:write'] },
50
+ });
@@ -0,0 +1,72 @@
1
+ import { Result, trail } from '@ontrails/core';
2
+ import { z } from 'zod';
3
+
4
+ import {
5
+ buildDevStats,
6
+ DEFAULT_TOPO_SNAPSHOT_RETENTION,
7
+ } from './dev-support.js';
8
+ import { resolveTrailRootDir } from './root-dir.js';
9
+ import { createIsolatedExampleInput } from './topo-support.js';
10
+
11
+ export const devStatsTrail = trail('dev.stats', {
12
+ blaze: (input, ctx) => {
13
+ const rootDirResult = resolveTrailRootDir(input.rootDir, ctx.cwd);
14
+ if (rootDirResult.isErr()) {
15
+ return rootDirResult;
16
+ }
17
+ const rootDir = rootDirResult.value;
18
+ return Result.ok(
19
+ buildDevStats({
20
+ maxAge: input.traceAgeMs,
21
+ maxRecords: input.traces,
22
+ rootDir,
23
+ snapshotRetention: input.snapshots,
24
+ })
25
+ );
26
+ },
27
+ description: 'Show local Trails workspace state and retention',
28
+ examples: [
29
+ {
30
+ input: { rootDir: createIsolatedExampleInput('dev-stats').rootDir },
31
+ name: 'Show local dev state',
32
+ },
33
+ ],
34
+ input: z.object({
35
+ rootDir: z.string().optional().describe('Workspace root directory'),
36
+ snapshots: z
37
+ .number()
38
+ .default(DEFAULT_TOPO_SNAPSHOT_RETENTION)
39
+ .describe('Unpinned topo snapshots to retain'),
40
+ traceAgeMs: z
41
+ .number()
42
+ .default(7 * 24 * 60 * 60 * 1000)
43
+ .describe('Maximum retained trace age in milliseconds'),
44
+ traces: z.number().default(10_000).describe('Maximum retained trace count'),
45
+ }),
46
+ intent: 'read',
47
+ output: z.object({
48
+ db: z.object({
49
+ exists: z.boolean(),
50
+ fileSizeBytes: z.number(),
51
+ path: z.string(),
52
+ }),
53
+ lock: z.object({
54
+ exists: z.boolean(),
55
+ fileSizeBytes: z.number(),
56
+ path: z.string(),
57
+ }),
58
+ retention: z.object({
59
+ snapshots: z.number(),
60
+ traceAgeMs: z.number(),
61
+ traces: z.number(),
62
+ }),
63
+ topo: z.object({
64
+ pinnedCount: z.number(),
65
+ prunableSnapshotCount: z.number(),
66
+ snapshotCount: z.number(),
67
+ }),
68
+ tracing: z.object({
69
+ recordCount: z.number(),
70
+ }),
71
+ }),
72
+ });