@effected/workspaces 0.17.1 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -199,8 +199,17 @@ var WorkspaceDiscovery = class WorkspaceDiscovery extends Context.Service()("@ef
199
199
  cause
200
200
  })));
201
201
  });
202
- const discover = Effect.gen(function* () {
203
- const root = yield* Effect.suspend(() => roots.find(options?.cwd ?? process.cwd()));
202
+ /**
203
+ * Discovery for ONE already-resolved root the whole read, with the root
204
+ * as a parameter rather than a closed-over constant.
205
+ *
206
+ * Everything derived here (patterns, member manifests, names, versions) is
207
+ * read beneath `root`, which is what makes the per-call-root methods
208
+ * honest: re-rooting an already-discovered package list onto a different
209
+ * directory rewrites paths but keeps the ORIGINAL root's manifests, so a
210
+ * branch that adds or removes a package stays invisible. This re-reads.
211
+ */
212
+ const discoverAt = (root) => Effect.gen(function* () {
204
213
  const patterns = yield* readPatterns(root).pipe(Effect.mapError((failure) => new WorkspaceDiscoveryError({
205
214
  root,
206
215
  path: failure.path,
@@ -233,8 +242,43 @@ var WorkspaceDiscovery = class WorkspaceDiscovery extends Context.Service()("@ef
233
242
  packages
234
243
  };
235
244
  }).pipe(Effect.provideService(FileSystem.FileSystem, fs), Effect.provideService(Path.Path, path));
245
+ const discover = Effect.gen(function* () {
246
+ const root = yield* Effect.suspend(() => roots.find(options?.cwd ?? process.cwd()));
247
+ return yield* discoverAt(root);
248
+ });
236
249
  const [resolveOnce, invalidate] = yield* Effect.cachedInvalidateWithTTL(discover, Duration.infinity);
237
250
  const memo = Effect.onExit(resolveOnce, (exit) => Exit.isSuccess(exit) ? Effect.void : invalidate);
251
+ /**
252
+ * Per-resolved-root memos for the `…In` methods, each success-only on the
253
+ * same discipline as the layer-bound one above.
254
+ *
255
+ * Deliberately SEPARATE from that memo rather than replacing it: the
256
+ * layer-bound path resolves its root once, at first use, and folding it
257
+ * into this map would make every call re-run the root ascent. A caller
258
+ * that asks for the layer's own root through `listPackagesIn` pays one
259
+ * extra discovery — the honest price for not changing what the existing
260
+ * methods do.
261
+ *
262
+ * The map grows one entry per distinct root, which is the point for a
263
+ * long-lived host serving many worktrees, and is why `refresh()` clears
264
+ * it wholesale rather than invalidating one cell.
265
+ */
266
+ const rootMemos = /* @__PURE__ */ new Map();
267
+ /** The memoized discovery for the workspace root containing `directory`. */
268
+ const memoIn = Effect.fn("WorkspaceDiscovery.memoIn")(function* (directory) {
269
+ const root = yield* roots.find(directory);
270
+ const existing = rootMemos.get(root);
271
+ if (existing !== void 0) return yield* existing.memo;
272
+ const [resolveOnce, invalidateOne] = yield* Effect.cachedInvalidateWithTTL(discoverAt(root), Duration.infinity);
273
+ const built = Effect.onExit(resolveOnce, (exit) => Exit.isSuccess(exit) ? Effect.void : invalidateOne);
274
+ const raced = rootMemos.get(root);
275
+ if (raced !== void 0) return yield* raced.memo;
276
+ rootMemos.set(root, {
277
+ memo: built,
278
+ invalidate: invalidateOne
279
+ });
280
+ return yield* built;
281
+ });
238
282
  const packages = memo.pipe(Effect.map((state) => state.packages));
239
283
  /**
240
284
  * The longest-prefix index, built once per package list.
@@ -292,6 +336,12 @@ var WorkspaceDiscovery = class WorkspaceDiscovery extends Context.Service()("@ef
292
336
  const all = yield* packages;
293
337
  return ownerOf(filePath, owners(all));
294
338
  }),
339
+ infoIn: Effect.fn("WorkspaceDiscovery.infoIn")(function* (directory) {
340
+ return (yield* memoIn(directory)).info;
341
+ }),
342
+ listPackagesIn: Effect.fn("WorkspaceDiscovery.listPackagesIn")(function* (directory) {
343
+ return (yield* memoIn(directory)).packages;
344
+ }),
295
345
  resolveFiles: Effect.fn("WorkspaceDiscovery.resolveFiles")(function* (filePaths) {
296
346
  const all = yield* packages;
297
347
  const index = owners(all);
@@ -302,7 +352,17 @@ var WorkspaceDiscovery = class WorkspaceDiscovery extends Context.Service()("@ef
302
352
  }
303
353
  return [...seen.values()].sort((a, b) => a.name.localeCompare(b.name));
304
354
  }),
305
- refresh: () => invalidate
355
+ refreshIn: Effect.fn("WorkspaceDiscovery.refreshIn")(function* (directory) {
356
+ const root = yield* roots.find(directory);
357
+ const cell = rootMemos.get(root);
358
+ if (cell === void 0) return;
359
+ yield* cell.invalidate;
360
+ rootMemos.delete(root);
361
+ }),
362
+ refresh: () => Effect.flatMap(Effect.forEach([...rootMemos.values()], (cell) => cell.invalidate, { discard: true }), () => {
363
+ rootMemos.clear();
364
+ return invalidate;
365
+ })
306
366
  };
307
367
  });
308
368
  /**
@@ -396,7 +456,10 @@ var WorkspaceDiscovery = class WorkspaceDiscovery extends Context.Service()("@ef
396
456
  }
397
457
  return [...seen.values()].sort((a, b) => a.name.localeCompare(b.name));
398
458
  }),
459
+ infoIn: () => Effect.die(/* @__PURE__ */ new Error("WorkspaceDiscovery.makeTest: infoIn() was called but not stubbed — a double cannot know what another root's workspace looks like; pass an `infoIn` override.")),
460
+ listPackagesIn: () => Effect.die(/* @__PURE__ */ new Error("WorkspaceDiscovery.makeTest: listPackagesIn() was called but not stubbed — deriving it from `listPackages` would model every root as identical, which is the bug this method exists to prevent; pass a `listPackagesIn` override.")),
399
461
  refresh: () => Effect.void,
462
+ refreshIn: () => Effect.void,
400
463
  ...overrides
401
464
  };
402
465
  };
@@ -104,6 +104,7 @@ var WorkspaceSnapshots = class WorkspaceSnapshots extends Context.Service()("@ef
104
104
  const roots = yield* WorkspaceRoot;
105
105
  const discovery = yield* WorkspaceDiscovery;
106
106
  const catalogsService = yield* WorkspaceCatalogs;
107
+ const seeded = (snapshot) => options?.seedCatalogs === void 0 ? snapshot : snapshot.withSeededCatalogs(options.seedCatalogs);
107
108
  /**
108
109
  * What a manager's lockfile records at the ref: its catalog set and its
109
110
  * importer versions, from ONE parse. Empty on both counts when the
@@ -166,11 +167,11 @@ var WorkspaceSnapshots = class WorkspaceSnapshots extends Context.Service()("@ef
166
167
  const packages = [];
167
168
  if (Option.isSome(rootPackage)) packages.push(rootPackage.value);
168
169
  for (const member of members) if (Option.isSome(member)) packages.push(member.value);
169
- return WorkspaceStateSnapshot.make({
170
+ return seeded(WorkspaceStateSnapshot.make({
170
171
  packages,
171
172
  catalogs,
172
173
  importerVersions: recorded.importerVersions
173
- });
174
+ }));
174
175
  });
175
176
  const atCaches = /* @__PURE__ */ new Map();
176
177
  return {
@@ -203,11 +204,11 @@ var WorkspaceSnapshots = class WorkspaceSnapshots extends Context.Service()("@ef
203
204
  peerDependencies: pkg.peerDependencies,
204
205
  optionalDependencies: pkg.optionalDependencies
205
206
  }));
206
- return WorkspaceStateSnapshot.make({
207
+ return seeded(WorkspaceStateSnapshot.make({
207
208
  packages: snapshotPackages,
208
209
  catalogs,
209
210
  importerVersions
210
- });
211
+ }));
211
212
  })
212
213
  };
