@ontrails/trails 1.0.0-beta.12 → 1.0.0-beta.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # trails
2
2
 
3
+ ## 1.0.0-beta.13
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [6944147]
8
+ - Updated dependencies
9
+ - @ontrails/core@1.0.0-beta.13
10
+ - @ontrails/cli@1.0.0-beta.13
11
+ - @ontrails/schema@1.0.0-beta.13
12
+ - @ontrails/warden@1.0.0-beta.13
13
+ - @ontrails/logging@1.0.0-beta.13
14
+
3
15
  ## 1.0.0-beta.12
4
16
 
5
17
  ### Patch Changes
@@ -17,17 +29,17 @@
17
29
 
18
30
  - Add services as a first-class primitive.
19
31
 
20
- Services make infrastructure dependencies declarative, injectable, and governable. Define a service with `service()`, declare it on a trail with `services: [db]`, and access it with `db.from(ctx)` or `ctx.service()`.
32
+ Services make infrastructure dependencies declarative, injectable, and governable. Define a service with `provision()`, declare it on a trail with `provisions: [db]`, and access it with `db.from(ctx)` or `ctx.provision()`.
21
33
 
22
- **Core:** `service()` factory, `ServiceSpec<T>`, `ServiceContext`, singleton resolution in `executeTrail`, in-flight creation dedup, `isService` guard, `findDuplicateServiceId`, topo service discovery and validation, `services` field on trail specs.
34
+ **Core:** `provision()` factory, `ServiceSpec<T>`, `ServiceContext`, singleton resolution in `executeTrail`, in-flight creation dedup, `isService` guard, `findDuplicateServiceId`, topo service discovery and validation, `services` field on trail specs.
23
35
 
24
- **Testing:** Auto-resolution of `mock` factories in `testAll`, `testExamples`, `testContracts`, and `testFollows`. Explicit `services` overrides with correct precedence (`explicit > ctx.extensions > auto-mock`). Service mock propagation through follow graphs.
36
+ **Testing:** Auto-resolution of `mock` factories in `testAll`, `testExamples`, `testContracts`, and `testCrosses`. Explicit `services` overrides with correct precedence (`explicit > ctx.extensions > auto-mock`). Service mock propagation through crossing graphs.
25
37
 
26
- **Warden:** `service-declarations` rule validates `db.from(ctx)` and `ctx.service()` usage matches declared `services: [...]`. `service-exists` rule validates declared service IDs resolve in project context. Scope-aware AST walking skips nested function boundaries.
38
+ **Warden:** `service-declarations` rule validates `db.from(ctx)` and `ctx.provision()` usage matches declared `provisions: [...]`. `service-exists` rule validates declared service IDs resolve in project context. Scope-aware AST walking skips nested function boundaries.
27
39
 
28
- **Surfaces:** Service overrides thread through `dispatch` and `blaze` on CLI, MCP, and HTTP.
40
+ **Trailheads:** Service overrides thread through `run` and `trailhead` on CLI, MCP, and HTTP.
29
41
 
30
- **Introspection:** Survey and surface map outputs include service graph. Topo exposes `.services`, `.getService()`, `.hasService()`, `.listServices()`, `.serviceIds()`, `.serviceCount`.
42
+ **Introspection:** Survey and trailhead map outputs include service graph. Topo exposes `.services`, `.getService()`, `.hasService()`, `.listServices()`, `.serviceIds()`, `.serviceCount`.
31
43
 
32
44
  **Docs:** ADR-009 accepted. Unified services guide, updated vocabulary, getting-started, architecture, and package READMEs.
33
45
 
@@ -75,9 +87,9 @@
75
87
 
76
88
  ### Minor Changes
77
89
 
78
- - HTTP surface and OpenAPI generation.
90
+ - HTTP trailhead and OpenAPI generation.
79
91
 
