@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,66 @@
1
+ import type { Result } from '@ontrails/core';
2
+
3
+ import { tryLoadFreshAppLease } from './load-app.js';
4
+ import type { FreshAppLease } from './load-app.js';
5
+ import { resolveTrailRootDir } from './root-dir.js';
6
+
7
+ interface RootDirInput {
8
+ readonly rootDir?: string | undefined;
9
+ }
10
+
11
+ interface FreshAppInput extends RootDirInput {
12
+ readonly module?: string | undefined;
13
+ }
14
+
15
+ interface RootDirContext {
16
+ readonly cwd?: string | undefined;
17
+ }
18
+
19
+ interface FreshAppContext {
20
+ readonly lease: FreshAppLease;
21
+ readonly rootDir: string;
22
+ }
23
+
24
+ export const withOperatorRootDir = async <T>(
25
+ input: RootDirInput,
26
+ ctx: RootDirContext,
27
+ consume: (rootDir: string) => Result<T, Error> | Promise<Result<T, Error>>
28
+ ): Promise<Result<T, Error>> => {
29
+ const rootDirResult = resolveTrailRootDir(input.rootDir, ctx.cwd);
30
+ if (rootDirResult.isErr()) {
31
+ return rootDirResult;
32
+ }
33
+ return await consume(rootDirResult.value);
34
+ };
35
+
36
+ export const withFreshAppLease = async <T>(
37
+ modulePath: string | undefined,
38
+ rootDir: string,
39
+ consume: (
40
+ lease: FreshAppLease
41
+ ) => Result<T, Error> | Promise<Result<T, Error>>
42
+ ): Promise<Result<T, Error>> => {
43
+ const leaseResult = await tryLoadFreshAppLease(modulePath, rootDir);
44
+ if (leaseResult.isErr()) {
45
+ return leaseResult;
46
+ }
47
+ const lease = leaseResult.value;
48
+ try {
49
+ return await consume(lease);
50
+ } finally {
51
+ lease.release();
52
+ }
53
+ };
54
+
55
+ export const withFreshOperatorApp = async <T>(
56
+ input: FreshAppInput,
57
+ ctx: RootDirContext,
58
+ consume: (
59
+ context: FreshAppContext
60
+ ) => Result<T, Error> | Promise<Result<T, Error>>
61
+ ): Promise<Result<T, Error>> =>
62
+ withOperatorRootDir(input, ctx, (rootDir) =>
63
+ withFreshAppLease(input.module, rootDir, (lease) =>
64
+ consume({ lease, rootDir })
65
+ )
66
+ );
@@ -0,0 +1,304 @@
1
+ import { z } from 'zod';
2
+
3
+ import { Result, ValidationError } from '@ontrails/core';
4
+ import type { WorkspaceView } from '@ontrails/topography';
5
+
6
+ import type {
7
+ OperatorAppProjectContext,
8
+ OperatorModuleSource,
9
+ OperatorProjectContext,
10
+ OperatorSelectionProvenance,
11
+ OperatorWorkspaceProjectContext,
12
+ } from './project-context.js';
13
+
14
+ const moduleSourceSchema = z.enum(['config', 'convention', 'module']);
15
+ const selectionProvenanceSchema = z.enum(['app', 'cwd', 'root-dir']);
16
+
17
+ const projectAppBaseSchema = z.object({
18
+ appRoot: z.string(),
19
+ artifactPath: z.string(),
20
+ modulePath: z.string(),
21
+ moduleSource: moduleSourceSchema,
22
+ });
23
+
24
+ const projectAppSchema = projectAppBaseSchema.extend({
25
+ appId: z.string(),
26
+ });
27
+
28
+ const configuredProjectAppSchema = projectAppSchema
29
+ .extend({ configured: z.literal(true) })
30
+ .strict();
31
+
32
+ const standaloneProjectAppSchema = projectAppBaseSchema
33
+ .extend({ configured: z.literal(false) })
34
+ .strict();
35
+
36
+ const projectContextBase = {
37
+ configuredAppIds: z.array(z.string()),
38
+ projectRoot: z.string(),
39
+ selectionProvenance: selectionProvenanceSchema,
40
+ } as const;
41
+
42
+ const appProjectSelectionShape = {
43
+ ...projectContextBase,
44
+ ...projectAppSchema.shape,
45
+ completeness: z.literal('complete'),
46
+ } as const;
47
+
48
+ export const configuredAppProjectSelectionSchema = z.object({
49
+ ...appProjectSelectionShape,
50
+ selectedExtent: z.literal('configured-app'),
51
+ });
52
+
53
+ export const standaloneAppProjectSelectionSchema = z.object({
54
+ ...appProjectSelectionShape,
55
+ selectedExtent: z.literal('standalone-app'),
56
+ });
57
+
58
+ export const appProjectSelectionSchema = z.discriminatedUnion(
59
+ 'selectedExtent',
60
+ [configuredAppProjectSelectionSchema, standaloneAppProjectSelectionSchema]
61
+ );
62
+
63
+ export const workspaceProjectSelectionSchema = z.object({
64
+ ...projectContextBase,
65
+ apps: z.array(
66
+ projectAppSchema.extend({
67
+ binding: z.literal('matched'),
68
+ freshness: z.literal('fresh'),
69
+ status: z.literal('available'),
70
+ })
71
+ ),
72
+ completeness: z.literal('complete'),
73
+ selectedExtent: z.literal('workspace'),
74
+ workspaceViewHash: z.string(),
75
+ });
76
+
77
+ export const operatorProjectContextOutputSchema = z
78
+ .discriminatedUnion('selectedExtent', [
79
+ z.object({
80
+ ...projectContextBase,
81
+ app: configuredProjectAppSchema,
82
+ selectedExtent: z.literal('configured-app'),
83
+ }),
84
+ z.object({
85
+ ...projectContextBase,
86
+ app: standaloneProjectAppSchema,
87
+ configuredAppIds: z.tuple([]),
88
+ selectedExtent: z.literal('standalone-app'),
89
+ selectionProvenance: z.enum(['cwd', 'root-dir']),
90
+ }),
91
+ z.object({
92
+ ...projectContextBase,
93
+ apps: z.array(configuredProjectAppSchema),
94
+ selectedExtent: z.literal('workspace'),
95
+ selectionProvenance: z.enum(['cwd', 'root-dir']),
96
+ }),
97
+ ])
98
+ .superRefine((value, ctx) => {
99
+ if (value.selectedExtent === 'configured-app') {
100
+ if (!value.configuredAppIds.includes(value.app.appId)) {
101
+ ctx.addIssue({
102
+ code: 'custom',
103
+ message:
104
+ 'The selected configured app must be named in configuredAppIds.',
105
+ path: ['configuredAppIds'],
106
+ });
107
+ }
108
+ return;
109
+ }
110
+ if (value.selectedExtent !== 'workspace') {
111
+ return;
112
+ }
113
+ const appIds = value.apps.map((app) => app.appId).toSorted();
114
+ const configuredAppIds = [...value.configuredAppIds].toSorted();
115
+ if (JSON.stringify(appIds) !== JSON.stringify(configuredAppIds)) {
116
+ ctx.addIssue({
117
+ code: 'custom',
118
+ message: 'Workspace apps must exactly match configuredAppIds.',
119
+ path: ['apps'],
120
+ });
121
+ }
122
+ });
123
+
124
+ export type OperatorProjectContextOutput = z.output<
125
+ typeof operatorProjectContextOutputSchema
126
+ >;
127
+
128
+ export type ConfiguredAppProjectSelection = z.output<
129
+ typeof configuredAppProjectSelectionSchema
130
+ >;
131
+ export type StandaloneAppProjectSelection = z.output<
132
+ typeof standaloneAppProjectSelectionSchema
133
+ >;
134
+ export type AppProjectSelection =
135
+ | ConfiguredAppProjectSelection
136
+ | StandaloneAppProjectSelection;
137
+ export type WorkspaceProjectSelection = z.output<
138
+ typeof workspaceProjectSelectionSchema
139
+ >;
140
+
141
+ const configuredIds = (
142
+ context: OperatorAppProjectContext | OperatorWorkspaceProjectContext
143
+ ): string[] => context.identity.apps.map((app) => app.id).toSorted();
144
+
145
+ const configuredProjectApp = (
146
+ app: OperatorAppProjectContext['app']
147
+ ): z.output<typeof configuredProjectAppSchema> => ({
148
+ appId: app.id as string,
149
+ appRoot: app.root,
150
+ artifactPath: app.lockPath,
151
+ configured: true,
152
+ modulePath: app.modulePath,
153
+ moduleSource: app.moduleSource,
154
+ });
155
+
156
+ const standaloneProjectApp = (
157
+ app: OperatorAppProjectContext['app']
158
+ ): z.output<typeof standaloneProjectAppSchema> => ({
159
+ appRoot: app.root,
160
+ artifactPath: app.lockPath,
161
+ configured: false,
162
+ modulePath: app.modulePath,
163
+ moduleSource: app.moduleSource,
164
+ });
165
+
166
+ export const operatorProjectContextOutput = (
167
+ context: OperatorProjectContext
168
+ ): OperatorProjectContextOutput => {
169
+ const base = {
170
+ configuredAppIds: context.identity.apps.map((app) => app.id).toSorted(),
171
+ projectRoot: context.projectRoot,
172
+ selectionProvenance: context.selectionProvenance,
173
+ } as const;
174
+ if (context.selectedExtent === 'workspace') {
175
+ return {
176
+ ...base,
177
+ apps: context.apps.map(configuredProjectApp),
178
+ selectedExtent: 'workspace',
179
+ selectionProvenance: context.selectionProvenance as 'cwd' | 'root-dir',
180
+ };
181
+ }
182
+ if (context.selectedExtent === 'standalone-app') {
183
+ return {
184
+ app: standaloneProjectApp(context.app),
185
+ configuredAppIds: [],
186
+ projectRoot: context.projectRoot,
187
+ selectedExtent: 'standalone-app',
188
+ selectionProvenance: context.selectionProvenance as 'cwd' | 'root-dir',
189
+ };
190
+ }
191
+ return {
192
+ ...base,
193
+ app: configuredProjectApp(context.app),
194
+ selectedExtent: 'configured-app',
195
+ };
196
+ };
197
+
198
+ const appProjectSelectionBase = (
199
+ context: OperatorAppProjectContext,
200
+ actualAppId: string
201
+ ): Omit<AppProjectSelection, 'selectedExtent'> => ({
202
+ appId: actualAppId,
203
+ appRoot: context.app.root,
204
+ artifactPath: context.app.lockPath,
205
+ completeness: 'complete',
206
+ configuredAppIds: configuredIds(context),
207
+ modulePath: context.app.modulePath,
208
+ moduleSource: context.app.moduleSource as OperatorModuleSource,
209
+ projectRoot: context.projectRoot,
210
+ selectionProvenance:
211
+ context.selectionProvenance as OperatorSelectionProvenance,
212
+ });
213
+
214
+ export const configuredAppProjectSelection = (
215
+ context: OperatorAppProjectContext,
216
+ actualAppId: string
217
+ ): ConfiguredAppProjectSelection => ({
218
+ ...appProjectSelectionBase(context, actualAppId),
219
+ selectedExtent: 'configured-app',
220
+ });
221
+
222
+ export const standaloneAppProjectSelection = (
223
+ context: OperatorAppProjectContext,
224
+ actualAppId: string
225
+ ): StandaloneAppProjectSelection => ({
226
+ ...appProjectSelectionBase(context, actualAppId),
227
+ selectedExtent: 'standalone-app',
228
+ });
229
+
230
+ export const appProjectSelection = (
231
+ context: OperatorAppProjectContext,
232
+ actualAppId: string
233
+ ): AppProjectSelection =>
234
+ context.selectedExtent === 'configured-app'
235
+ ? configuredAppProjectSelection(context, actualAppId)
236
+ : standaloneAppProjectSelection(context, actualAppId);
237
+
238
+ export const workspaceProjectSelection = (
239
+ context: OperatorWorkspaceProjectContext,
240
+ view: WorkspaceView
241
+ ): Result<WorkspaceProjectSelection, ValidationError> => {
242
+ if (
243
+ view.workspaceViewHash === null ||
244
+ view.evidence.configuredCompleteness !== 'complete'
245
+ ) {
246
+ return Result.err(
247
+ new ValidationError(
248
+ 'A workspace selection requires a complete workspace view.',
249
+ {
250
+ context: {
251
+ completeness: view.evidence.configuredCompleteness,
252
+ projectRoot: context.projectRoot,
253
+ reason: 'workspace-incomplete',
254
+ },
255
+ }
256
+ )
257
+ );
258
+ }
259
+ const observations = new Map(
260
+ view.evidence.apps.map((observation) => [observation.id, observation])
261
+ );
262
+ const apps: WorkspaceProjectSelection['apps'][number][] = [];
263
+ for (const app of context.apps) {
264
+ const observation = observations.get(app.id as string);
265
+ if (
266
+ observation?.binding !== 'matched' ||
267
+ observation.freshness !== 'fresh' ||
268
+ observation.status !== 'available'
269
+ ) {
270
+ return Result.err(
271
+ new ValidationError(
272
+ `Workspace app ${String(app.id)} is not complete and fresh.`,
273
+ {
274
+ context: {
275
+ appId: app.id,
276
+ evidence: observation,
277
+ projectRoot: context.projectRoot,
278
+ reason: 'workspace-incomplete',
279
+ },
280
+ }
281
+ )
282
+ );
283
+ }
284
+ apps.push({
285
+ appId: app.id as string,
286
+ appRoot: app.root,
287
+ artifactPath: app.lockPath,
288
+ binding: observation.binding,
289
+ freshness: observation.freshness,
290
+ modulePath: app.modulePath,
291
+ moduleSource: app.moduleSource,
292
+ status: observation.status,
293
+ });
294
+ }
295
+ return Result.ok({
296
+ apps,
297
+ completeness: 'complete',
298
+ configuredAppIds: configuredIds(context),
299
+ projectRoot: context.projectRoot,
300
+ selectedExtent: 'workspace',
301
+ selectionProvenance: context.selectionProvenance,
302
+ workspaceViewHash: view.workspaceViewHash,
303
+ });
304
+ };