@ontrails/trails 1.0.0-beta.15 → 1.0.0-beta.17

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 (201) hide show
  1. package/CHANGELOG.md +215 -2
  2. package/README.md +27 -0
  3. package/package.json +21 -8
  4. package/src/app.ts +15 -5
  5. package/src/cli.ts +303 -10
  6. package/src/completions.ts +240 -0
  7. package/src/load-app-mirror.ts +160 -0
  8. package/src/local-state-io.ts +153 -0
  9. package/src/project-writes.ts +320 -0
  10. package/src/run-collision.ts +125 -0
  11. package/src/run-completions-install.ts +179 -0
  12. package/src/run-example.ts +149 -0
  13. package/src/run-examples.ts +148 -0
  14. package/src/run-quiet.ts +75 -0
  15. package/src/run-trace.ts +273 -0
  16. package/src/run-warden.ts +39 -0
  17. package/src/run-watch.ts +432 -0
  18. package/src/scaffold-versions.generated.ts +12 -0
  19. package/src/trails/add-surface.ts +45 -23
  20. package/src/trails/add-trail.ts +27 -17
  21. package/src/trails/add-verify.ts +57 -17
  22. package/src/trails/completions-complete.ts +165 -0
  23. package/src/trails/completions.ts +47 -0
  24. package/src/trails/create-scaffold.ts +86 -33
  25. package/src/trails/create.ts +11 -3
  26. package/src/trails/dev-clean.ts +6 -1
  27. package/src/trails/dev-reset.ts +6 -1
  28. package/src/trails/dev-stats.ts +6 -1
  29. package/src/trails/dev-support.ts +29 -17
  30. package/src/trails/draft-promote.ts +289 -80
  31. package/src/trails/guide.ts +54 -34
  32. package/src/trails/load-app.ts +251 -56
  33. package/src/trails/root-dir.ts +21 -0
  34. package/src/trails/run-example.ts +482 -0
  35. package/src/trails/run-examples.ts +141 -0
  36. package/src/trails/run.ts +403 -0
  37. package/src/trails/survey.ts +534 -200
  38. package/src/trails/topo-activation.ts +385 -0
  39. package/src/trails/topo-compile.ts +55 -0
  40. package/src/trails/topo-history.ts +6 -1
  41. package/src/trails/topo-output-schemas.ts +219 -0
  42. package/src/trails/topo-pin.ts +19 -6
  43. package/src/trails/topo-read-support.ts +171 -228
  44. package/src/trails/topo-reports.ts +587 -25
  45. package/src/trails/topo-store-support.ts +43 -19
  46. package/src/trails/topo-support.ts +18 -28
  47. package/src/trails/topo-unpin.ts +6 -1
  48. package/src/trails/topo-verify.ts +18 -5
  49. package/src/trails/topo.ts +60 -23
  50. package/src/trails/warden-guide.ts +121 -0
  51. package/src/trails/warden.ts +137 -56
  52. package/src/versions.ts +3 -18
  53. package/.turbo/turbo-build.log +0 -1
  54. package/.turbo/turbo-lint.log +0 -3
  55. package/.turbo/turbo-typecheck.log +0 -1
  56. package/__tests__/examples.test.ts +0 -45
  57. package/dist/bin/trails.d.ts +0 -3
  58. package/dist/bin/trails.d.ts.map +0 -1
  59. package/dist/bin/trails.js +0 -4
  60. package/dist/bin/trails.js.map +0 -1
  61. package/dist/src/app.d.ts +0 -2
  62. package/dist/src/app.d.ts.map +0 -1
  63. package/dist/src/app.js +0 -22
  64. package/dist/src/app.js.map +0 -1
  65. package/dist/src/clack.d.ts +0 -9
  66. package/dist/src/clack.d.ts.map +0 -1
  67. package/dist/src/clack.js +0 -84
  68. package/dist/src/clack.js.map +0 -1
  69. package/dist/src/cli.d.ts +0 -2
  70. package/dist/src/cli.d.ts.map +0 -1
  71. package/dist/src/cli.js +0 -14
  72. package/dist/src/cli.js.map +0 -1
  73. package/dist/src/trails/add-surface.d.ts +0 -13
  74. package/dist/src/trails/add-surface.d.ts.map +0 -1
  75. package/dist/src/trails/add-surface.js +0 -110
  76. package/dist/src/trails/add-surface.js.map +0 -1
  77. package/dist/src/trails/add-trail.d.ts +0 -12
  78. package/dist/src/trails/add-trail.d.ts.map +0 -1
  79. package/dist/src/trails/add-trail.js +0 -104
  80. package/dist/src/trails/add-trail.js.map +0 -1
  81. package/dist/src/trails/add-trailhead.d.ts +0 -13
  82. package/dist/src/trails/add-trailhead.d.ts.map +0 -1
  83. package/dist/src/trails/add-trailhead.js +0 -88
  84. package/dist/src/trails/add-trailhead.js.map +0 -1
  85. package/dist/src/trails/add-verify.d.ts +0 -10
  86. package/dist/src/trails/add-verify.d.ts.map +0 -1
  87. package/dist/src/trails/add-verify.js +0 -68
  88. package/dist/src/trails/add-verify.js.map +0 -1
  89. package/dist/src/trails/create-scaffold.d.ts +0 -15
  90. package/dist/src/trails/create-scaffold.d.ts.map +0 -1
  91. package/dist/src/trails/create-scaffold.js +0 -295
  92. package/dist/src/trails/create-scaffold.js.map +0 -1
  93. package/dist/src/trails/create.d.ts +0 -18
  94. package/dist/src/trails/create.d.ts.map +0 -1
  95. package/dist/src/trails/create.js +0 -126
  96. package/dist/src/trails/create.js.map +0 -1
  97. package/dist/src/trails/dev-clean.d.ts +0 -9
  98. package/dist/src/trails/dev-clean.d.ts.map +0 -1
  99. package/dist/src/trails/dev-clean.js +0 -66
  100. package/dist/src/trails/dev-clean.js.map +0 -1
  101. package/dist/src/trails/dev-reset.d.ts +0 -6
  102. package/dist/src/trails/dev-reset.d.ts.map +0 -1
  103. package/dist/src/trails/dev-reset.js +0 -39
  104. package/dist/src/trails/dev-reset.js.map +0 -1
  105. package/dist/src/trails/dev-stats.d.ts +0 -7
  106. package/dist/src/trails/dev-stats.d.ts.map +0 -1
  107. package/dist/src/trails/dev-stats.js +0 -61
  108. package/dist/src/trails/dev-stats.js.map +0 -1
  109. package/dist/src/trails/dev-support.d.ts +0 -64
  110. package/dist/src/trails/dev-support.d.ts.map +0 -1
  111. package/dist/src/trails/dev-support.js +0 -181
  112. package/dist/src/trails/dev-support.js.map +0 -1
  113. package/dist/src/trails/draft-promote.d.ts +0 -18
  114. package/dist/src/trails/draft-promote.d.ts.map +0 -1
  115. package/dist/src/trails/draft-promote.js +0 -400
  116. package/dist/src/trails/draft-promote.js.map +0 -1
  117. package/dist/src/trails/guide.d.ts +0 -21
  118. package/dist/src/trails/guide.d.ts.map +0 -1
  119. package/dist/src/trails/guide.js +0 -61
  120. package/dist/src/trails/guide.js.map +0 -1
  121. package/dist/src/trails/load-app.d.ts +0 -12
  122. package/dist/src/trails/load-app.d.ts.map +0 -1
  123. package/dist/src/trails/load-app.js +0 -415
  124. package/dist/src/trails/load-app.js.map +0 -1
  125. package/dist/src/trails/project.d.ts +0 -8
  126. package/dist/src/trails/project.d.ts.map +0 -1
  127. package/dist/src/trails/project.js +0 -54
  128. package/dist/src/trails/project.js.map +0 -1
  129. package/dist/src/trails/survey.d.ts +0 -18
  130. package/dist/src/trails/survey.d.ts.map +0 -1
  131. package/dist/src/trails/survey.js +0 -234
  132. package/dist/src/trails/survey.js.map +0 -1
  133. package/dist/src/trails/topo-constants.d.ts +0 -3
  134. package/dist/src/trails/topo-constants.d.ts.map +0 -1
  135. package/dist/src/trails/topo-constants.js +0 -3
  136. package/dist/src/trails/topo-constants.js.map +0 -1
  137. package/dist/src/trails/topo-export.d.ts +0 -19
  138. package/dist/src/trails/topo-export.d.ts.map +0 -1
  139. package/dist/src/trails/topo-export.js +0 -31
  140. package/dist/src/trails/topo-export.js.map +0 -1
  141. package/dist/src/trails/topo-history.d.ts +0 -20
  142. package/dist/src/trails/topo-history.d.ts.map +0 -1
  143. package/dist/src/trails/topo-history.js +0 -32
  144. package/dist/src/trails/topo-history.js.map +0 -1
  145. package/dist/src/trails/topo-pin.d.ts +0 -17
  146. package/dist/src/trails/topo-pin.d.ts.map +0 -1
  147. package/dist/src/trails/topo-pin.js +0 -31
  148. package/dist/src/trails/topo-pin.js.map +0 -1
  149. package/dist/src/trails/topo-read-support.d.ts +0 -58
  150. package/dist/src/trails/topo-read-support.d.ts.map +0 -1
  151. package/dist/src/trails/topo-read-support.js +0 -167
  152. package/dist/src/trails/topo-read-support.js.map +0 -1
  153. package/dist/src/trails/topo-reports.d.ts +0 -54
  154. package/dist/src/trails/topo-reports.d.ts.map +0 -1
  155. package/dist/src/trails/topo-reports.js +0 -128
  156. package/dist/src/trails/topo-reports.js.map +0 -1
  157. package/dist/src/trails/topo-show.d.ts +0 -23
  158. package/dist/src/trails/topo-show.d.ts.map +0 -1
  159. package/dist/src/trails/topo-show.js +0 -49
  160. package/dist/src/trails/topo-show.js.map +0 -1
  161. package/dist/src/trails/topo-store-support.d.ts +0 -13
  162. package/dist/src/trails/topo-store-support.d.ts.map +0 -1
  163. package/dist/src/trails/topo-store-support.js +0 -55
  164. package/dist/src/trails/topo-store-support.js.map +0 -1
  165. package/dist/src/trails/topo-support.d.ts +0 -76
  166. package/dist/src/trails/topo-support.d.ts.map +0 -1
  167. package/dist/src/trails/topo-support.js +0 -132
  168. package/dist/src/trails/topo-support.js.map +0 -1
  169. package/dist/src/trails/topo-unpin.d.ts +0 -20
  170. package/dist/src/trails/topo-unpin.d.ts.map +0 -1
  171. package/dist/src/trails/topo-unpin.js +0 -44
  172. package/dist/src/trails/topo-unpin.js.map +0 -1
  173. package/dist/src/trails/topo-verify.d.ts +0 -5
  174. package/dist/src/trails/topo-verify.d.ts.map +0 -1
  175. package/dist/src/trails/topo-verify.js +0 -24
  176. package/dist/src/trails/topo-verify.js.map +0 -1
  177. package/dist/src/trails/topo.d.ts +0 -5
  178. package/dist/src/trails/topo.d.ts.map +0 -1
  179. package/dist/src/trails/topo.js +0 -63
  180. package/dist/src/trails/topo.js.map +0 -1
  181. package/dist/src/trails/warden.d.ts +0 -20
  182. package/dist/src/trails/warden.d.ts.map +0 -1
  183. package/dist/src/trails/warden.js +0 -98
  184. package/dist/src/trails/warden.js.map +0 -1
  185. package/dist/src/versions.d.ts +0 -12
  186. package/dist/src/versions.d.ts.map +0 -1
  187. package/dist/src/versions.js +0 -23
  188. package/dist/src/versions.js.map +0 -1
  189. package/dist/tsconfig.tsbuildinfo +0 -1
  190. package/src/__tests__/add-trail.test.ts +0 -97
  191. package/src/__tests__/create.test.ts +0 -415
  192. package/src/__tests__/draft-promote.test.ts +0 -144
  193. package/src/__tests__/guide.test.ts +0 -96
  194. package/src/__tests__/load-app.test.ts +0 -419
  195. package/src/__tests__/survey.test.ts +0 -377
  196. package/src/__tests__/topo-dev.test.ts +0 -426
  197. package/src/__tests__/warden.test.ts +0 -74
  198. package/src/trails/topo-export.ts +0 -35
  199. package/src/trails/topo-show.ts +0 -54
  200. package/tsconfig.json +0 -9
  201. package/tsconfig.tests.json +0 -10