80
- **http**: New `@ontrails/http` package — Hono-based HTTP adapter. `blaze()` derives routes from trail IDs, maps intent to HTTP verbs (read→GET, write→POST, destroy→DELETE), and maps error taxonomy to status codes. Returns the Hono instance.
92
+ **http**: New `@ontrails/http` package — Hono-based HTTP connector. `trailhead()` derives routes from trail IDs, maps intent to HTTP verbs (read→GET, write→POST, destroy→DELETE), and maps error taxonomy to status codes. Returns the Hono instance.
81
93
 
82
94
  **schema**: Add `generateOpenApiSpec(topo)` — generates a complete OpenAPI 3.1 spec from the topo. Each trail becomes an operation with path, method, schemas, and error responses derived from the contract.
83
95
 
@@ -122,15 +134,15 @@
122
134
 
123
135
  **BREAKING CHANGES:**
124
136
 
125
- - `hike()` removed — use `trail()` with optional `follow: [...]` field
126
- - `follows` renamed to `follow` (singular, matching `ctx.follow()`)
137
+ - `hike()` removed — use `trail()` with optional `crosses: [...]` field
138
+ - `follows` renamed to `crosses` (matching `ctx.cross()`)
127
139
  - `topo.hikes` removed — single `topo.trails` map
128
140
  - `kind: 'hike'` removed — everything is `kind: 'trail'`
129
141
  - `readOnly`/`destructive` booleans replaced by `intent: 'read' | 'write' | 'destroy'`
130
142
  - `implementation` field renamed to `run`
131
143
  - `markers` field renamed to `metadata`
132
- - `testHike` renamed to `testFollows`, `HikeScenario` to `FollowScenario`
133
- - `blaze()` now returns the surface handle (`Command` for CLI, `Server` for MCP)
144
+ - `testHike` renamed to `testCrosses`, `HikeScenario` to `CrossScenario`
145
+ - `trailhead()` now returns the trailhead handle (`Command` for CLI, `Server` for MCP)
134
146
 
135
147
  ### Patch Changes
136
148
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ontrails/trails",
3
- "version": "1.0.0-beta.12",
3
+ "version": "1.0.0-beta.13",
4
4
  "bin": {
5
5
  "trails": "./bin/trails.ts"
6
6
  },
@@ -11,19 +11,19 @@ import { basename, dirname, join } from 'node:path';
11
11
 
12
12
  import { Result } from '@ontrails/core';
13
13
 
14
- import { addSurface } from '../trails/add-surface.js';
14
+ import { addTrailhead } from '../trails/add-trailhead.js';
15
15
  import { addVerify } from '../trails/add-verify.js';
16
16
  import { createRoute } from '../trails/create.js';
17
17
  import { createScaffold } from '../trails/create-scaffold.js';
18
18
  import { isInsideProject } from '../trails/project.js';
19
19
 
20
20
  type Starter = 'empty' | 'entity' | 'hello';
21
- type Surface = 'cli' | 'mcp';
21
+ type Trailhead = 'cli' | 'mcp';
22
22
 
23
23
  const makeTempProject = (): string =>
24
24
  join(
25
25
  tmpdir(),
26
- `trails-blaze-test-${Date.now()}-${Math.random().toString(36).slice(2)}`
26
+ `trails-create-test-${Date.now()}-${Math.random().toString(36).slice(2)}`
27
27
  );
28
28
 
29
29
  const readJson = (dir: string, relativePath: string): Record<string, unknown> =>
@@ -68,43 +68,43 @@ const expectErr = <T>(result: Result<T, Error>): Error => {
68
68
  return result.error;
69
69
  };
70
70
 
