@ontrails/trails 1.0.0-beta.4 → 1.0.0-beta.41

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 (165) hide show
  1. package/CHANGELOG.md +1104 -4
  2. package/README.md +27 -0
  3. package/package.json +31 -7
  4. package/src/app.ts +147 -3
  5. package/src/cli.ts +345 -11
  6. package/src/completions.ts +240 -0
  7. package/src/lifecycle-source-io.ts +33 -0
  8. package/src/load-app-mirror.ts +202 -0
  9. package/src/local-state-io.ts +173 -0
  10. package/src/mcp-app.ts +42 -0
  11. package/src/mcp-options.ts +91 -0
  12. package/src/mcp.ts +8 -0
  13. package/src/project-writes.ts +377 -0
  14. package/src/regrade/config.ts +152 -0
  15. package/src/regrade/history.ts +430 -0
  16. package/src/regrade/live-api-preserve.ts +8 -0
  17. package/src/regrade/plan-artifact.ts +285 -0
  18. package/src/release/bindings.ts +39 -0
  19. package/src/release/check.ts +844 -0
  20. package/src/release/config.ts +63 -0
  21. package/src/release/contract-facts.ts +425 -0
  22. package/src/release/index.ts +142 -0
  23. package/src/release/lock-roundtrip-smoke.ts +230 -0
  24. package/src/release/native-bun-publish.ts +651 -0
  25. package/src/release/native-bun-registry.ts +765 -0
  26. package/src/release/notes-cli.ts +171 -0
  27. package/src/release/notes.ts +390 -0
  28. package/src/release/pack-coherence.ts +455 -0
  29. package/src/release/packed-artifacts-smoke.ts +236 -0
  30. package/src/release/policy.ts +1687 -0
  31. package/src/release/semver.ts +104 -0
  32. package/src/release/smoke.ts +56 -0
  33. package/src/release/wayfinder-dogfood-smoke.ts +760 -0
  34. package/src/retired-topo-command.ts +36 -0
  35. package/src/run-adapter-check.ts +76 -0
  36. package/src/run-collision.ts +126 -0
  37. package/src/run-completions-install.ts +179 -0
  38. package/src/run-example.ts +149 -0
  39. package/src/run-examples.ts +148 -0
  40. package/src/run-quiet.ts +75 -0
  41. package/src/run-release-check.ts +74 -0
  42. package/src/run-schema.ts +74 -0
  43. package/src/run-trace.ts +273 -0
  44. package/src/run-warden.ts +39 -0
  45. package/src/run-watch.ts +432 -0
  46. package/src/run-wayfind-outline.ts +170 -0
  47. package/src/scaffold-version-sync.ts +183 -0
  48. package/src/scaffold-versions.generated.ts +12 -0
  49. package/src/trails/adapter-check.ts +244 -0
  50. package/src/trails/add-surface.ts +99 -45
  51. package/src/trails/add-trail.ts +84 -37
  52. package/src/trails/add-verify.ts +100 -30
  53. package/src/trails/compile.ts +58 -0
  54. package/src/trails/completions-complete.ts +165 -0
  55. package/src/trails/completions.ts +47 -0
  56. package/src/trails/create-adapter.ts +785 -0
  57. package/src/trails/create-scaffold.ts +401 -106
  58. package/src/trails/create-versions.ts +62 -0
  59. package/src/trails/create.ts +186 -72
  60. package/src/trails/deprecate.ts +59 -0
  61. package/src/trails/dev-clean.ts +82 -0
  62. package/src/trails/dev-reset.ts +50 -0
  63. package/src/trails/dev-stats.ts +72 -0
  64. package/src/trails/dev-support.ts +360 -0
  65. package/src/trails/doctor.ts +77 -0
  66. package/src/trails/draft-promote.ts +949 -0
  67. package/src/trails/guide.ts +70 -68
  68. package/src/trails/load-app.ts +1123 -15
  69. package/src/trails/operator-context.ts +66 -0
  70. package/src/trails/project.ts +17 -3
  71. package/src/trails/regrade.ts +3091 -0
  72. package/src/trails/release-check.ts +105 -0
  73. package/src/trails/release-smoke.ts +49 -0
  74. package/src/trails/revise.ts +53 -0
  75. package/src/trails/root-dir.ts +21 -0
  76. package/src/trails/run-example.ts +475 -0
  77. package/src/trails/run-examples.ts +129 -0
  78. package/src/trails/run.ts +434 -0
  79. package/src/trails/scaffold-json.ts +58 -0
  80. package/src/trails/survey.ts +877 -214
  81. package/src/trails/topo-activation.ts +14 -0
  82. package/src/trails/topo-constants.ts +2 -0
  83. package/src/trails/topo-history.ts +47 -0
  84. package/src/trails/topo-output-schemas.ts +259 -0
  85. package/src/trails/topo-pin.ts +38 -0
  86. package/src/trails/topo-read-support.ts +368 -0
  87. package/src/trails/topo-reports.ts +809 -0
  88. package/src/trails/topo-store-support.ts +325 -0
  89. package/src/trails/topo-support.ts +220 -0
  90. package/src/trails/topo-unpin.ts +61 -0
  91. package/src/trails/topo.ts +92 -0
  92. package/src/trails/validate.ts +27 -0
  93. package/src/trails/version-lifecycle-support.ts +936 -0
  94. package/src/trails/warden-guide.ts +134 -0
  95. package/src/trails/warden.ts +198 -58
  96. package/src/trails/wayfind-outline.ts +876 -0
  97. package/src/trails/wayfind.ts +1053 -0
  98. package/src/versions.ts +31 -0
  99. package/.turbo/turbo-build.log +0 -1
  100. package/.turbo/turbo-lint.log +0 -3
  101. package/.turbo/turbo-typecheck.log +0 -1
  102. package/__tests__/examples.test.ts +0 -6
  103. package/dist/bin/trails.d.ts +0 -3
  104. package/dist/bin/trails.d.ts.map +0 -1
  105. package/dist/bin/trails.js +0 -4
  106. package/dist/bin/trails.js.map +0 -1
  107. package/dist/src/app.d.ts +0 -2
  108. package/dist/src/app.d.ts.map +0 -1
  109. package/dist/src/app.js +0 -11
  110. package/dist/src/app.js.map +0 -1
  111. package/dist/src/clack.d.ts +0 -9
  112. package/dist/src/clack.d.ts.map +0 -1
  113. package/dist/src/clack.js +0 -84
  114. package/dist/src/clack.js.map +0 -1
  115. package/dist/src/cli.d.ts +0 -2
  116. package/dist/src/cli.d.ts.map +0 -1
  117. package/dist/src/cli.js +0 -13
  118. package/dist/src/cli.js.map +0 -1
  119. package/dist/src/trails/add-surface.d.ts +0 -13
  120. package/dist/src/trails/add-surface.d.ts.map +0 -1
  121. package/dist/src/trails/add-surface.js +0 -88
  122. package/dist/src/trails/add-surface.js.map +0 -1
  123. package/dist/src/trails/add-trail.d.ts +0 -10
  124. package/dist/src/trails/add-trail.d.ts.map +0 -1
  125. package/dist/src/trails/add-trail.js +0 -77
  126. package/dist/src/trails/add-trail.js.map +0 -1
  127. package/dist/src/trails/add-verify.d.ts +0 -10
  128. package/dist/src/trails/add-verify.d.ts.map +0 -1
  129. package/dist/src/trails/add-verify.js +0 -67
  130. package/dist/src/trails/add-verify.js.map +0 -1
  131. package/dist/src/trails/create-scaffold.d.ts +0 -15
  132. package/dist/src/trails/create-scaffold.d.ts.map +0 -1
  133. package/dist/src/trails/create-scaffold.js +0 -288
  134. package/dist/src/trails/create-scaffold.js.map +0 -1
  135. package/dist/src/trails/create.d.ts +0 -22
  136. package/dist/src/trails/create.d.ts.map +0 -1
  137. package/dist/src/trails/create.js +0 -121
  138. package/dist/src/trails/create.js.map +0 -1
  139. package/dist/src/trails/guide.d.ts +0 -11
  140. package/dist/src/trails/guide.d.ts.map +0 -1
  141. package/dist/src/trails/guide.js +0 -80
  142. package/dist/src/trails/guide.js.map +0 -1
  143. package/dist/src/trails/load-app.d.ts +0 -4
  144. package/dist/src/trails/load-app.d.ts.map +0 -1
  145. package/dist/src/trails/load-app.js +0 -24
  146. package/dist/src/trails/load-app.js.map +0 -1
  147. package/dist/src/trails/project.d.ts +0 -8
  148. package/dist/src/trails/project.d.ts.map +0 -1
  149. package/dist/src/trails/project.js +0 -43
  150. package/dist/src/trails/project.js.map +0 -1
  151. package/dist/src/trails/survey.d.ts +0 -31
  152. package/dist/src/trails/survey.d.ts.map +0 -1
  153. package/dist/src/trails/survey.js +0 -221
  154. package/dist/src/trails/survey.js.map +0 -1
  155. package/dist/src/trails/warden.d.ts +0 -19
  156. package/dist/src/trails/warden.d.ts.map +0 -1
  157. package/dist/src/trails/warden.js +0 -88
  158. package/dist/src/trails/warden.js.map +0 -1
  159. package/dist/tsconfig.tsbuildinfo +0 -1
  160. package/src/__tests__/create.test.ts +0 -349
  161. package/src/__tests__/guide.test.ts +0 -91
  162. package/src/__tests__/load-app.test.ts +0 -15
  163. package/src/__tests__/survey.test.ts +0 -159
  164. package/src/__tests__/warden.test.ts +0 -74
  165. 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
