@happyvertical/smrt-core 0.57.46 → 0.57.47

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 (189) hide show
  1. package/AGENTS.md +11 -3
  2. package/agents/browser-entry.md +16 -0
  3. package/agents/build-knowledge.md +58 -14
  4. package/agents/change-feed.md +20 -0
  5. package/agents/generators.md +69 -3
  6. package/agents/schema-paths.md +82 -0
  7. package/dist/_virtual/_rolldown/runtime.js +22 -1
  8. package/dist/browser.d.ts +2 -2
  9. package/dist/browser.d.ts.map +1 -1
  10. package/dist/browser.js +2 -2
  11. package/dist/cascade.d.ts +46 -0
  12. package/dist/cascade.d.ts.map +1 -1
  13. package/dist/cascade.js +96 -7
  14. package/dist/cascade.js.map +1 -1
  15. package/dist/change-feed.d.ts.map +1 -1
  16. package/dist/change-feed.js +7 -4
  17. package/dist/change-feed.js.map +1 -1
  18. package/dist/class.d.ts.map +1 -1
  19. package/dist/class.js +3 -3
  20. package/dist/class.js.map +1 -1
  21. package/dist/consumer-plugin/index.d.ts +13 -0
  22. package/dist/consumer-plugin/index.d.ts.map +1 -1
  23. package/dist/consumer-plugin/index.js +69 -13
  24. package/dist/consumer-plugin/index.js.map +1 -1
  25. package/dist/consumer-plugin.js +2 -2
  26. package/dist/database.d.ts.map +1 -1
  27. package/dist/database.js +3 -3
  28. package/dist/database.js.map +1 -1
  29. package/dist/dispatch/bus.d.ts.map +1 -1
  30. package/dist/dispatch/bus.js +1 -4
  31. package/dist/dispatch/bus.js.map +1 -1
  32. package/dist/dispatch/index.d.ts +1 -1
  33. package/dist/dispatch/index.d.ts.map +1 -1
  34. package/dist/dispatch/types.d.ts +22 -3
  35. package/dist/dispatch/types.d.ts.map +1 -1
  36. package/dist/generators/changes-route.d.ts.map +1 -1
  37. package/dist/generators/changes-route.js +2 -2
  38. package/dist/generators/changes-route.js.map +1 -1
  39. package/dist/generators/custom-action.d.ts +11 -0
  40. package/dist/generators/custom-action.d.ts.map +1 -1
  41. package/dist/generators/custom-action.js +23 -4
  42. package/dist/generators/custom-action.js.map +1 -1
  43. package/dist/index.d.ts +2 -2
  44. package/dist/index.d.ts.map +1 -1
  45. package/dist/index.js +3 -2
  46. package/dist/lazy-config.d.ts +120 -3
  47. package/dist/lazy-config.d.ts.map +1 -1
  48. package/dist/lazy-config.js +81 -10
  49. package/dist/lazy-config.js.map +1 -1
  50. package/dist/manifest/generator.d.ts +8 -2
  51. package/dist/manifest/generator.d.ts.map +1 -1
  52. package/dist/manifest/generator.js +6 -5
  53. package/dist/manifest/generator.js.map +1 -1
  54. package/dist/manifest/index.d.ts +1 -0
  55. package/dist/manifest/index.d.ts.map +1 -1
  56. package/dist/manifest/index.js +2 -1
  57. package/dist/manifest/index.js.map +1 -1
  58. package/dist/manifest/local-manifest-reader.d.ts +13 -2
  59. package/dist/manifest/local-manifest-reader.d.ts.map +1 -1
  60. package/dist/manifest/local-manifest-reader.js +38 -6
  61. package/dist/manifest/local-manifest-reader.js.map +1 -1
  62. package/dist/manifest/manifest-loader.d.ts +9 -8
  63. package/dist/manifest/manifest-loader.d.ts.map +1 -1
  64. package/dist/manifest/manifest-loader.js +67 -27
  65. package/dist/manifest/manifest-loader.js.map +1 -1
  66. package/dist/manifest/object-declarations.d.ts +116 -0
  67. package/dist/manifest/object-declarations.d.ts.map +1 -0
  68. package/dist/manifest/object-declarations.js +161 -0
  69. package/dist/manifest/object-declarations.js.map +1 -0
  70. package/dist/manifest/static-manifest.d.ts.map +1 -1
  71. package/dist/manifest/static-manifest.js +5 -3
  72. package/dist/manifest/static-manifest.js.map +1 -1
  73. package/dist/manifest/store.d.ts.map +1 -1
  74. package/dist/manifest/store.js +18 -2
  75. package/dist/manifest/store.js.map +1 -1
  76. package/dist/manifest/test-artifacts.d.ts +57 -0
  77. package/dist/manifest/test-artifacts.d.ts.map +1 -0
  78. package/dist/manifest/test-artifacts.js +111 -0
  79. package/dist/manifest/test-artifacts.js.map +1 -0
  80. package/dist/manifest.js +2 -1
  81. package/dist/manifest.json +5 -3
  82. package/dist/migrations/differ.d.ts.map +1 -1
  83. package/dist/migrations/differ.js +11 -3
  84. package/dist/migrations/differ.js.map +1 -1
  85. package/dist/migrations/index.d.ts +2 -0
  86. package/dist/migrations/index.d.ts.map +1 -1
  87. package/dist/migrations/index.js +3 -1
  88. package/dist/migrations/qualified-name-aliases.d.ts.map +1 -1
  89. package/dist/migrations/qualified-name-aliases.js +1 -1
  90. package/dist/migrations/qualified-name-aliases.js.map +1 -1
  91. package/dist/migrations.js +3 -1
  92. package/dist/object.d.ts.map +1 -1
  93. package/dist/object.js +2 -2
  94. package/dist/object.js.map +1 -1
  95. package/dist/postgres-permissions.d.ts.map +1 -1
  96. package/dist/postgres-permissions.js +4 -3
  97. package/dist/postgres-permissions.js.map +1 -1
  98. package/dist/postgres-timeouts.d.ts +30 -3
  99. package/dist/postgres-timeouts.d.ts.map +1 -1
  100. package/dist/postgres-timeouts.js +39 -2
  101. package/dist/postgres-timeouts.js.map +1 -1
  102. package/dist/prebuild/index.d.ts.map +1 -1
  103. package/dist/prebuild/index.js +14 -11
  104. package/dist/prebuild/index.js.map +1 -1
  105. package/dist/registry/class-registration.d.ts.map +1 -1
  106. package/dist/registry/class-registration.js +24 -2
  107. package/dist/registry/class-registration.js.map +1 -1
  108. package/dist/registry/collection-exposure.d.ts +27 -0
  109. package/dist/registry/collection-exposure.d.ts.map +1 -1
  110. package/dist/registry/collection-exposure.js +74 -1
  111. package/dist/registry/collection-exposure.js.map +1 -1
  112. package/dist/registry/collection-resolution.d.ts.map +1 -1
  113. package/dist/registry/collection-resolution.js +1 -0
  114. package/dist/registry/collection-resolution.js.map +1 -1
  115. package/dist/registry/framework-base-classes.d.ts.map +1 -1
  116. package/dist/registry/framework-base-classes.js +5 -0
  117. package/dist/registry/framework-base-classes.js.map +1 -1
  118. package/dist/registry/schema-builder.d.ts +8 -1
  119. package/dist/registry/schema-builder.d.ts.map +1 -1
  120. package/dist/registry/schema-builder.js +17 -4
  121. package/dist/registry/schema-builder.js.map +1 -1
  122. package/dist/registry/types.d.ts +12 -0
  123. package/dist/registry/types.d.ts.map +1 -1
  124. package/dist/registry.d.ts +2 -1
  125. package/dist/registry.d.ts.map +1 -1
  126. package/dist/registry.js +30 -10
  127. package/dist/registry.js.map +1 -1
  128. package/dist/scanner/manifest-generator.d.ts.map +1 -1
  129. package/dist/scanner/manifest-generator.js +10 -8
  130. package/dist/scanner/manifest-generator.js.map +1 -1
  131. package/dist/scanner/types.d.ts +29 -0
  132. package/dist/scanner/types.d.ts.map +1 -1
  133. package/dist/scanner/types.js.map +1 -1
  134. package/dist/schema/generator.d.ts +32 -0
  135. package/dist/schema/generator.d.ts.map +1 -1
  136. package/dist/schema/generator.js +59 -0
  137. package/dist/schema/generator.js.map +1 -1
  138. package/dist/schema/live-parity.d.ts.map +1 -1
  139. package/dist/schema/live-parity.js +4 -2
  140. package/dist/schema/live-parity.js.map +1 -1
  141. package/dist/scripts/browser-unbundled-dev.mjs +90 -0
  142. package/dist/scripts/generate-test-manifest.js +9 -49
  143. package/dist/smrt-knowledge.json +16 -16
  144. package/dist/system/compatibility.d.ts.map +1 -1
  145. package/dist/system/compatibility.js +12 -4
  146. package/dist/system/compatibility.js.map +1 -1
  147. package/dist/system/schema.d.ts +70 -8
  148. package/dist/system/schema.d.ts.map +1 -1
  149. package/dist/system/schema.js +231 -24
  150. package/dist/system/schema.js.map +1 -1
  151. package/dist/testing/database.d.ts.map +1 -1
  152. package/dist/testing/database.js +14 -2
  153. package/dist/testing/database.js.map +1 -1
  154. package/dist/tools/tool-generator.d.ts.map +1 -1
  155. package/dist/tools/tool-generator.js +2 -2
  156. package/dist/tools/tool-generator.js.map +1 -1
  157. package/dist/utils/import-workspace-module.d.ts.map +1 -1
  158. package/dist/utils/import-workspace-module.js +23 -0
  159. package/dist/utils/import-workspace-module.js.map +1 -1
  160. package/dist/utils/naming.js +5 -3
  161. package/dist/utils/naming.js.map +1 -1
  162. package/dist/vendor/pluralize/pluralize.js +421 -0
  163. package/dist/vendor/pluralize/pluralize.js.map +1 -0
  164. package/dist/vite-plugin/api-client-entries.d.ts +5 -1
  165. package/dist/vite-plugin/api-client-entries.d.ts.map +1 -1
  166. package/dist/vite-plugin/api-client-entries.js +11 -6
  167. package/dist/vite-plugin/api-client-entries.js.map +1 -1
  168. package/dist/vite-plugin/consumer-import.d.ts +28 -0
  169. package/dist/vite-plugin/consumer-import.d.ts.map +1 -0
  170. package/dist/vite-plugin/consumer-import.js +42 -0
  171. package/dist/vite-plugin/consumer-import.js.map +1 -0
  172. package/dist/vite-plugin/dev-plane-route.d.ts +6 -1
  173. package/dist/vite-plugin/dev-plane-route.d.ts.map +1 -1
  174. package/dist/vite-plugin/dev-plane-route.js +8 -9
  175. package/dist/vite-plugin/dev-plane-route.js.map +1 -1
  176. package/dist/vite-plugin/index.d.ts.map +1 -1
  177. package/dist/vite-plugin/index.js +12 -9
  178. package/dist/vite-plugin/index.js.map +1 -1
  179. package/dist/vite-plugin/resources-route.d.ts.map +1 -1
  180. package/dist/vite-plugin/resources-route.js +3 -8
  181. package/dist/vite-plugin/resources-route.js.map +1 -1
  182. package/dist/vite-plugin/sveltekit-generator.d.ts.map +1 -1
  183. package/dist/vite-plugin/sveltekit-generator.js +7 -11
  184. package/dist/vite-plugin/sveltekit-generator.js.map +1 -1
  185. package/dist/vite-plugin/sync-apply-route.d.ts +16 -0
  186. package/dist/vite-plugin/sync-apply-route.d.ts.map +1 -1
  187. package/dist/vite-plugin/sync-apply-route.js +48 -6
  188. package/dist/vite-plugin/sync-apply-route.js.map +1 -1
  189. package/package.json +12 -12
@@ -46,14 +46,122 @@
46
46
  * Both shapes can be combined freely. Global resolvers always take precedence
47
47
  * for `$env` sentinels; class resolvers fill in / override fields by name.
48
48
  *
49
+ * ## Resolution context and precedence (#2806)
50
+ *
51
+ * Every resolver receives a {@link ConfigResolverContext}: the identity of
52
+ * the agent instance being resolved (`agentId`, canonical `agentClass`,
53
+ * `tenantId`, `scheduleId`, `jobId` — whatever the caller knows) and, for a
54
+ * class resolver, the value this instance's own persisted config holds for the
55
+ * key (`persisted`, `hasPersisted`). Zero-argument resolvers keep working.
56
+ *
57
+ * Precedence for a class resolver's key, highest first:
58
+ *
59
+ * 1. a non-null value the resolver returns;
60
+ * 2. the instance's persisted value (after `$env` sentinel resolution) — kept
61
+ * when the resolver returns `undefined` or `null`;
62
+ * 3. absent.
63
+ *
64
+ * A resolver that must not clobber an agent's own persisted value (a
65
+ * per-agent credential with a deployment-wide fallback) defers by returning
66
+ * `undefined` when `context.hasPersisted`, or is wrapped in
67
+ * {@link preferPersisted}. The persisted value a resolver sees is always the
68
+ * resolving instance's own: the context is built per call from the config
69
+ * being resolved and is never looked up by scanning other agents' or the
70
+ * tenant's rows, so with several agents of one class per tenant no resolver
71
+ * can be handed another agent's value by the framework.
72
+ *
49
73
  * @module
50
74
  */
