@ontrails/warden 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 (188) hide show
  1. package/CHANGELOG.md +1556 -0
  2. package/README.md +200 -0
  3. package/bin/warden.ts +51 -0
  4. package/package.json +51 -0
  5. package/src/adapter-check.ts +136 -0
  6. package/src/cli.ts +2159 -0
  7. package/src/command.ts +1167 -0
  8. package/src/config.ts +193 -0
  9. package/src/draft.ts +22 -0
  10. package/src/drift.ts +260 -0
  11. package/src/fix.ts +126 -0
  12. package/src/formatters.ts +178 -0
  13. package/src/guide.ts +245 -0
  14. package/src/index.ts +295 -0
  15. package/src/project-context.ts +446 -0
  16. package/src/project-rules.ts +290 -0
  17. package/src/regrade-history.ts +188 -0
  18. package/src/regrade-receipt.ts +484 -0
  19. package/src/resolve.ts +531 -0
  20. package/src/rules/activation-orphan.ts +97 -0
  21. package/src/rules/captured-kernel.ts +375 -0
  22. package/src/rules/circular-refs.ts +150 -0
  23. package/src/rules/cli-command-route-coherence.ts +177 -0
  24. package/src/rules/composes-declarations.ts +839 -0
  25. package/src/rules/context-no-surface-types.ts +214 -0
  26. package/src/rules/dead-internal-trail.ts +161 -0
  27. package/src/rules/dead-public-trail.ts +258 -0
  28. package/src/rules/draft-file-marking.ts +155 -0
  29. package/src/rules/draft-visible-debt.ts +83 -0
  30. package/src/rules/duplicate-exported-symbol.ts +172 -0
  31. package/src/rules/duplicate-public-contract.ts +137 -0
  32. package/src/rules/entity-exists.ts +254 -0
  33. package/src/rules/entity-ids.ts +15 -0
  34. package/src/rules/error-mapping-completeness.ts +290 -0
  35. package/src/rules/example-valid.ts +395 -0
  36. package/src/rules/fires-declarations.ts +740 -0
  37. package/src/rules/governed-symbol-residue.ts +535 -0
  38. package/src/rules/governed-vocabulary-permutation-watch.ts +77 -0
  39. package/src/rules/implementation-returns-result.ts +1733 -0
  40. package/src/rules/incomplete-accessor-for-standard-op.ts +272 -0
  41. package/src/rules/incomplete-crud.ts +583 -0
  42. package/src/rules/index.ts +328 -0
  43. package/src/rules/intent-propagation.ts +125 -0
  44. package/src/rules/layer-field-name-drift.ts +102 -0
  45. package/src/rules/library-render-coherence.ts +97 -0
  46. package/src/rules/metadata.ts +929 -0
  47. package/src/rules/missing-reconcile.ts +97 -0
  48. package/src/rules/missing-visibility.ts +111 -0
  49. package/src/rules/no-destructured-compose.ts +196 -0
  50. package/src/rules/no-dev-permit-in-source.ts +99 -0
  51. package/src/rules/no-direct-implementation-call.ts +52 -0
  52. package/src/rules/no-legacy-cli-alias-export.ts +247 -0
  53. package/src/rules/no-legacy-layer-imports.ts +211 -0
  54. package/src/rules/no-native-error-result.ts +118 -0
  55. package/src/rules/no-redundant-result-error-wrap.ts +382 -0
  56. package/src/rules/no-retired-cross-vocabulary.ts +203 -0
  57. package/src/rules/no-sync-result-assumption.ts +1199 -0
  58. package/src/rules/no-throw-in-detour-recover.ts +225 -0
  59. package/src/rules/no-throw-in-implementation.ts +48 -0
  60. package/src/rules/no-top-level-surface.ts +371 -0
  61. package/src/rules/on-references-exist.ts +194 -0
  62. package/src/rules/orphaned-signal.ts +149 -0
  63. package/src/rules/owner-render-parity.ts +143 -0
  64. package/src/rules/permit-governance.ts +25 -0
  65. package/src/rules/prefer-schema-inference.ts +141 -0
  66. package/src/rules/public-export-example-coverage.ts +561 -0
  67. package/src/rules/public-internal-deep-imports.ts +454 -0
  68. package/src/rules/public-output-schema.ts +29 -0
  69. package/src/rules/public-union-output-discriminants.ts +150 -0
  70. package/src/rules/read-intent-fires.ts +188 -0
  71. package/src/rules/reference-exists.ts +97 -0
  72. package/src/rules/registry-names.ts +171 -0
  73. package/src/rules/resolved-import-boundary.ts +146 -0
  74. package/src/rules/resource-declarations.ts +697 -0
  75. package/src/rules/resource-exists.ts +181 -0
  76. package/src/rules/resource-id-grammar.ts +65 -0
  77. package/src/rules/resource-mock-coverage.ts +115 -0
  78. package/src/rules/retired-vocabulary.ts +1407 -0
  79. package/src/rules/scan.ts +59 -0
  80. package/src/rules/scheduled-destroy-intent.ts +44 -0
  81. package/src/rules/signal-graph-coaching.ts +220 -0
  82. package/src/rules/source/composition.ts +165 -0
  83. package/src/rules/source/drafts.ts +164 -0
  84. package/src/rules/source/entities.ts +618 -0
  85. package/src/rules/source/pragmas.ts +45 -0
  86. package/src/rules/source/resources.ts +64 -0
  87. package/src/rules/source/signals.ts +397 -0
  88. package/src/rules/source/stores.ts +310 -0
  89. package/src/rules/specs.ts +388 -0
  90. package/src/rules/static-resource-accessor-preference.ts +654 -0
  91. package/src/rules/structure.ts +234 -0
  92. package/src/rules/surface-overlay-coherence.ts +262 -0
  93. package/src/rules/surface-trailhead-coherence.ts +366 -0
  94. package/src/rules/trail-fork-coaching.ts +625 -0
  95. package/src/rules/trail-versioning-source.ts +1076 -0
  96. package/src/rules/trail-versioning-topo.ts +172 -0
  97. package/src/rules/trailhead-override-divergence.ts +356 -0
  98. package/src/rules/types.ts +464 -0
  99. package/src/rules/unmaterialized-activation-source.ts +85 -0
  100. package/src/rules/unreachable-detour-shadowing.ts +343 -0
  101. package/src/rules/valid-describe-refs.ts +224 -0
  102. package/src/rules/valid-detour-contract.ts +78 -0
  103. package/src/rules/warden-export-symmetry.ts +540 -0
  104. package/src/rules/warden-rules-use-ast.ts +1109 -0
  105. package/src/rules/webhook-route-collision.ts +306 -0
  106. package/src/rules/workspace-lock-ownership.ts +34 -0
  107. package/src/trails/activation-orphan.trail.ts +84 -0
  108. package/src/trails/captured-kernel.trail.ts +108 -0
  109. package/src/trails/circular-refs.trail.ts +29 -0
  110. package/src/trails/cli-command-route-coherence.trail.ts +47 -0
  111. package/src/trails/composes-declarations.trail.ts +22 -0
  112. package/src/trails/context-no-surface-types.trail.ts +21 -0
  113. package/src/trails/dead-internal-trail.trail.ts +26 -0
  114. package/src/trails/dead-public-trail.trail.ts +31 -0
  115. package/src/trails/deprecation-without-guidance.trail.ts +21 -0
  116. package/src/trails/draft-file-marking.trail.ts +16 -0
  117. package/src/trails/draft-visible-debt.trail.ts +16 -0
  118. package/src/trails/duplicate-exported-symbol.trail.ts +48 -0
  119. package/src/trails/duplicate-public-contract.trail.ts +47 -0
  120. package/src/trails/entity-exists.trail.ts +21 -0
  121. package/src/trails/error-mapping-completeness.trail.ts +30 -0
  122. package/src/trails/example-valid.trail.ts +25 -0
  123. package/src/trails/fires-declarations.trail.ts +23 -0
  124. package/src/trails/fork-without-preserved-implementation.trail.ts +31 -0
  125. package/src/trails/governed-symbol-residue.trail.ts +24 -0
  126. package/src/trails/governed-vocabulary-permutation-watch.trail.ts +16 -0
  127. package/src/trails/implementation-returns-result.trail.ts +20 -0
  128. package/src/trails/incomplete-accessor-for-standard-op.trail.ts +76 -0
  129. package/src/trails/incomplete-crud.trail.ts +39 -0
  130. package/src/trails/index.ts +91 -0
  131. package/src/trails/intent-propagation.trail.ts +30 -0
  132. package/src/trails/layer-field-name-drift.trail.ts +39 -0
  133. package/src/trails/library-render-coherence.trail.ts +43 -0
  134. package/src/trails/marker-schema-unsupported.trail.ts +23 -0
  135. package/src/trails/missing-reconcile.trail.ts +33 -0
  136. package/src/trails/missing-visibility.trail.ts +22 -0
  137. package/src/trails/no-destructured-compose.trail.ts +44 -0
  138. package/src/trails/no-dev-permit-in-source.trail.ts +16 -0
  139. package/src/trails/no-direct-implementation-call.trail.ts +16 -0
  140. package/src/trails/no-legacy-cli-alias-export.trail.ts +41 -0
  141. package/src/trails/no-legacy-layer-imports.trail.ts +41 -0
  142. package/src/trails/no-native-error-result.trail.ts +18 -0
  143. package/src/trails/no-redundant-result-error-wrap.trail.ts +55 -0
  144. package/src/trails/no-retired-cross-vocabulary.trail.ts +42 -0
  145. package/src/trails/no-sync-result-assumption.trail.ts +19 -0
  146. package/src/trails/no-throw-in-detour-recover.trail.ts +24 -0
  147. package/src/trails/no-throw-in-implementation.trail.ts +20 -0
  148. package/src/trails/no-top-level-surface.trail.ts +43 -0
  149. package/src/trails/on-references-exist.trail.ts +21 -0
  150. package/src/trails/orphaned-signal.trail.ts +36 -0
  151. package/src/trails/owner-render-parity.trail.ts +26 -0
  152. package/src/trails/pending-force.trail.ts +21 -0
  153. package/src/trails/permit-governance.trail.ts +51 -0
  154. package/src/trails/prefer-schema-inference.trail.ts +21 -0
  155. package/src/trails/public-export-example-coverage.trail.ts +16 -0
  156. package/src/trails/public-internal-deep-imports.trail.ts +94 -0
  157. package/src/trails/public-output-schema.trail.ts +55 -0
  158. package/src/trails/public-union-output-discriminants.trail.ts +33 -0
  159. package/src/trails/read-intent-fires.trail.ts +20 -0
  160. package/src/trails/reference-exists.trail.ts +25 -0
  161. package/src/trails/resolved-import-boundary.trail.ts +109 -0
  162. package/src/trails/resource-declarations.trail.ts +25 -0
  163. package/src/trails/resource-exists.trail.ts +27 -0
  164. package/src/trails/resource-id-grammar.trail.ts +39 -0
  165. package/src/trails/resource-mock-coverage.trail.ts +40 -0
  166. package/src/trails/run.ts +205 -0
  167. package/src/trails/scheduled-destroy-intent.trail.ts +56 -0
  168. package/src/trails/schema.ts +295 -0
  169. package/src/trails/signal-graph-coaching.trail.ts +77 -0
  170. package/src/trails/static-resource-accessor-preference.trail.ts +25 -0
  171. package/src/trails/surface-overlay-coherence.trail.ts +24 -0
  172. package/src/trails/surface-trailhead-coherence.trail.ts +25 -0
  173. package/src/trails/topo.ts +6 -0
  174. package/src/trails/trail-fork-coaching.trail.ts +42 -0
  175. package/src/trails/trailhead-override-divergence.trail.ts +47 -0
  176. package/src/trails/unmaterialized-activation-source.trail.ts +72 -0
  177. package/src/trails/unreachable-detour-shadowing.trail.ts +45 -0
  178. package/src/trails/valid-describe-refs.trail.ts +18 -0
  179. package/src/trails/valid-detour-contract.trail.ts +71 -0
  180. package/src/trails/version-gap.trail.ts +35 -0
  181. package/src/trails/version-pinned-compose.trail.ts +23 -0
  182. package/src/trails/version-without-examples.trail.ts +38 -0
  183. package/src/trails/warden-export-symmetry.trail.ts +16 -0
  184. package/src/trails/warden-rules-use-ast.trail.ts +64 -0
  185. package/src/trails/webhook-route-collision.trail.ts +50 -0
  186. package/src/trails/workspace-lock-ownership.trail.ts +16 -0
  187. package/src/trails/wrap-rule.ts +269 -0
  188. package/src/workspaces.ts +264 -0