71
- const runFollow = async (
71
+ const runCross = async (
72
72
  id: string,
73
73
  input: unknown
74
74
  ): Promise<Result<unknown, Error>> => {
75
75
  switch (id) {
76
76
  case 'create.scaffold': {
77
- return await createScaffold.run(input as never, {} as never);
77
+ return await createScaffold.blaze(input as never, {} as never);
78
78
  }
79
- case 'add.surface': {
80
- return await addSurface.run(input as never, {} as never);
79
+ case 'add.trailhead': {
80
+ return await addTrailhead.blaze(input as never, {} as never);
81
81
  }
82
82
  case 'add.verify': {
83
- return await addVerify.run(input as never, {} as never);
83
+ return await addVerify.blaze(input as never, {} as never);
84
84
  }
85
85
  default: {
86
- return Result.err(new Error(`Unknown follow target: ${id}`));
86
+ return Result.err(new Error(`Unknown cross target: ${id}`));
87
87
  }
88
88
  }
89
89
  };
90
90
 
91
- const runBlaze = (
91
+ const runCreate = (
92
92
  projectDir: string,
93
93
  overrides?: Partial<{
94
94
  starter: Starter;
95
- surfaces: readonly Surface[];
95
+ trailheads: readonly Trailhead[];
96
96
  verify: boolean;
97
97
  }>
98
98
  ) =>
99
- createRoute.run(
99
+ createRoute.blaze(
100
100
  {
101
101
  dir: dirname(projectDir),
102
102
  name: basename(projectDir),
103
103
  starter: overrides?.starter ?? 'hello',
104
- surfaces: [...(overrides?.surfaces ?? ['cli'])],
104
+ trailheads: [...(overrides?.trailheads ?? ['cli'])],
105
105
  verify: overrides?.verify ?? true,
106
106
  },
107
- { follow: runFollow } as never
107
+ { cross: runCross } as never
108
108
  );
109
109
 
110
110
  const setupMinimalProject = (dir: string): void => {
@@ -177,7 +177,7 @@ const assertEntityStarter = (dir: string): void => {
177
177
  'src/trails/entity.ts',
178
178
  'src/trails/search.ts',
179
179
  'src/trails/onboard.ts',
180
- 'src/events/entity-events.ts',
180
+ 'src/signals/entity-signals.ts',
181
181
  'src/store.ts',
182
182
  ],
183
183
  true
@@ -187,7 +187,7 @@ const assertEntityStarter = (dir: string): void => {
187
187
  "import * as entity from './trails/entity.js'",
188
188
  "import * as search from './trails/search.js'",
189
189
  "import * as onboard from './trails/onboard.js'",
190
- "import * as entityEvents from './events/entity-events.js'",
190
+ "import * as entitySignals from './signals/entity-signals.js'",
191
191
  ]);
192
192
  expectContainsAll(readText(dir, 'src/trails/entity.ts'), [
193
193
  "import { Result, trail } from '@ontrails/core'",
@@ -204,12 +204,12 @@ const assertEntityStarter = (dir: string): void => {
204
204
  ]);
205
205
  };
206
206
 
207
- const assertMcpSurface = (dir: string): void => {
207
+ const assertMcpTrailhead = (dir: string): void => {
208
208
  expectPaths(dir, ['src/mcp.ts'], true);
209
209
  expectPaths(dir, ['src/cli.ts'], false);
210
210
  expectContainsAll(readText(dir, 'src/mcp.ts'), [
211
- "import { blaze } from '@ontrails/mcp'",
212
- 'await blaze(app)',
211
+ "import { trailhead } from '@ontrails/mcp'",
212
+ 'await trailhead(app)',
213
213
  ]);
214
214
 
215
215
  const deps = readJson(dir, 'package.json')['dependencies'] as Record<
@@ -244,11 +244,11 @@ const withTempProject = async (
244
244
  }
245
245
  };
246
246
 
247
- describe('trails blaze', () => {
247
+ describe('trails create', () => {
248
248
  describe('create mode', () => {
249
249
  test('generates project structure with defaults', async () => {
250
250
  await withTempProject(async (dir) => {
251
- expectOk(await runBlaze(dir));
251
+ expectOk(await runCreate(dir));
252
252
  assertDefaultProjectFiles(dir);
253
253
  assertCliPackage(dir);
254
254
  assertHelloApp(dir);
@@ -257,46 +257,46 @@ describe('trails blaze', () => {
257
257
 
258
258
  test('generates with entity starter', async () => {
259
259
  await withTempProject(async (dir) => {
260
- expectOk(await runBlaze(dir, { starter: 'entity' }));
260
+ expectOk(await runCreate(dir, { starter: 'entity' }));
261
261
  assertEntityStarter(dir);
262
262
  });
263
263
  });
264
264
 
265
- test('generates with MCP surface', async () => {
265
+ test('generates with MCP trailhead', async () => {
266
266
  await withTempProject(async (dir) => {
267
- expectOk(await runBlaze(dir, { surfaces: ['mcp'] }));
268
- assertMcpSurface(dir);
267
+ expectOk(await runCreate(dir, { trailheads: ['mcp'] }));
268
+ assertMcpTrailhead(dir);
269
269
  });
270
270
  });
271
271
 
272
272
  test('skips verification when verify is false', async () => {
273
273
  await withTempProject(async (dir) => {
274
- expectOk(await runBlaze(dir, { verify: false }));
274
+ expectOk(await runCreate(dir, { verify: false }));
275
275
  assertVerifySkipped(dir);
276
276
  });
277
277
  });
278
278
 
279
279
  test('generates with empty starter', async () => {
280
280
  await withTempProject(async (dir) => {
281
- expectOk(await runBlaze(dir, { starter: 'empty' }));
281
+ expectOk(await runCreate(dir, { starter: 'empty' }));
282
282
  assertEmptyStarter(dir);
283
283
  });
284
284
  });
285
285
  });
286
286
 
287
- describe('add-surface mode', () => {
287
+ describe('add-trailhead mode', () => {
288
288
  test('adds MCP to existing project', async () => {
289
289
  await withTempProject(async (dir) => {
290
290
  setupMinimalProject(dir);
291
291
  const result = expectOk(
292
- await addSurface.run({ dir, surface: 'mcp' }, {} as never)
292
+ await addTrailhead.blaze({ dir, trailhead: 'mcp' }, {} as never)
293
293
  );
294
294
 
295
295
  expect(result.created).toBe('src/mcp.ts');
296
296
  expect(result.dependency).toBe('@ontrails/mcp');
297
297
  expectPaths(dir, ['src/mcp.ts'], true);
298
298
  expectContainsAll(readText(dir, 'src/mcp.ts'), [
299
- "import { blaze } from '@ontrails/mcp'",
299
+ "import { trailhead } from '@ontrails/mcp'",
300
300
  ]);
301
301
  const deps = readJson(dir, 'package.json')['dependencies'] as Record<
302
302
  string,
@@ -306,16 +306,18 @@ describe('trails blaze', () => {
306
306
  });
307
307
  });
308
308
 
309
- test('detects already-blazed surface', async () => {
309
+ test('detects existing trailhead entrypoint', async () => {
310
310
  await withTempProject(async (dir) => {
311
311
  mkdirSync(join(dir, 'src'), { recursive: true });
312
312
  mkdirSync(join(dir, '.trails'), { recursive: true });
313
313
  writeFileSync(join(dir, 'src', 'mcp.ts'), 'existing content');
314
314
 
315
315
  const error = expectErr(
316
- await addSurface.run({ dir, surface: 'mcp' }, {} as never)
316
+ await addTrailhead.blaze({ dir, trailhead: 'mcp' }, {} as never)
317
+ );
318
+ expect(error.message).toBe(
319
+ 'MCP trailhead already exists. Nothing to do.'
317
320
  );
318
- expect(error.message).toBe('MCP is already blazed. Nothing to do.');
319
321
  });
320
322
  });
321
323
  });
@@ -9,6 +9,10 @@ import { z } from 'zod';
9
9
  // ---------------------------------------------------------------------------
10
10
 
11
11
  const helloTrail = trail('hello', {
12
+ blaze: (input) => {
13
+ const name = input.name ?? 'world';
14
+ return Result.ok({ message: `Hello, ${name}!` });
15
+ },
12
16
  description: 'Say hello',
13
17
  detours: {
14
18
  NotFoundError: ['search'],
@@ -28,10 +32,6 @@ const helloTrail = trail('hello', {
28
32
  input: z.object({ name: z.string().optional() }),
29
33
  intent: 'read',
30
34
  output: z.object({ message: z.string() }),
31
- run: (input) => {
32
- const name = input.name ?? 'world';
33
- return Result.ok({ message: `Hello, ${name}!` });
34
- },
35
35
  });
36
36
 
37
37
  const app = topo('test-app', { hello: helloTrail });
@@ -1,12 +1,12 @@
1
1
  import { describe, expect, test } from 'bun:test';
2
2
 
3
- import { Result, service, topo, trail } from '@ontrails/core';
3
+ import { Result, provision, topo, trail } from '@ontrails/core';
4
4
  import {
5
- generateSurfaceMap,
6
- hashSurfaceMap,
7
- diffSurfaceMaps,
5
+ generateTrailheadMap,
6
+ hashTrailheadMap,
7
+ diffTrailheadMaps,
8
8
  } from '@ontrails/schema';
9
- import type { SurfaceMap } from '@ontrails/schema';
9
+ import type { TrailheadMap } from '@ontrails/schema';
10
10
  import { z } from 'zod';
11
11
 
12
12
  import {
@@ -25,6 +25,10 @@ import type {
25
25
  // ---------------------------------------------------------------------------
26
26
 
27
27
  const helloTrail = trail('hello', {
28
+ blaze: (input) => {
29
+ const name = input.name ?? 'world';
30
+ return Result.ok({ message: `Hello, ${name}!` });
31
+ },
28
32
  description: 'Say hello',
29
33
  detours: {
30
34
  NotFoundError: ['search'],
@@ -39,32 +43,28 @@ const helloTrail = trail('hello', {
39
43
  input: z.object({ name: z.string().optional() }),
40
44
  intent: 'read',
41
45
  output: z.object({ message: z.string() }),
42
- run: (input) => {
43
- const name = input.name ?? 'world';
44
- return Result.ok({ message: `Hello, ${name}!` });
45
- },
46
- services: [
47
- service('db.main', {
46
+ provisions: [
47
+ provision('db.main', {
48
48
  create: () => Result.ok({ source: 'factory' }),
49
49
  }),
50
50
  ],
51
51
  });
52
52
 
53
53
  const byeTrail = trail('bye', {
54
+ blaze: (input) => Result.ok({ message: `Goodbye, ${input.name}!` }),
54
55
  description: 'Say goodbye',
55
56
  input: z.object({ name: z.string() }),
56
57
  output: z.object({ message: z.string() }),
57
- run: (input) => Result.ok({ message: `Goodbye, ${input.name}!` }),
58
58
  });
59
59
 
60
- const [dbService] = helloTrail.services;
61
- if (!dbService) {
60
+ const [dbProvision] = helloTrail.provisions;
61
+ if (!dbProvision) {
62
62
  throw new Error('Expected helloTrail to declare db.main');
63
63
  }
64
64
 
65
65
  const app = topo('test-app', {
66
66
  bye: byeTrail,
67
- dbService,
67
+ dbProvision,
68
68
  hello: helloTrail,
69
69
  });
70
70
 
@@ -73,46 +73,46 @@ const app = topo('test-app', {
73
73
  // ---------------------------------------------------------------------------
74
74
 
75
75
  describe('trails survey', () => {
76
- test('generateSurfaceMap includes all trails', () => {
77
- const surfaceMap = generateSurfaceMap(app);
78
- expect(surfaceMap.entries.length).toBe(3);
79
- const ids = surfaceMap.entries.map((e) => e.id);
76
+ test('generateTrailheadMap includes all trails', () => {
77
+ const trailheadMap = generateTrailheadMap(app);
78
+ expect(trailheadMap.entries.length).toBe(3);
79
+ const ids = trailheadMap.entries.map((e) => e.id);
80
80
  expect(ids).toContain('hello');
81
81
  expect(ids).toContain('bye');
82
82
  expect(ids).toContain('db.main');
83
83
  });
84
84
 
85
- test('surface map entries have expected fields', () => {
86
- const surfaceMap = generateSurfaceMap(app);
87
- const hello = surfaceMap.entries.find((e) => e.id === 'hello');
85
+ test('trailhead map entries have expected fields', () => {
86
+ const trailheadMap = generateTrailheadMap(app);
87
+ const hello = trailheadMap.entries.find((e) => e.id === 'hello');
88
88
  expect(hello).toBeDefined();
89
89
  expect(hello?.kind).toBe('trail');
90
90
  expect(hello?.intent).toBe('read');
91
91
  expect(hello?.exampleCount).toBe(1);
92
- expect(hello?.services).toEqual(['db.main']);
92
+ expect(hello?.provisions).toEqual(['db.main']);
93
93
  });
94
94
 
95
95
  test('JSON output is valid JSON', () => {
96
- const surfaceMap = generateSurfaceMap(app);
97
- const json = JSON.stringify(surfaceMap, null, 2);
98
- const parsed = JSON.parse(json) as SurfaceMap;
96
+ const trailheadMap = generateTrailheadMap(app);
97
+ const json = JSON.stringify(trailheadMap, null, 2);
98
+ const parsed = JSON.parse(json) as TrailheadMap;
99
99
  expect(parsed.version).toBe('1.0');
100
100
  expect(parsed.entries.length).toBe(3);
101
101
  });
102
102
 
103
- test('hashSurfaceMap produces stable hash', () => {
104
- const surfaceMap = generateSurfaceMap(app);
105
- const hash1 = hashSurfaceMap(surfaceMap);
106
- const hash2 = hashSurfaceMap(surfaceMap);
103
+ test('hashTrailheadMap produces stable hash', () => {
104
+ const trailheadMap = generateTrailheadMap(app);
105
+ const hash1 = hashTrailheadMap(trailheadMap);
106
+ const hash2 = hashTrailheadMap(trailheadMap);
107
107
  expect(hash1).toBe(hash2);
108
108
  // SHA-256 hex
109
109
  expect(hash1.length).toBe(64);
110
110
  });
111
111
 
112
- test('diffSurfaceMaps detects added trails', () => {
113
- const prev = generateSurfaceMap(topo('test', { hello: helloTrail }));
114
- const curr = generateSurfaceMap(app);
115
- const diff = diffSurfaceMaps(prev, curr);
112
+ test('diffTrailheadMaps detects added trails', () => {
113
+ const prev = generateTrailheadMap(topo('test', { hello: helloTrail }));
114
+ const curr = generateTrailheadMap(app);
115
+ const diff = diffTrailheadMaps(prev, curr);
116
116
 
117
117
  expect(diff.info.length).toBeGreaterThan(0);
118
118
  const addedBye = diff.info.find((e) => e.id === 'bye');
@@ -120,10 +120,10 @@ describe('trails survey', () => {
120
120
  expect(addedBye?.change).toBe('added');
121
121
  });
122
122
 
123
- test('diffSurfaceMaps detects removed trails', () => {
124
- const prev = generateSurfaceMap(app);
125
- const curr = generateSurfaceMap(topo('test', { hello: helloTrail }));
126
- const diff = diffSurfaceMaps(prev, curr);
123
+ test('diffTrailheadMaps detects removed trails', () => {
124
+ const prev = generateTrailheadMap(app);
125
+ const curr = generateTrailheadMap(topo('test', { hello: helloTrail }));
126
+ const diff = diffTrailheadMaps(prev, curr);
127
127
 
128
128
  expect(diff.hasBreaking).toBe(true);
129
129
  const removedBye = diff.breaking.find((e) => e.id === 'bye');
@@ -131,9 +131,9 @@ describe('trails survey', () => {
131
131
  expect(removedBye?.change).toBe('removed');
132
132
  });
133
133
 
134
- test('diffSurfaceMaps returns empty for identical maps', () => {
135
- const map = generateSurfaceMap(app);
136
- const diff = diffSurfaceMaps(map, map);
134
+ test('diffTrailheadMaps returns empty for identical maps', () => {
135
+ const trailheadMap = generateTrailheadMap(app);
136
+ const diff = diffTrailheadMaps(trailheadMap, trailheadMap);
137
137
  expect(diff.entries.length).toBe(0);
138
138
  expect(diff.hasBreaking).toBe(false);
139
139
  });
@@ -153,8 +153,8 @@ describe('trails survey --brief', () => {
153
153
  test('report includes correct trail count', () => {
154
154
  const report = generateBriefReport(app);
155
155
  expect(report.trails).toBe(2);
156
- expect(report.events).toBe(0);
157
- expect(report.services).toBe(1);
156
+ expect(report.signals).toBe(0);
157
+ expect(report.provisions).toBe(1);
158
158
  });
159
159
 
160
160
  test('detects features in use', () => {
@@ -162,8 +162,8 @@ describe('trails survey --brief', () => {
162
162
  expect(report.features.outputSchemas).toBe(true);
163
163
  expect(report.features.examples).toBe(true);
164
164
  expect(report.features.detours).toBe(true);
165
- expect(report.features.events).toBe(false);
166
- expect(report.features.services).toBe(true);
165
+ expect(report.features.signals).toBe(false);
166
+ expect(report.features.provisions).toBe(true);
167
167
  });
168
168
 
169
169
  test('JSON output is valid', () => {
@@ -172,7 +172,7 @@ describe('trails survey --brief', () => {
172
172
  const parsed = JSON.parse(json) as BriefReport;
173
173
  expect(parsed.name).toBe('test-app');
174
174
  expect(parsed.trails).toBe(2);
175
- expect(parsed.services).toBe(1);
175
+ expect(parsed.provisions).toBe(1);
176
176
  });
177
177
 
178
178
  test('empty app reports zero features', () => {
@@ -182,33 +182,33 @@ describe('trails survey --brief', () => {
182
182
  expect(report.features.outputSchemas).toBe(false);
183
183
  expect(report.features.examples).toBe(false);
184
184
  expect(report.features.detours).toBe(false);
185
- expect(report.features.services).toBe(false);
185
+ expect(report.features.provisions).toBe(false);
186
186
  });
187
187
  });
188
188
 
189
189
  describe('trails survey detail', () => {
190
- test('trail detail includes declared services, follow, and intent', () => {
190
+ test('trail detail includes declared provisions, crossings, and intent', () => {
191
191
  const detail = generateTrailDetail(helloTrail);
192
192
  const parsed = structuredClone(detail) as TrailDetailReport;
193
193
 
194
- expect(parsed.follow).toEqual([]);
194
+ expect(parsed.crosses).toEqual([]);
195
195
  expect(parsed.intent).toBe('read');
196
- expect(parsed.services).toEqual(['db.main']);
196
+ expect(parsed.provisions).toEqual(['db.main']);
197
197
  });
198
198
  });
199
199
 
200
- describe('trails survey services section', () => {
201
- test('list output includes service lifetime and health status', () => {
200
+ describe('trails survey provisions section', () => {
201
+ test('list output includes provision lifetime and health status', () => {
202
202
  const report = generateSurveyList(app);
203
203
  const parsed = structuredClone(report) as SurveyListReport;
204
- const db = parsed.services.find((entry) => entry.id === 'db.main');
204
+ const db = parsed.provisions.find((entry) => entry.id === 'db.main');
205
205
 
206
- expect(parsed.serviceCount).toBe(1);
206
+ expect(parsed.provisionCount).toBe(1);
207
207
  expect(db).toEqual({
208
208
  description: null,
209
209
  health: 'none',
210
210
  id: 'db.main',
211
- kind: 'service',
211
+ kind: 'provision',
212
212
  lifetime: 'singleton',
213
213
  usedBy: ['hello'],
214
214
  });
@@ -21,7 +21,7 @@ describe('trails warden', () => {
21
21
  writeFileSync(
22
22
  join(dir, 'good.ts'),
23
23
  `trail("hello", {
24
- run: async (input, ctx) => {
24
+ blaze: async (input, ctx) => {
25
25
  return Result.ok({ message: "hi" });
26
26
  }
27
27
  })`
@@ -54,7 +54,7 @@ describe('trails warden', () => {
54
54
  writeFileSync(
55
55
  join(dir, 'bad.ts'),
56
56
  `trail("x", {
57
- run: async () => { throw new Error("boom"); }
57
+ blaze: async () => { throw new Error("boom"); }
58
58
  })`
59
59
  );
60
60
  const report = await runWarden({ driftOnly: true, rootDir: dir });
package/src/app.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { topo } from '@ontrails/core';
2
2
 
3
- import * as addSurface from './trails/add-surface.js';
3
+ import * as addTrailhead from './trails/add-trailhead.js';
4
4
  import * as addTrail from './trails/add-trail.js';
5
5
  import * as addVerify from './trails/add-verify.js';
6
6
  import * as create from './trails/create.js';
@@ -16,7 +16,7 @@ export const app = topo(
16
16
  warden,
17
17
  create,
18
18
  createScaffold,
19
- addSurface,
19
+ addTrailhead,
20
20
  addVerify,
21
21
  addTrail
22
22
  );
package/src/clack.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Clack-backed input resolver for the Trails CLI.
3
3
  *
4
- * This stays at the app layer so @ontrails/cli remains prompt-library agnostic.
4
+ * This stays at the app gate so @ontrails/cli remains prompt-library agnostic.
5
5
  */
6
6
 
7
7
  import type { Field, InputResolver, ResolveInputOptions } from '@ontrails/cli';
package/src/cli.ts CHANGED
@@ -1,11 +1,11 @@
1
1
  import { outputModePreset } from '@ontrails/cli';
2
- import { blaze } from '@ontrails/cli/commander';
2
+ import { trailhead } from '@ontrails/cli/commander';
3
3
 
4
4
  import { app } from './app.js';
5
5
  import { resolveInputWithClack } from './clack.js';
6
6
 
7
7
  // oxlint-disable-next-line require-hook -- CLI entry point
8
- blaze(app, {
8
+ trailhead(app, {
9
9
  description: 'Agent-native, contract-first TypeScript framework',
10
10
  name: 'trails',
11
11
  presets: [outputModePreset()],
@@ -29,7 +29,7 @@ export const ${id.replaceAll('.', '_')} = trail('${id}', {
29
29
  name: 'TODO: add example',
30
30
  },
31
31
  ],
32
- run: async (input) => {
32
+ blaze: async (input) => {
33
33
  return Result.ok({ message: 'TODO' });
34
34
  },
35
35
  input: z.object({}),${intentLine}
@@ -64,18 +64,7 @@ const writeWithDirs = async (
64
64
  };
65
65
 
66
66
  export const addTrail = trail('add.trail', {
67
- description: 'Scaffold a new trail with tests and examples',
68
- input: z.object({
69
- id: z.string().describe('Trail ID (e.g., entity.update)'),
70
- intent: z
71
- .enum(['read', 'write', 'destroy'])
72
- .default('write')
73
- .describe('Trail intent'),
74
- }),
75
- output: z.object({
76
- created: z.array(z.string()),
77
- }),
78
- run: async (input, ctx) => {
67
+ blaze: async (input, ctx) => {
79
68
  const { id } = input;
80
69
  const moduleName = id.replaceAll('.', '-');
81
70
  const cwd = resolve(ctx.cwd ?? '.');
@@ -91,4 +80,15 @@ export const addTrail = trail('add.trail', {
91
80
 
92
81
  return Result.ok({ created: [...files.keys()] });
93
82
  },
83
+ description: 'Scaffold a new trail with tests and examples',
84
+ input: z.object({
85
+ id: z.string().describe('Trail ID (e.g., entity.update)'),
86
+ intent: z
87
+ .enum(['read', 'write', 'destroy'])
88
+ .default('write')
89
+ .describe('Trail intent'),
90
+ }),
91
+ output: z.object({
92
+ created: z.array(z.string()),
93
+ }),
94
94
  });