213
214
  });
@@ -83,7 +83,7 @@ var PackageStateSnapshot = class extends Schema.Class("PackageStateSnapshot")({
83
83
  *
84
84
  * @public
85
85
  */
86
- var WorkspaceStateSnapshot = class extends Schema.Class("WorkspaceStateSnapshot")({
86
+ var WorkspaceStateSnapshot = class WorkspaceStateSnapshot extends Schema.Class("WorkspaceStateSnapshot")({
87
87
  /** Every workspace package captured at this moment. */
88
88
  packages: Schema.Array(PackageStateSnapshot),
89
89
  /** The catalog set assembled at this moment. */
@@ -99,7 +99,30 @@ var WorkspaceStateSnapshot = class extends Schema.Class("WorkspaceStateSnapshot"
99
99
  * the behavior those older values were captured under. Only pnpm records
100
100
  * importer versions; bun and npm yield an empty index.
101
101
  */
102
- importerVersions: Schema.optionalKey(Schema.Record(Schema.String, Schema.Record(Schema.String, Schema.String)))
102
+ importerVersions: Schema.optionalKey(Schema.Record(Schema.String, Schema.Record(Schema.String, Schema.String))),
103
+ /**
104
+ * Catalogs supplied from OUTSIDE this moment, consulted only when
105
+ * `catalogs` cannot answer.
106
+ *
107
+ * @remarks
108
+ * **This is deliberately not merged into `catalogs`.** That field means "the
109
+ * catalog set assembled at this moment" and a snapshot is a serializable
110
+ * value someone stores and diffs; blending an external set into it would
111
+ * quietly make the field mean something else, and nothing downstream could
112
+ * tell the two apart afterwards. Kept separate, the ref's own declaration
113
+ * always wins and both halves stay readable.
114
+ *
115
+ * The motivating case is a catalog injected by a config-dependency
116
+ * `pnpmfile` hook. It is recorded in no committed catalog source, so
117
+ * `WorkspaceSnapshots.at(ref)` — which never replays hooks, by design — cannot
118
+ * see it, and a `catalog:` specifier against it resolves to nothing on BOTH
119
+ * sides of a diff. Seeding the live hook-injected set, or the other side's
120
+ * set, restores a declared RANGE without executing any historical code.
121
+ *
122
+ * Defaults to absent, which makes the seed inert — exactly the behavior of
123
+ * every snapshot captured before this field existed.
124
+ */
125
+ seededCatalogs: Schema.optionalKey(CatalogSet)
103
126
  }) {
104
127
  #versionIndex;
105
128
  #packageIndex;
@@ -134,7 +157,16 @@ var WorkspaceStateSnapshot = class extends Schema.Class("WorkspaceStateSnapshot"
134
157
  * unparseable string — is `Option.none()`, because there is no indirection to
135
158
  * resolve. Total.
136
159
  *
137
- * A `catalog:` specifier the catalog set cannot resolve falls back to this
160
+ * A `catalog:` specifier resolves in three steps, and the order is the
161
+ * contract: this moment's own `catalogs` first,
162
+ * then `seededCatalogs` if one was supplied,
163
+ * then the `importerVersions` fallback below. The first two answer with a
164
+ * declared RANGE and the third with a concrete version, so a seeded snapshot
165
+ * reports a range change where an unseeded one could only report a version —
166
+ * which is the difference between a diff row and no row when both refs
167
+ * recorded the same installed version.
168
+ *
169
+ * A `catalog:` specifier neither catalog set can resolve falls back to this
138
170
  * snapshot's `importerVersions` — but only to a version
139
171
  * **every** importer recording that dependency agrees on. A catalog injected
140
172
  * by a config-dependency pnpmfile hook appears in no committed catalog source,
@@ -187,7 +219,7 @@ var WorkspaceStateSnapshot = class extends Schema.Class("WorkspaceStateSnapshot"
187
219
  const classified = exit.value;
188
220
  switch (classified._tag) {
189
221
  case "catalog": {
190
- const fromCatalogs = this.catalogs.rangeOf(dependency, classified.name);
222
+ const fromCatalogs = this.#catalogRange(dependency, classified.name);
191
223
  return Option.isSome(fromCatalogs) ? fromCatalogs : onUnresolvedCatalog();
192
224
  }
193
225
  case "workspace": return Option.fromUndefinedOr(this.#versions().get(dependency));
@@ -195,6 +227,126 @@ var WorkspaceStateSnapshot = class extends Schema.Class("WorkspaceStateSnapshot"
195
227
  }
196
228
  }
197
229
  /**
230
+ * The catalog half of resolution, in precedence order: this moment's own
231
+ * catalogs first, the external seed second.
232
+ *
233
+ * @remarks
234
+ * The ordering is the whole contract. What the ref itself declared can never
235
+ * be overridden by something handed in from outside, so a seed can only ever
236
+ * ADD an answer where there was none — which is why seeding is safe to do
237
+ * unconditionally and why an over-broad seed cannot corrupt a diff.
238
+ */
239
+ #catalogRange(dependency, catalog) {
240
+ const own = this.catalogs.rangeOf(dependency, catalog);
241
+ if (Option.isSome(own)) return own;
242
+ return this.seededCatalogs === void 0 ? Option.none() : this.seededCatalogs.rangeOf(dependency, catalog);
243
+ }
244
+ /**
245
+ * This snapshot with `seed` as its `seededCatalogs`
246
+ * — catalogs consulted only where this moment's own catalogs cannot answer.
247
+ *
248
+ * @remarks
249
+ * Returns a NEW snapshot; the receiver is untouched, and the seed REPLACES
250
+ * any seed already present rather than merging with it (a snapshot is a
251
+ * value, and an accumulating seed would make precedence depend on call
252
+ * order). {@link WorkspaceStateSnapshot.crossSeed} is the deliberate
253
+ * exception: it composes the two seeds explicitly, precisely because a bare
254
+ * replace would discard a layer-level seed. `catalogs`, `packages` and `importerVersions` are carried through
255
+ * unchanged, so what the ref declared is still exactly what it declared.
256
+ *
257
+ * The two seeds worth reaching for: the LIVE hook-injected catalog set (from
258
+ * a `WorkspaceCatalogs` built by one of the config-dependency layers), or the
259
+ * other side of a two-ref diff — see
260
+ * {@link WorkspaceStateSnapshot.crossSeed}.
261
+ *
262
+ * @param seed - The catalogs to consult as a fallback.
263
+ *
264
+ * @example
265
+ * ```ts
266
+ * import { WorkspaceCatalogs, WorkspaceSnapshots } from "@effected/workspaces";
267
+ * import { Effect } from "effect";
268
+ *
269
+ * const program = Effect.gen(function* () {
270
+ * const snapshots = yield* WorkspaceSnapshots;
271
+ * const catalogs = yield* WorkspaceCatalogs;
272
+ * // The live set includes hook-injected catalogs under a config-dependency
273
+ * // layer; the ref's own set never can.
274
+ * const live = yield* catalogs.set();
275
+ * const before = (yield* snapshots.at("origin/main")).withSeededCatalogs(live);
276
+ * return before.resolve("effect", "catalog:");
277
+ * });
278
+ * ```
279
+ */
280
+ withSeededCatalogs(seed) {
281
+ return WorkspaceStateSnapshot.make({
282
+ packages: this.packages,
283
+ catalogs: this.catalogs,
284
+ ...this.importerVersions === void 0 ? {} : { importerVersions: this.importerVersions },
285
+ seededCatalogs: seed
286
+ });
287
+ }
288
+ /**
289
+ * Both sides of a diff, each seeded with the other's catalogs.
290
+ *
291
+ * @remarks
292
+ * The two-ref symmetry the hook-catalog gap actually needs. A catalog
293
+ * injected by a config-dependency hook is declared in no committed source,
294
+ * so neither ref's snapshot can see it and a `catalog:` specifier against it
295
+ * resolves to nothing on both sides — a real version movement then produces
296
+ * no row. Cross-seeding restores a declared RANGE on whichever side is
297
+ * missing it, at strictly lower precedence than that side's own catalogs, so
298
+ * a genuine change between the refs still reads as a change.
299
+ *
300
+ * **The limitation is inherent and is not a defect to work around.** A range
301
+ * change made purely by bumping the config dependency BETWEEN the two refs is
302
+ * suppressed: neither committed source declares the catalog, so each side
303
+ * falls back to the other's value and the two agree by construction. Seeding
304
+ * the live hook-injected set via
305
+ * {@link WorkspaceStateSnapshot.withSeededCatalogs} has the same blind spot
306
+ * against history — recovering it would mean replaying each ref's pinned
307
+ * config-dependency code, which `at(ref)` will not do. If that case must be
308
+ * detected, diff `configDependencies` in `pnpm-workspace.yaml` directly; it
309
+ * is the only committed evidence that the injection changed.
310
+ *
311
+ * **The seeding relationship is symmetric; the RETURN ORDER is not.** Each
312
+ * snapshot is seeded with the other's catalogs, so neither argument is
313
+ * privileged and swapping them produces the same two values — but they come
314
+ * back mirroring the order they went in, so destructure in the order you
315
+ * passed. The `before`/`after` names describe the intended calling
316
+ * convention for a two-ref diff, not a constraint on what may be passed.
317
+ *
318
+ * **A seed already present on either snapshot is preserved**, beneath the
319
+ * other side's catalogs — which matters because
320
+ * `WorkspaceSnapshotsOptions.seedCatalogs` puts one there on every snapshot
321
+ * the service returns. Composing the two surfaces is therefore safe: the
322
+ * layer-level seed keeps answering what neither ref declared, while the
323
+ * other ref's committed declaration wins where it has one.
324
+ *
325
+ * @param before - One snapshot, conventionally the earlier one.
326
+ * @param after - The other snapshot, conventionally the later one.
327
+ * @returns Both snapshots in the order given, each carrying the other's
328
+ * catalogs as its seed.
329
+ *
330
+ * @example
331
+ * ```ts
332
+ * import { WorkspaceSnapshots, WorkspaceStateSnapshot } from "@effected/workspaces";
333
+ * import { Effect } from "effect";
334
+ *
335
+ * const program = Effect.gen(function* () {
336
+ * const snapshots = yield* WorkspaceSnapshots;
337
+ * const [before, after] = WorkspaceStateSnapshot.crossSeed(
338
+ * yield* snapshots.at("origin/main"),
339
+ * yield* snapshots.worktree(),
340
+ * );
341
+ * return { before: before.resolve("effect", "catalog:"), after: after.resolve("effect", "catalog:") };
342
+ * });
343
+ * ```
344
+ */
345
+ static crossSeed(before, after) {
346
+ const seedFor = (self, other) => self.seededCatalogs === void 0 ? other.catalogs : CatalogSet.merge(self.seededCatalogs, other.catalogs);
347
+ return [before.withSeededCatalogs(seedFor(before, after)), after.withSeededCatalogs(seedFor(after, before))];
348
+ }
349
+ /**
198
350
  * A `CatalogResolver` layer implementing `@effected/npm`'s contract against
199
351
  * THIS snapshot's catalog set — so code written to the contract resolves
200
352
  * `catalog:` specifiers as of this ref. Built once per instance and cached, so
@@ -207,7 +359,7 @@ var WorkspaceStateSnapshot = class extends Schema.Class("WorkspaceStateSnapshot"
207
359
  * `rangeOf` never fails.
208
360
  */
209
361
  get catalogResolver() {
210
- if (this.#catalogResolver === void 0) this.#catalogResolver = Layer.succeed(CatalogResolver, { rangeOf: (packageName, catalog) => Effect.succeed(this.catalogs.rangeOf(packageName, catalog)) });
362
+ if (this.#catalogResolver === void 0) this.#catalogResolver = Layer.succeed(CatalogResolver, { rangeOf: (packageName, catalog) => Effect.succeed(this.#catalogRange(packageName, catalog)) });
211
363
  return this.#catalogResolver;
212
364
  }
213
365
  /**
package/Workspaces.js CHANGED
@@ -19,14 +19,30 @@ const compose = (options, catalogsFactory) => {
19
19
  return Layer.mergeAll(roots, detector, discovery, lockfiles, catalogs);
20
20
  };
21
21
  const layer = (options) => compose(options, WorkspaceCatalogs.layer);
22
- const layerWithGit = (options) => {
23
- const core = layer(options);
22
+ /**
23
+ * The git half, over an ALREADY-BUILT core composite.
24
+ *
25
+ * @remarks
26
+ * Taking the core as a parameter rather than building it is what lets the
27
+ * config-dependency composites exist at all: the git services are identical
28
+ * across all three, and only the catalogs layer underneath differs. Copying
29
+ * this graph per variant is exactly what a consumer had to do downstream
30
+ * before `layerWithGitAndConfigDependencies` existed.
31
+ *
32
+ * `core` is threaded in as a value, so the caller's single `layer(options)`
33
+ * reference is shared by `ChangeDetector`, `WorkspaceSnapshots` and the merge —
34
+ * building it twice here would defeat layer memoization inside one composite.
35
+ */
36
+ const withGit = (core, options) => {
24
37
  const git = Git.layer;
25
38
  return Layer.mergeAll(core, git, ChangeDetector.layer.pipe(Layer.provide(git), Layer.provide(core)), WorkspaceSnapshots.layer(options).pipe(Layer.provide(git), Layer.provide(core)));
26
39
  };
40
+ const layerWithGit = (options) => withGit(layer(options), options);
27
41
  const resolvers = Layer.mergeAll(WorkspaceCatalogs.catalogResolver, WorkspaceDiscovery.workspaceResolver);
28
42
  const layerWithConfigDependencies = (options) => compose(options, WorkspaceCatalogs.layerWithConfigDependencies);
29
43
  const layerWithConfigDependenciesSubprocess = (options) => compose(options, WorkspaceCatalogs.layerWithConfigDependenciesSubprocess);
44
+ const layerWithGitAndConfigDependencies = (options) => withGit(layerWithConfigDependencies(options), options);
45
+ const layerWithGitAndConfigDependenciesSubprocess = (options) => withGit(layerWithConfigDependenciesSubprocess(options), options);
30
46
  const resolverLayer = (options) => resolvers.pipe(Layer.provide(layerWithConfigDependencies(options)));
31
47
  const resolveManifest = Effect.fn("Workspaces.resolveManifest")(function* (manifest, options) {
32
48
  return yield* manifest.resolve().pipe(Effect.provide(resolverLayer(options)));
@@ -148,6 +164,68 @@ var Workspaces = class {
148
164
  */
149
165
  static layerWithGit = layerWithGit;
150
166
  /**
167
+ * {@link Workspaces.layerWithGit} over
168
+ * {@link Workspaces.layerWithConfigDependencies} — snapshots, change
169
+ * detection and git, with catalog assembly that **replays config dependency
170
+ * `pnpmfile.cjs` hooks**.
171
+ *
172
+ * @remarks
173
+ * The composite that did not exist. `layerWithGit` hard-wires the no-op
174
+ * catalogs layer, so a consumer wanting snapshots + git + hook replay had to
175
+ * rebuild the entire service graph by hand — which is exactly what
176
+ * `@savvy-web/silk-effects` did, and the copy this static deletes.
177
+ *
178
+ * Requirements are unchanged from {@link Workspaces.layerWithGit}: a
179
+ * filesystem, a path service, and core's `ChildProcessSpawner` (behind
180
+ * `Git`). The in-process replay adds no requirement of its own — it is a
181
+ * dynamic `import()`, not a subprocess. **If the consumer is bundled, reach
182
+ * for {@link Workspaces.layerWithGitAndConfigDependenciesSubprocess}
183
+ * instead**; the computed import does not survive a bundler, and the
184
+ * `ChildProcessSpawner` the subprocess variant needs is already required
185
+ * here for git, so on this composite the subprocess form costs nothing extra.
186
+ *
187
+ * Opt in deliberately: this executes config-dependency code in process, and
188
+ * {@link Workspaces.layerWithGit} never does.
189
+ *
190
+ * **Bind the result to a `const`.** A parameterized factory mints a fresh
191
+ * reference per call and layers memoize by reference.
192
+ *
193
+ * @example
194
+ * ```ts
195
+ * import { Workspaces } from "@effected/workspaces";
196
+ *
197
+ * const KitLayer = Workspaces.layerWithGitAndConfigDependencies();
198
+ * ```
199
+ */
200
+ static layerWithGitAndConfigDependencies = layerWithGitAndConfigDependencies;
201
+ /**
202
+ * {@link Workspaces.layerWithGitAndConfigDependencies}, with the hook replay
203
+ * in a `node` **child process** rather than in process.
204
+ *
205
+ * @remarks
206
+ * Same typed semantics; the difference is mechanism, and it decides whether
207
+ * catalog assembly works at all in a **bundled** consumer — see
208
+ * {@link Workspaces.layerWithConfigDependenciesSubprocess} for why the
209
+ * in-process computed `import()` dies under a bundler.
210
+ *
211
+ * **This composite's requirement set is identical to the in-process one.**
212
+ * The subprocess replay needs core's `ChildProcessSpawner`, which this
213
+ * composite already requires for `Git` — so unlike the git-free pair, there
214
+ * is no R-widening to weigh here and a bundled consumer pays nothing to be
215
+ * correct. Prefer this variant whenever the program ships as a bundle.
216
+ *
217
+ * **Bind the result to a `const`.**
218
+ *
219
+ * @example
220
+ * ```ts
221
+ * import { Workspaces } from "@effected/workspaces";
222
+ *
223
+ * // A bundled CLI or MCP server: the computed import never enters the graph.
224
+ * const KitLayer = Workspaces.layerWithGitAndConfigDependenciesSubprocess();
225
+ * ```
226
+ */
227
+ static layerWithGitAndConfigDependenciesSubprocess = layerWithGitAndConfigDependenciesSubprocess;
228
+ /**
151
229
  * This package's implementation of `@effected/commands`' `LocalExec`
152
230
  * contract: how to run a project-local binary here.
153
231
  *
package/index.d.ts CHANGED
@@ -537,8 +537,65 @@ interface WorkspaceDiscoveryShape {
537
537
  readonly resolveFile: (filePath: string) => Effect.Effect<Option.Option<WorkspacePackage>, WorkspaceDiscoveryFailure>;
538
538
  /** The distinct packages owning any of `filePaths`. */
539
539
  readonly resolveFiles: (filePaths: ReadonlyArray<string>) => Effect.Effect<ReadonlyArray<WorkspacePackage>, WorkspaceDiscoveryFailure>;
540
- /** Drop the memoized discovery so the next call re-reads the filesystem. */
540
+ /**
541
+ * Facts about the workspace containing `directory`, discovered against THAT
542
+ * root rather than the layer-bound one.
543
+ */
544
+ readonly infoIn: (directory: string) => Effect.Effect<WorkspaceInfo, WorkspaceDiscoveryFailure>;
545
+ /**
546
+ * Every package of the workspace containing `directory`, discovered against
547
+ * THAT root rather than the layer-bound one.
548
+ *
549
+ * @remarks
550
+ * For a **long-lived host serving many roots** — an MCP server or a language
551
+ * server that resolves one workspace at startup and then answers calls
552
+ * scoped to a git worktree, a nested repository, or another project
553
+ * entirely. The layer-bound {@link WorkspaceDiscoveryShape.listPackages}
554
+ * answers about the root discovered from `options.cwd`, which such a host
555
+ * has no way to vary per call without building a fresh layer.
556
+ *
557
+ * **This re-reads; it does not re-root.** The tempting cheap fix — take the
558
+ * layer's package list and rewrite each `path` onto the caller's directory —
559
+ * produces correct-looking paths over the ORIGINAL root's manifests, so a
560
+ * worktree whose branch adds, removes or renames a package reports the other
561
+ * branch's membership with no error. Patterns, member manifests, names and
562
+ * versions all come from beneath `directory`'s own root here.
563
+ *
564
+ * `directory` may be the workspace root or anything inside it: the root is
565
+ * resolved by the same upward walk the layer-bound path uses, and results are
566
+ * memoized per RESOLVED root, so many directories in one workspace share one
567
+ * discovery. The memo holds one entry per distinct root for the layer's
568
+ * lifetime; {@link WorkspaceDiscoveryShape.refresh} drops all of them.
569
+ *
570
+ * @param directory - Absolute path to the workspace root, or to any
571
+ * directory inside it.
572
+ */
573
+ readonly listPackagesIn: (directory: string) => Effect.Effect<ReadonlyArray<WorkspacePackage>, WorkspaceDiscoveryFailure>;
574
+ /**
575
+ * Drop every memoized discovery — the layer-bound one and each per-root memo
576
+ * — so the next call re-reads the filesystem.
577
+ */
541
578
  readonly refresh: () => Effect.Effect<void>;
579
+ /**
580
+ * Drop only the memo for the workspace containing `directory`, leaving the
581
+ * layer-bound memo and every other root's untouched.
582
+ *
583
+ * @remarks
584
+ * The precise counterpart to {@link WorkspaceDiscoveryShape.refresh} for a
585
+ * host serving several roots: refreshing one worktree because it changed
586
+ * should not discard sibling worktrees that did not, which is all `refresh`
587
+ * can do.
588
+ *
589
+ * **Fails typed on a directory in no workspace**, exactly as
590
+ * {@link WorkspaceDiscoveryShape.listPackagesIn} does for the same input —
591
+ * the three per-root methods answer a bad path the same way, and a caller
592
+ * that would rather treat it as a no-op writes `Effect.ignore`. Refreshing a
593
+ * root that HAS no memo is an ordinary no-op and not an error.
594
+ *
595
+ * @param directory - Absolute path to the workspace root, or to any
596
+ * directory inside it.
597
+ */
598
+ readonly refreshIn: (directory: string) => Effect.Effect<void, WorkspaceRootNotFoundError>;
542
599
  }
543
600
  declare const WorkspaceDiscovery_base: Context.ServiceClass<WorkspaceDiscovery, "@effected/workspaces/WorkspaceDiscovery", WorkspaceDiscoveryShape>;
544
601
  /**
@@ -2687,6 +2744,29 @@ declare const WorkspaceStateSnapshot_base: Schema.Class<WorkspaceStateSnapshot,
2687
2744
  * importer versions; bun and npm yield an empty index.
2688
2745
  */
2689
2746
  readonly importerVersions: Schema.optionalKey<Schema.$Record<Schema.String, Schema.$Record<Schema.String, Schema.String>>>;
2747
+ /**
2748
+ * Catalogs supplied from OUTSIDE this moment, consulted only when
2749
+ * `catalogs` cannot answer.
2750
+ *
2751
+ * @remarks
2752
+ * **This is deliberately not merged into `catalogs`.** That field means "the
2753
+ * catalog set assembled at this moment" and a snapshot is a serializable
2754
+ * value someone stores and diffs; blending an external set into it would
2755
+ * quietly make the field mean something else, and nothing downstream could
2756
+ * tell the two apart afterwards. Kept separate, the ref's own declaration
2757
+ * always wins and both halves stay readable.
2758
+ *
2759
+ * The motivating case is a catalog injected by a config-dependency
2760
+ * `pnpmfile` hook. It is recorded in no committed catalog source, so
2761
+ * `WorkspaceSnapshots.at(ref)` — which never replays hooks, by design — cannot
2762
+ * see it, and a `catalog:` specifier against it resolves to nothing on BOTH
2763
+ * sides of a diff. Seeding the live hook-injected set, or the other side's
2764
+ * set, restores a declared RANGE without executing any historical code.
2765
+ *
2766
+ * Defaults to absent, which makes the seed inert — exactly the behavior of
2767
+ * every snapshot captured before this field existed.
2768
+ */
2769
+ readonly seededCatalogs: Schema.optionalKey<typeof CatalogSet>;
2690
2770
  }>, {}>;
2691
2771
  /**
2692
2772
  * The state of a whole workspace at one moment — its packages and its assembled
@@ -2736,7 +2816,16 @@ declare class WorkspaceStateSnapshot extends WorkspaceStateSnapshot_base {
2736
2816
  * unparseable string — is `Option.none()`, because there is no indirection to
2737
2817
  * resolve. Total.
2738
2818
  *
2739
- * A `catalog:` specifier the catalog set cannot resolve falls back to this
2819
+ * A `catalog:` specifier resolves in three steps, and the order is the
2820
+ * contract: this moment's own `catalogs` first,
2821
+ * then `seededCatalogs` if one was supplied,
2822
+ * then the `importerVersions` fallback below. The first two answer with a
2823
+ * declared RANGE and the third with a concrete version, so a seeded snapshot
2824
+ * reports a range change where an unseeded one could only report a version —
2825
+ * which is the difference between a diff row and no row when both refs
2826
+ * recorded the same installed version.
2827
+ *
2828
+ * A `catalog:` specifier neither catalog set can resolve falls back to this
2740
2829
  * snapshot's `importerVersions` — but only to a version
2741
2830
  * **every** importer recording that dependency agrees on. A catalog injected
2742
2831
  * by a config-dependency pnpmfile hook appears in no committed catalog source,
@@ -2773,6 +2862,101 @@ declare class WorkspaceStateSnapshot extends WorkspaceStateSnapshot_base {
2773
2862
  * @param specifier - The raw specifier string.
2774
2863
  */
2775
2864
  resolveIn(importerPath: string, dependency: string, specifier: string): Option.Option<string>;
2865
+ /**
2866
+ * This snapshot with `seed` as its `seededCatalogs`
2867
+ * — catalogs consulted only where this moment's own catalogs cannot answer.
2868
+ *
2869
+ * @remarks
2870
+ * Returns a NEW snapshot; the receiver is untouched, and the seed REPLACES
2871
+ * any seed already present rather than merging with it (a snapshot is a
2872
+ * value, and an accumulating seed would make precedence depend on call
2873
+ * order). {@link WorkspaceStateSnapshot.crossSeed} is the deliberate
2874
+ * exception: it composes the two seeds explicitly, precisely because a bare
2875
+ * replace would discard a layer-level seed. `catalogs`, `packages` and `importerVersions` are carried through
2876
+ * unchanged, so what the ref declared is still exactly what it declared.
2877
+ *
2878
+ * The two seeds worth reaching for: the LIVE hook-injected catalog set (from
2879
+ * a `WorkspaceCatalogs` built by one of the config-dependency layers), or the
2880
+ * other side of a two-ref diff — see
2881
+ * {@link WorkspaceStateSnapshot.crossSeed}.
2882
+ *
2883
+ * @param seed - The catalogs to consult as a fallback.
2884
+ *
2885
+ * @example
2886
+ * ```ts
2887
+ * import { WorkspaceCatalogs, WorkspaceSnapshots } from "@effected/workspaces";
2888
+ * import { Effect } from "effect";
2889
+ *
2890
+ * const program = Effect.gen(function* () {
2891
+ * const snapshots = yield* WorkspaceSnapshots;
2892
+ * const catalogs = yield* WorkspaceCatalogs;
2893
+ * // The live set includes hook-injected catalogs under a config-dependency
2894
+ * // layer; the ref's own set never can.
2895
+ * const live = yield* catalogs.set();
2896
+ * const before = (yield* snapshots.at("origin/main")).withSeededCatalogs(live);
2897
+ * return before.resolve("effect", "catalog:");
2898
+ * });
2899
+ * ```
2900
+ */
2901
+ withSeededCatalogs(seed: CatalogSet): WorkspaceStateSnapshot;
2902
+ /**
2903
+ * Both sides of a diff, each seeded with the other's catalogs.
2904
+ *
2905
+ * @remarks
2906
+ * The two-ref symmetry the hook-catalog gap actually needs. A catalog
2907
+ * injected by a config-dependency hook is declared in no committed source,
2908
+ * so neither ref's snapshot can see it and a `catalog:` specifier against it
2909
+ * resolves to nothing on both sides — a real version movement then produces
2910
+ * no row. Cross-seeding restores a declared RANGE on whichever side is
2911
+ * missing it, at strictly lower precedence than that side's own catalogs, so
2912
+ * a genuine change between the refs still reads as a change.
2913
+ *
2914
+ * **The limitation is inherent and is not a defect to work around.** A range
2915
+ * change made purely by bumping the config dependency BETWEEN the two refs is
2916
+ * suppressed: neither committed source declares the catalog, so each side
2917
+ * falls back to the other's value and the two agree by construction. Seeding
2918
+ * the live hook-injected set via
2919
+ * {@link WorkspaceStateSnapshot.withSeededCatalogs} has the same blind spot
2920
+ * against history — recovering it would mean replaying each ref's pinned
2921
+ * config-dependency code, which `at(ref)` will not do. If that case must be
2922
+ * detected, diff `configDependencies` in `pnpm-workspace.yaml` directly; it
2923
+ * is the only committed evidence that the injection changed.
2924
+ *
2925
+ * **The seeding relationship is symmetric; the RETURN ORDER is not.** Each
2926
+ * snapshot is seeded with the other's catalogs, so neither argument is
2927
+ * privileged and swapping them produces the same two values — but they come
2928
+ * back mirroring the order they went in, so destructure in the order you
2929
+ * passed. The `before`/`after` names describe the intended calling
2930
+ * convention for a two-ref diff, not a constraint on what may be passed.
2931
+ *
2932
+ * **A seed already present on either snapshot is preserved**, beneath the
2933
+ * other side's catalogs — which matters because
2934
+ * `WorkspaceSnapshotsOptions.seedCatalogs` puts one there on every snapshot
2935
+ * the service returns. Composing the two surfaces is therefore safe: the
2936
+ * layer-level seed keeps answering what neither ref declared, while the
2937
+ * other ref's committed declaration wins where it has one.
2938
+ *
2939
+ * @param before - One snapshot, conventionally the earlier one.
2940
+ * @param after - The other snapshot, conventionally the later one.
2941
+ * @returns Both snapshots in the order given, each carrying the other's
2942
+ * catalogs as its seed.
2943
+ *
2944
+ * @example
2945
+ * ```ts
2946
+ * import { WorkspaceSnapshots, WorkspaceStateSnapshot } from "@effected/workspaces";
2947
+ * import { Effect } from "effect";
2948
+ *
2949
+ * const program = Effect.gen(function* () {
2950
+ * const snapshots = yield* WorkspaceSnapshots;
2951
+ * const [before, after] = WorkspaceStateSnapshot.crossSeed(
2952
+ * yield* snapshots.at("origin/main"),
2953
+ * yield* snapshots.worktree(),
2954
+ * );
2955
+ * return { before: before.resolve("effect", "catalog:"), after: after.resolve("effect", "catalog:") };
2956
+ * });
2957
+ * ```
2958
+ */
2959
+ static crossSeed(before: WorkspaceStateSnapshot, after: WorkspaceStateSnapshot): readonly [WorkspaceStateSnapshot, WorkspaceStateSnapshot];
2776
2960
  /**
2777
2961
  * A `CatalogResolver` layer implementing `@effected/npm`'s contract against
2778
2962
  * THIS snapshot's catalog set — so code written to the contract resolves
@@ -2838,6 +3022,32 @@ interface WorkspaceSnapshotsOptions {
2838
3022
  * first call is honoured.
2839
3023
  */
2840
3024
  readonly cwd?: string;
3025
+ /**
3026
+ * Catalogs every snapshot this service produces carries as its
3027
+ * `seededCatalogs` — consulted only where the snapshot's own catalogs cannot
3028
+ * answer.
3029
+ *
3030
+ * @remarks
3031
+ * The layer-level spelling of `WorkspaceStateSnapshot.withSeededCatalogs`,
3032
+ * for the common case where the seed is the same for every read: a consumer
3033
+ * diffing many refs against one live workspace seeds once here instead of
3034
+ * remembering to call `withSeededCatalogs` at each site — and a forgotten
3035
+ * call is a silently missing diff row, not a type error.
3036
+ *
3037
+ * **This executes nothing.** The caller supplies the set; `at(ref)` still
3038
+ * never replays config-dependency hooks and still never fetches. The
3039
+ * deliberate at/worktree asymmetry is unchanged — this only lets a consumer
3040
+ * who has already paid for the live set share it with the ref side.
3041
+ *
3042
+ * Applied to `worktree()` too, for symmetry. There it is usually inert: a
3043
+ * live set assembled under a config-dependency layer already contains the
3044
+ * hook-injected catalogs at full precedence, so the seed answers nothing the
3045
+ * snapshot could not answer itself.
3046
+ *
3047
+ * @defaultValue absent — no seed, and resolution behaves exactly as before
3048
+ * this option existed.
3049
+ */
3050
+ readonly seedCatalogs?: CatalogSet;
2841
3051
  }
2842
3052
  /**
2843
3053
  * The {@link WorkspaceSnapshots} service shape.
@@ -2969,6 +3179,20 @@ interface WorkspacesOptions {
2969
3179
  /** Descent cap for segment-crossing `packages:` patterns. Defaults to 32. */
2970
3180
  readonly maxDepth?: number;
2971
3181
  }
3182
+ /**
3183
+ * Options for the composites that additionally provide {@link WorkspaceSnapshots}
3184
+ * — {@link Workspaces.layerWithGit} and the two config-dependency variants.
3185
+ *
3186
+ * @remarks
3187
+ * {@link WorkspacesOptions} plus whatever {@link WorkspaceSnapshotsOptions}
3188
+ * adds, so `seedCatalogs` reaches `at(ref)` through the composite instead of
3189
+ * forcing a consumer to hand-compose the graph just to pass one option. The
3190
+ * two interfaces agree on `cwd`, which stays one explicit concern applied
3191
+ * uniformly.
3192
+ *
3193
+ * @public
3194
+ */
3195
+ interface WorkspacesGitOptions extends WorkspacesOptions, WorkspaceSnapshotsOptions {}
2972
3196
  /**
2973
3197
  * Every service the git-free composite layer provides.
2974
3198
  *
@@ -3068,7 +3292,69 @@ declare class Workspaces {
3068
3292
  * provides `Git.layerTest({ … })` — git's own shipped double, whose
3069
3293
  * unstubbed members die named — and needs no repository on disk.
3070
3294
  */
3071
- static readonly layerWithGit: (options?: WorkspacesOptions) => Layer.Layer<WorkspacesServices | ChangeDetector | WorkspaceSnapshots | Git, never, FileSystem.FileSystem | Path.Path | ChildProcessSpawner.ChildProcessSpawner>;
3295
+ static readonly layerWithGit: (options?: WorkspacesGitOptions) => Layer.Layer<WorkspacesServices | ChangeDetector | WorkspaceSnapshots | Git, never, FileSystem.FileSystem | Path.Path | ChildProcessSpawner.ChildProcessSpawner>;
3296
+ /**
3297
+ * {@link Workspaces.layerWithGit} over
3298
+ * {@link Workspaces.layerWithConfigDependencies} — snapshots, change
3299
+ * detection and git, with catalog assembly that **replays config dependency
3300
+ * `pnpmfile.cjs` hooks**.
3301
+ *
3302
+ * @remarks
3303
+ * The composite that did not exist. `layerWithGit` hard-wires the no-op
3304
+ * catalogs layer, so a consumer wanting snapshots + git + hook replay had to
3305
+ * rebuild the entire service graph by hand — which is exactly what
3306
+ * `@savvy-web/silk-effects` did, and the copy this static deletes.
3307
+ *
3308
+ * Requirements are unchanged from {@link Workspaces.layerWithGit}: a
3309
+ * filesystem, a path service, and core's `ChildProcessSpawner` (behind
3310
+ * `Git`). The in-process replay adds no requirement of its own — it is a
3311
+ * dynamic `import()`, not a subprocess. **If the consumer is bundled, reach
3312
+ * for {@link Workspaces.layerWithGitAndConfigDependenciesSubprocess}
3313
+ * instead**; the computed import does not survive a bundler, and the
3314
+ * `ChildProcessSpawner` the subprocess variant needs is already required
3315
+ * here for git, so on this composite the subprocess form costs nothing extra.
3316
+ *
3317
+ * Opt in deliberately: this executes config-dependency code in process, and
3318
+ * {@link Workspaces.layerWithGit} never does.
3319
+ *
3320
+ * **Bind the result to a `const`.** A parameterized factory mints a fresh
3321
+ * reference per call and layers memoize by reference.
3322
+ *
3323
+ * @example
3324
+ * ```ts
3325
+ * import { Workspaces } from "@effected/workspaces";
3326
+ *
3327
+ * const KitLayer = Workspaces.layerWithGitAndConfigDependencies();
3328
+ * ```
3329
+ */
3330
+ static readonly layerWithGitAndConfigDependencies: (options?: WorkspacesGitOptions) => Layer.Layer<WorkspacesServices | ChangeDetector | WorkspaceSnapshots | Git, never, FileSystem.FileSystem | Path.Path | ChildProcessSpawner.ChildProcessSpawner>;
3331
+ /**
3332
+ * {@link Workspaces.layerWithGitAndConfigDependencies}, with the hook replay
3333
+ * in a `node` **child process** rather than in process.
3334
+ *
3335
+ * @remarks
3336
+ * Same typed semantics; the difference is mechanism, and it decides whether
3337
+ * catalog assembly works at all in a **bundled** consumer — see
3338
+ * {@link Workspaces.layerWithConfigDependenciesSubprocess} for why the
3339
+ * in-process computed `import()` dies under a bundler.
3340
+ *
3341
+ * **This composite's requirement set is identical to the in-process one.**
3342
+ * The subprocess replay needs core's `ChildProcessSpawner`, which this
3343
+ * composite already requires for `Git` — so unlike the git-free pair, there
3344
+ * is no R-widening to weigh here and a bundled consumer pays nothing to be
3345
+ * correct. Prefer this variant whenever the program ships as a bundle.
3346
+ *
3347
+ * **Bind the result to a `const`.**
3348
+ *
3349
+ * @example
3350
+ * ```ts
3351
+ * import { Workspaces } from "@effected/workspaces";
3352
+ *
3353
+ * // A bundled CLI or MCP server: the computed import never enters the graph.
3354
+ * const KitLayer = Workspaces.layerWithGitAndConfigDependenciesSubprocess();
3355
+ * ```
3356
+ */
3357
+ static readonly layerWithGitAndConfigDependenciesSubprocess: (options?: WorkspacesGitOptions) => Layer.Layer<WorkspacesServices | ChangeDetector | WorkspaceSnapshots | Git, never, FileSystem.FileSystem | Path.Path | ChildProcessSpawner.ChildProcessSpawner>;
3072
3358
  /**
3073
3359
  * This package's implementation of `@effected/commands`' `LocalExec`
3074
3360
  * contract: how to run a project-local binary here.
@@ -3454,5 +3740,5 @@ interface GetWorkspacePackagesSyncOptions extends WorkspacesSyncOptions {
3454
3740
  */
3455
3741
  declare const getWorkspacePackagesSync: (root: string, options: GetWorkspacePackagesSyncOptions) => ReadonlyArray<WorkspacePackage>;
3456
3742
  //#endregion
3457
- export { type CatalogAssemblyFailure, CatalogSet, ChangeDetectionError, type ChangeDetectionFailure, ChangeDetectionOptions, ChangeDetector, type ChangeDetectorShape, type ClassifyOptions, ConfigDependencyHooks, type ConfigDependencyHooksShape, CyclicDependencyError, type DependencyDiff, DependencyGraph, DetectedPackageManager, type FindWorkspaceRootOptions, type GetWorkspacePackagesSyncOptions, type HookInjection, type ImporterVersions, LockfileReadError, type LockfileReadFailure, LockfileReader, type LockfileReaderOptions, type LockfileReaderShape, NoPeerDependencyRules, PackageManagerDetectionError, type PackageManagerDetectionFailure, PackageManagerDetector, type PackageManagerDetectorShape, PackageManagerEvidence, PackageManagerName, PackageNotFoundError, type PackageRelease, PackageStateSnapshot, PeerCheck, type PeerCheckOptions, type PeerDependencyRules, PeerParent, PublishConfig, PublishTarget, PublishabilityDetector, type PublishabilityDetectorShape, ReleaseTag, type SyncDirectoryEntry, type SyncFileSystem, type SyncPath, type TagClassification, type TagFormatOptions, TagStyle, TrackingTag, type TrackingTagOptions, UnsatisfiedPeer, type UnverifiedReason, type VersioningDetectOptions, VersioningStrategy, VersioningStrategyType, WORKSPACE_MARKERS, WorkspaceCatalogs, type WorkspaceCatalogsOptions, type WorkspaceCatalogsShape, WorkspaceDiscovery, WorkspaceDiscoveryError, type WorkspaceDiscoveryFailure, type WorkspaceDiscoveryOptions, type WorkspaceDiscoveryShape, WorkspaceInfo, type WorkspaceLookupFailure, WorkspaceManifestError, WorkspacePackage, WorkspacePatternError, WorkspaceRoot, WorkspaceRootNotFoundError, type WorkspaceRootShape, type WorkspaceSnapshotAtFailure, type WorkspaceSnapshotWorktreeFailure, WorkspaceSnapshots, type WorkspaceSnapshotsOptions, type WorkspaceSnapshotsShape, WorkspaceStateSnapshot, Workspaces, type WorkspacesOptions, type WorkspacesServices, type WorkspacesSyncOptions, classifyTag, findWorkspaceRootSync, getWorkspacePackagesSync };
3743
+ export { type CatalogAssemblyFailure, CatalogSet, ChangeDetectionError, type ChangeDetectionFailure, ChangeDetectionOptions, ChangeDetector, type ChangeDetectorShape, type ClassifyOptions, ConfigDependencyHooks, type ConfigDependencyHooksShape, CyclicDependencyError, type DependencyDiff, DependencyGraph, DetectedPackageManager, type FindWorkspaceRootOptions, type GetWorkspacePackagesSyncOptions, type HookInjection, type ImporterVersions, LockfileReadError, type LockfileReadFailure, LockfileReader, type LockfileReaderOptions, type LockfileReaderShape, NoPeerDependencyRules, PackageManagerDetectionError, type PackageManagerDetectionFailure, PackageManagerDetector, type PackageManagerDetectorShape, PackageManagerEvidence, PackageManagerName, PackageNotFoundError, type PackageRelease, PackageStateSnapshot, PeerCheck, type PeerCheckOptions, type PeerDependencyRules, PeerParent, PublishConfig, PublishTarget, PublishabilityDetector, type PublishabilityDetectorShape, ReleaseTag, type SyncDirectoryEntry, type SyncFileSystem, type SyncPath, type TagClassification, type TagFormatOptions, TagStyle, TrackingTag, type TrackingTagOptions, UnsatisfiedPeer, type UnverifiedReason, type VersioningDetectOptions, VersioningStrategy, VersioningStrategyType, WORKSPACE_MARKERS, WorkspaceCatalogs, type WorkspaceCatalogsOptions, type WorkspaceCatalogsShape, WorkspaceDiscovery, WorkspaceDiscoveryError, type WorkspaceDiscoveryFailure, type WorkspaceDiscoveryOptions, type WorkspaceDiscoveryShape, WorkspaceInfo, type WorkspaceLookupFailure, WorkspaceManifestError, WorkspacePackage, WorkspacePatternError, WorkspaceRoot, WorkspaceRootNotFoundError, type WorkspaceRootShape, type WorkspaceSnapshotAtFailure, type WorkspaceSnapshotWorktreeFailure, WorkspaceSnapshots, type WorkspaceSnapshotsOptions, type WorkspaceSnapshotsShape, WorkspaceStateSnapshot, Workspaces, type WorkspacesGitOptions, type WorkspacesOptions, type WorkspacesServices, type WorkspacesSyncOptions, classifyTag, findWorkspaceRootSync, getWorkspacePackagesSync };
3458
3744
  //# sourceMappingURL=index.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@effected/workspaces",
3
- "version": "0.17.1",
3
+ "version": "0.18.0",
4
4
  "private": false,
5
5
  "description": "Monorepo workspace tooling as Effect services — root discovery, package enumeration, the dependency graph, package-manager detection, pnpm catalog resolution, lockfile IO and git-based change detection.",
6
6
  "keywords": [
@@ -49,12 +49,12 @@
49
49
  "@effected/commands": "^0.5.0",
50
50
  "@effected/git": "^0.9.0",
51
51
  "@effected/glob": "^0.4.0",
52
- "@effected/lockfiles": "^0.6.2",
52
+ "@effected/lockfiles": "^0.6.3",
53
53
  "@effected/npm": "^0.11.1",
54
54
  "@effected/package-json": "^0.10.2",
55
55
  "@effected/semver": "^0.5.0",
56
56
  "@effected/walker": "^0.5.0",
57
- "@effected/yaml": "^0.10.0",
57
+ "@effected/yaml": "^0.11.0",
58
58
  "@pnpm/catalogs.config": "^1100.0.5",
59
59
  "@pnpm/catalogs.protocol-parser": "^1100.0.0",
60
60
  "@pnpm/catalogs.resolver": "^1100.0.0",
@@ -5,7 +5,7 @@
5
5
  "toolPackages": [
6
6
  {
7
7
  "packageName": "@microsoft/api-extractor",
8
- "packageVersion": "7.58.13"
8
+ "packageVersion": "7.59.0"
9
9
  }
10
10
  ]
11
11
  }