+ description: 'Sync generated scaffold dependency versions',
31
+ examples: [
32
+ {
33
+ input: { check: true },
34
+ name: 'Verify generated scaffold versions are current',
35
+ },
36
+ ],
37
+ implementation: async (input, ctx) => {
38
+ const rootDirResult = resolveTrailRootDir(input.rootDir, ctx.cwd);
39
+ if (rootDirResult.isErr()) {
40
+ return rootDirResult;
41
+ }
42
+
43
+ try {
44
+ return Result.ok(
45
+ await syncScaffoldVersions({
46
+ check: input.check,
47
+ rootDir: rootDirResult.value,
48
+ })
49
+ );
50
+ } catch (error) {
51
+ return Result.err(
52
+ new ValidationError(
53
+ error instanceof Error ? error.message : String(error)
54
+ )
55
+ );
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, trail } 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
- return Result.err(result.error);
89
+ return result;
90
+ }
91
+ if (result.value.created !== null) {
92
+ created.push(result.value.created);
81
93
  }
82
- created.push(result.value.created);
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,94 @@ 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 exists;
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 = trail('create', {
202
+ export const createTrail = trail('create', {
203
+ composes: ['create.scaffold', 'add.surface', 'add.verify'],
150
204
  description: 'Create a new Trails project',
151
205
  fields: {
152
206
  starter: {
@@ -157,7 +211,7 @@ export const createRoute = trail('create', {
157
211
  value: 'hello',
158
212
  },
159
213
  {
160
- hint: '4 trails, event, store',
214
+ hint: '4 trails, signal, store',
161
215
  label: 'Entity CRUD',
162
216
  value: 'entity',
163
217
  },
@@ -172,19 +226,84 @@ export const createRoute = trail('create', {
172
226
  label: 'MCP',
173
227
  value: 'mcp',
174
228
  },
229
+ {
230
+ hint: 'Hono-powered HTTP endpoints',
231
+ label: 'HTTP',
232
+ value: 'http',
233
+ },
175
234
  ],
176
235
  },
177
236
  },
178
- follow: ['create.scaffold', 'add.surface', 'add.verify'],
237
+ implementation: async (input: CreateInput, ctx) => {
238
+ if (!hasCompose(ctx)) {
239
+ return Result.err(new InternalError('create trail requires ctx.compose'));
240
+ }
241
+
242
+ const scaffolded = await ctx.compose<ScaffoldedProject>(
243
+ 'create.scaffold',
244
+ buildScaffoldInput(input)
245
+ );
246
+ if (scaffolded.isErr()) {
247
+ return scaffolded;
248
+ }
249
+
250
+ const finishCreate = async (): Promise<
251
+ Result<{ created: string[]; dir: string; name: string }, Error>
252
+ > => {
253
+ const surfaceFiles = await collectSurfaceFiles(
254
+ input.surfaces,
255
+ (surface) =>
256
+ ctx.compose<SurfaceResult>(
257
+ 'add.surface',
258
+ buildSurfaceInput(scaffolded.value.dir, surface)
259
+ )
260
+ );
261
+ if (surfaceFiles.isErr()) {
262
+ return surfaceFiles;
263
+ }
264
+
265
+ const verifyFiles = await collectVerifyFiles(input.verify, () =>
266
+ ctx.compose<{ created: string[] }>(
267
+ 'add.verify',
268
+ buildVerifyInput(input)
269
+ )
270
+ );
271
+ if (verifyFiles.isErr()) {
272
+ return verifyFiles;
273
+ }
274
+
275
+ const readmeFile = await writeReadme(input, scaffolded.value.dir);
276
+ if (readmeFile.isErr()) {
277
+ return readmeFile;
278
+ }
279
+
280
+ return Result.ok({
281
+ created: collectCreatedFiles(
282
+ scaffolded.value.created,
283
+ surfaceFiles.value,
284
+ verifyFiles.value,
285
+ readmeFile.value
286
+ ),
287
+ dir: scaffolded.value.dir,
288
+ name: input.name,
289
+ });
290
+ };
291
+
292
+ return finishCreate();
293
+ },
179
294
  input: z.object({
180
295
  dir: z.string().optional().describe('Parent directory'),
181
- name: z.string().describe('Project name'),
296
+ name: z
297
+ .string()
298
+ .regex(PROJECT_NAME_PATTERN, PROJECT_NAME_MESSAGE)
299
+ .describe('Project name'),
182
300
  starter: z
183
301
  .enum(['hello', 'entity', 'empty'])
184
302
  .default('hello')
185
303
  .describe('Starter trail'),
186
304
  surfaces: z
187
- .array(z.enum(['cli', 'mcp']))
305
+ .array(z.enum(['cli', 'http', 'mcp']))
306
+ .min(1)
188
307
  .default(['cli'])
189
308
  .describe('Surfaces'),
190
309
  verify: z.boolean().default(true).describe('Include testing + warden'),
@@ -194,10 +313,5 @@ export const createRoute = trail('create', {
194
313
  dir: z.string(),
195
314
  name: z.string(),
196
315
  }),
197
- run: async (input: BlazeInput, ctx) => {
198
- if (!ctx.follow) {
199
- return Result.err(new Error('create route requires ctx.follow'));
200
- }
201
- return await runCreate(ctx.follow, input);
202
- },
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
+ description: 'Mark a historical trail version deprecated or archived',
13
+ implementation: async (input, ctx) =>
14
+ withLifecycleApp(input, ctx.cwd, async (_app, rootDir) => {
15
+ const target = parseLifecycleTarget(input.target);
16
+ if (target.isErr()) {
17
+ return target;
18
+ }
19
+ return setVersionStatusSource(
20
+ rootDir,
21
+ target.value,
22
+ input.archive ? 'archived' : 'deprecated',
23
+ {
24
+ migration: input.migration,
25
+ note: input.note,
26
+ reason: input.reason,
27
+ successor: input.successor,
28
+ }
29
+ );
30
+ }),
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
+ filePath: 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
+ description: 'Prune unpinned topo snapshots and old trace records',
13
+ examples: [
14
+ {
15
+ input: {
16
+ dryRun: true,
17
+ rootDir: createIsolatedExampleInput('dev-clean').rootDir,
18
+ },
19
+ name: 'Preview local cleanup',
20
+ },
21
+ ],
22
+ implementation: (input, ctx) => {
23
+ if (input.dryRun !== true && input.yes !== true) {
24
+ return Result.err(
25
+ new ValidationError(
26
+ 'Refusing to clean local state without `--yes` or `--dry-run`.'
27
+ )
28
+ );
29
+ }
30
+
31
+ const rootDirResult = resolveTrailRootDir(input.rootDir, ctx.cwd);
32
+ if (rootDirResult.isErr()) {
33
+ return rootDirResult;
34
+ }
35
+ const rootDir = rootDirResult.value;
36
+ return Result.ok(
37
+ cleanDevState({
38
+ dryRun: input.dryRun,
39
+ maxAge: input.traceAgeMs,
40
+ maxRecords: input.traces,
41
+ rootDir,
42
+ snapshotRetention: input.snapshots,
43
+ })
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
+ description: 'Remove local Trails database artifacts',
10
+ examples: [
11
+ {
12
+ input: {
13
+ dryRun: true,
14
+ rootDir: createIsolatedExampleInput('dev-reset').rootDir,
15
+ },
16
+ name: 'Preview local reset',
17
+ },
18
+ ],
19
+ implementation: (input, ctx) => {
20
+ if (input.dryRun !== true && input.yes !== true) {
21
+ return Result.err(
22
+ new ValidationError(
23
+ 'Refusing to reset local state without `--yes` or `--dry-run`.'
24
+ )
25
+ );
26
+ }
27
+
28
+ const rootDirResult = resolveTrailRootDir(input.rootDir, ctx.cwd);
29
+ if (rootDirResult.isErr()) {
30
+ return rootDirResult;
31
+ }
32
+ const rootDir = rootDirResult.value;
33
+ return Result.ok(resetDevState({ dryRun: input.dryRun, rootDir }));
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
+ description: 'Show local Trails workspace state and retention',
13
+ examples: [
14
+ {
15
+ input: { rootDir: createIsolatedExampleInput('dev-stats').rootDir },
16
+ name: 'Show local dev state',
17
+ },
18
+ ],
19
+ implementation: (input, ctx) => {
20
+ const rootDirResult = resolveTrailRootDir(input.rootDir, ctx.cwd);
21
+ if (rootDirResult.isErr()) {
22
+ return rootDirResult;
23
+ }
24
+ const rootDir = rootDirResult.value;
25
+ return Result.ok(
26
+ buildDevStats({
27
+ maxAge: input.traceAgeMs,
28
+ maxRecords: input.traces,
29
+ rootDir,
30
+ snapshotRetention: input.snapshots,
31
+ })
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
+ });