@@ -5,19 +5,26 @@
5
5
  * keeping module boundaries clean.
6
6
  */
7
7
 
8
- import type { Topo, TopoSnapshot } from '@ontrails/core';
9
- import { InternalError, Result } from '@ontrails/core';
10
- import type { StoredTopoExport } from '@ontrails/core/internal/topo-store';
11
- import {
12
- createTopoSnapshot,
13
- getStoredTopoExport,
14
- } from '@ontrails/core/internal/topo-store';
8
+ import { Database } from 'bun:sqlite';
9
+
10
+ import type { Topo } from '@ontrails/core';
15
11
  import {
16
- openWriteTrailsDb,
17
12
  deriveTrailsDir,
18
- } from '@ontrails/core/internal/trails-db';
19
- import type { SurfaceLock, SurfaceMap } from '@ontrails/schema';
20
- import { writeSurfaceLock, writeSurfaceMap } from '@ontrails/schema';
13
+ InternalError,
14
+ openWriteTrailsDb,
15
+ Result,
16
+ } from '@ontrails/core';
17
+ import type {
18
+ LockManifest,
19
+ TopoGraph,
20
+ TopoSnapshot,
21
+ } from '@ontrails/topographer';
22
+ import type { StoredTopoExport } from '@ontrails/topographer/backend-support';
23
+ import { writeLockManifest, writeTopoGraph } from '@ontrails/topographer';
24
+ import {
25
+ createStoredTopoSnapshot,
26
+ getStoredTopoExport,
27
+ } from '@ontrails/topographer/backend-support';
21
28
 