75
+ /**
76
+ * Identity of the agent instance a config is being resolved for (#2806).
77
+ * Every field is optional: a caller supplies what it knows. The job runner
78
+ * supplies all of them for a scheduled or object-bound job.
79
+ */
80
+ export interface ConfigResolutionContext {
81
+ /** The agent instance's id (`_smrt_agent_schedules.agent_id` / job `objectId`). */
82
+ agentId?: string;
83
+ /** Canonical qualified agent class name (`@scope/pkg:Class`). */
84
+ agentClass?: string;
85
+ /** Tenant the resolution runs for, when the job is tenant-scoped. */
86
+ tenantId?: string | null;
87
+ /**
88
+ * Schedule that produced the job, when scheduled. Informational only: the
89
+ * runner echoes it from the job's args without verifying it against
90
+ * `_smrt_agent_schedules`, so never key a per-agent value (a credential) on
91
+ * it — use `agentId`.
92
+ */
93
+ scheduleId?: string;
94
+ /** Job being executed, when resolved by the job runner. */
95
+ jobId?: string;
96
+ }
97
+ /**
98
+ * The argument every {@link ConfigResolver} receives. It is a fresh object
99
+ * per call, frozen one level deep: `persisted` is neither copied nor frozen,
100
+ * so treat it as read-only — mutating it changes the config the agent is
101
+ * constructed with.
102
+ */
103
+ export interface ConfigResolverContext extends Readonly<ConfigResolutionContext> {
104
+ /**
105
+ * The config key being produced: the class resolver's key, or the resolver
106
+ * name for a `$env` / `$resolver` sentinel.
107
+ */
108
+ readonly key: string;
109
+ /** Location of the value in the config tree (`['ai']`, `['storage', 'primary']`). */
110
+ readonly path: readonly string[];
111
+ /** `'class'` for a static `configResolvers` entry, `'sentinel'` for a named resolver. */
112
+ readonly source: 'class' | 'sentinel';
113
+ /**
114
+ * Whether this instance's persisted config carries a non-null value for the
115
+ * key. Always `false` for a sentinel: the sentinel *is* the persisted value.
116
+ * Only `undefined` / `null` count as absent; an empty object or a keyless
117
+ * credential is present — inspect `persisted` when that matters.
118
+ */
119
+ readonly hasPersisted: boolean;
120
+ /**
121
+ * This instance's persisted value for the key, after sentinel resolution —
122
+ * the value that is kept when the resolver returns `undefined` or `null`.
123
+ */
124
+ readonly persisted: unknown;
125
+ }
51
126
  /**
52
127
  * A lazy resolver — synchronous or async. Return values are merged into the
53
128
  * config tree at execute time. Throwing is allowed but is treated as a
54
129
  * resolution failure (see {@link ResolveLazyConfigOptions.onError}).
130
+ *
131
+ * The {@link ConfigResolverContext} argument is optional to declare, so
132
+ * existing zero-argument resolvers remain source-compatible.
133
+ */
134
+ export type ConfigResolver = (context: ConfigResolverContext) => unknown | Promise<unknown>;
135
+ /**
136
+ * Build the context argument for invoking a resolver directly (#2806) — for
137
+ * code that looks a resolver up with {@link getConfigResolver} or
138
+ * {@link getClassConfigResolvers} and calls it itself. The framework builds
139
+ * its own; this exists so such callers pass an explicit identity instead of
140
+ * none. It models a class-resolver call (`source: 'class'`, `path: [key]`);
141
+ * a sentinel resolver invoked through it sees `hasPersisted` reflect the
142
+ * `persisted` argument rather than the framework's always-`false`.
143
+ *
144
+ * @param key - Config key (class resolver) or resolver name (sentinel)
145
+ * @param identity - The agent instance being resolved for, if known
146
+ * @param persisted - That instance's persisted value for the key, if any
55
147
  */
56
- export type ConfigResolver = () => unknown | Promise<unknown>;
148
+ export declare function createConfigResolverContext(key: string, identity?: ConfigResolutionContext, persisted?: unknown): ConfigResolverContext;
149
+ /**
150
+ * Wrap a class resolver so it only fills a key the instance's persisted config
151
+ * leaves empty — `undefined` or `null` (#2806); a present but unusable value
152
+ * such as `{}` is kept, so check `context.persisted` yourself if it can be. Use it for a deployment-wide fallback that must never
153
+ * replace an agent's own persisted value, such as its own provider credential.
154
+ *
155
+ * @example
156
+ * ```ts
157
+ * class Praeco extends Agent {
158
+ * static override configResolvers = {
159
+ * ai: preferPersisted(() => deploymentDefaultAi()),
160
+ * };
161
+ * }
162
+ * ```
163
+ */
164
+ export declare function preferPersisted(resolver: ConfigResolver): ConfigResolver;
57
165
  /**
58
166
  * Sentinel object recognised inside agent_config to defer a value to a named
59
167
  * resolver. The runtime accepts both `$env` (preferred) and `$resolver` for
@@ -67,6 +175,12 @@ export interface LazyConfigSentinel {
67
175
  * Options for resolving the lazy parts of a config tree.
68
176
  */
