@effected/workspaces 0.23.0 → 0.24.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.d.ts CHANGED
@@ -894,11 +894,13 @@ export declare class ChangeDetector extends ChangeDetector_base {
894
894
  * replayed hook returns `{ allowedVersions, ignoreMissing, allowAny }` intact,
895
895
  * needing no reshaping.
896
896
  *
897
- * Two keys are **carried but not yet consumed**. `ignoreMissing` and
898
- * `allowAny` are separate suppression axes that have not been measured, and an
899
- * unmeasured suppression is precisely what produced the false positives this
900
- * seam exists to remove — so they travel through the seam and no kit code acts
901
- * on them. Only `allowedVersions` is consumed today.
897
+ * All three axes are consumed by `PeerCheck`, each established by measurement
898
+ * against `pnpm peers check` with a firing control. `ignoreMissing` and
899
+ * `allowAny` are lists of **peer-name patterns** in pnpm's `@pnpm/matcher`
900
+ * grammar (`*` wildcard, leading `!` negation) — not `parent>peer` keys, which
901
+ * match nothing on those two axes. `ignoreMissing` hides only a required peer
902
+ * that resolved to nothing; `allowAny` hides only a peer that resolved outside
903
+ * its wanted range; the two never cross.
902
904
  *
903
905
  * `allowedVersions` keys come in two spellings in the wild, and both must be
904
906
  * handled: parent-versioned (`"@effect/ai-anthropic@4.0.0-rc.109>effect"`, as
@@ -910,9 +912,9 @@ export declare class ChangeDetector extends ChangeDetector_base {
910
912
  interface PeerDependencyRules {
911
913
  /** `parent>peer` → the peer version or range the rule permits. */
912
914
  readonly allowedVersions: Readonly<Record<string, string>>;
913
- /** Peer names whose absence pnpm does not report. Carried, not consumed. */
915
+ /** Peer-name patterns whose absence pnpm does not report (a required peer that resolved to nothing). */
914
916
  readonly ignoreMissing: ReadonlyArray<string>;
915
- /** Peer names for which any version is accepted. Carried, not consumed. */
917
+ /** Peer-name patterns for which any resolved version is accepted. */
916
918
  readonly allowAny: ReadonlyArray<string>;
917
919
  }
918
920
  /**
@@ -930,6 +932,34 @@ interface PeerDependencyRules {
930
932
  * @public
931
933
  */
932
934
  export declare const NoPeerDependencyRules: PeerDependencyRules;
935
+ /**
936
+ * Where a replayed config dependency's declared version was found: the
937
+ * `node_modules/.pnpm-config` copy, the pnpm store's `links/` tree, or a
938
+ * {@link ConfigDependencyHooks.layerFrom} entry.
939
+ *
940
+ * @public
941
+ */
942
+ type HookReplaySource = "installed" | "store" | "supplied";
943
+ /**
944
+ * Which version of a config dependency a replay actually loaded, and where
945
+ * that version came from.
946
+ *
947
+ * @remarks
948
+ * Recorded per dependency so a consumer diffing two refs can tell not just
949
+ * WHAT the hooks injected but which pinned version injected it — the
950
+ * evidence that a "range moved" row came from a config-dependency bump and
951
+ * not from a hook edit. `installed` is the `node_modules/.pnpm-config` copy,
952
+ * `store` the pnpm store's `links/` tree, `supplied` a
953
+ * {@link ConfigDependencyHooks.layerFrom} entry.
954
+ *
955
+ * @public
956
+ */
957
+ interface HookReplay {
958
+ /** The declared version that was resolved and replayed. */
959
+ readonly version: string;
960
+ /** Which resolution rung answered. */
961
+ readonly source: HookReplaySource;
962
+ }
933
963
  /**
934
964
  * The result of replaying a workspace's `configDependencies` hooks: the catalogs
935
965
  * the hooks yield, and the release-age gate contribution they leave on the
@@ -967,6 +997,13 @@ interface HookInjection {
967
997
  * overwrites for pnpm too — so do not "repair" it into a merger.
968
998
  */
969
999
  readonly peerDependencyRules: PeerDependencyRules;
1000
+ /**
1001
+ * Which version each declared config dependency was replayed from, keyed
1002
+ * by name — every dependency the layer resolved, including one that ships
1003
+ * no pnpmfile (resolved, contributed nothing). `{}` under
1004
+ * {@link ConfigDependencyHooks.layerNoop}, which resolves nothing.
1005
+ */
1006
+ readonly replays: Readonly<Record<string, HookReplay>>;
970
1007
  }
971
1008
  /**
972
1009
  * The {@link ConfigDependencyHooks} service shape.
@@ -1000,9 +1037,12 @@ interface ConfigDependencyHooksShape {
1000
1037
  * `hooks`-source `CatalogAssemblyError`, never a silent skip.
1001
1038
  *
1002
1039
  * @param root - The workspace root; config dependencies resolve under
1003
- * `<root>/node_modules/.pnpm-config/<name>`.
1040
+ * `<root>/node_modules/.pnpm-config/<name>` when that holds the declared
1041
+ * version, else through the pnpm store.
1004
1042
  * @param configDependencies - The `configDependencies` map (name →
1005
- * version+integrity) declared in `pnpm-workspace.yaml`.
1043
+ * `<version>+<integrity>`, or a bare `<version>`) declared in the
1044
+ * `pnpm-workspace.yaml` being read — the live one, or the one at a ref.
1045
+ * The replaying layers load the pnpmfile of the DECLARED version.
1006
1046
  * @param seed - The inline catalogs, as `catalog name → dependency → range`.
1007
1047
  * @param rules - The workspace file's `peerDependencyRules`, seeded into the
1008
1048
  * threaded config so hooks merge onto them rather than replacing them.
@@ -1018,12 +1058,21 @@ declare const ConfigDependencyHooks_base: Context.ServiceClass<ConfigDependencyH
1018
1058
  * assembly.
1019
1059
  *
1020
1060
  * @remarks
1021
- * A contract-only service: it declares the shape and ships two layers, never a
1061
+ * A contract-only service: it declares the shape and ships layers, never a
1022
1062
  * baked-in default. {@link ConfigDependencyHooks.layerNoop} executes no
1023
1063
  * config-dependency code (it returns the seed untouched) and is what the default
1024
1064
  * {@link WorkspaceCatalogs} layer wires; {@link ConfigDependencyHooks.layerLive}
1025
- * dynamically imports each `pnpmfile.cjs` and replays it, and is wired only by the
1026
- * explicit `WorkspaceCatalogs.layerWithConfigDependencies` opt-in.
1065
+ * dynamically imports each pnpmfile and replays it, and is wired only by the
1066
+ * explicit `WorkspaceCatalogs.layerWithConfigDependencies` opt-in;
1067
+ * {@link ConfigDependencyHooks.layerSubprocess} is its bundler-safe twin; and
1068
+ * {@link ConfigDependencyHooks.layerFrom} is the hermetic test seam that
1069
+ * replays caller-supplied files with no resolution.
1070
+ *
1071
+ * Every replaying layer replays the pnpmfile of the version each
1072
+ * `configDependencies` entry **declares** — `layerLive` and `layerSubprocess`
1073
+ * resolve it through `node_modules/.pnpm-config` and then the pnpm store,
1074
+ * failing closed when neither holds that version — so a snapshot read at a
1075
+ * past ref replays that ref's pinned hook, not today's.
1027
1076
  *
1028
1077
  * @public
1029
1078
  */
@@ -1035,22 +1084,76 @@ export declare class ConfigDependencyHooks extends ConfigDependencyHooks_base {
1035
1084
  */
1036
1085
  static readonly layerNoop: Layer.Layer<ConfigDependencyHooks>;
1037
1086
  /**
1038
- * The live layer: dynamically imports each config dependency's `pnpmfile.cjs`
1039
- * (in process, no subprocess) and replays its `updateConfig` hook over the
1040
- * seed, in declaration order. A dependency without a `pnpmfile.cjs` contributes
1041
- * nothing; a dependency whose file fails to load or replay fails typed with a
1042
- * `hooks`-source `CatalogAssemblyError`, never a silent skip.
1087
+ * The live layer: resolves each config dependency to the pnpmfile of its
1088
+ * **declared** version, dynamically imports it (in process, no subprocess)
1089
+ * and replays its `updateConfig` hook over the seed, in declaration order.
1090
+ * A dependency that ships no pnpmfile contributes nothing; a dependency
1091
+ * whose declared version is installed nowhere, or whose file fails to load
1092
+ * or replay, fails typed with a `hooks`-source `CatalogAssemblyError`, never
1093
+ * a silent skip.
1043
1094
  *
1044
1095
  * @remarks
1045
- * Runtime-coupled by design, not node-exclusive. The `import()` below loads
1096
+ * Resolution is a ladder, fail-closed, with no registry fetch ever:
1097
+ * `<root>/node_modules/.pnpm-config/<name>` when its `package.json`
1098
+ * `version` equals the declared version (the text before the first `+` of
1099
+ * the `configDependencies` value), else the pnpm store's
1100
+ * `links/<name>/<declared>/*\/node_modules/<name>` (the store is located
1101
+ * from `node_modules/.modules.yaml`, the realpath of any `.pnpm-config`
1102
+ * entry, then `$PNPM_HOME` / `$XDG_DATA_HOME` / the platform default), else
1103
+ * a typed error naming the package, the declared version, what is
1104
+ * installed, the stores searched, and the remediation
1105
+ * (`pnpm add --config <name>@<version>` in a throwaway workspace). In the
1106
+ * chosen directory the first of `pnpmfile.mjs`, `pnpmfile.cjs`,
1107
+ * `pnpmfile.js` present in one directory listing is loaded; a listed but
1108
+ * unreadable file fails typed at import time, never as "ships no hook".
1109
+ *
1110
+ * Runtime-coupled by design, not node-exclusive. The `import()` loads
1046
1111
  * **and executes** a config dependency's pnpmfile in-process — code execution,
1047
- * not IO, so no `FileSystem` / `Path` service abstracts it. The `node:path` and
1048
- * `node:url` imports (`join`, `pathToFileURL`) exist only to build the URL that
1049
- * `import()` consumes; node and bun both implement those builtins and dynamic
1050
- * import, so this layer runs on either runtime. Only ever wired by
1051
- * `WorkspaceCatalogs.layerWithConfigDependencies`.
1112
+ * not IO, so no `FileSystem` / `Path` service abstracts it — and the ladder
1113
+ * reads the real disk through `node:fs` for the same reason: the store it
1114
+ * searches is real even when the caller's `FileSystem` is virtual. Only ever
1115
+ * wired by `WorkspaceCatalogs.layerWithConfigDependencies` and the
1116
+ * `Workspaces` composites of the same name.
1052
1117
  */
1053
1118
  static readonly layerLive: Layer.Layer<ConfigDependencyHooks>;
1119
+ /**
1120
+ * The hermetic layer: replays caller-supplied pnpmfiles, keyed
1121
+ * `"<name>@<version>"` → absolute path, with **no resolution** — it consults
1122
+ * neither `node_modules/.pnpm-config` nor the pnpm store, and never the
1123
+ * effect `FileSystem` service, so it works unchanged under an
1124
+ * `@effected/memfs` volume.
1125
+ *
1126
+ * @remarks
1127
+ * The test seam for anything that replays hooks: a suite can pin what
1128
+ * `at(ref)` replays for a ref declaring `plugin@1.0.0` versus one declaring
1129
+ * `plugin@2.0.0` by mapping both keys to fixture files, with no store on
1130
+ * disk. The version half of the key is the declared version — the text
1131
+ * before the first `+` of the `configDependencies` value — so a
1132
+ * `<version>+<integrity>` spec and a bare `<version>` spec look up the same
1133
+ * entry.
1134
+ *
1135
+ * Same typed semantics as {@link ConfigDependencyHooks.layerLive} past the
1136
+ * lookup: the same in-process `import()`, the same hook-locator shapes, the
1137
+ * same tolerant threading, the same `hooks`-source attribution. A declared
1138
+ * `(name, version)` with no entry fails closed, the same shape as an
1139
+ * uninstalled version under `layerLive`; an empty `configDependencies`
1140
+ * returns the seed untouched. A `..` segment in a name is refused before
1141
+ * any lookup.
1142
+ *
1143
+ * @param entries - `"<name>@<version>"` → the absolute path of the pnpmfile
1144
+ * to replay for that declared version.
1145
+ *
1146
+ * @example
1147
+ * ```ts
1148
+ * import { ConfigDependencyHooks } from "@effected/workspaces";
1149
+ *
1150
+ * const hooks = ConfigDependencyHooks.layerFrom({
1151
+ * "@scope/plugin@1.0.0": "/fixtures/plugin-1/pnpmfile.mjs",
1152
+ * "@scope/plugin@2.0.0": "/fixtures/plugin-2/pnpmfile.mjs",
1153
+ * });
1154
+ * ```
1155
+ */
1156
+ static readonly layerFrom: (entries: Readonly<Record<string, string>>) => Layer.Layer<ConfigDependencyHooks>;
1054
1157
  /**
1055
1158
  * The subprocess layer: replays each config dependency's pnpmfile in a `node`
1056
1159
  * child process instead of an in-process dynamic `import()`, with identical
@@ -1064,24 +1167,26 @@ export declare class ConfigDependencyHooks extends ConfigDependencyHooks_base {
1064
1167
  * consumer, a GitHub Action above all, the in-process replay is unreachable.
1065
1168
  * This layer keeps every computed load out of the bundle graph: the replay
1066
1169
  * program is a **static** string constant passed via argv
1067
- * (`node --input-type=module -e <script> <root> <seed> <...names>`), and the
1170
+ * (`node --input-type=module -e <script> <seed> <rules> <entries>`), and the
1068
1171
  * child process performs the computed imports where no bundler rewrote them.
1069
1172
  * A subprocess also keeps config-dependency code out of the consumer's own
1070
1173
  * process.
1071
1174
  *
1072
- * The contract's semantics are unchanged, not the downstream fail-open shape:
1073
- * an empty `configDependencies` returns the seed without spawning anything; a
1074
- * `..` path segment in a dependency name fails typed **before** any spawn; a
1075
- * missing pnpmfile (neither `.mjs` nor `.cjs`) is the one legitimate skip,
1076
- * discriminated inside the child by `err.url` equality exactly as
1077
- * `layerLive` discriminates in process; any other load failure — a syntax
1078
- * error, a throwing top level, an `ERR_MODULE_NOT_FOUND` for a module the
1079
- * pnpmfile itself imports — and a hook that throws when called are serialized
1080
- * back per-name and surface typed as a `hooks`-source `CatalogAssemblyError`
1081
- * naming that dependency. A hook's returned *data* stays tolerantly threaded
1082
- * (last well-formed write wins), never fatal. Spawn and transport failures —
1083
- * `node` absent, a non-zero exit without a result payload, unparseable
1084
- * output — fail typed too, never a defect and never a silent skip.
1175
+ * The declared-version resolution is the PARENT's: the same ladder as
1176
+ * `layerLive` (`.pnpm-config` when it holds the declared version, else the
1177
+ * pnpm store, else a typed fail-closed error) runs here, and the child
1178
+ * receives the resolved `[name, fileUrl]` pairs as one JSON argv
1179
+ * argument. An empty `configDependencies`, or one whose dependencies all
1180
+ * ship no pnpmfile, returns the seed without spawning anything; a `..` path
1181
+ * segment in a dependency name fails typed **before** any spawn; a
1182
+ * load failure in the child — a syntax error, a throwing top level, an
1183
+ * `ERR_MODULE_NOT_FOUND` for a module the pnpmfile itself imports — and a
1184
+ * hook that throws when called are serialized back per-name and surface
1185
+ * typed as a `hooks`-source `CatalogAssemblyError` naming that dependency.
1186
+ * A hook's returned *data* stays tolerantly threaded (last well-formed write
1187
+ * wins), never fatal. Spawn and transport failures — `node` absent, a
1188
+ * non-zero exit without a result payload, unparseable output — fail typed
1189
+ * too, never a defect and never a silent skip.
1085
1190
  *
1086
1191
  * Two bounds this layer imposes that `layerLive` cannot: the replay is
1087
1192
  * given thirty seconds (a pnpmfile that loops or awaits forever fails typed
@@ -1829,9 +1934,9 @@ export declare class LockfileReader extends LockfileReader_base {
1829
1934
  * so pnpm's post-hoc suppression could not be replicated and some reported
1830
1935
  * rows may be ones pnpm hides. **Omitting the option always produces this**,
1831
1936
  * because "nobody looked" and "I looked and there are none" are different
1832
- * facts. Supplied rules with a non-empty `ignoreMissing` or `allowAny` also
1833
- * produce it: only `allowedVersions` is applied, so an unimplemented axis
1834
- * degrades to fail-closed rather than to a wrong answer.
1937
+ * facts. Supplied rules never produce it: all three axes —
1938
+ * `allowedVersions`, `ignoreMissing` and `allowAny` — are applied with
1939
+ * pnpm's measured semantics.
1835
1940
  * - `"unresolvedEdge"` — some instance records an edge this model could not
1836
1941
  * name (`ResolvedPackage.unresolvedEdges`), so a peer that edge satisfies
1837
1942
  * cannot be verified either way.
@@ -1853,10 +1958,11 @@ type UnverifiedReason = "peerRulesNotApplied" | "unresolvedEdge";
1853
1958
  * different results, because a gate must be able to tell "clean" from
1854
1959
  * "unchecked".
1855
1960
  *
1856
- * **Only `allowedVersions` is applied.** Rules whose `ignoreMissing` or
1857
- * `allowAny` is non-empty describe a suppression policy this module does not
1858
- * implement, so they also yield `"peerRulesNotApplied"` — the unimplemented
1859
- * axes fail closed instead of being silently ignored.
1961
+ * **All three axes of the supplied rules are applied**, each with the
1962
+ * semantics measured against pnpm (see {@link PeerCheck.run}): `allowedVersions`
1963
+ * permits a resolved version by range, `ignoreMissing` hides a required peer
1964
+ * nothing resolved for, and `allowAny` hides a peer that resolved outside its
1965
+ * range. The two list axes are name patterns, never `parent>peer` keys.
1860
1966
  *
1861
1967
  * @public
1862
1968
  */
@@ -2736,10 +2842,8 @@ interface WorkspaceCatalogsShape {
2736
2842
  *
2737
2843
  * A consumer needs these to avoid **reporting** what pnpm suppresses: pnpm
2738
2844
  * computes the same peer violations and then hides the ones a rule allows,
2739
- * so a checker without them reports findings pnpm calls clean.
2740
- *
2741
- * `ignoreMissing` and `allowAny` are carried but unmeasured — no kit code
2742
- * acts on them today.
2845
+ * so a checker without them reports findings pnpm calls clean. `PeerCheck`
2846
+ * applies all three axes.
2743
2847
  */
2744
2848
  readonly peerDependencyRules: () => Effect.Effect<PeerDependencyRules, CatalogAssemblyFailure>;
2745
2849
  /**
@@ -2755,6 +2859,18 @@ interface WorkspaceCatalogsShape {
2755
2859
  * nothing rather than failing.
2756
2860
  */
2757
2861
  readonly importerVersions: () => Effect.Effect<ImporterVersions, CatalogAssemblyFailure>;
2862
+ /**
2863
+ * Which version each declared config dependency was replayed from, keyed
2864
+ * by name — off the same single memoized assemble pass as `set()`.
2865
+ *
2866
+ * @remarks
2867
+ * Every dependency the hooks layer resolved when a `pnpm-workspace.yaml`
2868
+ * was read — `{}` when it declares no config dependencies or under the
2869
+ * no-op layer, which resolves nothing. Config dependencies are a pnpm
2870
+ * feature, so the bun / `package.json` path yields `{}` too, exactly as
2871
+ * `importerVersions` does.
2872
+ */
2873
+ readonly hookReplays: () => Effect.Effect<Readonly<Record<string, HookReplay>>, CatalogAssemblyFailure>;
2758
2874
  /**
2759
2875
  * Discard the memoized assembly so the **next** read re-assembles — the same
2760
2876
  * single read and hook replay as the first, over the workspace as it stands
@@ -2818,6 +2934,26 @@ declare const WorkspaceCatalogs_base: Context.ServiceClass<WorkspaceCatalogs, "@
2818
2934
  export declare class WorkspaceCatalogs extends WorkspaceCatalogs_base {
2819
2935
  /** Builds the service. */
2820
2936
  static readonly make: (options?: WorkspaceCatalogsOptions) => Effect.Effect<WorkspaceCatalogsShape, never, WorkspaceRoot | LockfileReader | ConfigDependencyHooks | FileSystem.FileSystem | Path.Path>;
2937
+ /**
2938
+ * The service over an explicit {@link ConfigDependencyHooks} layer — the
2939
+ * one builder every `layer*` static and the `Workspaces` composites
2940
+ * delegate to, so the hooks layer is provided to {@link WorkspaceCatalogs.make}
2941
+ * in exactly one place.
2942
+ *
2943
+ * @remarks
2944
+ * Reach for it when the hooks layer is chosen elsewhere — a composite that
2945
+ * must hand ONE hooks reference to both this service and
2946
+ * `WorkspaceSnapshots` (layers memoize by reference, so two references
2947
+ * would replay twice and could run two policies), or a test wiring
2948
+ * {@link ConfigDependencyHooks.layerFrom}. The three `layer*` statics are
2949
+ * this with the hooks layer fixed. `R` is the hooks layer's own
2950
+ * requirement (the subprocess variant's `ChildProcessSpawner`), threaded
2951
+ * through. Parameterized, so bind it to a `const` and reuse it.
2952
+ *
2953
+ * @param hooks - The {@link ConfigDependencyHooks} layer to replay through.
2954
+ * @param options - The same options the other layer statics take.
2955
+ */
2956
+ static readonly layerWithHooks: <R = never>(hooks: Layer.Layer<ConfigDependencyHooks, never, R>, options?: WorkspaceCatalogsOptions) => Layer.Layer<WorkspaceCatalogs, never, WorkspaceRoot | LockfileReader | FileSystem.FileSystem | Path.Path | R>;
2821
2957
  /**
2822
2958
  * The live layer — the default, which **never executes config-dependency
2823
2959
  * code**: it wires {@link ConfigDependencyHooks.layerNoop}, whose hooks return
@@ -2875,9 +3011,10 @@ export declare class WorkspaceCatalogs extends WorkspaceCatalogs_base {
2875
3011
  * `resolveSpecifier` answers from that `CatalogSet`'s own
2876
3012
  * {@link CatalogSet.resolveSpecifier} — exactly what the live service runs
2877
3013
  * over its assembled set — so the two stay consistent by construction.
2878
- * `releaseAgeGate` and `importerVersions` are **not** derivable from a
2879
- * catalog set (the gate comes from release-age keys and hook contributions,
2880
- * the importer index from the lockfile's importer blocks — neither is in a
3014
+ * `releaseAgeGate`, `importerVersions` and `hookReplays` are **not**
3015
+ * derivable from a catalog set (the gate comes from release-age keys and
3016
+ * hook contributions, the importer index from the lockfile's importer
3017
+ * blocks, the replay record from the hooks layer — none is in a
2881
3018
  * `CatalogSet`) and always die unless stubbed.
2882
3019
  *
2883
3020
  * `refresh` defaults to `Effect.void` honestly: the double holds no memo,
@@ -3009,6 +3146,23 @@ declare const WorkspaceStateSnapshot_base: Schema.Class<WorkspaceStateSnapshot,
3009
3146
  * importer versions; bun and npm yield an empty index.
3010
3147
  */
3011
3148
  readonly importerVersions: Schema.optionalKey<Schema.$Record<Schema.String, Schema.$Record<Schema.String, Schema.String>>>;
3149
+ /**
3150
+ * Which version each declared config dependency's hook was replayed from at
3151
+ * this moment, keyed by name.
3152
+ *
3153
+ * @remarks
3154
+ * Versions only — WHERE this machine found each pnpmfile (the `source` of a
3155
+ * live `HookReplay`) is machine-local provenance, not part of "what the
3156
+ * workspace looked like then", so it stays on the live `HookInjection`
3157
+ * diagnostic and off this serializable value. Every fresh read sets the
3158
+ * field: `{}` under the no-op layer, which resolves nothing, when the file
3159
+ * declares no config dependencies, or on the bun / `package.json` path,
3160
+ * where config dependencies do not exist. Absent only when decoding a
3161
+ * snapshot serialized before the field existed, so stored values stay
3162
+ * readable. Carried through `withSeededCatalogs` and `crossSeed`
3163
+ * unchanged, like `importerVersions`.
3164
+ */
3165
+ readonly hookReplays: Schema.optionalKey<Schema.$Record<Schema.String, Schema.String>>;
3012
3166
  /**
3013
3167
  * Catalogs supplied from OUTSIDE this moment, consulted only when
3014
3168
  * `catalogs` cannot answer.
@@ -3022,11 +3176,14 @@ declare const WorkspaceStateSnapshot_base: Schema.Class<WorkspaceStateSnapshot,
3022
3176
  * always wins and both halves stay readable.
3023
3177
  *
3024
3178
  * The motivating case is a catalog injected by a config-dependency
3025
- * `pnpmfile` hook. It is recorded in no committed catalog source, so
3026
- * `WorkspaceSnapshots.at(ref)` — which never replays hooks, by design — cannot
3027
- * see it, and a `catalog:` specifier against it resolves to nothing on BOTH
3028
- * sides of a diff. Seeding the live hook-injected set, or the other side's
3029
- * set, restores a declared RANGE without executing any historical code.
3179
+ * `pnpmfile` hook. It is recorded in no committed catalog source, so under
3180
+ * a non-replaying `ConfigDependencyHooks` layer (`layerNoop`, the default)
3181
+ * `WorkspaceSnapshots.at(ref)` cannot see it, and a `catalog:` specifier
3182
+ * against it resolves to nothing on BOTH sides of a diff. Seeding the live
3183
+ * hook-injected set, or the other side's set, restores a declared RANGE
3184
+ * without executing any historical code. Under a replaying layer `at(ref)`
3185
+ * replays the ref's own pinned hook and the seed answers only what neither
3186
+ * source declared.
3030
3187
  *
3031
3188
  * Defaults to absent, which makes the seed inert — exactly the behavior of
3032
3189
  * every snapshot captured before this field existed.
@@ -3147,8 +3304,8 @@ export declare class WorkspaceStateSnapshot extends WorkspaceStateSnapshot_base
3147
3304
  * value, and an accumulating seed would make precedence depend on call
3148
3305
  * order). {@link WorkspaceStateSnapshot.crossSeed} is the deliberate
3149
3306
  * exception: it composes the two seeds explicitly, precisely because a bare
3150
- * replace would discard a layer-level seed. `catalogs`, `packages` and `importerVersions` are carried through
3151
- * unchanged, so what the ref declared is still exactly what it declared.
3307
+ * replace would discard a layer-level seed. `catalogs`, `packages`, `importerVersions` and `hookReplays` are
3308
+ * carried through unchanged, so what the ref declared is still exactly what it declared.
3152
3309
  *
3153
3310
  * The two seeds worth reaching for: the LIVE hook-injected catalog set (from
3154
3311
  * a `WorkspaceCatalogs` built by one of the config-dependency layers), or the
@@ -3186,16 +3343,19 @@ export declare class WorkspaceStateSnapshot extends WorkspaceStateSnapshot_base
3186
3343
  * missing it, at strictly lower precedence than that side's own catalogs, so
3187
3344
  * a genuine change between the refs still reads as a change.
3188
3345
  *
3189
- * **The limitation is inherent and is not a defect to work around.** A range
3190
- * change made purely by bumping the config dependency BETWEEN the two refs is
3191
- * suppressed: neither committed source declares the catalog, so each side
3192
- * falls back to the other's value and the two agree by construction. Seeding
3193
- * the live hook-injected set via
3194
- * {@link WorkspaceStateSnapshot.withSeededCatalogs} has the same blind spot
3195
- * against history — recovering it would mean replaying each ref's pinned
3196
- * config-dependency code, which `at(ref)` will not do. If that case must be
3197
- * detected, diff `configDependencies` in `pnpm-workspace.yaml` directly; it
3198
- * is the only committed evidence that the injection changed.
3346
+ * **What cross-seeding can and cannot see depends on the hooks layer.** A
3347
+ * range change made purely by bumping the config dependency BETWEEN the two
3348
+ * refs is detected when the snapshots were read under a replaying
3349
+ * `ConfigDependencyHooks` layer: `WorkspaceSnapshots.at(ref)` replays each
3350
+ * ref's hook at the version that ref's `configDependencies` declares, so
3351
+ * each side's OWN catalogs carry that side's injected range and the seed
3352
+ * never gets a say. Under `layerNoop` the case stays suppressed — neither
3353
+ * committed source declares the catalog, so each side falls back to the
3354
+ * other's value and the two agree by construction; there, diff
3355
+ * `configDependencies` in `pnpm-workspace.yaml` directly, the only committed
3356
+ * evidence that the injection changed. Cross-seeding is harmless under a
3357
+ * replaying layer (own catalogs take precedence) and still worthwhile under
3358
+ * the no-op one, which is why it stays.
3199
3359
  *
3200
3360
  * **The seeding relationship is symmetric; the RETURN ORDER is not.** Each
3201
3361
  * snapshot is seeded with the other's catalogs, so neither argument is
@@ -3258,15 +3418,15 @@ export declare class WorkspaceStateSnapshot extends WorkspaceStateSnapshot_base
3258
3418
  //#region src/WorkspaceSnapshots.d.ts
3259
3419
  /**
3260
3420
  * Every failure `WorkspaceSnapshots.at` can surface: git's own typed
3261
- * errors, a catalog-assembly failure from the inline config source at the ref,
3262
- * or an unfindable workspace root.
3421
+ * errors, a catalog-assembly failure from the inline config source or the
3422
+ * config-dependency hook replay at the ref, or an unfindable workspace root.
3263
3423
  *
3264
3424
  * @remarks
3265
3425
  * Narrow by design. `at` reads through git and never enumerates the live
3266
3426
  * filesystem, so no `WorkspaceDiscoveryError` / `WorkspacePatternError` appears —
3267
3427
  * and a malformed *lockfile* at the ref degrades to no catalogs (a lockfile is a
3268
- * record, not a source of truth), so only the inline source raises
3269
- * `CatalogAssemblyError`.
3428
+ * record, not a source of truth), so only the inline source and a
3429
+ * `hooks`-source replay failure raise `CatalogAssemblyError`.
3270
3430
  *
3271
3431
  * @public
3272
3432
  */
@@ -3309,10 +3469,13 @@ interface WorkspaceSnapshotsOptions {
3309
3469
  * remembering to call `withSeededCatalogs` at each site — and a forgotten
3310
3470
  * call is a silently missing diff row, not a type error.
3311
3471
  *
3312
- * **This executes nothing.** The caller supplies the set; `at(ref)` still
3313
- * never replays config-dependency hooks and still never fetches. The
3314
- * deliberate at/worktree asymmetry is unchanged — this only lets a consumer
3315
- * who has already paid for the live set share it with the ref side.
3472
+ * **This executes nothing by itself.** The caller supplies the set, and
3473
+ * `at(ref)` still never fetches. Whether `at(ref)` replays that ref's
3474
+ * config-dependency hooks is decided by the {@link ConfigDependencyHooks}
3475
+ * layer in scope, not by this option: under a replaying layer the ref's own
3476
+ * replay answers first and the seed fills only what neither source declared;
3477
+ * under `layerNoop` the seed is the only way a hook-injected catalog reaches
3478
+ * the ref side at all.
3316
3479
  *
3317
3480
  * Applied to `worktree()` too, for symmetry. There it is usually inert: a
3318
3481
  * live set assembled under a config-dependency layer already contains the
@@ -3330,7 +3493,18 @@ interface WorkspaceSnapshotsOptions {
3330
3493
  * @public
3331
3494
  */
3332
3495
  interface WorkspaceSnapshotsShape {
3333
- /** The workspace state at a git ref, read with no checkout. Cached per `(root, ref)`. */
3496
+ /**
3497
+ * The workspace state at a git ref, read with no checkout. Cached per
3498
+ * `(root, ref)`.
3499
+ *
3500
+ * @remarks
3501
+ * The ref's `pnpm-workspace.yaml` `configDependencies` are replayed through
3502
+ * the {@link ConfigDependencyHooks} layer in scope at the versions THAT ref
3503
+ * declares (a replaying layer resolves them via `.pnpm-config` or the pnpm
3504
+ * store and fails closed when a declared version is installed nowhere;
3505
+ * `layerNoop` executes nothing). The injected catalogs merge above the
3506
+ * ref's lockfile and inline sources, exactly as the live assembler does.
3507
+ */
3334
3508
  readonly at: (ref: string) => Effect.Effect<WorkspaceStateSnapshot, WorkspaceSnapshotAtFailure>;
3335
3509
  /** The live workspace state, over discovery and catalog assembly. Uncached. */
3336
3510
  readonly worktree: () => Effect.Effect<WorkspaceStateSnapshot, WorkspaceSnapshotWorktreeFailure>;
@@ -3362,8 +3536,17 @@ declare const WorkspaceSnapshots_base: Context.ServiceClass<WorkspaceSnapshots,
3362
3536
  * @public
3363
3537
  */
3364
3538
  export declare class WorkspaceSnapshots extends WorkspaceSnapshots_base {
3365
- /** Builds the service over `Git`, {@link WorkspaceRoot}, {@link WorkspaceDiscovery} and {@link WorkspaceCatalogs}. */
3366
- static readonly make: (options?: WorkspaceSnapshotsOptions) => Effect.Effect<WorkspaceSnapshotsShape, never, Git | WorkspaceRoot | WorkspaceDiscovery | WorkspaceCatalogs>;
3539
+ /**
3540
+ * Builds the service over `Git`, {@link WorkspaceRoot}, {@link WorkspaceDiscovery},
3541
+ * {@link WorkspaceCatalogs} and {@link ConfigDependencyHooks}.
3542
+ *
3543
+ * @remarks
3544
+ * `ConfigDependencyHooks` is what `at(ref)` replays a ref's
3545
+ * `configDependencies` through — at the versions THAT ref declares, which is
3546
+ * why the composites hand the same hooks layer to this service and to
3547
+ * `WorkspaceCatalogs`. Under `layerNoop` the ref read executes nothing.
3548
+ */
3549
+ static readonly make: (options?: WorkspaceSnapshotsOptions) => Effect.Effect<WorkspaceSnapshotsShape, never, Git | WorkspaceRoot | WorkspaceDiscovery | WorkspaceCatalogs | ConfigDependencyHooks>;
3367
3550
  /**
3368
3551
  * The live layer.
3369
3552
  *
@@ -3371,7 +3554,7 @@ export declare class WorkspaceSnapshots extends WorkspaceSnapshots_base {
3371
3554
  * Parameterized, so it mints a fresh reference per call — bind it to a
3372
3555
  * `const` and reuse it, or layer memoization does not apply.
3373
3556
  */
3374
- static readonly layer: (options?: WorkspaceSnapshotsOptions) => Layer.Layer<WorkspaceSnapshots, never, Git | WorkspaceRoot | WorkspaceDiscovery | WorkspaceCatalogs>;
3557
+ static readonly layer: (options?: WorkspaceSnapshotsOptions) => Layer.Layer<WorkspaceSnapshots, never, Git | WorkspaceRoot | WorkspaceDiscovery | WorkspaceCatalogs | ConfigDependencyHooks>;
3375
3558
  /**
3376
3559
  * A test double satisfying the full {@link WorkspaceSnapshotsShape} with no
3377
3560
  * git, filesystem, discovery, or catalog assembly.
@@ -3630,6 +3813,43 @@ export declare class Workspaces {
3630
3813
  * ```
3631
3814
  */
3632
3815
  static readonly layerWithGitAndConfigDependenciesSubprocess: (options?: WorkspacesGitOptions) => Layer.Layer<WorkspacesServices | ChangeDetector | WorkspaceSnapshots | Git, never, FileSystem.FileSystem | Path.Path | ChildProcessSpawner.ChildProcessSpawner>;
3816
+ /**
3817
+ * The git composite over a **caller-supplied** hooks layer — the same graph
3818
+ * as {@link Workspaces.layerWithGit} and its two config-dependency
3819
+ * variants, with the `ConfigDependencyHooks` policy chosen by the caller.
3820
+ *
3821
+ * @remarks
3822
+ * The three fixed git composites are this function applied to
3823
+ * {@link ConfigDependencyHooks.layerNoop}, {@link ConfigDependencyHooks.layerLive}
3824
+ * and {@link ConfigDependencyHooks.layerSubprocess}. It exists for the fourth
3825
+ * policy: {@link ConfigDependencyHooks.layerFrom}, the hermetic seam, which a
3826
+ * snapshot-backed test otherwise cannot reach without rebuilding the whole
3827
+ * git graph by hand — the copy `layerWithGitAndConfigDependencies` was added
3828
+ * to delete. The ONE hooks reference is handed to both `WorkspaceCatalogs`
3829
+ * and `WorkspaceSnapshots`, so `at(ref)` and `worktree()` cannot drift on
3830
+ * which policy they replay. A hooks layer carrying its own requirement (the
3831
+ * subprocess variant's `ChildProcessSpawner`) threads it through to the
3832
+ * composite's `R` unchanged.
3833
+ *
3834
+ * **Bind the result to a `const`.**
3835
+ *
3836
+ * @param hooks - The `ConfigDependencyHooks` layer to wire on both sides.
3837
+ * @param options - The composite options, as for {@link Workspaces.layerWithGit}.
3838
+ *
3839
+ * @example
3840
+ * ```ts
3841
+ * import { ConfigDependencyHooks, Workspaces } from "@effected/workspaces";
3842
+ *
3843
+ * // A test pinning what at(ref) replays for two declared plugin versions.
3844
+ * const KitLayer = Workspaces.layerWithGitAndHooks(
3845
+ * ConfigDependencyHooks.layerFrom({
3846
+ * "@scope/plugin@1.0.0": "/fixtures/plugin-1/pnpmfile.mjs",
3847
+ * "@scope/plugin@2.0.0": "/fixtures/plugin-2/pnpmfile.mjs",
3848
+ * }),
3849
+ * );
3850
+ * ```
3851
+ */
3852
+ static readonly layerWithGitAndHooks: <R = never>(hooks: Layer.Layer<ConfigDependencyHooks, never, R>, options?: WorkspacesGitOptions) => Layer.Layer<WorkspacesServices | ChangeDetector | WorkspaceSnapshots | Git, never, FileSystem.FileSystem | Path.Path | ChildProcessSpawner.ChildProcessSpawner | R>;
3633
3853
  /**
3634
3854
  * This package's implementation of `@effected/commands`' `LocalExec`
3635
3855
  * contract: how to run a project-local binary here.
@@ -4092,5 +4312,5 @@ interface GetWorkspacePackagesSyncOptions extends WorkspacesSyncOptions {
4092
4312
  */
4093
4313
  export declare const getWorkspacePackagesSync: (root: string, options: GetWorkspacePackagesSyncOptions) => ReadonlyArray<WorkspacePackage>;
4094
4314
  //#endregion
4095
- export type { CatalogAssemblyFailure, ChangeDetectionFailure, ChangeDetectorShape, ClassifyOptions, ConfigDependencyHooksShape, DependencyDiff, DuplicateCheckOptions, FindWorkspaceRootOptions, GetWorkspacePackagesSyncOptions, HookInjection, ImporterVersions, LockfileReadFailure, LockfileReaderOptions, LockfileReaderShape, PackageManagerDetectionFailure, PackageManagerDetectorShape, PackageRelease, PeerCheckOptions, PeerDependencyRules, PublishabilityDetectorShape, SyncDirectoryEntry, SyncFileSystem, SyncPath, TagClassification, TagFormatOptions, TrackingTagOptions, UnverifiedReason, VersioningDetectOptions, WorkspaceCatalogsOptions, WorkspaceCatalogsShape, WorkspaceDiscoveryFailure, WorkspaceDiscoveryOptions, WorkspaceDiscoveryShape, WorkspaceDiscoverySkip, WorkspaceDiscoverySkipKind, WorkspaceLookupFailure, WorkspaceRootShape, WorkspaceSnapshotAtFailure, WorkspaceSnapshotWorktreeFailure, WorkspaceSnapshotsOptions, WorkspaceSnapshotsShape, WorkspacesGitOptions, WorkspacesOptions, WorkspacesServices, WorkspacesSyncOptions };
4315
+ export type { CatalogAssemblyFailure, ChangeDetectionFailure, ChangeDetectorShape, ClassifyOptions, ConfigDependencyHooksShape, DependencyDiff, DuplicateCheckOptions, FindWorkspaceRootOptions, GetWorkspacePackagesSyncOptions, HookInjection, HookReplay, HookReplaySource, ImporterVersions, LockfileReadFailure, LockfileReaderOptions, LockfileReaderShape, PackageManagerDetectionFailure, PackageManagerDetectorShape, PackageRelease, PeerCheckOptions, PeerDependencyRules, PublishabilityDetectorShape, SyncDirectoryEntry, SyncFileSystem, SyncPath, TagClassification, TagFormatOptions, TrackingTagOptions, UnverifiedReason, VersioningDetectOptions, WorkspaceCatalogsOptions, WorkspaceCatalogsShape, WorkspaceDiscoveryFailure, WorkspaceDiscoveryOptions, WorkspaceDiscoveryShape, WorkspaceDiscoverySkip, WorkspaceDiscoverySkipKind, WorkspaceLookupFailure, WorkspaceRootShape, WorkspaceSnapshotAtFailure, WorkspaceSnapshotWorktreeFailure, WorkspaceSnapshotsOptions, WorkspaceSnapshotsShape, WorkspacesGitOptions, WorkspacesOptions, WorkspacesServices, WorkspacesSyncOptions };
4096
4316
  //# sourceMappingURL=index.d.ts.map