@lmzhen/dsh-memory 0.44.0 → 0.45.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.
@@ -0,0 +1,261 @@
1
+ /**
2
+ * Local layered memory provider for `ctx.memory`.
3
+ * @module @lmzhen/dsh-memory-files
4
+ */
5
+ import { createHash } from 'node:crypto';
6
+ import { resolve } from 'node:path';
7
+ import z from '@deepseek-ai/schemastery';
8
+ import { DEFAULT_CONSOLIDATION_FAILURES, DEFAULT_MEMORY_CHAR_LIMIT, DEFAULT_USER_CHAR_LIMIT, paramRowId, payloadText, userSetKeys, evolutionIoAdapter, makeSerialQueue, MemoryStore, clampOnce, liveBool, unwrapLive } from '@lmzhen/dsh-evolution-core';
9
+ export const name = 'memory-files';
10
+ export const inject = ['memory', 'evolutionIo'];
11
+ // G1: the four E3 keys of this row carry `.volatile()`, so the platform hands the
12
+ // plugin a stable reference it updates in place and the settings surface can edit
13
+ // them live. The schema keeps NO annotation: a volatile field's output is a
14
+ // reference, which the annotated `z<Config>` reading cannot express (TS2375) — the
15
+ // platform's own volatile rows are unannotated for the same reason.
16
+ export const Config = z.object({
17
+ providerName: z.string().default('files'),
18
+ // The canonical registry ids. No .default() on either one: a schema default would
19
+ // fill the canonical key and make the deprecated alias below unreachable through
20
+ // the store's alias fallback (DEFAULT_MEMORY_CHAR_LIMIT /
21
+ // DEFAULT_USER_CHAR_LIMIT are resolved there).
22
+ memoryChars: z.number().min(1).volatile(),
23
+ userChars: z.number().min(1).volatile(),
24
+ memoryCharLimit: z.number().min(1).default(DEFAULT_MEMORY_CHAR_LIMIT),
25
+ userCharLimit: z.number().min(1).default(DEFAULT_USER_CHAR_LIMIT),
26
+ addDatePrefix: z.boolean().default(false).volatile(),
27
+ root: z.string().default(''),
28
+ maxConsolidationFailures: z.number().min(1).default(DEFAULT_CONSOLIDATION_FAILURES).volatile(),
29
+ threatExemptLabels: z.array(z.string()).default([]),
30
+ });
31
+ export function apply(ctx, rawConfig = {}) {
32
+ // G1: suppress the platform's auto-generated settings page for this row (the
33
+ // family renders its own card from the registry). Probed, not assumed: a host
34
+ // without the capability still loads.
35
+ ctx.inject(['settings'], (injected) => {
36
+ const settings = injected.settings;
37
+ if (typeof settings?.configure !== 'function')
38
+ return;
39
+ // v46 S1.12b (finding 6-1): the owner argument is what the platform looks the presentation up by.
40
+ // Omitting it registers the suppression on the settings service's own fiber, so a row that
41
+ // re-mounts keeps a dead key and the auto form can return (platform precedent:
42
+ // settings.configure({ auto: false }, plugin.fiber) — settings/src/index.ts:266).
43
+ const disposer = settings.configure({ auto: false }, ctx.fiber);
44
+ if (typeof disposer === 'function')
45
+ ctx.effect(() => disposer, 'memory-files: settings presentation');
46
+ });
47
+ // G3.1 (0.3.23): clamp the numeric memory limit so a 0/negative/NaN/±Infinity
48
+ // value falls back to the package default. A 0 limit is never an "unbounded"
49
+ // meaning (MemoryStore keeps its own internal defense); the schema `.min(1)`
50
+ // rejects 0/negative at load, this clamp also covers NaN/±Infinity (which
51
+ // schemastery lets a bare number schema through) and direct construction.
52
+ // G1 §8.3: every read happens at USE time, so the correction warns once per KEY
53
+ // per mount instead of once at assembly.
54
+ const clampedKeys = new Set();
55
+ /**
56
+ * Clamp one supplied numeric value, warning once per key.
57
+ * @param name - the config key, for the warning.
58
+ * @param value - the value as supplied by the deployment or the user.
59
+ * @param fallback - the package default a corrected value falls back to.
60
+ * @returns the value the store may use.
61
+ */
62
+ const field = (name, value, fallback) => clampOnce(clampedKeys, (message) => { ctx.logger.warn(message); }, `memory-files: ${name}`, value, fallback, 1);
63
+ // P2-23 (v37): a fractional char limit reached the store unchanged, and the
64
+ // tool surface declares `limit` as an integer — every memory call then failed
65
+ // platform output validation AFTER the write had landed. The char limit is a
66
+ // counted quantity, so it is floored at the one point where it enters the store
67
+ // (floor, not round: a limit must never grow past what the operator configured).
68
+ const floorLimit = (value) => Math.floor(value);
69
+ // S2-12③ (FLOW5-4): the memory budget has TWO configuration surfaces — this
70
+ // package's `memoryCharLimit`/`userCharLimit` (what the STORE enforces) and
71
+ // `evolution-policy`'s `memoryChars`/`userChars` (what the review pipeline
72
+ // PLANS against). They used to default independently, so moving one left the
73
+ // other behind: the reviewer planned ops for a budget the store then refused.
74
+ // Explicit config still wins (the operator said so); an UNSET limit follows the
75
+ // policy when that service is already mounted, and otherwise keeps the default
76
+ // (row order is not something this plugin can force — doctor compares the two
77
+ // surfaces again at run time and flags a disagreement, including the
78
+ // order-induced one).
79
+ const policyBudget = () => {
80
+ const policy = ctx.get('evolutionPolicy');
81
+ const snapshot = policy?.get?.();
82
+ return { memory: snapshot?.memoryChars, user: snapshot?.userChars };
83
+ };
84
+ // The row fills SCHEMA DEFAULTS into the deprecated alias, so "the operator set
85
+ // the alias" is read as "differs from that default" — the same test the review
86
+ // row uses for shadowed fields. A row that pins the alias default is therefore
87
+ // indistinguishable from one that omits it, and follows the policy like the
88
+ // latter.
89
+ const aliasDefaults = Config['~standard'].validate({}).value;
90
+ const ALIAS_OF = { memoryChars: 'memoryCharLimit', userChars: 'userCharLimit' };
91
+ const DEFAULTS = { memoryChars: DEFAULT_MEMORY_CHAR_LIMIT, userChars: DEFAULT_USER_CHAR_LIMIT };
92
+ /**
93
+ * The live value of one canonical budget key (G1 §8.3).
94
+ * @param id - the canonical registry id.
95
+ * @returns the value the platform resolved (user layer included), or undefined.
96
+ */
97
+ const canonicalBudget = (id) => unwrapLive(id === 'memoryChars' ? rawConfig.memoryChars : rawConfig.userChars);
98
+ /**
99
+ * Whether the OPERATOR set this budget on THIS ROW (G0/S0.4 + G1).
100
+ *
101
+ * The canonical key answers through its live reference — a user-layer value
102
+ * arrives the same way, which is what keeps a user choice ahead of the policy —
103
+ * and the deprecated alias through the default comparison above. The pair is
104
+ * documented in the Config JSDoc and reported by /evolution doctor (budgetIssues).
105
+ * @param id - the canonical registry id.
106
+ * @returns whether the row or the user supplied a value.
107
+ */
108
+ const explicitBudget = (id) => {
109
+ if (canonicalBudget(id) !== undefined)
110
+ return true;
111
+ const alias = rawConfig[ALIAS_OF[id]];
112
+ return alias !== undefined && alias !== aliasDefaults[ALIAS_OF[id]];
113
+ };
114
+ /**
115
+ * The budget one key resolves to (G1 §8.4-A): the row's own value — canonical or
116
+ * alias — over the mounted policy, over the package default.
117
+ * @param id - the canonical registry id.
118
+ * @returns the resolved, unclamped budget.
119
+ */
120
+ const budget = (id) => {
121
+ const explicit = canonicalBudget(id) ?? (explicitBudget(id) ? rawConfig[ALIAS_OF[id]] : undefined);
122
+ if (explicit !== undefined)
123
+ return explicit;
124
+ const policy = policyBudget();
125
+ return (id === 'memoryChars' ? policy.memory : policy.user) ?? DEFAULTS[id];
126
+ };
127
+ // BOTH spellings carry the RESOLVED value downstream, so the store, the budget
128
+ // view and the doctor read one number whichever name the deployment wrote.
129
+ const memoryChars = () => floorLimit(field('memoryCharLimit', budget('memoryChars'), DEFAULT_MEMORY_CHAR_LIMIT));
130
+ const userChars = () => floorLimit(field('userCharLimit', budget('userChars'), DEFAULT_USER_CHAR_LIMIT));
131
+ const consolidationFailures = () => field('maxConsolidationFailures', rawConfig.maxConsolidationFailures, DEFAULT_CONSOLIDATION_FAILURES);
132
+ const addDatePrefix = () => liveBool(rawConfig.addDatePrefix);
133
+ // The one disagreement this plugin can see at load: an EXPLICIT row value that
134
+ // contradicts the mounted policy. Doctor re-checks the same pair at run time (it
135
+ // can see the policy mount that happened after this row).
136
+ const policyAtMount = policyBudget();
137
+ if (explicitBudget('memoryChars') && policyAtMount.memory !== undefined && memoryChars() !== policyAtMount.memory) {
138
+ ctx.logger.warn(`memory-files: the row's memoryChars=${memoryChars()} contradicts evolution-policy memoryChars=${policyAtMount.memory} — the store enforces the row value while the review plans against the policy value; align them or leave the row unset`);
139
+ }
140
+ if (explicitBudget('userChars') && policyAtMount.user !== undefined && userChars() !== policyAtMount.user) {
141
+ ctx.logger.warn(`memory-files: the row's userChars=${userChars()} contradicts evolution-policy userChars=${policyAtMount.user} — same divergence as memoryChars`);
142
+ }
143
+ // The IO provider is resolved lazily so `memory-files` does not depend on
144
+ // row order: the first write happens only after the preset has fully mounted.
145
+ const io = evolutionIoAdapter(() => ctx.evolutionIo.provider());
146
+ const authority = ctx.get('evolutionMutationAuthority');
147
+ // In-process write serialization: applyBatch is read-modify-write, so two
148
+ // concurrent callers (multi-session host) can otherwise compute on the same
149
+ // old entries and the last rename wins, silently dropping the other's ops.
150
+ // 0.3.17 (S2.8, T-1): the queue factory is shared with state-json now.
151
+ const serializedWrite = makeSerialQueue();
152
+ // V5-11 (0.3.32): a whitespace-only `root` was truthy and resolved to a
153
+ // CWD-relative path — trim like resolveSkillsRoot/state-json (V4-09 third
154
+ // occurrence); empty/whitespace both fall through to the default root.
155
+ // PLAN S2.2-adjacent P2-31 (2026-09-16): an EXPLICIT non-empty root is now
156
+ // run through resolve() — the same standard as core's evolutionRoot
157
+ // (state-store.ts) — so the store root is pinned to an absolute path at
158
+ // mount instead of staying CWD-relative and letting every io call land
159
+ // wherever the process CWD points at that moment. Empty/whitespace keeps
160
+ // the memoryRoot() default unchanged.
161
+ const trimmedRoot = (rawConfig.root || '').trim();
162
+ const resolvedRoot = trimmedRoot === '' ? '' : resolve(trimmedRoot);
163
+ // G3/S3.2 + G1: the user layer and the deployment carriers both land in the four
164
+ // live values above, so the store is DERIVED from them rather than kept in step
165
+ // by a settings hook. `MemoryStore` reads its limits in its CONSTRUCTOR, so an
166
+ // option set that changed since the last operation rebuilds it — registration-
167
+ // level facts, the one thing the platform's `onChange` hook existed for. A
168
+ // rebuild resets the store's consolidation-failure counter; the previous hook
169
+ // rebuilt on ANY committed change, so this fires strictly less often. One
170
+ // operation takes one store (the read/write pair inside `snapshot` must not
171
+ // straddle a rebuild), and the write queue lives outside the store either way.
172
+ let store;
173
+ let storeKey = '';
174
+ /**
175
+ * The store for the configuration as it stands right now.
176
+ * @returns the current store, rebuilt when a resolved limit changed.
177
+ */
178
+ const currentStore = () => {
179
+ const options = {
180
+ memoryCharLimit: memoryChars(),
181
+ userCharLimit: userChars(),
182
+ addDatePrefix: addDatePrefix(),
183
+ maxConsolidationFailures: consolidationFailures(),
184
+ };
185
+ const key = `${options.memoryCharLimit}|${options.userCharLimit}|${options.addDatePrefix}|${options.maxConsolidationFailures}`;
186
+ if (store !== undefined && key === storeKey)
187
+ return store;
188
+ store = new MemoryStore({
189
+ ...options,
190
+ // F-2 (v18): the memory store's own exemption list, now configurable.
191
+ ...rawConfig.threatExemptLabels !== undefined ? { threatExemptLabels: rawConfig.threatExemptLabels } : {},
192
+ ...resolvedRoot !== '' ? { root: resolvedRoot } : {},
193
+ io,
194
+ ...(authority?.homeFence === undefined ? {} : { fence: authority.homeFence }),
195
+ });
196
+ storeKey = key;
197
+ return store;
198
+ };
199
+ const provider = {
200
+ name: rawConfig.providerName ?? 'files',
201
+ read: (target) => currentStore().read(target),
202
+ applyBatch: async (target, operations) => {
203
+ // T3-06/A34: the payload alias is resolved by core's ONE rule, so this
204
+ // provider cannot disagree with the validator or the store about a blank
205
+ // `facts` sitting next to a real `content`.
206
+ const normalized = operations.map(op => ({ action: op.action, facts: payloadText(op), old_text: op.old_text }));
207
+ return await serializedWrite(() => currentStore().applyBatch(target, normalized));
208
+ },
209
+ snapshot: async () => {
210
+ // 0.3.17 (E-73): serial reads — a concurrent write between the two
211
+ // Promise.all reads used to produce a mixed-generation snapshot. V4-12:
212
+ // reading memory and user as two separate awaited reads still lets a
213
+ // serializedWrite (an applyBatch) yield BETWEEN them, so the two reads
214
+ // now run inside one serializedWrite task: no write can interleave, the
215
+ // snapshot is a single generation, and the sha256 pins a state that truly
216
+ // existed. (The cross-process window is documented as accepted — the
217
+ // single-process chain is this family's second layer.)
218
+ const [memory, user] = await serializedWrite(async () => {
219
+ const current = currentStore();
220
+ return [await current.read('memory'), await current.read('user')];
221
+ });
222
+ const text = JSON.stringify([memory, user]);
223
+ return { version: 1, sha256: createHash('sha256').update(text).digest('hex'), memory, user };
224
+ },
225
+ // P2-3 (v14): the SAME single-generation rule as `snapshot` — this is the
226
+ // path the model actually reads (tool-memory injects `renderContext`), and
227
+ // it read memory and user as two independent awaits, so a concurrent
228
+ // applyBatch could land between them and produce a mixed-generation
229
+ // context. Queue both reads as one serialized step.
230
+ renderContext: () => serializedWrite(() => currentStore().renderContext()),
231
+ };
232
+ // S2.9 (T3-10): the settings user-layer KEY read is ONE core function (`userSetKeys`); this row
233
+ // only names its own namespace. The review, curator and skill rows call the same reader.
234
+ /**
235
+ * Which surface supplied one budget.
236
+ * @param id - the canonical registry id.
237
+ * @returns 'user', 'config', 'policy' or 'default'.
238
+ */
239
+ const limitSource = (id) => {
240
+ if (userSetKeys(ctx, paramRowId('memory-files')).has(id))
241
+ return 'user';
242
+ if (explicitBudget(id))
243
+ return 'config';
244
+ return (id === 'memoryChars' ? policyBudget().memory : policyBudget().user) === undefined ? 'default' : 'policy';
245
+ };
246
+ // S2-12③: publish the EFFECTIVE budget so doctor can compare the two surfaces
247
+ // at run time (the same one-writer/one-reader contract as evolutionFeedback /
248
+ // evolutionReplay). Every member is a LIVE VIEW (G1 §8.3): the user layer can
249
+ // change at run time, and a policy mounted after this row is visible too.
250
+ // `source` names the winning surface; 'user' is the highest-priority one.
251
+ ctx.provide('evolutionMemoryBudget', {
252
+ get memoryCharLimit() { return memoryChars(); },
253
+ get userCharLimit() { return userChars(); },
254
+ get memorySource() { return limitSource('memoryChars'); },
255
+ get userSource() { return limitSource('userChars'); },
256
+ get policyMemoryChars() { return policyBudget().memory; },
257
+ get policyUserChars() { return policyBudget().user; },
258
+ });
259
+ ctx.effect(() => ctx.memory.registerProvider(provider), 'memory-files.provider');
260
+ }
261
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Memory provider registry for the evolution family.
3
+ * Service Definition role; concrete providers register here.
4
+ * @module @lmzhen/dsh-memory
5
+ */
6
+ import { Service } from '@deepseek-ai/cordis';
7
+ import z from '@deepseek-ai/schemastery';
8
+ export class MemoryRegistry extends Service {
9
+ /**
10
+ * V27 G6.3: the same provider-pin contract the io/state seams use. Before
11
+ * this, every delegation called `provider()` with no name, so with two
12
+ * providers mounted the session-visible store was decided by ROW ORDER while
13
+ * `memory-files.providerName` renamed a registration nothing selected — a
14
+ * config no consumer read. Empty keeps the old behavior (first registered).
15
+ */
16
+ static Config = z.object({
17
+ provider: z.string().default(''),
18
+ });
19
+ providers = new Map();
20
+ /** Dispose handles by provider name, so a stale handle cannot remove a newer registration. */
21
+ disposals = new Map();
22
+ providerName;
23
+ pinWarned = false;
24
+ constructor(ctx, config = {}) {
25
+ super(ctx, 'memory');
26
+ this.providerName = config.provider ?? '';
27
+ // A mount-time check like evolution-state's S-07 is not available here: the
28
+ // registry and the provider-registration API are the SAME service, so a
29
+ // provider can only register after this constructor and the registry is
30
+ // always empty at mount. The pin is therefore verified at the two points
31
+ // that exist — a warning when a differently-named provider registers (the
32
+ // earliest visible signal), and the hard `provider(name)` throw on the first
33
+ // read or write, which names the pin.
34
+ }
35
+ registerProvider(provider) {
36
+ // 1-8/A11: parity with the io and state-storage registries. The three registries serve ONE role
37
+ // (a provider table on the Service Definition side), and both siblings treat re-registering the
38
+ // IDENTICAL object as idempotent — they return the original dispose and do not throw, which is
39
+ // what lets a backend re-apply on HMR (a re-mounted row) without a crash. Memory threw
40
+ // `already registered` for the same sequence. A DIFFERENT object under a registered name still
41
+ // throws in all three.
42
+ const idempotent = this.providers.get(provider.name) === provider;
43
+ if (!idempotent && this.providers.has(provider.name))
44
+ throw new Error(`memory provider "${provider.name}" already registered`);
45
+ if (idempotent) {
46
+ const existing = this.disposals.get(provider.name);
47
+ if (existing !== undefined)
48
+ return existing;
49
+ }
50
+ this.providers.set(provider.name, provider);
51
+ this.ctx.emit('evolution/memory-provider-registered', { name: provider.name });
52
+ if (this.providerName && provider.name !== this.providerName && !this.providers.has(this.providerName) && !this.pinWarned) {
53
+ this.pinWarned = true;
54
+ this.ctx.logger.warn(`memory: config.provider="${this.providerName}" is not registered (mounted: "${provider.name}"); memory reads and writes fail until that provider mounts`);
55
+ }
56
+ const dispose = () => {
57
+ // P3 (v16) parity with the io registry: a handle acts only while it is
58
+ // still the CURRENT one for this name. An object-identity check cannot
59
+ // tell generations apart, so after dispose → re-register(same object) a
60
+ // stale handle used to delete the LIVE registration.
61
+ if (this.disposals.get(provider.name) !== dispose)
62
+ return;
63
+ this.providers.delete(provider.name);
64
+ this.disposals.delete(provider.name);
65
+ };
66
+ this.disposals.set(provider.name, dispose);
67
+ return dispose;
68
+ }
69
+ /** Configured provider: the pinned name, or the first registered one. */
70
+ selected() {
71
+ return this.provider(this.providerName || undefined);
72
+ }
73
+ /** 0.3.17 (E-73): named lookup like the io/state-storage registries; no
74
+ * name = first registered (backward compatible). A named miss throws (F-333,
75
+ * 0.3.23) instead of silently falling back to the first provider, so a wrong
76
+ * name surfaces rather than writing to the wrong memory store. */
77
+ provider(name) {
78
+ if (name) {
79
+ const byName = this.providers.get(name);
80
+ if (!byName)
81
+ throw new Error(`memory provider "${name}" is not registered`);
82
+ return byName;
83
+ }
84
+ const first = this.providers.values().next().value;
85
+ if (!first)
86
+ throw new Error('memory: no provider registered');
87
+ return first;
88
+ }
89
+ read(target) {
90
+ return this.selected().read(target);
91
+ }
92
+ async applyBatch(target, operations) {
93
+ const result = await this.selected().applyBatch(target, operations);
94
+ // P2 fix: every successful write refreshes whatever listens — the snapshot
95
+ // subscriber (tool-memory) re-renders the model-visible context. This is
96
+ // the single write sink, so bypass paths are covered without per-path fixes.
97
+ if (result.ok)
98
+ this.ctx.emit('evolution/memory-applied', {
99
+ target,
100
+ chars: result.chars,
101
+ entries: result.entries.length,
102
+ });
103
+ return result;
104
+ }
105
+ /** V6-44 (0.3.37): test-support API — production consumers read the
106
+ * model-visible context via `renderContext()`; snapshot() has no production
107
+ * consumer (tests inspect the raw snapshot, kept by declaration).
108
+ * @internal Exported for this package's own tests only. */
109
+ snapshot() {
110
+ return this.selected().snapshot();
111
+ }
112
+ renderContext() {
113
+ return this.selected().renderContext();
114
+ }
115
+ }
116
+ export default MemoryRegistry;
117
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Model-facing memory tool and runtime-context memory snapshot.
3
+ * @module @deepseek-ai/dsh-tool-memory
4
+ */
5
+ import type { Context, Volatile } from '@deepseek-ai/cordis';
6
+ import z from '@deepseek-ai/schemastery';
7
+ export declare const name = "tool-memory";
8
+ export declare const inject: string[];
9
+ /**
10
+ * System-prompt memory guidance, aligned with Hermes `MEMORY_GUIDANCE`.
11
+ * Reached by the model every turn, so it is the place to steer behavior: what
12
+ * to save durably, what not to save, and how to phrase an entry. The tool
13
+ * schema description (`MEMORY_TOOL_DESCRIPTION`) carries the complementary
14
+ * operation-level guidance (add/replace/remove, batch, targets, when).
15
+ */
16
+ export declare const MEMORY_GUIDANCE: string;
17
+ /**
18
+ * Tool-schema description for `memory`, aligned with Hermes `MEMORY_SCHEMA`.
19
+ * Carries the operation-level guidance: how to batch, when to save, the
20
+ * priority order, the target semantics, and what to skip. Reached only when
21
+ * the model is choosing/using the tool, so it complements the always-on
22
+ * `MEMORY_GUIDANCE` system prompt.
23
+ */
24
+ export declare const MEMORY_TOOL_DESCRIPTION: string;
25
+ /** G3/S3.2: `memoryEnabled` stays a deployment switch on purpose — it decides whether the
26
+ * tool and its prompt section are REGISTERED at all, so making it live would mean dynamic
27
+ * registration with catalog-visible effects (registry tier E2). The display knob the user
28
+ * may tune (`entryPreviewChars`) is a volatile row field instead (G1 §8.1). */
29
+ export interface Config {
30
+ memoryEnabled?: boolean;
31
+ /** Maximum characters of each memory entry echoed back in tool results (live reference). */
32
+ entryPreviewChars?: Volatile<number>;
33
+ }
34
+ export declare const Config: z<Schemastery.ObjectS<NoInfer<{
35
+ memoryEnabled: z<boolean, boolean, "defined">;
36
+ entryPreviewChars: z<number, number, "volatile-defined">;
37
+ }>>, Schemastery.ObjectT<NoInfer<{
38
+ memoryEnabled: z<boolean, boolean, "defined">;
39
+ entryPreviewChars: z<number, number, "volatile-defined">;
40
+ }>>, "plain">;
41
+ export declare function apply(ctx: Context, rawConfig?: Config): Promise<void>;
42
+ //# sourceMappingURL=index.d.ts.map