22
29
  import type { TopoExportReport } from './topo-support.js';
23
30
  import {
@@ -34,7 +41,7 @@ const persistAndReadStoredExport = (
34
41
  { snapshot: TopoSnapshot; storedExport: StoredTopoExport },
35
42
  Error
36
43
  > => {
37
- const snapshotResult = createTopoSnapshot(db, app, {
44
+ const snapshotResult = createStoredTopoSnapshot(db, app, {
38
45
  ...readGitState(rootDir),
39
46
  ...deriveTopoCounts(app),
40
47
  });
@@ -59,23 +66,40 @@ const persistAndReadStoredExport = (
59
66
  });
60
67
  };
61
68
 
69
+ export const deriveCurrentTopoExport = (
70
+ app: Topo,
71
+ options?: { readonly rootDir?: string }
72
+ ): Result<StoredTopoExport, Error> => {
73
+ const rootDir = deriveRootDir(options?.rootDir);
74
+ const db = new Database(':memory:');
75
+
76
+ try {
77
+ const projected = persistAndReadStoredExport(app, db, rootDir);
78
+ return projected.isErr()
79
+ ? projected
80
+ : Result.ok(projected.value.storedExport);
81
+ } finally {
82
+ db.close();
83
+ }
84
+ };
85
+
62
86
  const writeStoredExportArtifacts = async (
63
87
  storedExport: StoredTopoExport,
64
88
  trailsDir: string
65
- ): Promise<Pick<TopoExportReport, 'hash' | 'lockPath' | 'mapPath'>> => {
66
- const mapPath = await writeSurfaceMap(
67
- JSON.parse(storedExport.surfaceMapJson) as SurfaceMap,
89
+ ): Promise<Pick<TopoExportReport, 'hash' | 'lockPath' | 'topoPath'>> => {
90
+ const topoPath = await writeTopoGraph(
91
+ JSON.parse(storedExport.topoGraphJson) as TopoGraph,
68
92
  { dir: trailsDir }
69
93
  );
70
- const lockPath = await writeSurfaceLock(
71
- JSON.parse(storedExport.lockContent) as SurfaceLock,
94
+ const lockPath = await writeLockManifest(
95
+ JSON.parse(storedExport.lockManifestJson) as LockManifest,
72
96
  { dir: trailsDir }
73
97
  );
74
98
 
75
99
  return {
76
- hash: storedExport.surfaceHash,
100
+ hash: storedExport.topoGraphHash,
77
101
  lockPath,
78
- mapPath,
102
+ topoPath,
79
103
  };
80
104
  };
81
105
 
@@ -1,18 +1,23 @@
1
- import { existsSync, mkdirSync, rmSync } from 'node:fs';
2
- import { tmpdir } from 'node:os';
3
- import { join } from 'node:path';
1
+ import { existsSync } from 'node:fs';
4
2
  import { fileURLToPath } from 'node:url';
5
3
 
4
+ import { deriveTrailsDbPath } from '@ontrails/core';
5
+ import type { Topo } from '@ontrails/core';
6
6
  import {
7
7
  createTopoSnapshot as persistTopoSnapshot,
8
8
  listTopoSnapshots as readTopoSnapshots,
9
9
  pinTopoSnapshot,
10
10
  unpinTopoSnapshot,
11
- } from '@ontrails/core';
12
- import type { Topo, TopoSnapshot } from '@ontrails/core';
13
- import { deriveTrailsDbPath } from '@ontrails/core/internal/trails-db';
11
+ } from '@ontrails/topographer';
12
+ import type { TopoSnapshot } from '@ontrails/topographer';
14
13
  import { z } from 'zod';
15
14
 
15
+ import {
16
+ createIsolatedExampleRoot,
17
+ writeIsolatedExampleAppModule,
18
+ } from '../local-state-io.js';
19
+
20
+ import { requireTrailRootDir } from './root-dir.js';
16
21
  import type { BriefReport, SurveyListReport } from './topo-reports.js';
17
22
 
18
23
  /** Output schema for a topo snapshot record. Shared across topo trails. */
@@ -27,11 +32,13 @@ export const topoSnapshotOutput = z.object({
27
32
  trailCount: z.number(),
28
33
  });
29
34
 
30
- export const DEFAULT_APP_MODULE = './src/app.ts';
31
35
  export const DEFAULT_TOPO_HISTORY_LIMIT = 10;
32
36
  export const LOCK_PATH = '.trails/trails.lock';
33
37
  const EXAMPLE_APP_MODULE = fileURLToPath(new URL('../app.ts', import.meta.url));
34
38
 
39
+ const uniqueExampleRootName = (name: string): string =>
40
+ `${name}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
41
+
35
42
  export interface TopoSummaryReport {
36
43
  readonly app: BriefReport;
37
44
  readonly dbPath: string;
@@ -51,8 +58,8 @@ export interface TopoHistoryReport {
51
58
  export interface TopoExportReport {
52
59
  readonly hash: string;
53
60
  readonly lockPath: string;
54
- readonly mapPath: string;
55
61
  readonly snapshot: TopoSnapshot;
62
+ readonly topoPath: string;
56
63
  }
57
64
 
58
65
  export interface TopoVerifyReport {
@@ -62,7 +69,7 @@ export interface TopoVerifyReport {
62
69
  readonly stale: false;
63
70
  }
64
71
 
65
- export const deriveRootDir = (cwd?: string): string => cwd ?? process.cwd();
72
+ export const deriveRootDir = (cwd?: string): string => requireTrailRootDir(cwd);
66
73
 
67
74
  const safeGit = (cwd: string, args: readonly string[]): string | undefined => {
68
75
  const proc = Bun.spawnSync({
@@ -137,30 +144,13 @@ const buildSnapshotInput = (
137
144
  export const createIsolatedExampleInput = (
138
145
  name: string
139
146
  ): { readonly module: string; readonly rootDir: string } => {
140
- const rootDir = join(tmpdir(), 'ontrails-trails-examples', name);
141
- rmSync(rootDir, { force: true, recursive: true });
142
- mkdirSync(rootDir, { recursive: true });
147
+ const rootDir = createIsolatedExampleRoot(uniqueExampleRootName(name));
143
148
  return {
144
- module: EXAMPLE_APP_MODULE,
149
+ module: writeIsolatedExampleAppModule(rootDir, EXAMPLE_APP_MODULE),
145
150
  rootDir,
146
151
  };
147
152
  };
148
153
 
149
- export const createCurrentTopoSnapshot = (
150
- app: Topo,
151
- options?: { readonly rootDir?: string }
152
- ): TopoSnapshot => {
153
- const rootDir = deriveRootDir(options?.rootDir);
154
- const result = persistTopoSnapshot(app, {
155
- rootDir,
156
- ...buildSnapshotInput(app, rootDir),
157
- });
158
- if (result.isErr()) {
159
- throw result.error;
160
- }
161
- return result.value;
162
- };
163
-
164
154
  export const listTopoHistory = (options?: {
165
155
  readonly limit?: number;
166
156
  readonly rootDir?: string;
@@ -6,6 +6,7 @@ import {
6
6
  removePinnedTopoSnapshot,
7
7
  topoSnapshotOutput,
8
8
  } from './topo-support.js';
9
+ import { resolveTrailRootDir } from './root-dir.js';
9
10
 
10
11
  export const topoUnpinTrail = trail('topo.unpin', {
11
12
  blaze: (input, ctx) => {
@@ -17,7 +18,11 @@ export const topoUnpinTrail = trail('topo.unpin', {
17
18
  );
18
19
  }
19
20
 
20
- const rootDir = input.rootDir ?? ctx.cwd ?? process.cwd();
21
+ const rootDirResult = resolveTrailRootDir(input.rootDir, ctx.cwd);
22
+ if (rootDirResult.isErr()) {
23
+ return Result.err(rootDirResult.error);
24
+ }
25
+ const rootDir = rootDirResult.value;
21
26
  return Result.ok(
22
27
  removePinnedTopoSnapshot({
23
28
  dryRun: input.dryRun,
@@ -1,14 +1,27 @@
1
- import { trail } from '@ontrails/core';
1
+ import { Result, trail } from '@ontrails/core';
2
2
  import { z } from 'zod';
3
3
 
4
- import { loadApp } from './load-app.js';
4
+ import { tryLoadFreshAppLease } from './load-app.js';
5
+ import { resolveTrailRootDir } from './root-dir.js';
5
6
  import { verifyCurrentTopo } from './topo-read-support.js';
6
7
 
7
8
  export const topoVerifyTrail = trail('topo.verify', {
8
9
  blaze: async (input, ctx) => {
9
- const rootDir = input.rootDir ?? ctx.cwd ?? process.cwd();
10
- const app = await loadApp(input.module, rootDir);
11
- return verifyCurrentTopo(app, { rootDir });
10
+ const rootDirResult = resolveTrailRootDir(input.rootDir, ctx.cwd);
11
+ if (rootDirResult.isErr()) {
12
+ return Result.err(rootDirResult.error);
13
+ }
14
+ const rootDir = rootDirResult.value;
15
+ const leaseResult = await tryLoadFreshAppLease(input.module, rootDir);
16
+ if (leaseResult.isErr()) {
17
+ return Result.err(leaseResult.error);
18
+ }
19
+ const lease = leaseResult.value;
20
+ try {
21
+ return await verifyCurrentTopo(lease.app, { rootDir });
22
+ } finally {
23
+ lease.release();
24
+ }
12
25
  },
13
26
  description: 'Verify that the committed lockfile matches the current topo',
14
27
  input: z.object({
@@ -1,8 +1,11 @@
1
1
  import { Result, trail } from '@ontrails/core';
2
2
  import { z } from 'zod';
3
3
 
4
- import { loadApp } from './load-app.js';
4
+ import { tryLoadFreshAppLease } from './load-app.js';
5
+ import { resolveTrailRootDir } from './root-dir.js';
6
+ import { activationOverviewOutput } from './topo-output-schemas.js';
5
7
  import { buildTopoSummary } from './topo-read-support.js';
8
+ import { createIsolatedExampleInput } from './topo-support.js';
6
9
 
7
10
  const summaryOutput = z.object({
8
11
  app: z.object({
@@ -22,26 +25,48 @@ const summaryOutput = z.object({
22
25
  }),
23
26
  dbPath: z.string(),
24
27
  list: z.object({
28
+ activation: activationOverviewOutput,
25
29
  count: z.number(),
26
- entries: z.array(
27
- z.object({
28
- examples: z.number(),
29
- id: z.string(),
30
- kind: z.string(),
31
- safety: z.string(),
32
- })
33
- ),
30
+ entries: z
31
+ .array(
32
+ z.object({
33
+ activatedBy: z.array(z.string()).readonly(),
34
+ activates: z.array(z.string()).readonly(),
35
+ examples: z.number(),
36
+ id: z.string(),
37
+ kind: z.string(),
38
+ safety: z.string(),
39
+ })
40
+ )
41
+ .readonly(),
34
42
  resourceCount: z.number(),
35
- resources: z.array(
36
- z.object({
37
- description: z.string().nullable(),
38
- health: z.enum(['available', 'none']),
39
- id: z.string(),
40
- kind: z.literal('resource'),
41
- lifetime: z.literal('singleton'),
42
- usedBy: z.array(z.string()),
43
- })
44
- ),
43
+ resources: z
44
+ .array(
45
+ z.object({
46
+ description: z.string().nullable(),
47
+ health: z.enum(['available', 'none']),
48
+ id: z.string(),
49
+ kind: z.literal('resource'),
50
+ lifetime: z.literal('singleton'),
51
+ usedBy: z.array(z.string()).readonly(),
52
+ })
53
+ )
54
+ .readonly(),
55
+ signalCount: z.number(),
56
+ signals: z
57
+ .array(
58
+ z.object({
59
+ consumers: z.array(z.string()).readonly(),
60
+ description: z.string().nullable(),
61
+ examples: z.number(),
62
+ from: z.array(z.string()).readonly(),
63
+ id: z.string(),
64
+ kind: z.literal('signal'),
65
+ payloadSchema: z.boolean(),
66
+ producers: z.array(z.string()).readonly(),
67
+ })
68
+ )
69
+ .readonly(),
45
70
  }),
46
71
  lockExists: z.boolean(),
47
72
  lockPath: z.string(),
@@ -49,14 +74,26 @@ const summaryOutput = z.object({
49
74
 
50
75
  export const topoTrail = trail('topo', {
51
76
  blaze: async (input, ctx) => {
52
- const rootDir = input.rootDir ?? ctx.cwd ?? process.cwd();
53
- const app = await loadApp(input.module, rootDir);
54
- return Result.ok(buildTopoSummary(app, { rootDir }));
77
+ const rootDirResult = resolveTrailRootDir(input.rootDir, ctx.cwd);
78
+ if (rootDirResult.isErr()) {
79
+ return Result.err(rootDirResult.error);
80
+ }
81
+ const rootDir = rootDirResult.value;
82
+ const leaseResult = await tryLoadFreshAppLease(input.module, rootDir);
83
+ if (leaseResult.isErr()) {
84
+ return Result.err(leaseResult.error);
85
+ }
86
+ const lease = leaseResult.value;
87
+ try {
88
+ return Result.ok(buildTopoSummary(lease.app, { rootDir }));
89
+ } finally {
90
+ lease.release();
91
+ }
55
92
  },
56
93
  description: 'Show the current topo summary and entry list',
57
94
  examples: [
58
95
  {
59
- input: {},
96
+ input: createIsolatedExampleInput('topo-summary'),
60
97
  name: 'Show the current topo summary',
61
98
  },
62
99
  ],
@@ -0,0 +1,121 @@
1
+ import type { FieldOverride } from '@ontrails/core';
2
+ import { Result, trail } from '@ontrails/core';
3
+ import {
4
+ buildWardenGuideManifest,
5
+ formatWardenGuide,
6
+ wardenDepthValues,
7
+ wardenGuideFormatValues,
8
+ wardenRuleConcerns,
9
+ wardenRuleLifecycleStates,
10
+ wardenRuleScopes,
11
+ wardenRuleTiers,
12
+ } from '@ontrails/warden';
13
+ import { z } from 'zod';
14
+
15
+ const wardenGuidanceLinkSchema = z.object({
16
+ label: z.string(),
17
+ path: z.string().optional(),
18
+ url: z.string().optional(),
19
+ });
20
+
21
+ const wardenGuidanceSchema = z.object({
22
+ commands: z.array(z.string()).readonly().optional(),
23
+ docs: z.array(wardenGuidanceLinkSchema).readonly().optional(),
24
+ relatedRules: z.array(z.string()).readonly().optional(),
25
+ steps: z.array(z.string()).readonly().optional(),
26
+ summary: z.string(),
27
+ });
28
+
29
+ const wardenRuleGuideEntrySchema = z.object({
30
+ concern: z.enum(wardenRuleConcerns),
31
+ depth: z.enum(wardenDepthValues),
32
+ description: z.string(),
33
+ docs: z.array(wardenGuidanceLinkSchema).readonly(),
34
+ guidance: wardenGuidanceSchema.optional(),
35
+ id: z.string(),
36
+ invariant: z.string(),
37
+ lifecycle: z.object({
38
+ retireWhen: z.string().optional(),
39
+ state: z.enum(wardenRuleLifecycleStates),
40
+ }),
41
+ scope: z.enum(wardenRuleScopes),
42
+ severity: z.enum(['error', 'warn']),
43
+ tier: z.enum(wardenRuleTiers),
44
+ });
45
+
46
+ const wardenGuideManifestSchema = z.object({
47
+ generatedFrom: z.object({
48
+ package: z.literal('@ontrails/warden'),
49
+ registries: z.tuple([
50
+ z.literal('wardenRules'),
51
+ z.literal('wardenTopoRules'),
52
+ ]),
53
+ source: z.literal('builtin-rule-metadata'),
54
+ }),
55
+ kind: z.literal('trails-warden-guide-manifest'),
56
+ ruleCount: z.number(),
57
+ rules: z.array(wardenRuleGuideEntrySchema).readonly(),
58
+ version: z.literal(1),
59
+ });
60
+
61
+ const wardenGuideInputSchema = z.object({
62
+ guideFormat: z
63
+ .enum(wardenGuideFormatValues)
64
+ .default('markdown')
65
+ .describe('Guide output format'),
66
+ });
67
+
68
+ const wardenGuideFields = {
69
+ guideFormat: {
70
+ aliases: true,
71
+ options: [
72
+ {
73
+ hint: 'Human-readable Warden guide',
74
+ label: 'Markdown',
75
+ value: 'markdown',
76
+ },
77
+ {
78
+ hint: 'Compact guidance for agent context',
79
+ label: 'Agent JSON',
80
+ value: 'agent-json',
81
+ },
82
+ {
83
+ hint: 'Full structured rule manifest',
84
+ label: 'Manifest',
85
+ value: 'manifest',
86
+ },
87
+ ],
88
+ },
89
+ } satisfies Readonly<
90
+ Record<'guideFormat', FieldOverride & { readonly aliases: true }>
91
+ >;
92
+
93
+ export const wardenGuideTrail = trail('warden.guide', {
94
+ blaze: (input) => {
95
+ const manifest = buildWardenGuideManifest();
96
+ return Result.ok({
97
+ format: input.guideFormat,
98
+ formatted: formatWardenGuide(manifest, input.guideFormat),
99
+ manifest,
100
+ });
101
+ },
102
+ description: 'Project Warden rule guidance as markdown or JSON',
103
+ examples: [
104
+ {
105
+ input: { guideFormat: 'markdown' },
106
+ name: 'Markdown guide',
107
+ },
108
+ {
109
+ input: { guideFormat: 'agent-json' },
110
+ name: 'Agent JSON guide',
111
+ },
112
+ ],
113
+ fields: wardenGuideFields,
114
+ input: wardenGuideInputSchema,
115
+ intent: 'read',
116
+ output: z.object({
117
+ format: z.enum(wardenGuideFormatValues),
118
+ formatted: z.string(),
119
+ manifest: wardenGuideManifestSchema,
120
+ }),
121
+ });