@@ -0,0 +1,1407 @@
1
+ import { z } from 'zod';
2
+
3
+ export const governedVocabularyTransitionStatuses = [
4
+ 'planned',
5
+ 'active',
6
+ 'complete',
7
+ ] as const;
8
+
9
+ const governedVocabularySingleTargetSchema = z.object({
10
+ kind: z.literal('single'),
11
+ to: z.string().min(1),
12
+ });
13
+
14
+ const governedVocabularyClassifiedTargetSchema = z.object({
15
+ guidance: z.string().min(1),
16
+ kind: z.literal('classified'),
17
+ options: z
18
+ .array(
19
+ z.object({
20
+ to: z.string().min(1),
21
+ when: z.string().min(1),
22
+ })
23
+ )
24
+ .min(1),
25
+ });
26
+
27
+ export const governedVocabularyTargetSchema = z.discriminatedUnion('kind', [
28
+ governedVocabularySingleTargetSchema,
29
+ governedVocabularyClassifiedTargetSchema,
30
+ ]);
31
+
32
+ export const governedVocabularyPreserveRuleSchema = z.object({
33
+ paths: z.array(z.string().min(1)).optional(),
34
+ pattern: z.string().min(1),
35
+ reason: z.string().min(1),
36
+ });
37
+
38
+ export const governedVocabularyScopeSchema = z.object({
39
+ exclude: z.array(z.string().min(1)).optional(),
40
+ extensions: z.array(z.string().min(1)).optional(),
41
+ ignoredDirectories: z.array(z.string().min(1)).optional(),
42
+ include: z.array(z.string().min(1)).optional(),
43
+ policyClassified: z
44
+ .array(
45
+ z.object({
46
+ disposition: z.enum(['explicit-preserve', 'historical-by-policy']),
47
+ expectMatches: z.boolean().optional(),
48
+ paths: z.array(z.string().min(1)).min(1),
49
+ reason: z.string().min(1),
50
+ })
51
+ )
52
+ .optional(),
53
+ teachingSurfaces: z.array(z.string().min(1)).optional(),
54
+ });
55
+
56
+ export const governedVocabularySymbolRenameMatchModes = [
57
+ 'exact',
58
+ 'identifier-segment',
59
+ ] as const;
60
+
61
+ export const governedVocabularySymbolRenameSchema = z.object({
62
+ from: z.string().min(1),
63
+ match: z.enum(governedVocabularySymbolRenameMatchModes).default('exact'),
64
+ reviewDeclarationTypes: z.array(z.string().min(1)).default([]),
65
+ safety: z.enum(['safe', 'review']).optional(),
66
+ to: z.string().min(1),
67
+ });
68
+
69
+ export const governedVocabularyLiteralRenameSchema = z.object({
70
+ from: z.string().min(1),
71
+ match: z.enum(['exact', 'property-key', 'review']).optional(),
72
+ moduleSpecifier: z
73
+ .object({
74
+ targetPackage: z.string().min(1),
75
+ })
76
+ .optional(),
77
+ to: z.string().min(1),
78
+ });
79
+
80
+ export const governedVocabularyFileRenameSchema = z.object({
81
+ from: z.string().min(1),
82
+ to: z.string().min(1),
83
+ });
84
+
85
+ export const governedVocabularyProvenancePolicySchema = z.discriminatedUnion(
86
+ 'mode',
87
+ [
88
+ z.object({ mode: z.literal('regrade-history') }),
89
+ z.object({
90
+ mode: z.literal('legacy'),
91
+ reason: z.string().min(1),
92
+ }),
93
+ ]
94
+ );
95
+
96
+ export const governedVocabularyHistoryProvenanceSchema = z
97
+ .object({
98
+ disposition: z.enum(['applied-clean', 'review-follow-up']),
99
+ kind: z.literal('governed-vocabulary'),
100
+ planContentHash: z.string().regex(/^[0-9a-f]{64}$/),
101
+ reviewPending: z.number().int().nonnegative(),
102
+ safeApplied: z.number().int().nonnegative(),
103
+ sourceHashAfter: z.string().regex(/^[0-9a-f]{64}$/),
104
+ sourceHashBefore: z.string().regex(/^[0-9a-f]{64}$/),
105
+ transitionId: z.string().min(1),
106
+ })
107
+ .strict();
108
+
109
+ export const governedVocabularyTransitionSchema = z.object({
110
+ codeIdentifiers: z.array(z.string().min(1)).default([]),
111
+ docs: z.object({
112
+ guidance: z.array(z.string().min(1)).default([]),
113
+ summary: z.string().min(1),
114
+ }),
115
+ fileRenames: z.array(governedVocabularyFileRenameSchema).default([]),
116
+ from: z.string().min(1),
117
+ id: z.string().min(1),
118
+ intent: z.string().min(1),
119
+ kind: z.literal('vocabulary'),
120
+ oldForms: z.array(z.string().min(1)).min(1),
121
+ overrides: z.record(z.string().min(1), z.string().min(1)).default({}),
122
+ preserve: z.array(governedVocabularyPreserveRuleSchema).default([]),
123
+ provenance: governedVocabularyProvenancePolicySchema.default({
124
+ mode: 'legacy',
125
+ reason:
126
+ 'Transition completed before committed Regrade history provenance became enforceable.',
127
+ }),
128
+ reviewForms: z.array(z.string().min(1)).default([]),
129
+ safeRewriteForms: z.record(z.string().min(1), z.string().min(1)).default({}),
130
+ scope: governedVocabularyScopeSchema.optional(),
131
+ status: z.enum(governedVocabularyTransitionStatuses),
132
+ stringLiteralRenames: z
133
+ .array(governedVocabularyLiteralRenameSchema)
134
+ .default([]),
135
+ symbolRenames: z.array(governedVocabularySymbolRenameSchema).default([]),
136
+ target: governedVocabularyTargetSchema,
137
+ });
138
+
139
+ export const governedVocabularyRegistrySchema = z
140
+ .array(governedVocabularyTransitionSchema)
141
+ .superRefine((transitions, ctx) => {
142
+ const seenIds = new Set<string>();
143
+ const seenFrom = new Set<string>();
144
+
145
+ for (const [index, transition] of transitions.entries()) {
146
+ if (seenIds.has(transition.id)) {
147
+ ctx.addIssue({
148
+ code: 'custom',
149
+ message: `Duplicate governed vocabulary transition id "${transition.id}".`,
150
+ path: [index, 'id'],
151
+ });
152
+ }
153
+ seenIds.add(transition.id);
154
+
155
+ if (seenFrom.has(transition.from)) {
156
+ ctx.addIssue({
157
+ code: 'custom',
158
+ message: `Duplicate governed vocabulary source "${transition.from}".`,
159
+ path: [index, 'from'],
160
+ });
161
+ }
162
+ seenFrom.add(transition.from);
163
+
164
+ if (
165
+ transition.status === 'planned' &&
166
+ transition.provenance.mode !== 'regrade-history'
167
+ ) {
168
+ ctx.addIssue({
169
+ code: 'custom',
170
+ message:
171
+ 'Planned governed transitions must require committed Regrade history provenance.',
172
+ path: [index, 'provenance'],
173
+ });
174
+ }
175
+ }
176
+ });
177
+
178
+ export type GovernedVocabularyPreserveRule = z.output<
179
+ typeof governedVocabularyPreserveRuleSchema
180
+ >;
181
+ export type GovernedVocabularyScope = z.output<
182
+ typeof governedVocabularyScopeSchema
183
+ >;
184
+ export type GovernedVocabularyTarget = z.output<
185
+ typeof governedVocabularyTargetSchema
186
+ >;
187
+ export type GovernedVocabularySymbolRename = z.output<
188
+ typeof governedVocabularySymbolRenameSchema
189
+ >;
190
+ export type GovernedVocabularyLiteralRename = z.output<
191
+ typeof governedVocabularyLiteralRenameSchema
192
+ >;
193
+ export type GovernedVocabularyTransition = z.output<
194
+ typeof governedVocabularyTransitionSchema
195
+ >;
196
+ export type GovernedVocabularyHistoryProvenance = z.output<
197
+ typeof governedVocabularyHistoryProvenanceSchema
198
+ >;
199
+ export type GovernedVocabularyProvenancePolicy = z.output<
200
+ typeof governedVocabularyProvenancePolicySchema
201
+ >;
202
+ export type GovernedVocabularyTransitionInput = z.input<
203
+ typeof governedVocabularyTransitionSchema
204
+ >;
205
+
206
+ const defineTransition = (
207
+ input: GovernedVocabularyTransitionInput
208
+ ): GovernedVocabularyTransition =>
209
+ governedVocabularyTransitionSchema.parse(input);
210
+
211
+ const reviewFunctionParamDeclarations = {
212
+ reviewDeclarationTypes: ['FunctionParam'],
213
+ };
214
+
215
+ const v1VocabularyHardExcludes = [
216
+ '.scratch/**',
217
+ '**/.scratch/**',
218
+ '**/.tmp-tests/**',
219
+ ];
220
+
221
+ const v1VocabularyHistoricalPaths = [
222
+ '.agents/goals/**',
223
+ '**/.agents/goals/**',
224
+ '.agents/memory/**',
225
+ '**/.agents/memory/**',
226
+ '.agents/notes/**',
227
+ '**/.agents/notes/**',
228
+ '.agents/plans/**',
229
+ '**/.agents/plans/**',
230
+ '.claude/agent-memory/**',
231
+ '**/.claude/agent-memory/**',
232
+ '.changeset/**',
233
+ '**/.changeset/**',
234
+ '.trails/regrade/*.json',
235
+ '**/.trails/regrade/*.json',
236
+ '**/CHANGELOG.md',
237
+ 'docs/adr/0*.md',
238
+ 'docs/adr/decision-map.json',
239
+ 'docs/migration/*-to-adapter.md',
240
+ 'docs/migration/*-to-compose.md',
241
+ 'docs/migration/trailhead-to-surface.md',
242
+ 'docs/releases/beta*.md',
243
+ 'docs/releases/v1-vocabulary-reset.md',
244
+ 'docs/releases/v1-vocabulary-transition-workflow.md',
245
+ 'packages/warden/src/__tests__/retired-vocabulary.test.ts',
246
+ 'packages/warden/src/rules/retired-vocabulary.ts',
247
+ ];
248
+
249
+ const unique = (values: readonly string[]): string[] => [...new Set(values)];
250
+
251
+ const defineV1Transition = (
252
+ input: GovernedVocabularyTransitionInput
253
+ ): GovernedVocabularyTransition =>
254
+ defineTransition({
255
+ ...input,
256
+ preserve: input.preserve ?? [],
257
+ scope: {
258
+ ...input.scope,
259
+ exclude: unique([
260
+ ...v1VocabularyHardExcludes,
261
+ ...(input.scope?.exclude ?? []),
262
+ ]),
263
+ policyClassified: [
264
+ {
265
+ disposition: 'historical-by-policy',
266
+ paths: v1VocabularyHistoricalPaths,
267
+ reason:
268
+ 'Preserve authored migration plans and historical decision/release evidence while keeping occurrences visible to the run ledger.',
269
+ },
270
+ ...(input.scope?.policyClassified ?? []),
271
+ ],
272
+ ...(input.scope?.teachingSurfaces === undefined
273
+ ? {}
274
+ : { teachingSurfaces: input.scope.teachingSurfaces }),
275
+ },
276
+ });
277
+
278
+ export const governedVocabularyTransitions =
279
+ governedVocabularyRegistrySchema.parse([
280
+ defineTransition({
281
+ codeIdentifiers: ['ctx.cross', 'crossInput', 'crosses'],
282
+ docs: {
283
+ guidance: [
284
+ 'Exact ctx.cross, crossInput, and crosses forms are mechanically rewritten.',
285
+ 'Broader cross or Cross forms route to review because a text rule cannot prove the intended symbol boundary.',
286
+ ],
287
+ summary:
288
+ 'Beta.19 retired cross composition vocabulary in favor of compose.',
289
+ },
290
+ from: 'cross',
291
+ id: 'cross-compose',
292
+ intent:
293
+ 'Retire beta.19 cross composition vocabulary in favor of compose.',
294
+ kind: 'vocabulary',
295
+ oldForms: ['cross', 'Cross', 'crosses', 'crossInput', 'ctx.cross'],
296
+ reviewForms: ['Cross', 'cross'],
297
+ safeRewriteForms: {
298
+ crossInput: 'composeInput',
299
+ crosses: 'composes',
300
+ 'ctx.cross': 'ctx.compose',
301
+ },
302
+ status: 'complete',
303
+ symbolRenames: [
304
+ { ...reviewFunctionParamDeclarations, from: 'cross', to: 'compose' },
305
+ {
306
+ ...reviewFunctionParamDeclarations,
307
+ from: 'crossInput',
308
+ to: 'composeInput',
309
+ },
310
+ { ...reviewFunctionParamDeclarations, from: 'crosses', to: 'composes' },
311
+ ],
312
+ target: { kind: 'single', to: 'compose' },
313
+ }),
314
+ defineV1Transition({
315
+ codeIdentifiers: ['blaze', 'blazes'],
316
+ docs: {
317
+ guidance: [
318
+ 'Treat code/API identifiers as governed symbols, not prose-only vocabulary.',
319
+ 'Review inflected forms such as blazed or blazing instead of forcing a mechanical rewrite.',
320
+ ],
321
+ summary:
322
+ 'The authored trail behavior field is moving from blaze to implementation.',
323
+ },
324
+ from: 'blaze',
325
+ id: 'v1-blaze-implementation',
326
+ intent:
327
+ 'Move the authored trail behavior field from blaze to implementation for v1.',
328
+ kind: 'vocabulary',
329
+ oldForms: ['blaze', 'blazes', 'Blaze'],
330
+ reviewForms: ['Blaze', 'blazing', 'blazed', 'trailblaze'],
331
+ safeRewriteForms: {
332
+ blaze: 'implementation',
333
+ blazes: 'implementations',
334
+ },
335
+ status: 'complete',
336
+ stringLiteralRenames: [
337
+ { from: 'blaze', match: 'property-key', to: 'implementation' },
338
+ ],
339
+ symbolRenames: [
340
+ {
341
+ ...reviewFunctionParamDeclarations,
342
+ from: 'blaze',
343
+ match: 'identifier-segment',
344
+ to: 'implementation',
345
+ },
346
+ {
347
+ ...reviewFunctionParamDeclarations,
348
+ from: 'blazes',
349
+ to: 'implementations',
350
+ },
351
+ ],
352
+ target: { kind: 'single', to: 'implementation' },
353
+ }),
354
+ defineV1Transition({
355
+ codeIdentifiers: ['contour', 'contours', 'wayfind.contours'],
356
+ docs: {
357
+ guidance: [
358
+ 'Keep domain-object semantics distinct from entities in app data until the occurrence is classified.',
359
+ 'Treat contour code/API identifiers as governed symbols, with FunctionParam shadows routed to review.',
360
+ 'Rewrite exact framework string literals only; prose, substrings, and inflections stay outside mechanical apply.',
361
+ ],
362
+ summary:
363
+ 'The domain object declaration term is moving from contour to entity.',
364
+ },
365
+ from: 'contour',
366
+ id: 'v1-contour-entity',
367
+ intent:
368
+ 'Move the domain object declaration vocabulary from contour to entity for v1.',
369
+ kind: 'vocabulary',
370
+ oldForms: ['contour', 'contours', 'Contour'],
371
+ reviewForms: ['Contour', 'Contours', 'contoured', 'contouring'],
372
+ safeRewriteForms: {
373
+ contour: 'entity',
374
+ contours: 'entities',
375
+ },
376
+ status: 'complete',
377
+ stringLiteralRenames: [
378
+ { from: 'contour', match: 'review', to: 'entity' },
379
+ { from: 'contours', match: 'review', to: 'entities' },
380
+ { from: 'wayfind.contours', to: 'wayfind.entities' },
381
+ ],
382
+ symbolRenames: [
383
+ {
384
+ ...reviewFunctionParamDeclarations,
385
+ from: 'contour',
386
+ match: 'identifier-segment',
387
+ to: 'entity',
388
+ },
389
+ {
390
+ ...reviewFunctionParamDeclarations,
391
+ from: 'contours',
392
+ match: 'identifier-segment',
393
+ to: 'entities',
394
+ },
395
+ ],
396
+ target: { kind: 'single', to: 'entity' },
397
+ }),
398
+ defineV1Transition({
399
+ codeIdentifiers: [
400
+ '@ontrails/observe',
401
+ '@ontrails/observe/logtape',
402
+ '@ontrails/observe/pino',
403
+ 'packages/observe',
404
+ ],
405
+ docs: {
406
+ guidance: [
407
+ 'Treat the public package routes as exact code strings and module specifiers, not as general observability vocabulary.',
408
+ 'Rewrite the temporary Pino and LogTape subpaths only to their renamed temporary owner; their extraction to top-level adapters is a later transition.',
409
+ ],
410
+ summary:
411
+ 'The dependency-light observability owner moved from @ontrails/observe to @ontrails/observability.',
412
+ },
413
+ from: '@ontrails/observe',
414
+ id: 'v1-observe-observability',
415
+ intent:
416
+ 'Rename the dependency-light observability owner and its temporary adapter subpaths for v1.',
417
+ kind: 'vocabulary',
418
+ oldForms: [
419
+ '@ontrails/observe',
420
+ '@ontrails/observe/logtape',
421
+ '@ontrails/observe/pino',
422
+ 'packages/observe',
423
+ ],
424
+ reviewForms: [],
425
+ safeRewriteForms: {
426
+ '@ontrails/observe': '@ontrails/observability',
427
+ '@ontrails/observe/logtape': '@ontrails/observability/logtape',
428
+ '@ontrails/observe/pino': '@ontrails/observability/pino',
429
+ 'packages/observe': 'packages/observability',
430
+ },
431
+ status: 'complete',
432
+ stringLiteralRenames: [
433
+ {
434
+ from: '@ontrails/observe',
435
+ moduleSpecifier: { targetPackage: '@ontrails/observability' },
436
+ to: '@ontrails/observability',
437
+ },
438
+ {
439
+ from: '@ontrails/observe/logtape',
440
+ moduleSpecifier: { targetPackage: '@ontrails/observability' },
441
+ to: '@ontrails/observability/logtape',
442
+ },
443
+ {
444
+ from: '@ontrails/observe/pino',
445
+ moduleSpecifier: { targetPackage: '@ontrails/observability' },
446
+ to: '@ontrails/observability/pino',
447
+ },
448
+ { from: 'packages/observe', to: 'packages/observability' },
449
+ ],
450
+ target: { kind: 'single', to: '@ontrails/observability' },
451
+ }),
452
+ defineV1Transition({
453
+ codeIdentifiers: ['@ontrails/observability/logtape'],
454
+ docs: {
455
+ guidance: [
456
+ 'Move the temporary observability subpath to the extracted LogTape adapter package.',
457
+ 'Do not retain a compatibility subpath: the extracted package owns the real LogTape dependency boundary.',
458
+ ],
459
+ summary:
460
+ 'The temporary LogTape forwarding subpath was extracted into a real top-level adapter package.',
461
+ },
462
+ from: '@ontrails/observability/logtape',
463
+ id: 'v1-observability-logtape-extraction',
464
+ intent:
465
+ 'Replace the temporary LogTape forwarding subpath with the extracted @ontrails/logtape adapter.',
466
+ kind: 'vocabulary',
467
+ oldForms: ['@ontrails/observability/logtape'],
468
+ reviewForms: [],
469
+ safeRewriteForms: {
470
+ '@ontrails/observability/logtape': '@ontrails/logtape',
471
+ },
472
+ status: 'complete',
473
+ stringLiteralRenames: [
474
+ {
475
+ from: '@ontrails/observability/logtape',
476
+ moduleSpecifier: { targetPackage: '@ontrails/logtape' },
477
+ to: '@ontrails/logtape',
478
+ },
479
+ ],
480
+ target: { kind: 'single', to: '@ontrails/logtape' },
481
+ }),
482
+ defineV1Transition({
483
+ codeIdentifiers: ['@ontrails/observability/pino'],
484
+ docs: {
485
+ guidance: [
486
+ 'Move the temporary observability subpath to the extracted Pino adapter package.',
487
+ 'Do not retain a compatibility subpath: the extracted package owns the real Pino dependency boundary.',
488
+ ],
489
+ summary:
490
+ 'The temporary Pino forwarding subpath was extracted into a real top-level adapter package.',
491
+ },
492
+ from: '@ontrails/observability/pino',
493
+ id: 'v1-observability-pino-extraction',
494
+ intent:
495
+ 'Replace the temporary Pino forwarding subpath with the extracted @ontrails/pino adapter.',
496
+ kind: 'vocabulary',
497
+ oldForms: ['@ontrails/observability/pino'],
498
+ reviewForms: [],
499
+ safeRewriteForms: {
500
+ '@ontrails/observability/pino': '@ontrails/pino',
501
+ },
502
+ status: 'complete',
503
+ stringLiteralRenames: [
504
+ {
505
+ from: '@ontrails/observability/pino',
506
+ moduleSpecifier: { targetPackage: '@ontrails/pino' },
507
+ to: '@ontrails/pino',
508
+ },
509
+ ],
510
+ target: { kind: 'single', to: '@ontrails/pino' },
511
+ }),
512
+ defineV1Transition({
513
+ codeIdentifiers: ['@ontrails/tracing', 'packages/tracing'],
514
+ docs: {
515
+ guidance: [
516
+ 'Classify root imports by ownership: intrinsic trace contracts belong to @ontrails/core and developer-state APIs belong to @ontrails/observability/dev.',
517
+ 'Do not mechanically redirect the tracing root because it formerly combined more than one owner.',
518
+ ],
519
+ summary:
520
+ 'The former tracing root was folded into core and the observability developer-state subpath.',
521
+ },
522
+ from: '@ontrails/tracing',
523
+ id: 'v1-tracing-owner-fold',
524
+ intent:
525
+ 'Remove the multi-owner tracing package without inventing a false one-to-one package redirect.',
526
+ kind: 'vocabulary',
527
+ oldForms: ['@ontrails/tracing', 'packages/tracing'],
528
+ reviewForms: [],
529
+ safeRewriteForms: {},
530
+ status: 'complete',
531
+ target: {
532
+ guidance:
533
+ 'Root tracing imports require ownership classification; use @ontrails/core for intrinsic contracts and @ontrails/observability/dev for developer-state APIs.',
534
+ kind: 'classified',
535
+ options: [
536
+ {
537
+ to: '@ontrails/core',
538
+ when: 'The imported symbol is an intrinsic trace record, context, or sink registry contract.',
539
+ },
540
+ {
541
+ to: '@ontrails/observability/dev',
542
+ when: 'The imported symbol is developer-state storage, sampling, or tracing query/status tooling.',
543
+ },
544
+ ],
545
+ },
546
+ }),
547
+ defineV1Transition({
548
+ codeIdentifiers: ['@ontrails/tracing/otel'],
549
+ docs: {
550
+ guidance: [
551
+ 'Rewrite the exact OTel adapter subpath after the observability package declares its /otel export.',
552
+ ],
553
+ summary:
554
+ 'The supported OTel adapter moved to the observability package.',
555
+ },
556
+ from: '@ontrails/tracing/otel',
557
+ id: 'v1-tracing-otel-observability-otel',
558
+ intent:
559
+ 'Move the dependency-light OTel adapter from the removed tracing package to @ontrails/observability/otel.',
560
+ kind: 'vocabulary',
561
+ oldForms: ['@ontrails/tracing/otel'],
562
+ reviewForms: [],
563
+ safeRewriteForms: {
564
+ '@ontrails/tracing/otel': '@ontrails/observability/otel',
565
+ },
566
+ status: 'complete',
567
+ stringLiteralRenames: [
568
+ {
569
+ from: '@ontrails/tracing/otel',
570
+ moduleSpecifier: { targetPackage: '@ontrails/observability' },
571
+ to: '@ontrails/observability/otel',
572
+ },
573
+ ],
574
+ target: { kind: 'single', to: '@ontrails/observability/otel' },
575
+ }),
576
+ defineV1Transition({
577
+ codeIdentifiers: [
578
+ '@ontrails/topographer',
579
+ '@ontrails/topographer/backend-support',
580
+ '0042-core-topographer-boundary-doctrine',
581
+ 'core-topographer-boundary-doctrine',
582
+ 'Topographer-owned',
583
+ 'packages/topographer',
584
+ 'topographer',
585
+ 'topographers',
586
+ ],
587
+ docs: {
588
+ guidance: [
589
+ 'Treat topographer code/API identifiers as governed symbols, with case and compound forms handled by identifier-segment matching.',
590
+ 'Rewrite only exact package route/module-specifier occurrences; near routes and larger route strings stay untouched.',
591
+ ],
592
+ summary:
593
+ 'The graph artifact package and vocabulary moved from topographer to topography.',
594
+ },
595
+ from: 'topographer',
596
+ id: 'v1-topographer-topography',
597
+ intent:
598
+ 'Move graph artifact package routes and code vocabulary from topographer to topography for v1.',
599
+ kind: 'vocabulary',
600
+ oldForms: [
601
+ '@ontrails/topographer',
602
+ '@ontrails/topographer/backend-support',
603
+ '0042-core-topographer-boundary-doctrine',
604
+ 'core-topographer-boundary-doctrine',
605
+ 'Topographer-owned',
606
+ 'packages/topographer',
607
+ 'topographer',
608
+ 'topographers',
609
+ 'Topographer',
610
+ ],
611
+ reviewForms: ['Topographer'],
612
+ safeRewriteForms: {
613
+ '0042-core-topographer-boundary-doctrine':
614
+ '0042-core-topography-boundary-doctrine',
615
+ '@ontrails/topographer': '@ontrails/topography',
616
+ '@ontrails/topographer/backend-support':
617
+ '@ontrails/topography/backend-support',
618
+ 'Topographer-owned': 'Topography-owned',
619
+ 'core-topographer-boundary-doctrine':
620
+ 'core-topography-boundary-doctrine',
621
+ 'packages/topographer': 'packages/topography',
622
+ topographer: 'topography',
623
+ topographers: 'topographies',
624
+ },
625
+ status: 'complete',
626
+ stringLiteralRenames: [
627
+ {
628
+ from: '@ontrails/topographer',
629
+ moduleSpecifier: { targetPackage: '@ontrails/topography' },
630
+ to: '@ontrails/topography',
631
+ },
632
+ {
633
+ from: '@ontrails/topographer/backend-support',
634
+ moduleSpecifier: { targetPackage: '@ontrails/topography' },
635
+ to: '@ontrails/topography/backend-support',
636
+ },
637
+ {
638
+ from: '0042-core-topographer-boundary-doctrine',
639
+ to: '0042-core-topography-boundary-doctrine',
640
+ },
641
+ {
642
+ from: 'core-topographer-boundary-doctrine',
643
+ to: 'core-topography-boundary-doctrine',
644
+ },
645
+ { from: 'Topographer-owned', to: 'Topography-owned' },
646
+ { from: 'packages/topographer', to: 'packages/topography' },
647
+ ],
648
+ symbolRenames: [
649
+ {
650
+ ...reviewFunctionParamDeclarations,
651
+ from: 'topographer',
652
+ match: 'identifier-segment',
653
+ to: 'topography',
654
+ },
655
+ {
656
+ ...reviewFunctionParamDeclarations,
657
+ from: 'topographers',
658
+ match: 'identifier-segment',
659
+ to: 'topographies',
660
+ },
661
+ ],
662
+ target: { kind: 'single', to: 'topography' },
663
+ }),
664
+ defineV1Transition({
665
+ codeIdentifiers: [
666
+ 'facets',
667
+ 'facetId',
668
+ 'McpSurfaceFacetMap',
669
+ 'surface-facet-coherence',
670
+ 'wayfind.facets',
671
+ ],
672
+ docs: {
673
+ guidance: [
674
+ 'A trailhead is one grouped surface entry fronting several trails while preserving member identity.',
675
+ 'Code/API identifiers remain governed symbols and need structured preservation before broad application.',
676
+ ],
677
+ summary:
678
+ 'The grouped surface entry term is moving from facet to trailhead.',
679
+ },
680
+ from: 'facet',
681
+ id: 'v1-facet-trailhead',
682
+ intent:
683
+ 'Move grouped surface entry vocabulary from facet to trailhead for v1.',
684
+ kind: 'vocabulary',
685
+ oldForms: ['facet', 'facets', 'Facet'],
686
+ reviewForms: ['Facet'],
687
+ safeRewriteForms: {
688
+ facet: 'trailhead',
689
+ facets: 'trailheads',
690
+ },
691
+ status: 'complete',
692
+ stringLiteralRenames: [
693
+ { from: 'surface-facet-coherence', to: 'surface-trailhead-coherence' },
694
+ { from: 'wayfind.facets', to: 'wayfind.trailheads' },
695
+ ],
696
+ symbolRenames: [
697
+ { ...reviewFunctionParamDeclarations, from: 'facet', to: 'trailhead' },
698
+ {
699
+ ...reviewFunctionParamDeclarations,
700
+ from: 'facets',
701
+ to: 'trailheads',
702
+ },
703
+ {
704
+ ...reviewFunctionParamDeclarations,
705
+ from: 'facetId',
706
+ to: 'trailheadId',
707
+ },
708
+ {
709
+ ...reviewFunctionParamDeclarations,
710
+ from: 'McpSurfaceFacetMap',
711
+ to: 'McpSurfaceTrailheadMap',
712
+ },
713
+ ],
714
+ target: { kind: 'single', to: 'trailhead' },
715
+ }),
716
+ defineV1Transition({
717
+ codeIdentifiers: ['@ontrails/warden/ast'],
718
+ docs: {
719
+ guidance: [
720
+ 'Treat the package route as an exact code string and module specifier, not as vocabulary prose or an identifier segment.',
721
+ 'Rewrite only exact string literal/module-specifier occurrences; near routes and larger strings stay untouched.',
722
+ ],
723
+ summary:
724
+ 'The reusable AST helper route moved from @ontrails/warden/ast to @ontrails/source.',
725
+ },
726
+ from: '@ontrails/warden/ast',
727
+ id: 'v1-warden-ast-source',
728
+ intent:
729
+ 'Move reusable AST helper imports from @ontrails/warden/ast to @ontrails/source for v1.',
730
+ kind: 'vocabulary',
731
+ oldForms: ['@ontrails/warden/ast'],
732
+ preserve: [
733
+ {
734
+ paths: [
735
+ 'adapters/commander/src/__tests__/to-commander.test.ts',
736
+ 'apps/trails/src/__tests__/mcp.test.ts',
737
+ 'apps/trails/src/__tests__/regrade.test.ts',
738
+ 'packages/regrade/src/downstream/__tests__/ast-rewrite.test.ts',
739
+ 'packages/regrade/src/downstream/__tests__/vocabulary.test.ts',
740
+ 'packages/warden/src/__tests__/retired-vocabulary.test.ts',
741
+ 'scripts/verify-oxc-resolver-published.ts',
742
+ ],
743
+ pattern: '^@ontrails/warden/ast$',
744
+ reason:
745
+ 'Preserve transition regression fixtures and the intentional negative resolver assertion after the public route is retired.',
746
+ },
747
+ {
748
+ paths: [
749
+ 'apps/trails/src/__tests__/regrade.test.ts',
750
+ 'packages/regrade/src/downstream/__tests__/ast-rewrite.test.ts',
751
+ 'packages/regrade/src/downstream/__tests__/vocabulary.test.ts',
752
+ ],
753
+ pattern: '^@ontrails/warden/ast(?:\\.js|\\.utils|/utils)$',
754
+ reason:
755
+ 'Preserve the adjacent package-route variants used by the authored Warden AST transition regression fixtures.',
756
+ },
757
+ ],
758
+ reviewForms: [],
759
+ safeRewriteForms: {
760
+ '@ontrails/warden/ast': '@ontrails/source',
761
+ },
762
+ status: 'complete',
763
+ stringLiteralRenames: [
764
+ {
765
+ from: '@ontrails/warden/ast',
766
+ moduleSpecifier: { targetPackage: '@ontrails/source' },
767
+ to: '@ontrails/source',
768
+ },
769
+ ],
770
+ target: { kind: 'single', to: '@ontrails/source' },
771
+ }),
772
+ defineV1Transition({
773
+ codeIdentifiers: ['@ontrails/wayfinder'],
774
+ docs: {
775
+ guidance: [
776
+ 'Treat the package route as an exact code string and module specifier, not as Wayfind product vocabulary.',
777
+ 'Rewrite only the exact package route; subpaths, near routes, and larger strings stay untouched for review.',
778
+ ],
779
+ summary:
780
+ 'The Wayfinder package API moved into @ontrails/topography while Wayfind remains the operator-facing product name.',
781
+ },
782
+ from: '@ontrails/wayfinder',
783
+ id: 'v1-wayfinder-topography',
784
+ intent:
785
+ 'Move programmatic Wayfinder imports into @ontrails/topography while preserving Wayfind surface vocabulary.',
786
+ kind: 'vocabulary',
787
+ oldForms: ['@ontrails/wayfinder'],
788
+ reviewForms: [],
789
+ safeRewriteForms: {
790
+ '@ontrails/wayfinder': '@ontrails/topography',
791
+ },
792
+ status: 'complete',
793
+ stringLiteralRenames: [
794
+ {
795
+ from: '@ontrails/wayfinder',
796
+ moduleSpecifier: { targetPackage: '@ontrails/topography' },
797
+ to: '@ontrails/topography',
798
+ },
799
+ ],
800
+ target: { kind: 'single', to: '@ontrails/topography' },
801
+ }),
802
+ defineV1Transition({
803
+ codeIdentifiers: [
804
+ 'ActivationSourceProjection',
805
+ 'AstFieldProjection',
806
+ 'DeriveTrailCliCommandProjectionOptions',
807
+ 'ErrorClassSurfaceProjection',
808
+ 'ErrorDiagnosticsProjection',
809
+ 'HTTP_METHOD_PROJECTION_PATH',
810
+ 'HttpInputProjection',
811
+ 'HttpLayerInputProjection',
812
+ 'LayerFieldProjection',
813
+ 'LayerFlagProjection',
814
+ 'LibraryInputProjection',
815
+ 'LibraryLayerFieldProjection',
816
+ 'LibraryLayerInputProjection',
817
+ 'LibraryProjection',
818
+ 'McpInputProjection',
819
+ 'McpLayerInputProjection',
820
+ 'NormalizedTopoProjection',
821
+ 'OutputSchemaProjection',
822
+ 'PROJECTION_BLOCKING_RULES',
823
+ 'ProjectionMap',
824
+ 'ProjectedLayerField',
825
+ 'ProjectedPermitRequirement',
826
+ 'RenamedLayerFieldProjection',
827
+ 'ShippedSurfaceProjection',
828
+ 'SurfaceErrorProjection',
829
+ 'SurfaceProjectionSource',
830
+ 'SurfaceTrailVersionProjection',
831
+ 'TopoGraphLibraryProjection',
832
+ 'TopoStoreSurfaceProjectionRecord',
833
+ 'TopoSurfaceProjectionRow',
834
+ 'TrailCliCommandProjection',
835
+ 'TrailCliProjection',
836
+ 'TrailCliProjectionInput',
837
+ 'TrailErrorTaxonomyProjection',
838
+ 'buildOutputSchemaProjection',
839
+ 'buildProjectionDiagnostic',
840
+ 'cliProjection',
841
+ 'cliProjectionSchema',
842
+ 'collectLibraryProjection',
843
+ 'collectionExtensionProjectionForFileRenames',
844
+ 'extensionProjection',
845
+ 'filterProjectedTargetExtensions',
846
+ 'projectActivationEdge',
847
+ 'projectActivationSource',
848
+ 'projectActivationSourceDeclaration',
849
+ 'projectActual',
850
+ 'projectAstFields',
851
+ 'projectErrorClassSurface',
852
+ 'projectErrorDiagnostics',
853
+ 'projectExample',
854
+ 'projectHttpInputSchema',
855
+ 'projectHttpLayerInput',
856
+ 'projectInputForSchema',
857
+ 'projectLayerFieldName',
858
+ 'projectLayerFlags',
859
+ 'projectLayerInputFields',
860
+ 'projectLibraryInput',
861
+ 'projectMcpInputSchema',
862
+ 'projectMcpLayerInput',
863
+ 'projectMcpOutputSchema',
864
+ 'projectPermitRequirement',
865
+ 'projectPublicSurfaceError',
866
+ 'projectSchema',
867
+ 'projectSignalAssertion',
868
+ 'projectSignalAssertions',
869
+ 'projectSignalExample',
870
+ 'projectSingleLayerFlags',
871
+ 'projectSurfaceError',
872
+ 'projectSurfaceMapTool',
873
+ 'projectTrailVersionEntry',
874
+ 'projectTrailVersions',
875
+ 'projectVersionDetours',
876
+ 'projectVersionRuntimeRefs',
877
+ 'projectVocabularyText',
878
+ 'projected',
879
+ 'projectedEvidence',
880
+ 'projectedFileInScopeCount',
881
+ 'projectedTargetPaths',
882
+ 'SchemaProjector',
883
+ 'deriveShippedSurfaceProjectionInventory',
884
+ 'deriveTrailCliCommandProjection',
885
+ 'errorSurfaceProjectionSchema',
886
+ 'errorTaxonomyProjectionSchema',
887
+ 'expectProjectionCounts',
888
+ 'inputProjection',
889
+ 'isProjectionBlockingIssue',
890
+ 'isTrailCliProjection',
891
+ 'keepProjectionBlockingIssues',
892
+ 'layerProjection',
893
+ 'libraryProjectionCoherence',
894
+ 'libraryProjectionCoherenceTrail',
895
+ 'normalizeTopoProjection',
896
+ 'ownerProjectionParity',
897
+ 'ownerProjectionParityTrail',
898
+ 'projectionDb',
899
+ 'projectionKeys',
900
+ 'projectionSource',
901
+ 'seedLegacyProjectionStore',
902
+ 'simpleProjectionApp',
903
+ 'surfaceProjectionBaseOutput',
904
+ 'surfaceProjectionOutput',
905
+ 'taxonomyProjection',
906
+ 'topoGraphLibraryProjectionSchema',
907
+ 'trailCliProjectionFor',
908
+ 'withProjectionDb',
909
+ ],
910
+ docs: {
911
+ guidance: [
912
+ 'Use derive for contract-owned fact production.',
913
+ 'Use render for surface- or operator-facing presentation.',
914
+ 'Route every occurrence to review until the stage is classified.',
915
+ ],
916
+ summary: 'Projection vocabulary splits by stage into derive or render.',
917
+ },
918
+ fileRenames: [
919
+ {
920
+ from: 'docs/adr/drafts/20260608-release-provenance-as-lifecycle-projection.md',
921
+ to: 'docs/adr/drafts/20260608-release-provenance-as-lifecycle-derivation.md',
922
+ },
923
+ {
924
+ from: 'packages/cli/src/__tests__/layer-input-projection.test.ts',
925
+ to: 'packages/cli/src/__tests__/layer-input-rendering.test.ts',
926
+ },
927
+ {
928
+ from: 'packages/core/src/__tests__/activation-source-projection.test.ts',
929
+ to: 'packages/core/src/__tests__/activation-source-derivation.test.ts',
930
+ },
931
+ {
932
+ from: 'packages/core/src/__tests__/error-projection.test.ts',
933
+ to: 'packages/core/src/__tests__/error-rendering.test.ts',
934
+ },
935
+ {
936
+ from: 'packages/core/src/activation-source-projection.ts',
937
+ to: 'packages/core/src/activation-source-derivation.ts',
938
+ },
939
+ {
940
+ from: 'packages/core/src/error-projection.ts',
941
+ to: 'packages/core/src/error-rendering.ts',
942
+ },
943
+ {
944
+ from: 'packages/core/src/layer-projection.ts',
945
+ to: 'packages/core/src/layer-field-rendering.ts',
946
+ },
947
+ {
948
+ from: 'packages/http/src/__tests__/layer-input-projection.test.ts',
949
+ to: 'packages/http/src/__tests__/layer-input-rendering.test.ts',
950
+ },
951
+ {
952
+ from: 'packages/mcp/src/__tests__/layer-input-projection.test.ts',
953
+ to: 'packages/mcp/src/__tests__/layer-input-rendering.test.ts',
954
+ },
955
+ {
956
+ from: 'packages/topography/src/library-projection.ts',
957
+ to: 'packages/topography/src/library-derivation.ts',
958
+ },
959
+ {
960
+ from: 'packages/warden/src/__tests__/library-projection-coherence.test.ts',
961
+ to: 'packages/warden/src/__tests__/library-render-coherence.test.ts',
962
+ },
963
+ {
964
+ from: 'packages/warden/src/__tests__/owner-projection-parity.test.ts',
965
+ to: 'packages/warden/src/__tests__/owner-render-parity.test.ts',
966
+ },
967
+ {
968
+ from: 'packages/warden/src/rules/library-projection-coherence.ts',
969
+ to: 'packages/warden/src/rules/library-render-coherence.ts',
970
+ },
971
+ {
972
+ from: 'packages/warden/src/rules/owner-projection-parity.ts',
973
+ to: 'packages/warden/src/rules/owner-render-parity.ts',
974
+ },
975
+ {
976
+ from: 'packages/warden/src/trails/library-projection-coherence.trail.ts',
977
+ to: 'packages/warden/src/trails/library-render-coherence.trail.ts',
978
+ },
979
+ {
980
+ from: 'packages/warden/src/trails/owner-projection-parity.trail.ts',
981
+ to: 'packages/warden/src/trails/owner-render-parity.trail.ts',
982
+ },
983
+ ],
984
+ from: 'projection',
985
+ id: 'v1-projection-derive-render',
986
+ intent:
987
+ 'Split projection vocabulary into derive/render by lifecycle stage for v1.',
988
+ kind: 'vocabulary',
989
+ oldForms: [
990
+ 'projection',
991
+ 'projections',
992
+ 'project',
993
+ 'projects',
994
+ 'Projects',
995
+ 'projecting',
996
+ 'Projecting',
997
+ 'projected',
998
+ 'Projected',
999
+ ],
1000
+ preserve: [
1001
+ {
1002
+ pattern: "\\b[Pp]roject(?:[-/:]|['’]s\\b)",
1003
+ reason:
1004
+ 'Preserve compound project-domain nouns, permit scopes, paths, and possessives without suppressing the standalone retired verb.',
1005
+ },
1006
+ {
1007
+ pattern:
1008
+ '\\b[Pp]roject\\s+(?:root|directory|files?|paths?|state|scripts?|guidance|structure|name|marker|context|conventions?|vocabulary|rules?|findings?|operations?|key|source|config|metadata|policy|package|scope|entities|resources|diagnostics|history|update|control|instructions?|overview|documentation|boundary|helpers?|shape|dependencies|dependency|detection|substrate|truth|work|writing|management|settings?|skills?|issue|ids?)\\b',
1009
+ reason:
1010
+ 'Preserve project as an attributive noun before established repository and workspace concepts.',
1011
+ },
1012
+ {
1013
+ pattern:
1014
+ '\\b(?:A|An|The|This|That|a|an|the|this|that|each|entire|every|existing|new|current|same|target|nested|downstream|Trails|Linear|Node|UX|logical|adopting|generated|scaffolded|source-shaped|first-party|temp)\\s+project\\b',
1015
+ reason:
1016
+ 'Preserve project as an ordinary count noun selected by a determiner or domain adjective.',
1017
+ },
1018
+ {
1019
+ pattern:
1020
+ "\\b(?:Trails|Matt[’']s|Vite|ADR|adopting|application|consumer|customer|developer|framework|package|software|source|workspace|generated|scaffolded|new|existing|source-shaped|first-party|downstream|most|large|all|many|multiple|other|our|several|their|these|those|your)\\s+projects\\b",
1021
+ reason:
1022
+ 'Preserve projects as an ordinary plural repository or application noun selected by an established domain modifier.',
1023
+ },
1024
+ {
1025
+ pattern:
1026
+ '\\b(?:across|among|between|for|from|in|inside|of|outside|under|within|with|without)\\s+(?:the\\s+)?projects\\b',
1027
+ reason:
1028
+ 'Preserve projects as the object of a repository or application preposition.',
1029
+ },
1030
+ {
1031
+ pattern:
1032
+ '(?:[\'"`/.][Pp]rojects(?:[\'"`/]|\\b)|\\b[Pp]rojects\\b\\s*(?:=|[):;,]))',
1033
+ reason:
1034
+ 'Preserve exact projects domain literals, member accesses, route segments, and enum values.',
1035
+ },
1036
+ {
1037
+ pattern:
1038
+ '\\b(?:in|for|within|across|under|outside|inside|from|of|with|without|per|before|after)\\s+(?:the\\s+)?project\\b',
1039
+ reason:
1040
+ 'Preserve project as the object of a repository or workspace preposition.',
1041
+ },
1042
+ {
1043
+ pattern:
1044
+ '(?:[\'"`/]project(?:[\'"`/]|\\b)|\\bproject\\b\\s*(?:=|[);,]))',
1045
+ reason:
1046
+ 'Preserve exact project domain literals, example variables, route segments, and enum values.',
1047
+ },
1048
+ {
1049
+ pattern:
1050
+ '(?:\\bmemory:\\s*project\\b|\\bproject\\b(?=\\s+(?:from|health-check|show)\\b))',
1051
+ reason:
1052
+ 'Preserve project as an exact metadata value, import alias, or domain command segment.',
1053
+ },
1054
+ {
1055
+ pattern:
1056
+ '(?:\\bA Node project\\b|\\ba non-trivial UX project\\b|\\bproject\\s+`warden\\.)',
1057
+ reason:
1058
+ 'Preserve established multiword project nouns and project-scoped Warden configuration prose.',
1059
+ },
1060
+ ],
1061
+ provenance: { mode: 'regrade-history' },
1062
+ reviewForms: [
1063
+ 'projection',
1064
+ 'projections',
1065
+ 'project',
1066
+ 'projects',
1067
+ 'Projects',
1068
+ 'projecting',
1069
+ 'Projecting',
1070
+ 'projected',
1071
+ 'Projected',
1072
+ ],
1073
+ safeRewriteForms: {},
1074
+ scope: {
1075
+ policyClassified: [
1076
+ {
1077
+ disposition: 'explicit-preserve',
1078
+ expectMatches: true,
1079
+ paths: [
1080
+ 'apps/trails/src/__tests__/mcp.test.ts',
1081
+ 'apps/trails/src/__tests__/regrade.test.ts',
1082
+ 'packages/regrade/src/downstream/__tests__/**/*.test.ts',
1083
+ ],
1084
+ reason:
1085
+ 'Preserve exact old/new vocabulary fixtures that prove CLI, MCP, registry, and rewrite behavior without treating them as current teaching or API residue.',
1086
+ },
1087
+ {
1088
+ disposition: 'historical-by-policy',
1089
+ expectMatches: true,
1090
+ paths: ['docs/adr/0*.md'],
1091
+ reason:
1092
+ 'The Trails projection census records accepted ADR history that must remain visible during the v1 split.',
1093
+ },
1094
+ ],
1095
+ teachingSurfaces: ['docs/**'],
1096
+ },
1097
+ status: 'complete',
1098
+ stringLiteralRenames: [
1099
+ {
1100
+ from: 'library-projection-coherence',
1101
+ to: 'library-render-coherence',
1102
+ },
1103
+ { from: 'owner-projection-parity', to: 'owner-render-parity' },
1104
+ { from: 'surface-projects', to: 'surface-renders' },
1105
+ ],
1106
+ symbolRenames: [
1107
+ { from: 'ActivationSourceProjection', to: 'ActivationSourceFacts' },
1108
+ { from: 'AstFieldProjection', to: 'AstFieldView' },
1109
+ {
1110
+ from: 'DeriveTrailCliCommandProjectionOptions',
1111
+ to: 'DeriveTrailCliCommandOptions',
1112
+ },
1113
+ {
1114
+ from: 'ErrorClassSurfaceProjection',
1115
+ to: 'ErrorClassSurfaceRendering',
1116
+ },
1117
+ {
1118
+ from: 'ErrorDiagnosticsProjection',
1119
+ to: 'ErrorDiagnosticsRendering',
1120
+ },
1121
+ {
1122
+ from: 'HTTP_METHOD_PROJECTION_PATH',
1123
+ to: 'HTTP_METHOD_DERIVATION_PATH',
1124
+ },
1125
+ { from: 'HttpInputProjection', to: 'HttpInputRendering' },
1126
+ {
1127
+ from: 'HttpLayerInputProjection',
1128
+ to: 'HttpLayerInputRendering',
1129
+ },
1130
+ { from: 'LayerFieldProjection', to: 'LayerFieldRendering' },
1131
+ { from: 'LayerFlagProjection', to: 'LayerFlagRendering' },
1132
+ { from: 'LibraryInputProjection', to: 'LibraryInputRendering' },
1133
+ {
1134
+ from: 'LibraryLayerFieldProjection',
1135
+ to: 'LibraryLayerFieldRendering',
1136
+ },
1137
+ {
1138
+ from: 'LibraryLayerInputProjection',
1139
+ to: 'LibraryLayerInputRendering',
1140
+ },
1141
+ { from: 'LibraryProjection', to: 'LibraryRenderingPlan' },
1142
+ { from: 'McpInputProjection', to: 'McpInputRendering' },
1143
+ {
1144
+ from: 'McpLayerInputProjection',
1145
+ to: 'McpLayerInputRendering',
1146
+ },
1147
+ { from: 'NormalizedTopoProjection', to: 'NormalizedTopoFacts' },
1148
+ {
1149
+ from: 'OutputSchemaProjection',
1150
+ to: 'McpOutputSchemaRendering',
1151
+ },
1152
+ {
1153
+ from: 'PROJECTION_BLOCKING_RULES',
1154
+ to: 'DERIVATION_BLOCKING_RULES',
1155
+ },
1156
+ { from: 'ProjectionMap', to: 'IntentKeyMap' },
1157
+ { from: 'ProjectedLayerField', to: 'RenderedLayerField' },
1158
+ {
1159
+ from: 'ProjectedPermitRequirement',
1160
+ to: 'DerivedPermitRequirement',
1161
+ },
1162
+ {
1163
+ from: 'RenamedLayerFieldProjection',
1164
+ to: 'RenamedLayerFieldRendering',
1165
+ },
1166
+ { from: 'ShippedSurfaceProjection', to: 'ShippedSurfaceDerived' },
1167
+ { from: 'SurfaceErrorProjection', to: 'SurfaceErrorRendering' },
1168
+ { from: 'SurfaceProjectionSource', to: 'SurfaceDerivedSource' },
1169
+ {
1170
+ from: 'SurfaceTrailVersionProjection',
1171
+ to: 'SurfaceTrailVersionRendering',
1172
+ },
1173
+ {
1174
+ from: 'TopoGraphLibraryProjection',
1175
+ to: 'TopoGraphLibraryDerived',
1176
+ },
1177
+ {
1178
+ from: 'TopoStoreSurfaceProjectionRecord',
1179
+ to: 'TopoStoreSurfaceDerivedRecord',
1180
+ },
1181
+ {
1182
+ from: 'TopoSurfaceProjectionRow',
1183
+ to: 'TopoSurfaceDerivedRow',
1184
+ },
1185
+ {
1186
+ from: 'TrailCliCommandProjection',
1187
+ to: 'TrailCliCommandRendering',
1188
+ },
1189
+ { from: 'TrailCliProjection', to: 'TrailCliRendering' },
1190
+ {
1191
+ from: 'TrailCliProjectionInput',
1192
+ to: 'TrailCliRenderingInput',
1193
+ },
1194
+ {
1195
+ from: 'TrailErrorTaxonomyProjection',
1196
+ to: 'TrailErrorTaxonomyFacts',
1197
+ },
1198
+ {
1199
+ from: 'buildOutputSchemaProjection',
1200
+ to: 'buildMcpOutputSchemaRendering',
1201
+ },
1202
+ {
1203
+ from: 'buildProjectionDiagnostic',
1204
+ to: 'buildDerivationDiagnostic',
1205
+ },
1206
+ { from: 'cliProjection', to: 'cliRendering' },
1207
+ { from: 'cliProjectionSchema', to: 'cliDerivedSchema' },
1208
+ {
1209
+ from: 'collectLibraryProjection',
1210
+ to: 'deriveTopoGraphLibrary',
1211
+ },
1212
+ {
1213
+ from: 'collectionExtensionProjectionForFileRenames',
1214
+ to: 'deriveCollectionExtensionsForFileRenames',
1215
+ },
1216
+ { from: 'extensionProjection', to: 'derivedExtensions' },
1217
+ {
1218
+ from: 'filterProjectedTargetExtensions',
1219
+ to: 'filterDerivedTargetExtensions',
1220
+ },
1221
+ { from: 'projectActivationEdge', to: 'deriveActivationEdge' },
1222
+ { from: 'projectActivationSource', to: 'deriveActivationSource' },
1223
+ {
1224
+ from: 'projectActivationSourceDeclaration',
1225
+ to: 'deriveActivationSourceFacts',
1226
+ },
1227
+ { from: 'projectActual', to: 'deriveActualOutcome' },
1228
+ { from: 'projectAstFields', to: 'deriveAstFieldView' },
1229
+ {
1230
+ from: 'projectErrorClassSurface',
1231
+ to: 'renderErrorClassSurface',
1232
+ },
1233
+ { from: 'projectErrorDiagnostics', to: 'renderErrorDiagnostics' },
1234
+ { from: 'projectExample', to: 'deriveExample' },
1235
+ { from: 'projectHttpInputSchema', to: 'renderHttpInputSchema' },
1236
+ { from: 'projectHttpLayerInput', to: 'renderHttpLayerInput' },
1237
+ { from: 'projectInputForSchema', to: 'deriveInputForSchema' },
1238
+ { from: 'projectLayerFieldName', to: 'renderLayerFieldName' },
1239
+ { from: 'projectLayerFlags', to: 'renderLayerFlags' },
1240
+ { from: 'projectLayerInputFields', to: 'renderLayerInputFields' },
1241
+ { from: 'projectLibraryInput', to: 'renderLibraryInput' },
1242
+ { from: 'projectMcpInputSchema', to: 'renderMcpInputSchema' },
1243
+ { from: 'projectMcpLayerInput', to: 'renderMcpLayerInput' },
1244
+ { from: 'projectMcpOutputSchema', to: 'renderMcpOutputSchema' },
1245
+ { from: 'projectPermitRequirement', to: 'derivePermitRequirement' },
1246
+ {
1247
+ from: 'projectPublicSurfaceError',
1248
+ to: 'renderPublicSurfaceError',
1249
+ },
1250
+ { from: 'projectSchema', to: 'deriveSchema' },
1251
+ { from: 'projectSignalAssertion', to: 'deriveSignalAssertion' },
1252
+ { from: 'projectSignalAssertions', to: 'deriveSignalAssertions' },
1253
+ { from: 'projectSignalExample', to: 'deriveSignalExample' },
1254
+ { from: 'projectSingleLayerFlags', to: 'renderSingleLayerFlags' },
1255
+ { from: 'projectSurfaceError', to: 'renderSurfaceError' },
1256
+ { from: 'projectSurfaceMapTool', to: 'renderSurfaceMapTool' },
1257
+ { from: 'projectTrailVersionEntry', to: 'deriveTrailVersionEntry' },
1258
+ { from: 'projectTrailVersions', to: 'deriveTrailVersions' },
1259
+ { from: 'projectVersionDetours', to: 'deriveVersionDetours' },
1260
+ {
1261
+ from: 'projectVersionRuntimeRefs',
1262
+ to: 'deriveVersionRuntimeRefs',
1263
+ },
1264
+ { from: 'projectVocabularyText', to: 'deriveVocabularyText' },
1265
+ {
1266
+ from: 'projected',
1267
+ safety: 'review',
1268
+ to: 'derived',
1269
+ },
1270
+ { from: 'projectedEvidence', to: 'derivedEvidence' },
1271
+ {
1272
+ from: 'projectedFileInScopeCount',
1273
+ to: 'derivedFileInScopeCount',
1274
+ },
1275
+ { from: 'projectedTargetPaths', to: 'derivedTargetPaths' },
1276
+ { from: 'SchemaProjector', to: 'SchemaDeriver' },
1277
+ {
1278
+ from: 'deriveShippedSurfaceProjectionInventory',
1279
+ to: 'deriveShippedSurfaceInventory',
1280
+ },
1281
+ {
1282
+ from: 'deriveTrailCliCommandProjection',
1283
+ to: 'deriveTrailCliCommandRendering',
1284
+ },
1285
+ {
1286
+ from: 'errorSurfaceProjectionSchema',
1287
+ to: 'errorSurfaceDerivedSchema',
1288
+ },
1289
+ {
1290
+ from: 'errorTaxonomyProjectionSchema',
1291
+ to: 'errorTaxonomyDerivedSchema',
1292
+ },
1293
+ { from: 'expectProjectionCounts', to: 'expectDerivedRowCounts' },
1294
+ { from: 'inputProjection', to: 'inputRendering' },
1295
+ {
1296
+ from: 'isProjectionBlockingIssue',
1297
+ to: 'isDerivationBlockingIssue',
1298
+ },
1299
+ { from: 'isTrailCliProjection', to: 'isTrailCliRendering' },
1300
+ {
1301
+ from: 'keepProjectionBlockingIssues',
1302
+ to: 'keepDerivationBlockingIssues',
1303
+ },
1304
+ { from: 'layerProjection', to: 'layerRendering' },
1305
+ {
1306
+ from: 'libraryProjectionCoherence',
1307
+ to: 'libraryRenderCoherence',
1308
+ },
1309
+ {
1310
+ from: 'libraryProjectionCoherenceTrail',
1311
+ to: 'libraryRenderCoherenceTrail',
1312
+ },
1313
+ {
1314
+ from: 'normalizeTopoProjection',
1315
+ to: 'deriveNormalizedTopoRows',
1316
+ },
1317
+ { from: 'ownerProjectionParity', to: 'ownerRenderParity' },
1318
+ {
1319
+ from: 'ownerProjectionParityTrail',
1320
+ to: 'ownerRenderParityTrail',
1321
+ },
1322
+ { from: 'projectionDb', to: 'derivedDb' },
1323
+ { from: 'projectionKeys', to: 'derivedKeys' },
1324
+ { from: 'projectionSource', to: 'derivedSource' },
1325
+ {
1326
+ from: 'seedLegacyProjectionStore',
1327
+ to: 'seedLegacyDerivedStore',
1328
+ },
1329
+ { from: 'simpleProjectionApp', to: 'simpleDerivedRowsApp' },
1330
+ {
1331
+ from: 'surfaceProjectionBaseOutput',
1332
+ to: 'surfaceDerivedBaseOutput',
1333
+ },
1334
+ {
1335
+ from: 'surfaceProjectionOutput',
1336
+ to: 'surfaceDerivedOutput',
1337
+ },
1338
+ { from: 'taxonomyProjection', to: 'deriveTaxonomyFacts' },
1339
+ {
1340
+ from: 'topoGraphLibraryProjectionSchema',
1341
+ to: 'topoGraphLibraryDerivedSchema',
1342
+ },
1343
+ { from: 'trailCliProjectionFor', to: 'trailCliRenderingFor' },
1344
+ { from: 'withProjectionDb', to: 'withDerivedDb' },
1345
+ ],
1346
+ target: {
1347
+ guidance:
1348
+ 'No single replacement is safe. Classify by whether the occurrence produces contract facts or presents derived facts.',
1349
+ kind: 'classified',
1350
+ options: [
1351
+ {
1352
+ to: 'derive',
1353
+ when: 'The occurrence describes producing contract-owned facts or inferred data from authored inputs.',
1354
+ },
1355
+ {
1356
+ to: 'render',
1357
+ when: 'The occurrence describes presenting derived facts through a surface, report, guide, or operator output.',
1358
+ },
1359
+ ],
1360
+ },
1361
+ }),
1362
+ ]);
1363
+
1364
+ export const listGovernedVocabularyTransitions =
1365
+ (): readonly GovernedVocabularyTransition[] => governedVocabularyTransitions;
1366
+
1367
+ export const getGovernedVocabularyTransition = (
1368
+ id: string
1369
+ ): GovernedVocabularyTransition | undefined =>
1370
+ governedVocabularyTransitions.find((transition) => transition.id === id);
1371
+
1372
+ export const requireGovernedVocabularyTransition = (
1373
+ id: string
1374
+ ): GovernedVocabularyTransition => {
1375
+ const transition = getGovernedVocabularyTransition(id);
1376
+ if (transition === undefined) {
1377
+ throw new Error(`Unknown governed vocabulary transition "${id}".`);
1378
+ }
1379
+ return transition;
1380
+ };
1381
+
1382
+ export const formatGovernedVocabularyTransitionGuide = (
1383
+ transitions: readonly GovernedVocabularyTransition[] = governedVocabularyTransitions
1384
+ ): string =>
1385
+ transitions
1386
+ .map((transition) => {
1387
+ const target =
1388
+ transition.target.kind === 'single'
1389
+ ? transition.target.to
1390
+ : transition.target.options
1391
+ .map((option) => `${option.to} (${option.when})`)
1392
+ .join(' or ');
1393
+ const lines = [
1394
+ `- ${transition.id}: ${transition.from} -> ${target}`,
1395
+ ` - Status: ${transition.status}`,
1396
+ ` - Intent: ${transition.intent}`,
1397
+ ` - Provenance: ${
1398
+ transition.provenance.mode === 'regrade-history'
1399
+ ? 'committed Regrade history required'
1400
+ : `legacy (${transition.provenance.reason})`
1401
+ }`,
1402
+ ` - Safe rewrites: ${Object.keys(transition.safeRewriteForms).length}`,
1403
+ ` - Review forms: ${transition.reviewForms.join(', ') || 'none'}`,
1404
+ ];
1405
+ return lines.join('\n');
1406
+ })
1407
+ .join('\n');