69
177
  export interface ResolveLazyConfigOptions {
178
+ /**
179
+ * Identity of the agent instance being resolved, passed to every resolver
180
+ * (#2806). Supply only what is known for *this* instance; never derive it
181
+ * by scanning other agents' persisted rows.
182
+ */
183
+ context?: ConfigResolutionContext;
70
184
  /**
71
185
  * Per-class resolvers — typically the agent class's static
72
186
  * `configResolvers`. Applied as a key-by-key overlay on top of the
@@ -129,8 +243,11 @@ export declare function isLazyConfigSentinel(value: unknown): value is LazyConfi
129
243
  * sentinel placeholders replaced by their resolver's current return value.
130
244
  *
131
245
  * Class-level resolvers from {@link ResolveLazyConfigOptions.classResolvers}
132
- * are applied as a final overlay so they always reflect the live value
133
- * regardless of what's persisted.
246
+ * are applied as a final overlay: a non-null return value wins, and
247
+ * `undefined` / `null` keeps the persisted value. Each resolver receives the
248
+ * resolving instance's identity ({@link ResolveLazyConfigOptions.context}) and
249
+ * its own persisted value for the key, so it can defer to that value instead
250
+ * of clobbering it (see the module docs and {@link preferPersisted}).
134
251
  *
135
252
  * @example Sentinel resolution
136
253
  * ```ts
@@ -1 +1 @@
1
- {"version":3,"file":"lazy-config.d.ts","sourceRoot":"","sources":["../src/lazy-config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiDG;AAEH;;;;GAIG;AACH,MAAM,MAAM,cAAc,GAAG,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;AAE9D;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IACjC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACvC;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IAEhD;;;OAGG;IACH,SAAS,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IAEzE;;;;;;;;;OASG;IACH,OAAO,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,OAAO,CAAC;CACtC;AAID;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,cAAc,GACvB,IAAI,CAQN;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE9D;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CAE1E;AAED;;GAEG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,EAAE,CAE9C;AAED;;GAEG;AACH,wBAAgB,oBAAoB,IAAI,IAAI,CAE3C;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,OAAO,GACb,KAAK,IAAI,kBAAkB,CAU7B;AA2GD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAsB,iBAAiB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACvE,MAAM,EAAE,CAAC,EACT,OAAO,GAAE,wBAA6B,GACrC,OAAO,CAAC,CAAC,CAAC,CAwCZ;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,OAAO,GACZ,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,GAAG,SAAS,CAqB5C"}
1
+ {"version":3,"file":"lazy-config.d.ts","sourceRoot":"","sources":["../src/lazy-config.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,44 @@
1
1
  //#region src/lazy-config.ts
2
+ /**
3
+ * Build the context argument for invoking a resolver directly (#2806) — for
4
+ * code that looks a resolver up with {@link getConfigResolver} or
5
+ * {@link getClassConfigResolvers} and calls it itself. The framework builds
6
+ * its own; this exists so such callers pass an explicit identity instead of
7
+ * none. It models a class-resolver call (`source: 'class'`, `path: [key]`);
8
+ * a sentinel resolver invoked through it sees `hasPersisted` reflect the
9
+ * `persisted` argument rather than the framework's always-`false`.
10
+ *
11
+ * @param key - Config key (class resolver) or resolver name (sentinel)
12
+ * @param identity - The agent instance being resolved for, if known
13
+ * @param persisted - That instance's persisted value for the key, if any
14
+ */
15
+ function createConfigResolverContext(key, identity = {}, persisted) {
16
+ return resolverContext(identity, {
17
+ key,
18
+ path: [key],
19
+ source: "class",
20
+ hasPersisted: persisted !== void 0 && persisted !== null,
21
+ persisted
22
+ });
23
+ }
24
+ /**
25
+ * Wrap a class resolver so it only fills a key the instance's persisted config
26
+ * leaves empty — `undefined` or `null` (#2806); a present but unusable value
27
+ * such as `{}` is kept, so check `context.persisted` yourself if it can be. Use it for a deployment-wide fallback that must never
28
+ * replace an agent's own persisted value, such as its own provider credential.
29
+ *
30
+ * @example
31
+ * ```ts
32
+ * class Praeco extends Agent {
33
+ * static override configResolvers = {
34
+ * ai: preferPersisted(() => deploymentDefaultAi()),
35
+ * };
36
+ * }
37
+ * ```
38
+ */
39
+ function preferPersisted(resolver) {
40
+ return (context) => context.hasPersisted ? void 0 : resolver(context);
41
+ }
2
42
  var globalResolvers = /* @__PURE__ */ new Map();
3
43
  /**
4
44
  * Register a named resolver that participates in lazy config resolution.
@@ -72,7 +112,19 @@ var REMOVE = Symbol("LazyConfig.REMOVE");
72
112
  * value is returned verbatim — same fail-soft as for a missing resolver.
73
113
  */
74
114
  var MAX_DEPTH = 64;
75
- async function resolveValue(value, ctx, depth) {
115
+ /** Build the frozen per-call argument a resolver receives. */
116
+ function resolverContext(identity, fields) {
117
+ return Object.freeze({
118
+ agentId: identity.agentId,
119
+ agentClass: identity.agentClass,
120
+ tenantId: identity.tenantId,
121
+ scheduleId: identity.scheduleId,
122
+ jobId: identity.jobId,
123
+ ...fields,
124
+ path: Object.freeze([...fields.path])
125
+ });
126
+ }
127
+ async function resolveValue(value, ctx, depth, path = []) {
76
128
  if (isLazyConfigSentinel(value)) {
77
129
  const name = getSentinelName(value);
78
130
  const resolver = ctx.resolvers.get(name);
@@ -82,7 +134,13 @@ async function resolveValue(value, ctx, depth) {
82
134
  return value;
83
135
  }
84
136
  try {
85
- return await resolver();
137
+ return await resolver(resolverContext(ctx.identity, {
138
+ key: name,
139
+ path,
140
+ source: "sentinel",
141
+ hasPersisted: false,
142
+ persisted: void 0
143
+ }));
86
144
  } catch (error) {
87
145
  if (ctx.onError === "throw") throw error;
88
146
  if (ctx.onError === "omit") return REMOVE;
@@ -95,8 +153,8 @@ async function resolveValue(value, ctx, depth) {
95
153
  if (cached !== void 0) return cached;
96
154
  const next = [];
97
155
  ctx.resolved.set(value, next);
98
- for (const entry of value) {
99
- const resolved = await resolveValue(entry, ctx, depth + 1);
156
+ for (const [index, entry] of value.entries()) {
157
+ const resolved = await resolveValue(entry, ctx, depth + 1, [...path, String(index)]);
100
158
  if (resolved !== REMOVE) next.push(resolved);
101
159
  }
102
160
  return next;
@@ -107,7 +165,7 @@ async function resolveValue(value, ctx, depth) {
107
165
  const next = {};
108
166
  ctx.resolved.set(value, next);
109
167
  for (const [key, entry] of Object.entries(value)) {
110
- const resolved = await resolveValue(entry, ctx, depth + 1);
168
+ const resolved = await resolveValue(entry, ctx, depth + 1, [...path, key]);
111
169
  if (resolved !== REMOVE) next[key] = resolved;
112
170
  }
113
171
  return next;
@@ -120,8 +178,11 @@ async function resolveValue(value, ctx, depth) {
120
178
  * sentinel placeholders replaced by their resolver's current return value.
121
179
  *
122
180
  * Class-level resolvers from {@link ResolveLazyConfigOptions.classResolvers}
123
- * are applied as a final overlay so they always reflect the live value
124
- * regardless of what's persisted.
181
+ * are applied as a final overlay: a non-null return value wins, and
182
+ * `undefined` / `null` keeps the persisted value. Each resolver receives the
183
+ * resolving instance's identity ({@link ResolveLazyConfigOptions.context}) and
184
+ * its own persisted value for the key, so it can defer to that value instead
185
+ * of clobbering it (see the module docs and {@link preferPersisted}).
125
186
  *
126
187
  * @example Sentinel resolution
127
188
  * ```ts
@@ -141,13 +202,23 @@ async function resolveValue(value, ctx, depth) {
141
202
  */
142
203
  async function resolveLazyConfig(config, options = {}) {
143
204
  const onError = options.onError ?? "leave";
205
+ const resolvers = options.resolvers ? normalizeResolverMap(options.resolvers) : globalResolvers;
206
+ const identity = { ...options.context ?? {} };
144
207
  const result = { ...await resolveValue(config, {
145
- resolvers: options.resolvers ? normalizeResolverMap(options.resolvers) : globalResolvers,
208
+ resolvers,
209
+ identity,
146
210
  onError,
147
211
  resolved: /* @__PURE__ */ new WeakMap()
148
212
  }, 0) };
149
213
  if (options.classResolvers) for (const [key, resolver] of Object.entries(options.classResolvers)) try {
150
- const value = await resolver();
214
+ const persisted = Object.hasOwn(result, key) ? result[key] : void 0;
215
+ const value = await resolver(resolverContext(identity, {
216
+ key,
217
+ path: [key],
218
+ source: "class",
219
+ hasPersisted: persisted !== void 0 && persisted !== null,
220
+ persisted
221
+ }));
151
222
  if (value !== void 0 && value !== null) result[key] = value;
152
223
  } catch (error) {
153
224
  if (onError === "throw") throw error;
@@ -177,6 +248,6 @@ function getClassConfigResolvers(ctor) {
177
248
  return collected;
178
249
  }
179
250
  //#endregion
180
- export { getClassConfigResolvers, getConfigResolver, isLazyConfigSentinel, listConfigResolvers, registerConfigResolver, resetConfigResolvers, resolveLazyConfig, unregisterConfigResolver };
251
+ export { createConfigResolverContext, getClassConfigResolvers, getConfigResolver, isLazyConfigSentinel, listConfigResolvers, preferPersisted, registerConfigResolver, resetConfigResolvers, resolveLazyConfig, unregisterConfigResolver };
181
252
 
182
253
  //# sourceMappingURL=lazy-config.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"lazy-config.js","names":[],"sources":["../src/lazy-config.ts"],"sourcesContent":["/**\n * Lazy / execute-time agent_config resolvers.\n *\n * Persisted agent configs (e.g. `_smrt_agent_schedules.agent_config`) freeze\n * any value resolved at sync time, including env-derived ones. When operators\n * rotate an env var the persisted snapshot goes stale until the schedule is\n * rewritten.\n *\n * To avoid that, callers can store **sentinel references** in the persisted\n * config and register matching resolvers that run at execute time. The\n * runtime walks the agent_config tree, replaces every recognised sentinel\n * with the resolver's current return value, and only then hands the config\n * to the agent.\n *\n * Two registration shapes are supported:\n *\n * 1. **Global named resolvers** — register a callback by name once at\n * application startup. Use this for shared resolvers like\n * `resolveSharedAssetStorage`.\n *\n * ```ts\n * import { registerConfigResolver } from '@happyvertical/smrt-agents';\n *\n * registerConfigResolver('sharedAssetStorage', () =>\n * resolveSharedAssetStorage(),\n * );\n * ```\n *\n * Then in the persisted agent_config:\n * ```jsonc\n * { \"assetStorage\": { \"$env\": \"sharedAssetStorage\" } }\n * ```\n *\n * 2. **Per-agent class resolvers** — declare them as a static\n * `configResolvers` map on the agent class. The runtime applies them on\n * top of the persisted config keyed by the same name as the resolver.\n *\n * ```ts\n * class Praeco extends Agent {\n * static configResolvers = {\n * assetStorage: () => resolveSharedAssetStorage(),\n * };\n * }\n * ```\n *\n * Both shapes can be combined freely. Global resolvers always take precedence\n * for `$env` sentinels; class resolvers fill in / override fields by name.\n *\n * @module\n */\n\n/**\n * A lazy resolver — synchronous or async. Return values are merged into the\n * config tree at execute time. Throwing is allowed but is treated as a\n * resolution failure (see {@link ResolveLazyConfigOptions.onError}).\n */\nexport type ConfigResolver = () => unknown | Promise<unknown>;\n\n/**\n * Sentinel object recognised inside agent_config to defer a value to a named\n * resolver. The runtime accepts both `$env` (preferred) and `$resolver` for\n * symmetry with similar systems.\n */\nexport interface LazyConfigSentinel {\n $env?: string;\n $resolver?: string;\n}\n\n/**\n * Options for resolving the lazy parts of a config tree.\n */\nexport interface ResolveLazyConfigOptions {\n /**\n * Per-class resolvers — typically the agent class's static\n * `configResolvers`. Applied as a key-by-key overlay on top of the\n * sentinel-resolved config.\n */\n classResolvers?: Record<string, ConfigResolver>;\n\n /**\n * Optional override for the global resolver registry. When omitted the\n * shared module-scoped registry is used. Useful for tests.\n */\n resolvers?: Map<string, ConfigResolver> | Record<string, ConfigResolver>;\n\n /**\n * What to do when a sentinel references a resolver that isn't registered,\n * or when a resolver throws.\n *\n * - `'leave'` (default): the sentinel is left in place verbatim. The\n * downstream consumer is responsible for handling it.\n * - `'omit'`: the sentinel key is removed from the parent object/array.\n * - `'throw'`: re-throw the underlying error (or throw a new one for\n * missing resolvers).\n */\n onError?: 'leave' | 'omit' | 'throw';\n}\n\nconst globalResolvers = new Map<string, ConfigResolver>();\n\n/**\n * Register a named resolver that participates in lazy config resolution.\n *\n * Calling this with the same name twice replaces the previous resolver — the\n * registry is intentionally last-write-wins so application boot ordering\n * doesn't have to be fragile.\n */\nexport function registerConfigResolver(\n name: string,\n resolver: ConfigResolver,\n): void {\n if (typeof name !== 'string' || name.length === 0) {\n throw new Error('registerConfigResolver: name must be a non-empty string');\n }\n if (typeof resolver !== 'function') {\n throw new Error('registerConfigResolver: resolver must be a function');\n }\n globalResolvers.set(name, resolver);\n}\n\n/**\n * Remove a previously registered resolver. Returns `true` if a resolver was\n * removed.\n */\nexport function unregisterConfigResolver(name: string): boolean {\n return globalResolvers.delete(name);\n}\n\n/**\n * Get the resolver registered under the given name, if any.\n */\nexport function getConfigResolver(name: string): ConfigResolver | undefined {\n return globalResolvers.get(name);\n}\n\n/**\n * List the names of all currently-registered global resolvers.\n */\nexport function listConfigResolvers(): string[] {\n return Array.from(globalResolvers.keys()).sort();\n}\n\n/**\n * Reset the global resolver registry. Primarily for tests.\n */\nexport function resetConfigResolvers(): void {\n globalResolvers.clear();\n}\n\n/**\n * Type guard that returns `true` if `value` is a lazy config sentinel.\n *\n * Recognises objects of the form `{ $env: string }` or\n * `{ $resolver: string }` exactly — extra keys disqualify the value so\n * arbitrary user data isn't accidentally treated as a sentinel.\n */\nexport function isLazyConfigSentinel(\n value: unknown,\n): value is LazyConfigSentinel {\n if (!value || typeof value !== 'object' || Array.isArray(value)) {\n return false;\n }\n const keys = Object.keys(value as Record<string, unknown>);\n if (keys.length !== 1) return false;\n const key = keys[0];\n if (key !== '$env' && key !== '$resolver') return false;\n const resolverName = (value as Record<string, unknown>)[key];\n return typeof resolverName === 'string' && resolverName.length > 0;\n}\n\n/**\n * Extract the resolver name from a sentinel.\n */\nfunction getSentinelName(sentinel: LazyConfigSentinel): string {\n return (sentinel.$env ?? sentinel.$resolver) as string;\n}\n\nfunction normalizeResolverMap(\n resolvers: Map<string, ConfigResolver> | Record<string, ConfigResolver>,\n): Map<string, ConfigResolver> {\n if (resolvers instanceof Map) return resolvers;\n return new Map(Object.entries(resolvers));\n}\n\nconst REMOVE = Symbol('LazyConfig.REMOVE');\n\n/**\n * Maximum traversal depth before {@link resolveValue} bails out. Real\n * `agent_config` payloads are shallow (2–3 levels); a high cap here exists\n * solely to prevent stack overflow from a malicious or buggy persisted\n * config without rejecting any legitimate input. When the cap is hit the\n * value is returned verbatim — same fail-soft as for a missing resolver.\n */\nconst MAX_DEPTH = 64;\n\ninterface ResolveContext {\n resolvers: Map<string, ConfigResolver>;\n onError: NonNullable<ResolveLazyConfigOptions['onError']>;\n /**\n * Map from input object/array to its resolved replacement. Serves two\n * purposes simultaneously:\n * 1. Cycle protection — without it, a self-referential config would\n * recurse forever.\n * 2. DAG fidelity — a sub-tree referenced from two places gets cloned\n * once and the same clone is reused for every reference, preserving\n * structure and ensuring sentinels under shared references are still\n * resolved.\n */\n resolved: WeakMap<object, unknown>;\n}\n\nasync function resolveValue(\n value: unknown,\n ctx: ResolveContext,\n depth: number,\n): Promise<unknown> {\n if (isLazyConfigSentinel(value)) {\n const name = getSentinelName(value);\n const resolver = ctx.resolvers.get(name);\n if (!resolver) {\n if (ctx.onError === 'throw') {\n throw new Error(\n `Lazy config sentinel references unknown resolver \"${name}\"`,\n );\n }\n if (ctx.onError === 'omit') return REMOVE;\n return value;\n }\n\n try {\n return await resolver();\n } catch (error) {\n if (ctx.onError === 'throw') throw error;\n if (ctx.onError === 'omit') return REMOVE;\n return value;\n }\n }\n\n if (depth > MAX_DEPTH) {\n // Defensive: refuse to recurse further. The original sub-tree is\n // returned as-is; sentinels beyond this point will stay unresolved.\n return value;\n }\n\n if (Array.isArray(value)) {\n const cached = ctx.resolved.get(value);\n if (cached !== undefined) return cached;\n const next: unknown[] = [];\n // Register the placeholder *before* descending so that a cycle pointing\n // back at this array sees the in-progress array (and stops recursing).\n ctx.resolved.set(value, next);\n for (const entry of value) {\n const resolved = await resolveValue(entry, ctx, depth + 1);\n if (resolved !== REMOVE) next.push(resolved);\n }\n return next;\n }\n\n if (value && typeof value === 'object') {\n const cached = ctx.resolved.get(value);\n if (cached !== undefined) return cached;\n const next: Record<string, unknown> = {};\n ctx.resolved.set(value, next);\n for (const [key, entry] of Object.entries(\n value as Record<string, unknown>,\n )) {\n const resolved = await resolveValue(entry, ctx, depth + 1);\n if (resolved !== REMOVE) next[key] = resolved;\n }\n return next;\n }\n\n return value;\n}\n\n/**\n * Resolve every lazy sentinel inside an `agent_config`-style record at\n * execute time. The input is not mutated; a new object is returned with\n * sentinel placeholders replaced by their resolver's current return value.\n *\n * Class-level resolvers from {@link ResolveLazyConfigOptions.classResolvers}\n * are applied as a final overlay so they always reflect the live value\n * regardless of what's persisted.\n *\n * @example Sentinel resolution\n * ```ts\n * registerConfigResolver('sharedAssetStorage', () => resolveStorage());\n *\n * const stored = { assetStorage: { $env: 'sharedAssetStorage' } };\n * const live = await resolveLazyConfig(stored);\n * // live.assetStorage === resolveStorage()\n * ```\n *\n * @example Class-level resolvers\n * ```ts\n * const live = await resolveLazyConfig(stored, {\n * classResolvers: { assetStorage: () => resolveStorage() },\n * });\n * ```\n */\nexport async function resolveLazyConfig<T extends Record<string, unknown>>(\n config: T,\n options: ResolveLazyConfigOptions = {},\n): Promise<T> {\n const onError = options.onError ?? 'leave';\n const resolvers = options.resolvers\n ? normalizeResolverMap(options.resolvers)\n : globalResolvers;\n\n const ctx: ResolveContext = {\n resolvers,\n onError,\n resolved: new WeakMap<object, unknown>(),\n };\n const sentinelResolved = (await resolveValue(config, ctx, 0)) as Record<\n string,\n unknown\n >;\n\n const result: Record<string, unknown> = { ...sentinelResolved };\n\n if (options.classResolvers) {\n for (const [key, resolver] of Object.entries(options.classResolvers)) {\n try {\n const value = await resolver();\n // Both `undefined` and `null` are treated as \"no overlay value\" —\n // the persisted record's existing value (if any) is preserved.\n // This matches the documented contract on `Agent.configResolvers`\n // and makes the common pattern `() => process.env.X ?? null` safe.\n if (value !== undefined && value !== null) {\n result[key] = value;\n }\n } catch (error) {\n if (onError === 'throw') throw error;\n if (onError === 'omit') {\n delete result[key];\n }\n // 'leave': preserve the existing (possibly persisted) value.\n }\n }\n }\n\n return result as T;\n}\n\n/**\n * Look up the static `configResolvers` map on a class (or any of its\n * ancestors). Returns `undefined` if none is declared.\n *\n * The lookup walks the prototype chain so a subclass can extend its parent's\n * resolvers without redeclaring them.\n */\nexport function getClassConfigResolvers(\n ctor: unknown,\n): Record<string, ConfigResolver> | undefined {\n if (typeof ctor !== 'function') return undefined;\n\n let current: unknown = ctor;\n let collected: Record<string, ConfigResolver> | undefined;\n\n while (current && current !== Function.prototype) {\n // Each link in the prototype chain is an object/function; read its optional\n // static `configResolvers` map through a typed shape rather than `any`.\n const resolvers = (current as { configResolvers?: unknown })\n .configResolvers;\n if (resolvers && typeof resolvers === 'object') {\n collected = {\n ...(resolvers as Record<string, ConfigResolver>),\n ...(collected ?? {}),\n };\n }\n current = Object.getPrototypeOf(current);\n }\n\n return collected;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"lazy-config.js","names":[],"sources":["../src/lazy-config.ts"],"sourcesContent":["/**\n * Lazy / execute-time agent_config resolvers.\n *\n * Persisted agent configs (e.g. `_smrt_agent_schedules.agent_config`) freeze\n * any value resolved at sync time, including env-derived ones. When operators\n * rotate an env var the persisted snapshot goes stale until the schedule is\n * rewritten.\n *\n * To avoid that, callers can store **sentinel references** in the persisted\n * config and register matching resolvers that run at execute time. The\n * runtime walks the agent_config tree, replaces every recognised sentinel\n * with the resolver's current return value, and only then hands the config\n * to the agent.\n *\n * Two registration shapes are supported:\n *\n * 1. **Global named resolvers** — register a callback by name once at\n * application startup. Use this for shared resolvers like\n * `resolveSharedAssetStorage`.\n *\n * ```ts\n * import { registerConfigResolver } from '@happyvertical/smrt-agents';\n *\n * registerConfigResolver('sharedAssetStorage', () =>\n * resolveSharedAssetStorage(),\n * );\n * ```\n *\n * Then in the persisted agent_config:\n * ```jsonc\n * { \"assetStorage\": { \"$env\": \"sharedAssetStorage\" } }\n * ```\n *\n * 2. **Per-agent class resolvers** — declare them as a static\n * `configResolvers` map on the agent class. The runtime applies them on\n * top of the persisted config keyed by the same name as the resolver.\n *\n * ```ts\n * class Praeco extends Agent {\n * static configResolvers = {\n * assetStorage: () => resolveSharedAssetStorage(),\n * };\n * }\n * ```\n *\n * Both shapes can be combined freely. Global resolvers always take precedence\n * for `$env` sentinels; class resolvers fill in / override fields by name.\n *\n * ## Resolution context and precedence (#2806)\n *\n * Every resolver receives a {@link ConfigResolverContext}: the identity of\n * the agent instance being resolved (`agentId`, canonical `agentClass`,\n * `tenantId`, `scheduleId`, `jobId` — whatever the caller knows) and, for a\n * class resolver, the value this instance's own persisted config holds for the\n * key (`persisted`, `hasPersisted`). Zero-argument resolvers keep working.\n *\n * Precedence for a class resolver's key, highest first:\n *\n * 1. a non-null value the resolver returns;\n * 2. the instance's persisted value (after `$env` sentinel resolution) — kept\n * when the resolver returns `undefined` or `null`;\n * 3. absent.\n *\n * A resolver that must not clobber an agent's own persisted value (a\n * per-agent credential with a deployment-wide fallback) defers by returning\n * `undefined` when `context.hasPersisted`, or is wrapped in\n * {@link preferPersisted}. The persisted value a resolver sees is always the\n * resolving instance's own: the context is built per call from the config\n * being resolved and is never looked up by scanning other agents' or the\n * tenant's rows, so with several agents of one class per tenant no resolver\n * can be handed another agent's value by the framework.\n *\n * @module\n */\n\n/**\n * Identity of the agent instance a config is being resolved for (#2806).\n * Every field is optional: a caller supplies what it knows. The job runner\n * supplies all of them for a scheduled or object-bound job.\n */\nexport interface ConfigResolutionContext {\n /** The agent instance's id (`_smrt_agent_schedules.agent_id` / job `objectId`). */\n agentId?: string;\n /** Canonical qualified agent class name (`@scope/pkg:Class`). */\n agentClass?: string;\n /** Tenant the resolution runs for, when the job is tenant-scoped. */\n tenantId?: string | null;\n /**\n * Schedule that produced the job, when scheduled. Informational only: the\n * runner echoes it from the job's args without verifying it against\n * `_smrt_agent_schedules`, so never key a per-agent value (a credential) on\n * it — use `agentId`.\n */\n scheduleId?: string;\n /** Job being executed, when resolved by the job runner. */\n jobId?: string;\n}\n\n/**\n * The argument every {@link ConfigResolver} receives. It is a fresh object\n * per call, frozen one level deep: `persisted` is neither copied nor frozen,\n * so treat it as read-only — mutating it changes the config the agent is\n * constructed with.\n */\nexport interface ConfigResolverContext\n extends Readonly<ConfigResolutionContext> {\n /**\n * The config key being produced: the class resolver's key, or the resolver\n * name for a `$env` / `$resolver` sentinel.\n */\n readonly key: string;\n /** Location of the value in the config tree (`['ai']`, `['storage', 'primary']`). */\n readonly path: readonly string[];\n /** `'class'` for a static `configResolvers` entry, `'sentinel'` for a named resolver. */\n readonly source: 'class' | 'sentinel';\n /**\n * Whether this instance's persisted config carries a non-null value for the\n * key. Always `false` for a sentinel: the sentinel *is* the persisted value.\n * Only `undefined` / `null` count as absent; an empty object or a keyless\n * credential is present — inspect `persisted` when that matters.\n */\n readonly hasPersisted: boolean;\n /**\n * This instance's persisted value for the key, after sentinel resolution —\n * the value that is kept when the resolver returns `undefined` or `null`.\n */\n readonly persisted: unknown;\n}\n\n/**\n * A lazy resolver — synchronous or async. Return values are merged into the\n * config tree at execute time. Throwing is allowed but is treated as a\n * resolution failure (see {@link ResolveLazyConfigOptions.onError}).\n *\n * The {@link ConfigResolverContext} argument is optional to declare, so\n * existing zero-argument resolvers remain source-compatible.\n */\nexport type ConfigResolver = (\n context: ConfigResolverContext,\n) => unknown | Promise<unknown>;\n\n/**\n * Build the context argument for invoking a resolver directly (#2806) — for\n * code that looks a resolver up with {@link getConfigResolver} or\n * {@link getClassConfigResolvers} and calls it itself. The framework builds\n * its own; this exists so such callers pass an explicit identity instead of\n * none. It models a class-resolver call (`source: 'class'`, `path: [key]`);\n * a sentinel resolver invoked through it sees `hasPersisted` reflect the\n * `persisted` argument rather than the framework's always-`false`.\n *\n * @param key - Config key (class resolver) or resolver name (sentinel)\n * @param identity - The agent instance being resolved for, if known\n * @param persisted - That instance's persisted value for the key, if any\n */\nexport function createConfigResolverContext(\n key: string,\n identity: ConfigResolutionContext = {},\n persisted?: unknown,\n): ConfigResolverContext {\n return resolverContext(identity, {\n key,\n path: [key],\n source: 'class',\n hasPersisted: persisted !== undefined && persisted !== null,\n persisted,\n });\n}\n\n/**\n * Wrap a class resolver so it only fills a key the instance's persisted config\n * leaves empty — `undefined` or `null` (#2806); a present but unusable value\n * such as `{}` is kept, so check `context.persisted` yourself if it can be. Use it for a deployment-wide fallback that must never\n * replace an agent's own persisted value, such as its own provider credential.\n *\n * @example\n * ```ts\n * class Praeco extends Agent {\n * static override configResolvers = {\n * ai: preferPersisted(() => deploymentDefaultAi()),\n * };\n * }\n * ```\n */\nexport function preferPersisted(resolver: ConfigResolver): ConfigResolver {\n return (context) => (context.hasPersisted ? undefined : resolver(context));\n}\n\n/**\n * Sentinel object recognised inside agent_config to defer a value to a named\n * resolver. The runtime accepts both `$env` (preferred) and `$resolver` for\n * symmetry with similar systems.\n */\nexport interface LazyConfigSentinel {\n $env?: string;\n $resolver?: string;\n}\n\n/**\n * Options for resolving the lazy parts of a config tree.\n */\nexport interface ResolveLazyConfigOptions {\n /**\n * Identity of the agent instance being resolved, passed to every resolver\n * (#2806). Supply only what is known for *this* instance; never derive it\n * by scanning other agents' persisted rows.\n */\n context?: ConfigResolutionContext;\n\n /**\n * Per-class resolvers — typically the agent class's static\n * `configResolvers`. Applied as a key-by-key overlay on top of the\n * sentinel-resolved config.\n */\n classResolvers?: Record<string, ConfigResolver>;\n\n /**\n * Optional override for the global resolver registry. When omitted the\n * shared module-scoped registry is used. Useful for tests.\n */\n resolvers?: Map<string, ConfigResolver> | Record<string, ConfigResolver>;\n\n /**\n * What to do when a sentinel references a resolver that isn't registered,\n * or when a resolver throws.\n *\n * - `'leave'` (default): the sentinel is left in place verbatim. The\n * downstream consumer is responsible for handling it.\n * - `'omit'`: the sentinel key is removed from the parent object/array.\n * - `'throw'`: re-throw the underlying error (or throw a new one for\n * missing resolvers).\n */\n onError?: 'leave' | 'omit' | 'throw';\n}\n\nconst globalResolvers = new Map<string, ConfigResolver>();\n\n/**\n * Register a named resolver that participates in lazy config resolution.\n *\n * Calling this with the same name twice replaces the previous resolver — the\n * registry is intentionally last-write-wins so application boot ordering\n * doesn't have to be fragile.\n */\nexport function registerConfigResolver(\n name: string,\n resolver: ConfigResolver,\n): void {\n if (typeof name !== 'string' || name.length === 0) {\n throw new Error('registerConfigResolver: name must be a non-empty string');\n }\n if (typeof resolver !== 'function') {\n throw new Error('registerConfigResolver: resolver must be a function');\n }\n globalResolvers.set(name, resolver);\n}\n\n/**\n * Remove a previously registered resolver. Returns `true` if a resolver was\n * removed.\n */\nexport function unregisterConfigResolver(name: string): boolean {\n return globalResolvers.delete(name);\n}\n\n/**\n * Get the resolver registered under the given name, if any.\n */\nexport function getConfigResolver(name: string): ConfigResolver | undefined {\n return globalResolvers.get(name);\n}\n\n/**\n * List the names of all currently-registered global resolvers.\n */\nexport function listConfigResolvers(): string[] {\n return Array.from(globalResolvers.keys()).sort();\n}\n\n/**\n * Reset the global resolver registry. Primarily for tests.\n */\nexport function resetConfigResolvers(): void {\n globalResolvers.clear();\n}\n\n/**\n * Type guard that returns `true` if `value` is a lazy config sentinel.\n *\n * Recognises objects of the form `{ $env: string }` or\n * `{ $resolver: string }` exactly — extra keys disqualify the value so\n * arbitrary user data isn't accidentally treated as a sentinel.\n */\nexport function isLazyConfigSentinel(\n value: unknown,\n): value is LazyConfigSentinel {\n if (!value || typeof value !== 'object' || Array.isArray(value)) {\n return false;\n }\n const keys = Object.keys(value as Record<string, unknown>);\n if (keys.length !== 1) return false;\n const key = keys[0];\n if (key !== '$env' && key !== '$resolver') return false;\n const resolverName = (value as Record<string, unknown>)[key];\n return typeof resolverName === 'string' && resolverName.length > 0;\n}\n\n/**\n * Extract the resolver name from a sentinel.\n */\nfunction getSentinelName(sentinel: LazyConfigSentinel): string {\n return (sentinel.$env ?? sentinel.$resolver) as string;\n}\n\nfunction normalizeResolverMap(\n resolvers: Map<string, ConfigResolver> | Record<string, ConfigResolver>,\n): Map<string, ConfigResolver> {\n if (resolvers instanceof Map) return resolvers;\n return new Map(Object.entries(resolvers));\n}\n\nconst REMOVE = Symbol('LazyConfig.REMOVE');\n\n/**\n * Maximum traversal depth before {@link resolveValue} bails out. Real\n * `agent_config` payloads are shallow (2–3 levels); a high cap here exists\n * solely to prevent stack overflow from a malicious or buggy persisted\n * config without rejecting any legitimate input. When the cap is hit the\n * value is returned verbatim — same fail-soft as for a missing resolver.\n */\nconst MAX_DEPTH = 64;\n\ninterface ResolveContext {\n resolvers: Map<string, ConfigResolver>;\n identity: ConfigResolutionContext;\n onError: NonNullable<ResolveLazyConfigOptions['onError']>;\n /**\n * Map from input object/array to its resolved replacement. Serves two\n * purposes simultaneously:\n * 1. Cycle protection — without it, a self-referential config would\n * recurse forever.\n * 2. DAG fidelity — a sub-tree referenced from two places gets cloned\n * once and the same clone is reused for every reference, preserving\n * structure and ensuring sentinels under shared references are still\n * resolved.\n */\n resolved: WeakMap<object, unknown>;\n}\n\n/** Build the frozen per-call argument a resolver receives. */\nfunction resolverContext(\n identity: ConfigResolutionContext,\n fields: Omit<ConfigResolverContext, keyof ConfigResolutionContext>,\n): ConfigResolverContext {\n return Object.freeze({\n agentId: identity.agentId,\n agentClass: identity.agentClass,\n tenantId: identity.tenantId,\n scheduleId: identity.scheduleId,\n jobId: identity.jobId,\n ...fields,\n path: Object.freeze([...fields.path]),\n });\n}\n\nasync function resolveValue(\n value: unknown,\n ctx: ResolveContext,\n depth: number,\n path: readonly string[] = [],\n): Promise<unknown> {\n if (isLazyConfigSentinel(value)) {\n const name = getSentinelName(value);\n const resolver = ctx.resolvers.get(name);\n if (!resolver) {\n if (ctx.onError === 'throw') {\n throw new Error(\n `Lazy config sentinel references unknown resolver \"${name}\"`,\n );\n }\n if (ctx.onError === 'omit') return REMOVE;\n return value;\n }\n\n try {\n return await resolver(\n resolverContext(ctx.identity, {\n key: name,\n path,\n source: 'sentinel',\n hasPersisted: false,\n persisted: undefined,\n }),\n );\n } catch (error) {\n if (ctx.onError === 'throw') throw error;\n if (ctx.onError === 'omit') return REMOVE;\n return value;\n }\n }\n\n if (depth > MAX_DEPTH) {\n // Defensive: refuse to recurse further. The original sub-tree is\n // returned as-is; sentinels beyond this point will stay unresolved.\n return value;\n }\n\n if (Array.isArray(value)) {\n const cached = ctx.resolved.get(value);\n if (cached !== undefined) return cached;\n const next: unknown[] = [];\n // Register the placeholder *before* descending so that a cycle pointing\n // back at this array sees the in-progress array (and stops recursing).\n ctx.resolved.set(value, next);\n for (const [index, entry] of value.entries()) {\n const resolved = await resolveValue(entry, ctx, depth + 1, [\n ...path,\n String(index),\n ]);\n if (resolved !== REMOVE) next.push(resolved);\n }\n return next;\n }\n\n if (value && typeof value === 'object') {\n const cached = ctx.resolved.get(value);\n if (cached !== undefined) return cached;\n const next: Record<string, unknown> = {};\n ctx.resolved.set(value, next);\n for (const [key, entry] of Object.entries(\n value as Record<string, unknown>,\n )) {\n const resolved = await resolveValue(entry, ctx, depth + 1, [\n ...path,\n key,\n ]);\n if (resolved !== REMOVE) next[key] = resolved;\n }\n return next;\n }\n\n return value;\n}\n\n/**\n * Resolve every lazy sentinel inside an `agent_config`-style record at\n * execute time. The input is not mutated; a new object is returned with\n * sentinel placeholders replaced by their resolver's current return value.\n *\n * Class-level resolvers from {@link ResolveLazyConfigOptions.classResolvers}\n * are applied as a final overlay: a non-null return value wins, and\n * `undefined` / `null` keeps the persisted value. Each resolver receives the\n * resolving instance's identity ({@link ResolveLazyConfigOptions.context}) and\n * its own persisted value for the key, so it can defer to that value instead\n * of clobbering it (see the module docs and {@link preferPersisted}).\n *\n * @example Sentinel resolution\n * ```ts\n * registerConfigResolver('sharedAssetStorage', () => resolveStorage());\n *\n * const stored = { assetStorage: { $env: 'sharedAssetStorage' } };\n * const live = await resolveLazyConfig(stored);\n * // live.assetStorage === resolveStorage()\n * ```\n *\n * @example Class-level resolvers\n * ```ts\n * const live = await resolveLazyConfig(stored, {\n * classResolvers: { assetStorage: () => resolveStorage() },\n * });\n * ```\n */\nexport async function resolveLazyConfig<T extends Record<string, unknown>>(\n config: T,\n options: ResolveLazyConfigOptions = {},\n): Promise<T> {\n const onError = options.onError ?? 'leave';\n const resolvers = options.resolvers\n ? normalizeResolverMap(options.resolvers)\n : globalResolvers;\n\n const identity: ConfigResolutionContext = { ...(options.context ?? {}) };\n const ctx: ResolveContext = {\n resolvers,\n identity,\n onError,\n resolved: new WeakMap<object, unknown>(),\n };\n const sentinelResolved = (await resolveValue(config, ctx, 0)) as Record<\n string,\n unknown\n >;\n\n const result: Record<string, unknown> = { ...sentinelResolved };\n\n if (options.classResolvers) {\n for (const [key, resolver] of Object.entries(options.classResolvers)) {\n try {\n const persisted = Object.hasOwn(result, key) ? result[key] : undefined;\n const value = await resolver(\n resolverContext(identity, {\n key,\n path: [key],\n source: 'class',\n hasPersisted: persisted !== undefined && persisted !== null,\n persisted,\n }),\n );\n // Both `undefined` and `null` are treated as \"no overlay value\" —\n // the persisted record's existing value (if any) is preserved.\n // This matches the documented contract on `Agent.configResolvers`\n // and makes the common pattern `() => process.env.X ?? null` safe.\n if (value !== undefined && value !== null) {\n result[key] = value;\n }\n } catch (error) {\n if (onError === 'throw') throw error;\n if (onError === 'omit') {\n delete result[key];\n }\n // 'leave': preserve the existing (possibly persisted) value.\n }\n }\n }\n\n return result as T;\n}\n\n/**\n * Look up the static `configResolvers` map on a class (or any of its\n * ancestors). Returns `undefined` if none is declared.\n *\n * The lookup walks the prototype chain so a subclass can extend its parent's\n * resolvers without redeclaring them.\n */\nexport function getClassConfigResolvers(\n ctor: unknown,\n): Record<string, ConfigResolver> | undefined {\n if (typeof ctor !== 'function') return undefined;\n\n let current: unknown = ctor;\n let collected: Record<string, ConfigResolver> | undefined;\n\n while (current && current !== Function.prototype) {\n // Each link in the prototype chain is an object/function; read its optional\n // static `configResolvers` map through a typed shape rather than `any`.\n const resolvers = (current as { configResolvers?: unknown })\n .configResolvers;\n if (resolvers && typeof resolvers === 'object') {\n collected = {\n ...(resolvers as Record<string, ConfigResolver>),\n ...(collected ?? {}),\n };\n }\n current = Object.getPrototypeOf(current);\n }\n\n return collected;\n}\n"],"mappings":"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"}
@@ -23,8 +23,14 @@ export interface ManifestBuilderOptions {
23
23
  includeExternalBaseClasses?: boolean;
24
24
  includePrivateMethods?: boolean;
25
25
  includeStaticMethods?: boolean;
26
- /** Explicit output ownership; omitted retains legacy filename inference. */
27
- outputMode?: 'dev' | 'build';
26
+ /**
27
+ * Explicit output ownership; omitted retains legacy filename inference.
28
+ * `'dev'`/`'build'` also write the unified `.smrt/manifest.json` /
29
+ * `dist/manifest.json`. `'explicit'` writes only `outputDir/outputName`:
30
+ * test generation uses it so a test-inclusive scan never replaces a
31
+ * production manifest path (#4128).
32
+ */
33
+ outputMode?: 'dev' | 'build' | 'explicit';
28
34
  outputDir?: string;
29
35
  outputName?: string;
30
36
  generateTypeStub?: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"generator.d.ts","sourceRoot":"","sources":["../../src/manifest/generator.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAQH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AA0B/D;;;;GAIG;AACH,MAAM,WAAW,sBAAsB;IAErC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;;OAGG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAG9B,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,wBAAwB,CAAC,EAAE,OAAO,CAAC;IACnC,0BAA0B,CAAC,EAAE,OAAO,CAAC;IACrC,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAG/B,4EAA4E;IAC5E,UAAU,CAAC,EAAE,KAAK,GAAG,OAAO,CAAC;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAGlB,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,6EAA6E;IAC7E,eAAe,CAAC,EAAE,SAAS,GAAG,MAAM,CAAC;IAGrC;;OAEG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IAEpB;;OAEG;IACH,wBAAwB,CAAC,EAAE,MAAM,EAAE,CAAC;CACrC;AAuCD;;;;;GAKG;AACH,qBAAa,eAAe;IACd,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC;gBAAZ,WAAW,CAAC,EAAE,MAAM,YAAA;IAEjD,OAAO,CAAC,IAAI;IAGZ;;OAEG;IACG,QAAQ,CACZ,OAAO,GAAE,sBAA2B,GACnC,OAAO,CAAC,mBAAmB,CAAC;IAkC/B,OAAO,CAAC,kBAAkB;IAa1B,OAAO,CAAC,iBAAiB;IAmBzB;;OAEG;YACW,aAAa;IAc3B;;OAEG;YACW,gBAAgB;IAoD9B;;;;;OAKG;YACW,oBAAoB;IAyElC;;;;;;OAMG;IACH,OAAO,CAAC,cAAc;IAwCtB;;OAEG;YACW,WAAW;IA4CzB;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAuCxB;;OAEG;YACW,yBAAyB;IAgFvC;;OAEG;YACW,uBAAuB;IAwCrC;;OAEG;IACH,OAAO,CAAC,eAAe;IAavB;;OAEG;IACH,OAAO,CAAC,mBAAmB;CA4B5B"}
1
+ {"version":3,"file":"generator.d.ts","sourceRoot":"","sources":["../../src/manifest/generator.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAQH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AA0B/D;;;;GAIG;AACH,MAAM,WAAW,sBAAsB;IAErC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;;OAGG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAG9B,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,wBAAwB,CAAC,EAAE,OAAO,CAAC;IACnC,0BAA0B,CAAC,EAAE,OAAO,CAAC;IACrC,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAG/B;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,KAAK,GAAG,OAAO,GAAG,UAAU,CAAC;IAC1C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAGlB,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,6EAA6E;IAC7E,eAAe,CAAC,EAAE,SAAS,GAAG,MAAM,CAAC;IAGrC;;OAEG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IAEpB;;OAEG;IACH,wBAAwB,CAAC,EAAE,MAAM,EAAE,CAAC;CACrC;AAuCD;;;;;GAKG;AACH,qBAAa,eAAe;IACd,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC;gBAAZ,WAAW,CAAC,EAAE,MAAM,YAAA;IAEjD,OAAO,CAAC,IAAI;IAGZ;;OAEG;IACG,QAAQ,CACZ,OAAO,GAAE,sBAA2B,GACnC,OAAO,CAAC,mBAAmB,CAAC;IAkC/B,OAAO,CAAC,kBAAkB;IAa1B,OAAO,CAAC,iBAAiB;IAmBzB;;OAEG;YACW,aAAa;IAc3B;;OAEG;YACW,gBAAgB;IAoD9B;;;;;OAKG;YACW,oBAAoB;IAyElC;;;;;;OAMG;IACH,OAAO,CAAC,cAAc;IAwCtB;;OAEG;YACW,WAAW;IAgDzB;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAuCxB;;OAEG;YACW,yBAAyB;IAgFvC;;OAEG;YACW,uBAAuB;IAwCrC;;OAEG;IACH,OAAO,CAAC,eAAe;IAavB;;OAEG;IACH,OAAO,CAAC,mBAAmB;CA4B5B"}
@@ -194,14 +194,15 @@ var ManifestBuilder = class {
194
194
  async writeOutput(manifest, options) {
195
195
  const manager = new ManifestManager(this.root());
196
196
  const isTest = options.outputName?.includes("test") || options.stubName?.includes("test");
197
- const mode = options.outputMode ?? (isTest ? "dev" : "build");
198
- manager.write(manifest, mode);
197
+ const requestedMode = options.outputMode ?? (isTest ? "dev" : "build");
198
+ const mode = requestedMode === "explicit" ? null : requestedMode;
199
+ if (mode) manager.write(manifest, mode);
199
200
  const outputDir = resolve(this.root(), options.outputDir || "src/manifest");
200
201
  const outputName = options.outputName || "manifest.json";
201
202
  mkdirSync(outputDir, { recursive: true });
202
203
  const manifestPath = resolve(outputDir, outputName);
203
- const unifiedPath = manager.getOutputPath(mode);
204
- if (resolve(manifestPath) !== resolve(unifiedPath)) publishAtomicArtifact({
204
+ const unifiedPath = mode ? manager.getOutputPath(mode) : null;
205
+ if (!unifiedPath || resolve(manifestPath) !== resolve(unifiedPath)) publishAtomicArtifact({
205
206
  path: manifestPath,
206
207
  content: JSON.stringify(manifest, null, 2)
207
208
  });
@@ -212,7 +213,7 @@ var ManifestBuilder = class {
212
213
  }
213
214
  const objectCount = Object.keys(manifest.objects).length;
214
215
  logger.info(`[smrt] ✅ Generated manifest with ${objectCount} object(s)`);
215
- logger.debug(`[smrt] Unified: ${unifiedPath}`);
216
+ logger.debug(`[smrt] Output: ${unifiedPath ?? manifestPath}`);
216
217
  }
217
218
  /**
218
219
  * Generate TypeScript stub file with manifest constant
@@ -1 +1 @@
1
- {"version":3,"file":"generator.js","names":[],"sources":["../../src/manifest/generator.ts"],"sourcesContent":["/**\n * Manifest generation orchestrator\n * Consolidates manifest generation logic for both build and test manifests\n */\n\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { dirname, isAbsolute, relative, resolve } from 'node:path';\nimport { createLogger } from '@happyvertical/logger';\nimport { publishAtomicArtifact } from '../consumer-plugin/artifact-publication.js';\nimport { VERBOSE_ENABLED } from '../registry/shared-state.js';\nimport { ManifestGenerator } from '../scanner/manifest-generator.js';\nimport type { SmartObjectManifest } from '../scanner/types.js';\nimport { MANIFEST_TIMESTAMP } from '../scanner/types.js';\nimport { importProjectVite } from '../utils/import-project-vite.js';\nimport { importWorkspaceModule } from '../utils/import-workspace-module.js';\nimport type { ScannerModule } from '../utils/scanner-module.js';\nimport {\n discoverSmrtPackages,\n resolveManifestPath,\n} from './discover-smrt-packages.js';\nimport { ManifestManager } from './manager.js';\n\n// Build-time manifest-generation diagnostics route through the shared logger\n// (S14 / dim-9, #1579) so consumers can control verbosity instead of receiving\n// unconditional stdout writes. Level follows the repo-wide SMRT_VERBOSE/DEBUG\n// convention (#2896) so per-run detail is opt-in rather than always-on info.\nconst logger = createLogger({ level: VERBOSE_ENABLED ? 'debug' : 'info' });\n\nasync function importScanner() {\n return importWorkspaceModule<ScannerModule>({\n packageName: '@happyvertical/smrt-scanner',\n distEntry: 'packages/scanner/dist/index.js',\n sourceEntry: 'packages/scanner/src/index.ts',\n purpose: 'manifest generation',\n });\n}\n\n/**\n * Options for ManifestBuilder.generate()\n *\n * Controls file discovery, scanner configuration, output generation, and metadata injection\n */\nexport interface ManifestBuilderOptions {\n // File Discovery\n include?: string[];\n exclude?: string[];\n followImports?: boolean;\n /**\n * Follow symbolic links while discovering sources. Defaults to `false` —\n * see `OxcScannerOptions.followSymbolicLinks` (#2275).\n */\n followSymbolicLinks?: boolean;\n\n // Scanner Configuration\n baseClasses?: string[];\n loadViteConfig?: boolean;\n discoverExternalPackages?: boolean;\n includeExternalBaseClasses?: boolean;\n includePrivateMethods?: boolean;\n includeStaticMethods?: boolean;\n\n // Output Configuration\n /** Explicit output ownership; omitted retains legacy filename inference. */\n outputMode?: 'dev' | 'build';\n outputDir?: string;\n outputName?: string;\n generateTypeStub?: boolean;\n stubName?: string;\n\n // Metadata\n injectPackageInfo?: boolean;\n moduleType?: string;\n /** Test artifacts are local inputs, never dependency discovery providers. */\n artifactPurpose?: 'runtime' | 'test';\n\n // Tree Shaking (External Object Filtering)\n /**\n * @deprecated Tree shaking is no longer needed. The AST scanner no longer includes external objects in the manifest by default.\n */\n treeShake?: boolean;\n\n /**\n * @deprecated Whitelists are no longer needed. The AST scanner no longer includes external objects in the manifest by default.\n */\n externalObjectsWhitelist?: string[];\n}\n\ninterface ScannerConfig {\n baseClasses: string[];\n includePrivateMethods: boolean;\n includeStaticMethods: boolean;\n followImports: boolean;\n smrtDependencies?: string[];\n}\n\ninterface PackageInfo {\n name?: string;\n version?: string;\n}\n\n/**\n * Subset of the smrt-auto-service Vite plugin options we probe when reading\n * baseClasses out of a project's vite.config.ts.\n */\ninterface VitePluginOptions {\n baseClasses?: string[];\n followImports?: boolean;\n}\n\n/**\n * Structural shape of a loaded Vite plugin entry as probed by\n * loadViteConfigBaseClasses(). The plugin may expose its options directly, on\n * `_options`, or via an `api` object/function — all optional and discovered at\n * runtime, so each is modeled as optional here.\n */\ninterface VitePluginProbe {\n name?: string;\n options?: VitePluginOptions;\n _options?: VitePluginOptions;\n api?:\n | { options?: VitePluginOptions }\n | (() => { options?: VitePluginOptions });\n}\n\n/**\n * Orchestrates manifest generation with configurable options\n *\n * Consolidates logic from generate-manifest.js and generate-test-manifest.js\n * into a single, testable, and maintainable service.\n */\nexport class ManifestBuilder {\n constructor(private readonly projectRoot?: string) {}\n\n private root(): string {\n return this.projectRoot ?? process.cwd();\n }\n /**\n * Generate manifest with specified options\n */\n async generate(\n options: ManifestBuilderOptions = {},\n ): Promise<SmartObjectManifest> {\n // 1. Discover files to scan\n const files = await this.discoverFiles(options);\n\n if (files.length === 0) {\n logger.debug('[smrt] No source files found');\n return this.createEmptyManifest(options);\n }\n\n logger.debug(`[smrt] Scanning ${files.length} source file(s)...`);\n\n // 2. Configure scanner (baseClasses, external packages, vite config)\n const scannerConfig = await this.configureScanner(options);\n\n // 3. Scan files with OXC scanner\n const scannedManifest = await this.scanAndBuildManifest(\n scannerConfig,\n options,\n );\n\n // 4. Enrich manifest with metadata (smrtDependencies, packageInfo)\n const manifest = this.enrichManifest(\n scannedManifest,\n options,\n scannerConfig,\n );\n\n // 5. Write output files\n this.normalizeFilePaths(manifest);\n await this.writeOutput(manifest, options);\n\n return manifest;\n }\n\n private normalizeFilePaths(manifest: SmartObjectManifest): void {\n const workspaceRoot = this.findWorkspaceRoot(this.root());\n\n for (const obj of Object.values(manifest.objects || {})) {\n if (obj.filePath && isAbsolute(obj.filePath)) {\n obj.filePath = relative(workspaceRoot, obj.filePath).replace(\n /\\\\/g,\n '/',\n );\n }\n }\n }\n\n private findWorkspaceRoot(startDir: string): string {\n let current = resolve(startDir);\n\n while (true) {\n if (\n existsSync(resolve(current, 'pnpm-workspace.yaml')) ||\n existsSync(resolve(current, '.git'))\n ) {\n return current;\n }\n\n const parent = dirname(current);\n if (parent === current) {\n return resolve(startDir);\n }\n current = parent;\n }\n }\n\n /**\n * Discover source files based on include/exclude patterns\n */\n private async discoverFiles(\n options: ManifestBuilderOptions,\n ): Promise<string[]> {\n const include = options.include || ['src/**/*.ts'];\n const exclude = options.exclude || ['src/**/*.d.ts', 'node_modules/**'];\n const { discoverSourceFiles } = await importScanner();\n return discoverSourceFiles({\n cwd: this.root(),\n include,\n exclude,\n followSymbolicLinks: options.followSymbolicLinks ?? false,\n });\n }\n\n /**\n * Configure AST scanner with baseClasses, external packages, and vite config\n */\n private async configureScanner(\n options: ManifestBuilderOptions,\n ): Promise<ScannerConfig> {\n let baseClasses = options.baseClasses || [\n 'SmrtObject',\n 'SmrtClass',\n 'SmrtCollection',\n ];\n\n // Load from vite.config.ts if requested\n if (options.loadViteConfig) {\n const viteBaseClasses = await this.loadViteConfigBaseClasses();\n if (viteBaseClasses && viteBaseClasses.length > 0) {\n baseClasses = viteBaseClasses;\n logger.debug(\n `[smrt] Using baseClasses from vite.config.ts: ${baseClasses.join(', ')}`,\n );\n }\n }\n\n // Discover external SMRT packages\n let smrtDependencies: string[] = [];\n if (options.discoverExternalPackages) {\n logger.debug('[smrt] Discovering external SMRT packages...');\n smrtDependencies = discoverSmrtPackages({ baseDir: this.root() });\n logger.debug(\n `[smrt] Found ${smrtDependencies.length} SMRT package(s): ${smrtDependencies.join(', ')}`,\n );\n\n // Optionally add external base classes\n if (options.includeExternalBaseClasses) {\n const externalClasses =\n await this.loadExternalBaseClasses(smrtDependencies);\n if (externalClasses.length > 0) {\n baseClasses = [...baseClasses, ...externalClasses];\n logger.debug(\n `[smrt] Added ${externalClasses.length} external base class(es) to scanner`,\n );\n logger.debug(`[smrt] Total baseClasses: ${baseClasses.length}`);\n }\n }\n }\n\n return {\n baseClasses,\n includePrivateMethods: options.includePrivateMethods ?? false,\n includeStaticMethods: options.includeStaticMethods ?? true,\n followImports: options.followImports ?? false,\n smrtDependencies,\n };\n }\n\n /**\n * Scan files using OxcScanner and build manifest via ManifestAdapter.\n *\n * This replaces the old ASTScanner-based scanFiles() method.\n * Uses the same OXC pipeline as the Vite plugin's scanWithOxc().\n */\n private async scanAndBuildManifest(\n config: ScannerConfig,\n options: ManifestBuilderOptions,\n ): Promise<SmartObjectManifest> {\n const { OxcScanner, ManifestAdapter } = await importScanner();\n\n const scanner = new OxcScanner({\n cwd: this.root(),\n include: options.include || ['src/**/*.ts'],\n exclude: options.exclude || ['src/**/*.d.ts', 'node_modules/**'],\n baseClasses: config.baseClasses,\n includePrivateMethods: config.includePrivateMethods,\n includeStaticMethods: config.includeStaticMethods,\n followImports: config.followImports,\n followSymbolicLinks: options.followSymbolicLinks,\n });\n\n const { results, resolved } = await scanner.scanAndResolve();\n const scanErrors = results.errors.filter(\n (diagnostic) => diagnostic.severity === 'error',\n );\n if (scanErrors.length > 0) {\n const detail = scanErrors\n .map((diagnostic) => {\n const where = diagnostic.line\n ? `${diagnostic.filePath}:${diagnostic.line}`\n : diagnostic.filePath;\n return `${where} — ${diagnostic.message}`;\n })\n .join('; ');\n throw new Error(\n `[smrt] Build aborted due to ${scanErrors.length} scan error(s): ${detail}`,\n );\n }\n\n // Read package.json for metadata\n let packageName: string | undefined;\n let packageVersion: string | undefined;\n let packageJson: { name?: string; version?: string } | undefined;\n try {\n const pkgPath = resolve(this.root(), 'package.json');\n const pkgContent = readFileSync(pkgPath, 'utf-8');\n packageJson = JSON.parse(pkgContent);\n packageName = packageJson?.name || undefined;\n packageVersion = packageJson?.version || undefined;\n } catch {\n // package.json not found - continue without packageName\n }\n\n // Convert to manifest format\n const adapter = new ManifestAdapter();\n const manifest = adapter.toManifest(resolved, {\n packageName,\n packageVersion,\n typeAliases: results.typeAliases,\n recipes: results.recipes,\n uiSelectors: results.uiSelectors,\n });\n\n // Set smrtDependencies BEFORE mergeInheritedFields so external packages can be loaded\n if (config.smrtDependencies && config.smrtDependencies.length > 0) {\n manifest.smrtDependencies = config.smrtDependencies;\n }\n\n // Run THE manifest generation pass sequence (shared with\n // ManifestGenerator.generateManifest() and the Vite plugin, #2360 — this\n // path used to skip the report and conflict-key passes).\n const manifestGen = new ManifestGenerator();\n manifestGen.applyGenerationPasses(manifest, { packageName, packageJson });\n\n return manifest;\n }\n\n /**\n * Enrich manifest with metadata (moduleType, smrtDependencies, packageInfo).\n *\n * Note: As of Issue #1013, this method no longer merges external package\n * objects into the manifest. Dependencies are referenced via\n * `smrtDependencies` and resolved at runtime by manifest-loader.ts.\n */\n private enrichManifest(\n manifest: SmartObjectManifest,\n options: ManifestBuilderOptions,\n scannerConfig: ScannerConfig,\n ): SmartObjectManifest {\n // Add module type\n manifest.moduleType = options.moduleType || 'smrt';\n const purpose =\n options.artifactPurpose ??\n (options.outputName?.includes('test') ||\n options.stubName?.includes('test')\n ? 'test'\n : undefined);\n if (purpose) manifest.artifactPurpose = purpose;\n\n // Record dependency references (not their objects) so runtime\n // manifest-loader.ts can discover and load them on demand.\n if (\n scannerConfig.smrtDependencies &&\n scannerConfig.smrtDependencies.length > 0\n ) {\n manifest.smrtDependencies = scannerConfig.smrtDependencies;\n }\n\n // Inject package info\n if (options.injectPackageInfo) {\n const packageInfo = this.readPackageJson();\n if (packageInfo.name) {\n manifest.packageName = packageInfo.name;\n logger.debug(`[smrt] Injected package name: ${packageInfo.name}`);\n } else {\n logger.warn(\n '[smrt] Warning: Could not read package.json, packageName will be undefined',\n );\n }\n }\n\n return manifest;\n }\n\n /**\n * Write manifest JSON and optional TypeScript stub\n */\n private async writeOutput(\n manifest: SmartObjectManifest,\n options: ManifestBuilderOptions,\n ): Promise<void> {\n const manager = new ManifestManager(this.root());\n const isTest =\n options.outputName?.includes('test') ||\n options.stubName?.includes('test');\n const mode = options.outputMode ?? (isTest ? 'dev' : 'build');\n\n // 1. Always write to the unified location via ManifestManager\n manager.write(manifest, mode);\n\n // 2. Legacy/Explicit Output (if requested or for stubs)\n const outputDir = resolve(this.root(), options.outputDir || 'src/manifest');\n const outputName = options.outputName || 'manifest.json';\n\n // Ensure output directory exists for legacy/stubs\n mkdirSync(outputDir, { recursive: true });\n\n // Write JSON manifest to legacy location if different from unified path\n const manifestPath = resolve(outputDir, outputName);\n const unifiedPath = manager.getOutputPath(mode);\n\n if (resolve(manifestPath) !== resolve(unifiedPath)) {\n publishAtomicArtifact({\n path: manifestPath,\n content: JSON.stringify(manifest, null, 2),\n });\n }\n\n // Write TypeScript stub if requested\n if (options.generateTypeStub) {\n const stubName = options.stubName || 'manifest.ts';\n const tsContent = this.generateTypeStub(manifest, stubName);\n const stubPath = resolve(outputDir, stubName);\n writeFileSync(stubPath, tsContent);\n }\n\n const objectCount = Object.keys(manifest.objects).length;\n logger.info(`[smrt] ✅ Generated manifest with ${objectCount} object(s)`);\n logger.debug(`[smrt] Unified: ${unifiedPath}`);\n }\n\n /**\n * Generate TypeScript stub file with manifest constant\n */\n private generateTypeStub(\n manifest: SmartObjectManifest,\n filename: string,\n ): string {\n const isTestManifest = filename.includes('test');\n\n const comment = isTestManifest\n ? `/**\n * Test manifest stub\n * This file is replaced by generate-test-manifest.js during pretest\n * If tests run without pretest, this empty manifest is used\n *\n * DO NOT EDIT MANUALLY - This file is automatically generated\n */`\n : `/**\n * Auto-generated static manifest\n * Generated at build time from SMRT object scanning\n * DO NOT EDIT - This file is automatically generated\n */`;\n\n const exportName = isTestManifest ? 'testManifest' : 'staticManifest';\n\n // Use relative import for core package (to avoid self-reference during build),\n // package import for all other packages\n const isCorePackage = manifest.packageName === '@happyvertical/smrt-core';\n const importPath = isCorePackage\n ? '../scanner/types.js'\n : '@happyvertical/smrt-core/scanner/types';\n\n return `${comment}\n\nimport type { SmartObjectManifest } from '${importPath}';\n\nexport const ${exportName}: SmartObjectManifest = ${JSON.stringify(manifest, null, 2)} as const;\n\nexport default ${exportName};\n`;\n }\n\n /**\n * Load baseClasses from vite.config.ts\n */\n private async loadViteConfigBaseClasses(): Promise<string[] | null> {\n try {\n const viteConfigPath = resolve(this.root(), 'vite.config.ts');\n if (!existsSync(viteConfigPath)) {\n logger.debug('[smrt] vite.config.ts not found');\n return null;\n }\n\n logger.debug('[smrt] Found vite.config.ts, attempting to load...');\n\n // Use vite to load config which handles TypeScript\n const { loadConfigFromFile } = await importProjectVite(\n this.root(),\n 'Loading vite.config.ts',\n );\n const loaded = await loadConfigFromFile(\n { command: 'build', mode: 'test' },\n viteConfigPath,\n );\n\n if (!loaded?.config?.plugins) {\n logger.debug('[smrt] No plugins found in vite config');\n return null;\n }\n\n // Vite's loaded plugins are a recursive PluginOption[] (plugins, arrays,\n // falsy values, promises). We only probe a few optional fields, so model\n // them structurally and narrow defensively instead of using `any`.\n const plugins = loaded.config.plugins as readonly VitePluginProbe[];\n\n logger.debug('[smrt] Vite config loaded', {\n plugins: plugins.map((p) => p?.name),\n });\n\n // Find smrtPlugin in plugins array\n const smrtPlugin = plugins.find((p) => p?.name === 'smrt-auto-service');\n\n if (!smrtPlugin) {\n logger.debug(\n '[smrt] smrt-auto-service plugin not found in plugins array',\n );\n return null;\n }\n\n logger.debug('[smrt] Found smrt-auto-service plugin');\n\n // Try different ways to access options\n const api = smrtPlugin.api;\n let opts: VitePluginOptions | undefined =\n (typeof api === 'object' && api !== null ? api.options : undefined) ||\n smrtPlugin.options ||\n smrtPlugin._options;\n\n if (!opts && typeof api === 'function') {\n try {\n opts = api().options;\n } catch (e) {\n logger.debug('[smrt] Could not call plugin.api()');\n }\n }\n\n if (opts?.baseClasses) {\n logger.debug('[smrt] Plugin options', {\n baseClasses: opts.baseClasses,\n followImports: opts.followImports,\n });\n return opts.baseClasses;\n }\n\n logger.debug('[smrt] Could not access plugin options');\n return null;\n } catch (error) {\n logger.warn('[smrt] Error loading vite.config.ts', {\n error: error instanceof Error ? error.message : String(error),\n });\n logger.debug('[smrt] Using default baseClasses');\n return null;\n }\n }\n\n /**\n * Load base classes from external SMRT package manifests\n */\n private async loadExternalBaseClasses(packages: string[]): Promise<string[]> {\n const externalBaseClasses: string[] = [];\n\n for (const pkgName of packages) {\n // Resolve the manifest the same way discovery does (honors `.smrt/` and\n // `src/manifest/`, not just `dist/`) so source-only workspace packages\n // still contribute base classes (#1378).\n const manifestPath = resolveManifestPath(pkgName, this.root());\n if (!manifestPath) {\n logger.debug(`[smrt] ${pkgName}: no SMRT manifest resolved`);\n continue;\n }\n\n try {\n const manifestContent = readFileSync(manifestPath, 'utf-8');\n const manifest: SmartObjectManifest = JSON.parse(manifestContent);\n\n // Extract all class names from this package\n const foundClassNames: string[] = [];\n for (const objDef of Object.values(manifest.objects)) {\n if (objDef.className) {\n const className = objDef.className;\n foundClassNames.push(className);\n externalBaseClasses.push(className);\n }\n }\n\n logger.debug(\n `[smrt] ${pkgName}: found ${foundClassNames.length} class(es) - ${foundClassNames.join(', ')}`,\n );\n } catch (error) {\n logger.debug(\n `[smrt] ${pkgName}: manifest not found or invalid - ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n\n return externalBaseClasses;\n }\n\n /**\n * Read package.json from current working directory\n */\n private readPackageJson(): PackageInfo {\n try {\n const packageJsonPath = resolve(this.root(), 'package.json');\n const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));\n return {\n name: packageJson.name,\n version: packageJson.version,\n };\n } catch (error) {\n return {};\n }\n }\n\n /**\n * Create empty manifest when no files found\n */\n private createEmptyManifest(\n options: ManifestBuilderOptions,\n ): SmartObjectManifest {\n const manifest: SmartObjectManifest = {\n version: '1.0.0',\n timestamp: MANIFEST_TIMESTAMP,\n objects: {},\n moduleType: options.moduleType || 'smrt',\n ...(options.artifactPurpose\n ? { artifactPurpose: options.artifactPurpose }\n : {}),\n };\n\n if (options.discoverExternalPackages) {\n manifest.smrtDependencies = discoverSmrtPackages({\n baseDir: this.root(),\n });\n }\n\n if (options.injectPackageInfo) {\n const packageInfo = this.readPackageJson();\n if (packageInfo.name) {\n manifest.packageName = packageInfo.name;\n }\n }\n\n return manifest;\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"generator.js","names":[],"sources":["../../src/manifest/generator.ts"],"sourcesContent":["/**\n * Manifest generation orchestrator\n * Consolidates manifest generation logic for both build and test manifests\n */\n\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { dirname, isAbsolute, relative, resolve } from 'node:path';\nimport { createLogger } from '@happyvertical/logger';\nimport { publishAtomicArtifact } from '../consumer-plugin/artifact-publication.js';\nimport { VERBOSE_ENABLED } from '../registry/shared-state.js';\nimport { ManifestGenerator } from '../scanner/manifest-generator.js';\nimport type { SmartObjectManifest } from '../scanner/types.js';\nimport { MANIFEST_TIMESTAMP } from '../scanner/types.js';\nimport { importProjectVite } from '../utils/import-project-vite.js';\nimport { importWorkspaceModule } from '../utils/import-workspace-module.js';\nimport type { ScannerModule } from '../utils/scanner-module.js';\nimport {\n discoverSmrtPackages,\n resolveManifestPath,\n} from './discover-smrt-packages.js';\nimport { ManifestManager } from './manager.js';\n\n// Build-time manifest-generation diagnostics route through the shared logger\n// (S14 / dim-9, #1579) so consumers can control verbosity instead of receiving\n// unconditional stdout writes. Level follows the repo-wide SMRT_VERBOSE/DEBUG\n// convention (#2896) so per-run detail is opt-in rather than always-on info.\nconst logger = createLogger({ level: VERBOSE_ENABLED ? 'debug' : 'info' });\n\nasync function importScanner() {\n return importWorkspaceModule<ScannerModule>({\n packageName: '@happyvertical/smrt-scanner',\n distEntry: 'packages/scanner/dist/index.js',\n sourceEntry: 'packages/scanner/src/index.ts',\n purpose: 'manifest generation',\n });\n}\n\n/**\n * Options for ManifestBuilder.generate()\n *\n * Controls file discovery, scanner configuration, output generation, and metadata injection\n */\nexport interface ManifestBuilderOptions {\n // File Discovery\n include?: string[];\n exclude?: string[];\n followImports?: boolean;\n /**\n * Follow symbolic links while discovering sources. Defaults to `false` —\n * see `OxcScannerOptions.followSymbolicLinks` (#2275).\n */\n followSymbolicLinks?: boolean;\n\n // Scanner Configuration\n baseClasses?: string[];\n loadViteConfig?: boolean;\n discoverExternalPackages?: boolean;\n includeExternalBaseClasses?: boolean;\n includePrivateMethods?: boolean;\n includeStaticMethods?: boolean;\n\n // Output Configuration\n /**\n * Explicit output ownership; omitted retains legacy filename inference.\n * `'dev'`/`'build'` also write the unified `.smrt/manifest.json` /\n * `dist/manifest.json`. `'explicit'` writes only `outputDir/outputName`:\n * test generation uses it so a test-inclusive scan never replaces a\n * production manifest path (#4128).\n */\n outputMode?: 'dev' | 'build' | 'explicit';\n outputDir?: string;\n outputName?: string;\n generateTypeStub?: boolean;\n stubName?: string;\n\n // Metadata\n injectPackageInfo?: boolean;\n moduleType?: string;\n /** Test artifacts are local inputs, never dependency discovery providers. */\n artifactPurpose?: 'runtime' | 'test';\n\n // Tree Shaking (External Object Filtering)\n /**\n * @deprecated Tree shaking is no longer needed. The AST scanner no longer includes external objects in the manifest by default.\n */\n treeShake?: boolean;\n\n /**\n * @deprecated Whitelists are no longer needed. The AST scanner no longer includes external objects in the manifest by default.\n */\n externalObjectsWhitelist?: string[];\n}\n\ninterface ScannerConfig {\n baseClasses: string[];\n includePrivateMethods: boolean;\n includeStaticMethods: boolean;\n followImports: boolean;\n smrtDependencies?: string[];\n}\n\ninterface PackageInfo {\n name?: string;\n version?: string;\n}\n\n/**\n * Subset of the smrt-auto-service Vite plugin options we probe when reading\n * baseClasses out of a project's vite.config.ts.\n */\ninterface VitePluginOptions {\n baseClasses?: string[];\n followImports?: boolean;\n}\n\n/**\n * Structural shape of a loaded Vite plugin entry as probed by\n * loadViteConfigBaseClasses(). The plugin may expose its options directly, on\n * `_options`, or via an `api` object/function — all optional and discovered at\n * runtime, so each is modeled as optional here.\n */\ninterface VitePluginProbe {\n name?: string;\n options?: VitePluginOptions;\n _options?: VitePluginOptions;\n api?:\n | { options?: VitePluginOptions }\n | (() => { options?: VitePluginOptions });\n}\n\n/**\n * Orchestrates manifest generation with configurable options\n *\n * Consolidates logic from generate-manifest.js and generate-test-manifest.js\n * into a single, testable, and maintainable service.\n */\nexport class ManifestBuilder {\n constructor(private readonly projectRoot?: string) {}\n\n private root(): string {\n return this.projectRoot ?? process.cwd();\n }\n /**\n * Generate manifest with specified options\n */\n async generate(\n options: ManifestBuilderOptions = {},\n ): Promise<SmartObjectManifest> {\n // 1. Discover files to scan\n const files = await this.discoverFiles(options);\n\n if (files.length === 0) {\n logger.debug('[smrt] No source files found');\n return this.createEmptyManifest(options);\n }\n\n logger.debug(`[smrt] Scanning ${files.length} source file(s)...`);\n\n // 2. Configure scanner (baseClasses, external packages, vite config)\n const scannerConfig = await this.configureScanner(options);\n\n // 3. Scan files with OXC scanner\n const scannedManifest = await this.scanAndBuildManifest(\n scannerConfig,\n options,\n );\n\n // 4. Enrich manifest with metadata (smrtDependencies, packageInfo)\n const manifest = this.enrichManifest(\n scannedManifest,\n options,\n scannerConfig,\n );\n\n // 5. Write output files\n this.normalizeFilePaths(manifest);\n await this.writeOutput(manifest, options);\n\n return manifest;\n }\n\n private normalizeFilePaths(manifest: SmartObjectManifest): void {\n const workspaceRoot = this.findWorkspaceRoot(this.root());\n\n for (const obj of Object.values(manifest.objects || {})) {\n if (obj.filePath && isAbsolute(obj.filePath)) {\n obj.filePath = relative(workspaceRoot, obj.filePath).replace(\n /\\\\/g,\n '/',\n );\n }\n }\n }\n\n private findWorkspaceRoot(startDir: string): string {\n let current = resolve(startDir);\n\n while (true) {\n if (\n existsSync(resolve(current, 'pnpm-workspace.yaml')) ||\n existsSync(resolve(current, '.git'))\n ) {\n return current;\n }\n\n const parent = dirname(current);\n if (parent === current) {\n return resolve(startDir);\n }\n current = parent;\n }\n }\n\n /**\n * Discover source files based on include/exclude patterns\n */\n private async discoverFiles(\n options: ManifestBuilderOptions,\n ): Promise<string[]> {\n const include = options.include || ['src/**/*.ts'];\n const exclude = options.exclude || ['src/**/*.d.ts', 'node_modules/**'];\n const { discoverSourceFiles } = await importScanner();\n return discoverSourceFiles({\n cwd: this.root(),\n include,\n exclude,\n followSymbolicLinks: options.followSymbolicLinks ?? false,\n });\n }\n\n /**\n * Configure AST scanner with baseClasses, external packages, and vite config\n */\n private async configureScanner(\n options: ManifestBuilderOptions,\n ): Promise<ScannerConfig> {\n let baseClasses = options.baseClasses || [\n 'SmrtObject',\n 'SmrtClass',\n 'SmrtCollection',\n ];\n\n // Load from vite.config.ts if requested\n if (options.loadViteConfig) {\n const viteBaseClasses = await this.loadViteConfigBaseClasses();\n if (viteBaseClasses && viteBaseClasses.length > 0) {\n baseClasses = viteBaseClasses;\n logger.debug(\n `[smrt] Using baseClasses from vite.config.ts: ${baseClasses.join(', ')}`,\n );\n }\n }\n\n // Discover external SMRT packages\n let smrtDependencies: string[] = [];\n if (options.discoverExternalPackages) {\n logger.debug('[smrt] Discovering external SMRT packages...');\n smrtDependencies = discoverSmrtPackages({ baseDir: this.root() });\n logger.debug(\n `[smrt] Found ${smrtDependencies.length} SMRT package(s): ${smrtDependencies.join(', ')}`,\n );\n\n // Optionally add external base classes\n if (options.includeExternalBaseClasses) {\n const externalClasses =\n await this.loadExternalBaseClasses(smrtDependencies);\n if (externalClasses.length > 0) {\n baseClasses = [...baseClasses, ...externalClasses];\n logger.debug(\n `[smrt] Added ${externalClasses.length} external base class(es) to scanner`,\n );\n logger.debug(`[smrt] Total baseClasses: ${baseClasses.length}`);\n }\n }\n }\n\n return {\n baseClasses,\n includePrivateMethods: options.includePrivateMethods ?? false,\n includeStaticMethods: options.includeStaticMethods ?? true,\n followImports: options.followImports ?? false,\n smrtDependencies,\n };\n }\n\n /**\n * Scan files using OxcScanner and build manifest via ManifestAdapter.\n *\n * This replaces the old ASTScanner-based scanFiles() method.\n * Uses the same OXC pipeline as the Vite plugin's scanWithOxc().\n */\n private async scanAndBuildManifest(\n config: ScannerConfig,\n options: ManifestBuilderOptions,\n ): Promise<SmartObjectManifest> {\n const { OxcScanner, ManifestAdapter } = await importScanner();\n\n const scanner = new OxcScanner({\n cwd: this.root(),\n include: options.include || ['src/**/*.ts'],\n exclude: options.exclude || ['src/**/*.d.ts', 'node_modules/**'],\n baseClasses: config.baseClasses,\n includePrivateMethods: config.includePrivateMethods,\n includeStaticMethods: config.includeStaticMethods,\n followImports: config.followImports,\n followSymbolicLinks: options.followSymbolicLinks,\n });\n\n const { results, resolved } = await scanner.scanAndResolve();\n const scanErrors = results.errors.filter(\n (diagnostic) => diagnostic.severity === 'error',\n );\n if (scanErrors.length > 0) {\n const detail = scanErrors\n .map((diagnostic) => {\n const where = diagnostic.line\n ? `${diagnostic.filePath}:${diagnostic.line}`\n : diagnostic.filePath;\n return `${where} — ${diagnostic.message}`;\n })\n .join('; ');\n throw new Error(\n `[smrt] Build aborted due to ${scanErrors.length} scan error(s): ${detail}`,\n );\n }\n\n // Read package.json for metadata\n let packageName: string | undefined;\n let packageVersion: string | undefined;\n let packageJson: { name?: string; version?: string } | undefined;\n try {\n const pkgPath = resolve(this.root(), 'package.json');\n const pkgContent = readFileSync(pkgPath, 'utf-8');\n packageJson = JSON.parse(pkgContent);\n packageName = packageJson?.name || undefined;\n packageVersion = packageJson?.version || undefined;\n } catch {\n // package.json not found - continue without packageName\n }\n\n // Convert to manifest format\n const adapter = new ManifestAdapter();\n const manifest = adapter.toManifest(resolved, {\n packageName,\n packageVersion,\n typeAliases: results.typeAliases,\n recipes: results.recipes,\n uiSelectors: results.uiSelectors,\n });\n\n // Set smrtDependencies BEFORE mergeInheritedFields so external packages can be loaded\n if (config.smrtDependencies && config.smrtDependencies.length > 0) {\n manifest.smrtDependencies = config.smrtDependencies;\n }\n\n // Run THE manifest generation pass sequence (shared with\n // ManifestGenerator.generateManifest() and the Vite plugin, #2360 — this\n // path used to skip the report and conflict-key passes).\n const manifestGen = new ManifestGenerator();\n manifestGen.applyGenerationPasses(manifest, { packageName, packageJson });\n\n return manifest;\n }\n\n /**\n * Enrich manifest with metadata (moduleType, smrtDependencies, packageInfo).\n *\n * Note: As of Issue #1013, this method no longer merges external package\n * objects into the manifest. Dependencies are referenced via\n * `smrtDependencies` and resolved at runtime by manifest-loader.ts.\n */\n private enrichManifest(\n manifest: SmartObjectManifest,\n options: ManifestBuilderOptions,\n scannerConfig: ScannerConfig,\n ): SmartObjectManifest {\n // Add module type\n manifest.moduleType = options.moduleType || 'smrt';\n const purpose =\n options.artifactPurpose ??\n (options.outputName?.includes('test') ||\n options.stubName?.includes('test')\n ? 'test'\n : undefined);\n if (purpose) manifest.artifactPurpose = purpose;\n\n // Record dependency references (not their objects) so runtime\n // manifest-loader.ts can discover and load them on demand.\n if (\n scannerConfig.smrtDependencies &&\n scannerConfig.smrtDependencies.length > 0\n ) {\n manifest.smrtDependencies = scannerConfig.smrtDependencies;\n }\n\n // Inject package info\n if (options.injectPackageInfo) {\n const packageInfo = this.readPackageJson();\n if (packageInfo.name) {\n manifest.packageName = packageInfo.name;\n logger.debug(`[smrt] Injected package name: ${packageInfo.name}`);\n } else {\n logger.warn(\n '[smrt] Warning: Could not read package.json, packageName will be undefined',\n );\n }\n }\n\n return manifest;\n }\n\n /**\n * Write manifest JSON and optional TypeScript stub\n */\n private async writeOutput(\n manifest: SmartObjectManifest,\n options: ManifestBuilderOptions,\n ): Promise<void> {\n const manager = new ManifestManager(this.root());\n const isTest =\n options.outputName?.includes('test') ||\n options.stubName?.includes('test');\n const requestedMode = options.outputMode ?? (isTest ? 'dev' : 'build');\n const mode = requestedMode === 'explicit' ? null : requestedMode;\n\n // 1. Write the unified location via ManifestManager, unless the caller\n // owns an explicit, isolated output path.\n if (mode) {\n manager.write(manifest, mode);\n }\n\n // 2. Legacy/Explicit Output (if requested or for stubs)\n const outputDir = resolve(this.root(), options.outputDir || 'src/manifest');\n const outputName = options.outputName || 'manifest.json';\n\n // Ensure output directory exists for legacy/stubs\n mkdirSync(outputDir, { recursive: true });\n\n // Write JSON manifest to legacy location if different from unified path\n const manifestPath = resolve(outputDir, outputName);\n const unifiedPath = mode ? manager.getOutputPath(mode) : null;\n\n if (!unifiedPath || resolve(manifestPath) !== resolve(unifiedPath)) {\n publishAtomicArtifact({\n path: manifestPath,\n content: JSON.stringify(manifest, null, 2),\n });\n }\n\n // Write TypeScript stub if requested\n if (options.generateTypeStub) {\n const stubName = options.stubName || 'manifest.ts';\n const tsContent = this.generateTypeStub(manifest, stubName);\n const stubPath = resolve(outputDir, stubName);\n writeFileSync(stubPath, tsContent);\n }\n\n const objectCount = Object.keys(manifest.objects).length;\n logger.info(`[smrt] ✅ Generated manifest with ${objectCount} object(s)`);\n logger.debug(`[smrt] Output: ${unifiedPath ?? manifestPath}`);\n }\n\n /**\n * Generate TypeScript stub file with manifest constant\n */\n private generateTypeStub(\n manifest: SmartObjectManifest,\n filename: string,\n ): string {\n const isTestManifest = filename.includes('test');\n\n const comment = isTestManifest\n ? `/**\n * Test manifest stub\n * This file is replaced by generate-test-manifest.js during pretest\n * If tests run without pretest, this empty manifest is used\n *\n * DO NOT EDIT MANUALLY - This file is automatically generated\n */`\n : `/**\n * Auto-generated static manifest\n * Generated at build time from SMRT object scanning\n * DO NOT EDIT - This file is automatically generated\n */`;\n\n const exportName = isTestManifest ? 'testManifest' : 'staticManifest';\n\n // Use relative import for core package (to avoid self-reference during build),\n // package import for all other packages\n const isCorePackage = manifest.packageName === '@happyvertical/smrt-core';\n const importPath = isCorePackage\n ? '../scanner/types.js'\n : '@happyvertical/smrt-core/scanner/types';\n\n return `${comment}\n\nimport type { SmartObjectManifest } from '${importPath}';\n\nexport const ${exportName}: SmartObjectManifest = ${JSON.stringify(manifest, null, 2)} as const;\n\nexport default ${exportName};\n`;\n }\n\n /**\n * Load baseClasses from vite.config.ts\n */\n private async loadViteConfigBaseClasses(): Promise<string[] | null> {\n try {\n const viteConfigPath = resolve(this.root(), 'vite.config.ts');\n if (!existsSync(viteConfigPath)) {\n logger.debug('[smrt] vite.config.ts not found');\n return null;\n }\n\n logger.debug('[smrt] Found vite.config.ts, attempting to load...');\n\n // Use vite to load config which handles TypeScript\n const { loadConfigFromFile } = await importProjectVite(\n this.root(),\n 'Loading vite.config.ts',\n );\n const loaded = await loadConfigFromFile(\n { command: 'build', mode: 'test' },\n viteConfigPath,\n );\n\n if (!loaded?.config?.plugins) {\n logger.debug('[smrt] No plugins found in vite config');\n return null;\n }\n\n // Vite's loaded plugins are a recursive PluginOption[] (plugins, arrays,\n // falsy values, promises). We only probe a few optional fields, so model\n // them structurally and narrow defensively instead of using `any`.\n const plugins = loaded.config.plugins as readonly VitePluginProbe[];\n\n logger.debug('[smrt] Vite config loaded', {\n plugins: plugins.map((p) => p?.name),\n });\n\n // Find smrtPlugin in plugins array\n const smrtPlugin = plugins.find((p) => p?.name === 'smrt-auto-service');\n\n if (!smrtPlugin) {\n logger.debug(\n '[smrt] smrt-auto-service plugin not found in plugins array',\n );\n return null;\n }\n\n logger.debug('[smrt] Found smrt-auto-service plugin');\n\n // Try different ways to access options\n const api = smrtPlugin.api;\n let opts: VitePluginOptions | undefined =\n (typeof api === 'object' && api !== null ? api.options : undefined) ||\n smrtPlugin.options ||\n smrtPlugin._options;\n\n if (!opts && typeof api === 'function') {\n try {\n opts = api().options;\n } catch (e) {\n logger.debug('[smrt] Could not call plugin.api()');\n }\n }\n\n if (opts?.baseClasses) {\n logger.debug('[smrt] Plugin options', {\n baseClasses: opts.baseClasses,\n followImports: opts.followImports,\n });\n return opts.baseClasses;\n }\n\n logger.debug('[smrt] Could not access plugin options');\n return null;\n } catch (error) {\n logger.warn('[smrt] Error loading vite.config.ts', {\n error: error instanceof Error ? error.message : String(error),\n });\n logger.debug('[smrt] Using default baseClasses');\n return null;\n }\n }\n\n /**\n * Load base classes from external SMRT package manifests\n */\n private async loadExternalBaseClasses(packages: string[]): Promise<string[]> {\n const externalBaseClasses: string[] = [];\n\n for (const pkgName of packages) {\n // Resolve the manifest the same way discovery does (honors `.smrt/` and\n // `src/manifest/`, not just `dist/`) so source-only workspace packages\n // still contribute base classes (#1378).\n const manifestPath = resolveManifestPath(pkgName, this.root());\n if (!manifestPath) {\n logger.debug(`[smrt] ${pkgName}: no SMRT manifest resolved`);\n continue;\n }\n\n try {\n const manifestContent = readFileSync(manifestPath, 'utf-8');\n const manifest: SmartObjectManifest = JSON.parse(manifestContent);\n\n // Extract all class names from this package\n const foundClassNames: string[] = [];\n for (const objDef of Object.values(manifest.objects)) {\n if (objDef.className) {\n const className = objDef.className;\n foundClassNames.push(className);\n externalBaseClasses.push(className);\n }\n }\n\n logger.debug(\n `[smrt] ${pkgName}: found ${foundClassNames.length} class(es) - ${foundClassNames.join(', ')}`,\n );\n } catch (error) {\n logger.debug(\n `[smrt] ${pkgName}: manifest not found or invalid - ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n\n return externalBaseClasses;\n }\n\n /**\n * Read package.json from current working directory\n */\n private readPackageJson(): PackageInfo {\n try {\n const packageJsonPath = resolve(this.root(), 'package.json');\n const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));\n return {\n name: packageJson.name,\n version: packageJson.version,\n };\n } catch (error) {\n return {};\n }\n }\n\n /**\n * Create empty manifest when no files found\n */\n private createEmptyManifest(\n options: ManifestBuilderOptions,\n ): SmartObjectManifest {\n const manifest: SmartObjectManifest = {\n version: '1.0.0',\n timestamp: MANIFEST_TIMESTAMP,\n objects: {},\n moduleType: options.moduleType || 'smrt',\n ...(options.artifactPurpose\n ? { artifactPurpose: options.artifactPurpose }\n : {}),\n };\n\n if (options.discoverExternalPackages) {\n manifest.smrtDependencies = discoverSmrtPackages({\n baseDir: this.root(),\n });\n }\n\n if (options.injectPackageInfo) {\n const packageInfo = this.readPackageJson();\n if (packageInfo.name) {\n manifest.packageName = packageInfo.name;\n }\n }\n\n return manifest;\n }\n}\n"],"mappings":"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"}
@@ -9,6 +9,7 @@ export type { ManifestBuilderOptions } from "./generator.js";
9
9
  export { ManifestBuilder } from "./generator.js";
10
10
  export { ManifestManager } from "./manager.js";
11
11
  export { discoverManifestEntry, findManifestEntryByQualifiedName, loadExternalManifest, loadExternalManifestSync, loadLocalTestManifestSync, loadManifestFromPathSync, } from "./manifest-loader.js";
12
+ export { isActiveTestManifestPath, isRefusedTestArtifact, isTestArtifactManifest, registerActiveTestManifestPath, resolveActiveTestManifestPath, SMRT_TEST_MANIFEST_PATHS_ENV_KEY, } from "./test-artifacts.js";
12
13
  export declare const manifest: SmartObjectManifest;
13
14
  export declare const staticManifest: SmartObjectManifest;
14
15
  export default manifest;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/manifest/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qBAAkB,CAAC;AAG5D,OAAO,EAAE,iBAAiB,EAAE,MAAM,kCAA+B,CAAC;AAClE,YAAY,EACV,qBAAqB,EACrB,mBAAmB,GACpB,MAAM,qBAAkB,CAAC;AAC1B,YAAY,EAAE,sBAAsB,EAAE,MAAM,gBAAa,CAAC;AAC1D,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAa,CAAC;AAC9C,OAAO,EAAE,eAAe,EAAE,MAAM,cAAW,CAAC;AAC5C,OAAO,EACL,qBAAqB,EACrB,gCAAgC,EAChC,oBAAoB,EACpB,wBAAwB,EACxB,yBAAyB,EACzB,wBAAwB,GACzB,MAAM,sBAAmB,CAAC;AAgC3B,eAAO,MAAM,QAAQ,qBAAuB,CAAC;AAC7C,eAAO,MAAM,cAAc,qBAAW,CAAC;AACvC,eAAe,QAAQ,CAAC;AAExB;;;GAGG;AACH,wBAAgB,WAAW,iCAE1B"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/manifest/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qBAAkB,CAAC;AAG5D,OAAO,EAAE,iBAAiB,EAAE,MAAM,kCAA+B,CAAC;AAClE,YAAY,EACV,qBAAqB,EACrB,mBAAmB,GACpB,MAAM,qBAAkB,CAAC;AAC1B,YAAY,EAAE,sBAAsB,EAAE,MAAM,gBAAa,CAAC;AAC1D,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAa,CAAC;AAC9C,OAAO,EAAE,eAAe,EAAE,MAAM,cAAW,CAAC;AAC5C,OAAO,EACL,qBAAqB,EACrB,gCAAgC,EAChC,oBAAoB,EACpB,wBAAwB,EACxB,yBAAyB,EACzB,wBAAwB,GACzB,MAAM,sBAAmB,CAAC;AAC3B,OAAO,EACL,wBAAwB,EACxB,qBAAqB,EACrB,sBAAsB,EACtB,8BAA8B,EAC9B,6BAA6B,EAC7B,gCAAgC,GACjC,MAAM,qBAAkB,CAAC;AAgC1B,eAAO,MAAM,QAAQ,qBAAuB,CAAC;AAC7C,eAAO,MAAM,cAAc,qBAAW,CAAC;AACvC,eAAe,QAAQ,CAAC;AAExB;;;GAGG;AACH,wBAAgB,WAAW,iCAE1B"}