@lmzhen/dsh-evolution-curator 0.17.1 → 0.18.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/lib/index.js +45 -66
- package/lib/types/index.d.ts +3 -22
- package/package.json +8 -8
package/lib/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import { randomUUID } from "node:crypto";
|
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { BlockAssembler, createUserMessage } from "@deepseek-ai/dsh-llm";
|
|
5
5
|
import z from "@deepseek-ai/schemastery";
|
|
6
|
-
import { CURATOR_DRY_RUN_BANNER, CURATOR_PROMPT, DEFAULT_ARCHIVE_AFTER_DAYS, DEFAULT_CURATOR_BOOT_GRACE_SECONDS, DEFAULT_CURATOR_INTERVAL_HOURS, DEFAULT_CURATOR_MODEL, DEFAULT_CURATOR_REVIEW_MAX_TOKENS, DEFAULT_HEALTH_THRESHOLDS, DEFAULT_MIN_IDLE_HOURS, DEFAULT_SKILL_LIMITS, DEFAULT_STALE_AFTER_DAYS, EvolutionGateSet, INSTANCE_KEYS, MAX_TIMER_DELAY_MS, SKILL_NAME_RE, buildCuratorRunReport, claimInstance, clampedNumber, computeDedupGroups, computeLifecycleTransitions, computePrefixClusters, computeQualityScores, computeScopeView, contentHash, emptyRecord, entryTarget, evolutionHome, evolutionIoAdapter, foldCuratorFields, isPresent, isUnknown, loadSuppressedNames, loadUsage, markerEntryName, mutateUsage, newSkillLibrary, paramRowId, parseCuratorNominations, parseFrontmatter, policyStageLimits, probeList, probeMtime, readNumberParam, relatedSkillNames, releaseInstance, renderCuratorReportMarkdown, sessionLastEventTime, transactIo, updateSuppressedNames, usageObserved } from "@lmzhen/dsh-evolution-core";
|
|
6
|
+
import { CURATOR_DRY_RUN_BANNER, CURATOR_PROMPT, DEFAULT_ARCHIVE_AFTER_DAYS, DEFAULT_CURATOR_BOOT_GRACE_SECONDS, DEFAULT_CURATOR_INTERVAL_HOURS, DEFAULT_CURATOR_MODEL, DEFAULT_CURATOR_REVIEW_MAX_TOKENS, DEFAULT_HEALTH_THRESHOLDS, DEFAULT_MIN_IDLE_HOURS, DEFAULT_SKILL_LIMITS, DEFAULT_STALE_AFTER_DAYS, EvolutionGateSet, INSTANCE_KEYS, MAX_TIMER_DELAY_MS, PROMPT_BUNDLE, SKILL_NAME_RE, buildCuratorRunReport, claimInstance, clampOnce, clampedNumber, computeDedupGroups, computeLifecycleTransitions, computePrefixClusters, computeQualityScores, computeScopeView, contentHash, emptyRecord, entryTarget, evolutionHome, evolutionIoAdapter, foldCuratorFields, isPresent, isUnknown, loadSuppressedNames, loadUsage, markerEntryName, mutateUsage, newSkillLibrary, paramRowId, parseCuratorNominations, parseFrontmatter, pickWithPolicy, policyStageLimits, probeList, probeMtime, probeText, readNumberParam, relatedSkillNames, releaseInstance, renderCuratorReportMarkdown, reportTime, sessionLastEventTime, transactIo, updateSuppressedNames, usageObserved, userSetKeys, verifyPromptBundle } from "@lmzhen/dsh-evolution-core";
|
|
7
7
|
//#region lib/types/index.js
|
|
8
8
|
/**
|
|
9
9
|
* Deterministic skill lifecycle curator with interval gate and archive.
|
|
@@ -80,6 +80,10 @@ var EvolutionCurator = class extends Service {
|
|
|
80
80
|
bootGraceSeconds;
|
|
81
81
|
/** Keys whose supplied value had to be corrected; each warns once (G3.1). */
|
|
82
82
|
clampedKeys = /* @__PURE__ */ new Set();
|
|
83
|
+
/** The clamp warning sink — the LOGIC lives in core's `clampOnce` (S2.9/T3-10), the prefix here. */
|
|
84
|
+
clampWarn = (message) => {
|
|
85
|
+
this.ctx.logger.warn("evolution-curator: " + message);
|
|
86
|
+
};
|
|
83
87
|
lastRun = 0;
|
|
84
88
|
timer;
|
|
85
89
|
/** B-8 (v18): set by the fiber disposer; a triggered autoCheck must not
|
|
@@ -113,9 +117,10 @@ var EvolutionCurator = class extends Service {
|
|
|
113
117
|
ctx.inject(["settings"], (injected) => {
|
|
114
118
|
const settings = injected.settings;
|
|
115
119
|
if (typeof settings?.configure !== "function") return;
|
|
116
|
-
const disposer = settings.configure({ auto: false });
|
|
120
|
+
const disposer = settings.configure({ auto: false }, ctx.fiber);
|
|
117
121
|
if (typeof disposer === "function") ctx.effect(() => disposer, "evolution-curator: settings presentation");
|
|
118
122
|
});
|
|
123
|
+
if (!verifyPromptBundle(PROMPT_BUNDLE)) throw new Error("dsh-evolution prompt bundle integrity check failed; refusing to run the curator");
|
|
119
124
|
this.io = evolutionIoAdapter(() => ctx.evolutionIo.provider());
|
|
120
125
|
this.skills = newSkillLibrary({
|
|
121
126
|
config,
|
|
@@ -137,7 +142,7 @@ var EvolutionCurator = class extends Service {
|
|
|
137
142
|
this.manageUnmanaged = config.manageUnmanaged ?? false;
|
|
138
143
|
this.pruneBuiltins = config.pruneBuiltins ?? false;
|
|
139
144
|
this.referencedSkillNames = new Set(config.referencedSkillNames ?? []);
|
|
140
|
-
this.bootGraceSeconds = this.
|
|
145
|
+
this.bootGraceSeconds = clampOnce(this.clampedKeys, this.clampWarn, "bootGraceSeconds", config.bootGraceSeconds, DEFAULT_CURATOR_BOOT_GRACE_SECONDS, 0, 3600);
|
|
141
146
|
const archiveAtBoot = this.rowNumber("archiveAfterDays", DEFAULT_ARCHIVE_AFTER_DAYS, 1);
|
|
142
147
|
const staleAtBoot = this.rowNumber("staleAfterDays", DEFAULT_STALE_AFTER_DAYS, 1);
|
|
143
148
|
if (archiveAtBoot < staleAtBoot) this.ctx.logger.warn(`evolution-curator: archiveAfterDays (${archiveAtBoot}) < staleAfterDays (${staleAtBoot}); using staleAfterDays as the archive threshold`);
|
|
@@ -163,26 +168,6 @@ var EvolutionCurator = class extends Service {
|
|
|
163
168
|
if (config.autoStart ?? true) this.start();
|
|
164
169
|
}
|
|
165
170
|
/**
|
|
166
|
-
* Clamp one supplied numeric value, warning once per key (G3.1).
|
|
167
|
-
* @param key - the config key, for the warning.
|
|
168
|
-
* @param value - the value as supplied by the deployment or the user.
|
|
169
|
-
* @param fallback - the package default a corrected value falls back to.
|
|
170
|
-
* @param min - the smallest value the engine can act on.
|
|
171
|
-
* @param max - the largest value the carrier can hold, when bounded.
|
|
172
|
-
* @returns the value the engine may use.
|
|
173
|
-
*/
|
|
174
|
-
clampOnce(key, value, fallback, min, max) {
|
|
175
|
-
const result = clampedNumber(value, fallback, max === void 0 ? { min } : {
|
|
176
|
-
min,
|
|
177
|
-
max
|
|
178
|
-
});
|
|
179
|
-
if (value !== void 0 && result !== value && !this.clampedKeys.has(key)) {
|
|
180
|
-
this.clampedKeys.add(key);
|
|
181
|
-
this.ctx.logger.warn(`evolution-curator: ${key} provided an invalid value; falling back to the default`);
|
|
182
|
-
}
|
|
183
|
-
return result;
|
|
184
|
-
}
|
|
185
|
-
/**
|
|
186
171
|
* One live numeric row field at USE time (G1 §8.3): the row config carries the
|
|
187
172
|
* platform-resolved layers (schema default < bundle row < profile override) and the
|
|
188
173
|
* field stays a live reference, so a committed edit applies without a watcher.
|
|
@@ -202,7 +187,7 @@ var EvolutionCurator = class extends Service {
|
|
|
202
187
|
* @returns the value the engine may use.
|
|
203
188
|
*/
|
|
204
189
|
rowNumber(key, fallback, min, max) {
|
|
205
|
-
return this.
|
|
190
|
+
return clampOnce(this.clampedKeys, this.clampWarn, key, this.liveNumber(key), fallback, min, max);
|
|
206
191
|
}
|
|
207
192
|
/**
|
|
208
193
|
* One live boolean row field at USE time.
|
|
@@ -235,20 +220,6 @@ var EvolutionCurator = class extends Service {
|
|
|
235
220
|
healthChurnMinPatches: number("healthChurnMinPatches", DEFAULT_HEALTH_THRESHOLDS.churnMinPatches, 1)
|
|
236
221
|
};
|
|
237
222
|
}
|
|
238
|
-
/**
|
|
239
|
-
* The keys the user set in this row's namespace (G1 §8.4-A).
|
|
240
|
-
*
|
|
241
|
-
* This is the ONLY remaining read of the settings user layer, and it reads KEY
|
|
242
|
-
* NAMES, never values: the platform resolves the user layer into the row config,
|
|
243
|
-
* so a value is needed from `config` — but the precedence rule (user > policy)
|
|
244
|
-
* needs to know WHICH keys the user set, or a deployment policy would silently
|
|
245
|
-
* override the user.
|
|
246
|
-
* @returns the keys present in this row's user layer.
|
|
247
|
-
*/
|
|
248
|
-
userSetKeys() {
|
|
249
|
-
const entry = this.ctx.get("settings")?.describe?.({ redactSecrets: false }).find((item) => item.ns === paramRowId("evolution-curator"));
|
|
250
|
-
return new Set(Object.keys(entry?.user ?? {}));
|
|
251
|
-
}
|
|
252
223
|
/** The policy-snapshot carrier for this group (unmounted service = no policy). */
|
|
253
224
|
policyFields() {
|
|
254
225
|
return this.ctx.get("evolutionPolicy")?.get() ?? {};
|
|
@@ -265,11 +236,8 @@ var EvolutionCurator = class extends Service {
|
|
|
265
236
|
settings() {
|
|
266
237
|
const snapshot = this.policyFields();
|
|
267
238
|
const row = this.rowSettings();
|
|
268
|
-
const userSet = this.
|
|
269
|
-
const pick = (key) =>
|
|
270
|
-
if (userSet.has(key)) return row[key];
|
|
271
|
-
return snapshot[key] ?? row[key];
|
|
272
|
-
};
|
|
239
|
+
const userSet = userSetKeys(this.ctx, paramRowId("evolution-curator"));
|
|
240
|
+
const pick = (key) => pickWithPolicy(userSet, row, snapshot, key);
|
|
273
241
|
const number = (key, min, max) => {
|
|
274
242
|
const lower = row[key];
|
|
275
243
|
return clampedNumber(pick(key), lower, max === void 0 ? { min } : {
|
|
@@ -436,6 +404,7 @@ var EvolutionCurator = class extends Service {
|
|
|
436
404
|
"Return a YAML summary with consolidations and prunings lists. Nominate only actions whose archival/merge is clearly safe."
|
|
437
405
|
].join("\n");
|
|
438
406
|
const settings = this.settings();
|
|
407
|
+
const llmSignal = AbortSignal.timeout(settings.curatorReviewTimeoutMs);
|
|
439
408
|
try {
|
|
440
409
|
const assembler = new BlockAssembler();
|
|
441
410
|
for await (const chunk of llm.stream({
|
|
@@ -453,7 +422,7 @@ var EvolutionCurator = class extends Service {
|
|
|
453
422
|
}
|
|
454
423
|
})],
|
|
455
424
|
maxTokens: settings.curatorReviewMaxTokens,
|
|
456
|
-
signal:
|
|
425
|
+
signal: llmSignal
|
|
457
426
|
})) assembler.push(chunk);
|
|
458
427
|
const parsed = parseCuratorNominations(assembler.blocks().filter((block) => block.type === "text").map((block) => block.text).join("\n"));
|
|
459
428
|
return {
|
|
@@ -462,8 +431,12 @@ var EvolutionCurator = class extends Service {
|
|
|
462
431
|
warnings: parsed.warnings
|
|
463
432
|
};
|
|
464
433
|
} catch (error) {
|
|
465
|
-
|
|
466
|
-
|
|
434
|
+
const failure = llmSignal.aborted ? `LLM nomination pass timed out after ${settings.curatorReviewTimeoutMs} ms — no nominations this run (NOT "nothing to nominate": the pass did not complete)` : `LLM nomination pass failed: ${error instanceof Error ? error.message : String(error)} — no nominations this run (NOT "nothing to nominate": the pass did not complete)`;
|
|
435
|
+
this.ctx.logger.warn(`evolution-curator: ${failure}`);
|
|
436
|
+
return {
|
|
437
|
+
...empty,
|
|
438
|
+
warnings: [failure]
|
|
439
|
+
};
|
|
467
440
|
}
|
|
468
441
|
}
|
|
469
442
|
/** Optional curator-state service (evolution-state-json / storage-domain). */
|
|
@@ -966,8 +939,9 @@ var EvolutionCurator = class extends Service {
|
|
|
966
939
|
if (!new RegExp(`^${name}-\\d{14}(-[0-9a-z]{1,6})?$`).test(entry)) continue;
|
|
967
940
|
wasBundled = await this.io.exists(join(this.skills.root, ".archive", entry, markerEntryName("bundled")));
|
|
968
941
|
if (wasBundled) {
|
|
969
|
-
const
|
|
970
|
-
if (
|
|
942
|
+
const archivedProbe = await probeText(this.io, join(this.skills.root, ".archive", entry, "SKILL.md"));
|
|
943
|
+
if (isUnknown(archivedProbe)) this.ctx.logger.warn(`evolution-curator: the archived body of "${entry}" could not be read (${archivedProbe.reason}); keeping the bundled marker as the signal`);
|
|
944
|
+
else if (isPresent(archivedProbe) && parseFrontmatter(archivedProbe.value)?.frontmatter.name !== name) wasBundled = false;
|
|
971
945
|
}
|
|
972
946
|
if (wasBundled) break;
|
|
973
947
|
}
|
|
@@ -1137,24 +1111,22 @@ var EvolutionCurator = class extends Service {
|
|
|
1137
1111
|
for (const name of entries.filter((entry) => entry.startsWith("curator-") && entry.endsWith(".json"))) try {
|
|
1138
1112
|
const raw = await this.io.readText(join(reportsRoot, name));
|
|
1139
1113
|
if (raw === null) continue;
|
|
1140
|
-
const
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
if (isPresent(probe)) startedAt = probe.value;
|
|
1145
|
-
}
|
|
1146
|
-
if (!Number.isFinite(startedAt) && typeof parsed.at === "string") startedAt = Date.parse(parsed.at);
|
|
1147
|
-
if (Number.isFinite(startedAt)) (name.startsWith("curator-error-") ? errors : real).push({
|
|
1114
|
+
const startedAt = await reportTime(this.io, join(reportsRoot, name), JSON.parse(raw));
|
|
1115
|
+
const probed = await probeMtime(this.io, join(reportsRoot, name));
|
|
1116
|
+
const mtime = isPresent(probed) ? probed.value : null;
|
|
1117
|
+
if (startedAt !== null) (name.startsWith("curator-error-") ? errors : real).push({
|
|
1148
1118
|
name,
|
|
1149
|
-
startedAt
|
|
1119
|
+
startedAt,
|
|
1120
|
+
mtime
|
|
1150
1121
|
});
|
|
1151
1122
|
else if (!unorderableWarned) {
|
|
1152
1123
|
unorderableWarned = true;
|
|
1153
1124
|
this.ctx.logger.warn(`evolution-curator: report "${name}" carries no usable timestamp and this backend has no mtime probe - it is kept outside the retention window`);
|
|
1154
1125
|
}
|
|
1155
1126
|
} catch {}
|
|
1156
|
-
|
|
1157
|
-
|
|
1127
|
+
const newestFirst = (a, b) => b.startedAt - a.startedAt || (b.mtime ?? 0) - (a.mtime ?? 0) || b.name.localeCompare(a.name);
|
|
1128
|
+
real.sort(newestFirst);
|
|
1129
|
+
errors.sort(newestFirst);
|
|
1158
1130
|
await this.pruneReportList(reportsRoot, real, keep);
|
|
1159
1131
|
await this.pruneReportList(reportsRoot, errors, errorKeep);
|
|
1160
1132
|
}
|
|
@@ -1181,19 +1153,26 @@ var EvolutionCurator = class extends Service {
|
|
|
1181
1153
|
let latest = null;
|
|
1182
1154
|
let probeWarned = false;
|
|
1183
1155
|
for (const name of names) {
|
|
1184
|
-
const
|
|
1185
|
-
|
|
1156
|
+
const path = join(reportsRoot, name);
|
|
1157
|
+
const raw = await this.io.readText(path);
|
|
1158
|
+
if (raw === null) continue;
|
|
1159
|
+
let parsed;
|
|
1160
|
+
try {
|
|
1161
|
+
parsed = JSON.parse(raw);
|
|
1162
|
+
} catch {
|
|
1163
|
+
continue;
|
|
1164
|
+
}
|
|
1165
|
+
const at = await reportTime(this.io, path, parsed);
|
|
1166
|
+
if (at === null) {
|
|
1186
1167
|
if (!probeWarned) {
|
|
1187
1168
|
probeWarned = true;
|
|
1188
|
-
this.ctx.logger.warn(
|
|
1169
|
+
this.ctx.logger.warn("evolution-curator: latestReport found no usable report time (neither a declared startedAt/at nor an mtime probe) - the affected report(s) are not eligible as the latest");
|
|
1189
1170
|
}
|
|
1190
1171
|
continue;
|
|
1191
1172
|
}
|
|
1192
|
-
if (
|
|
1193
|
-
const mtime = probe.value;
|
|
1194
|
-
if (latest === null || mtime > latest.mtime) latest = {
|
|
1173
|
+
if (latest === null || at > latest.at) latest = {
|
|
1195
1174
|
name,
|
|
1196
|
-
|
|
1175
|
+
at
|
|
1197
1176
|
};
|
|
1198
1177
|
}
|
|
1199
1178
|
if (latest === null) return null;
|
package/lib/types/index.d.ts
CHANGED
|
@@ -29,7 +29,7 @@ export interface Config {
|
|
|
29
29
|
/** The due-ness interval. The policy snapshot shadows it in every shipped
|
|
30
30
|
* composition — configure `curatorIntervalHours` there; that name is the
|
|
31
31
|
* canonical id. Deprecated alias (G0/S0.3): still readable, refused by
|
|
32
|
-
* writes;
|
|
32
|
+
* writes; removal was planned for 0.7.0 and is deferred (the alias still ships — see the package README). */
|
|
33
33
|
intervalHours?: number;
|
|
34
34
|
staleAfterDays?: Volatile<number>;
|
|
35
35
|
archiveAfterDays?: Volatile<number>;
|
|
@@ -205,6 +205,8 @@ export declare class EvolutionCurator extends Service {
|
|
|
205
205
|
private readonly bootGraceSeconds;
|
|
206
206
|
/** Keys whose supplied value had to be corrected; each warns once (G3.1). */
|
|
207
207
|
private readonly clampedKeys;
|
|
208
|
+
/** The clamp warning sink — the LOGIC lives in core's `clampOnce` (S2.9/T3-10), the prefix here. */
|
|
209
|
+
private readonly clampWarn;
|
|
208
210
|
private lastRun;
|
|
209
211
|
private timer;
|
|
210
212
|
/** B-8 (v18): set by the fiber disposer; a triggered autoCheck must not
|
|
@@ -232,16 +234,6 @@ export declare class EvolutionCurator extends Service {
|
|
|
232
234
|
* yielding instance's log line names a concrete holder. */
|
|
233
235
|
private readonly instanceOwner;
|
|
234
236
|
constructor(ctx: Context, config?: Config);
|
|
235
|
-
/**
|
|
236
|
-
* Clamp one supplied numeric value, warning once per key (G3.1).
|
|
237
|
-
* @param key - the config key, for the warning.
|
|
238
|
-
* @param value - the value as supplied by the deployment or the user.
|
|
239
|
-
* @param fallback - the package default a corrected value falls back to.
|
|
240
|
-
* @param min - the smallest value the engine can act on.
|
|
241
|
-
* @param max - the largest value the carrier can hold, when bounded.
|
|
242
|
-
* @returns the value the engine may use.
|
|
243
|
-
*/
|
|
244
|
-
private clampOnce;
|
|
245
237
|
/**
|
|
246
238
|
* One live numeric row field at USE time (G1 §8.3): the row config carries the
|
|
247
239
|
* platform-resolved layers (schema default < bundle row < profile override) and the
|
|
@@ -272,17 +264,6 @@ export declare class EvolutionCurator extends Service {
|
|
|
272
264
|
* @returns the row layer of the curator's behaviour knobs.
|
|
273
265
|
*/
|
|
274
266
|
private rowSettings;
|
|
275
|
-
/**
|
|
276
|
-
* The keys the user set in this row's namespace (G1 §8.4-A).
|
|
277
|
-
*
|
|
278
|
-
* This is the ONLY remaining read of the settings user layer, and it reads KEY
|
|
279
|
-
* NAMES, never values: the platform resolves the user layer into the row config,
|
|
280
|
-
* so a value is needed from `config` — but the precedence rule (user > policy)
|
|
281
|
-
* needs to know WHICH keys the user set, or a deployment policy would silently
|
|
282
|
-
* override the user.
|
|
283
|
-
* @returns the keys present in this row's user layer.
|
|
284
|
-
*/
|
|
285
|
-
private userSetKeys;
|
|
286
267
|
/** The policy-snapshot carrier for this group (unmounted service = no policy). */
|
|
287
268
|
private policyFields;
|
|
288
269
|
/**
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lmzhen/dsh-evolution-curator",
|
|
3
3
|
"description": "Deterministic skill lifecycle and recovery (community build)",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.18.1",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -26,21 +26,21 @@
|
|
|
26
26
|
],
|
|
27
27
|
"license": "MIT",
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@deepseek-ai/schemastery": "^3.18.
|
|
30
|
-
"@lmzhen/dsh-evolution-core": "^0.
|
|
29
|
+
"@deepseek-ai/schemastery": "^3.18.4",
|
|
30
|
+
"@lmzhen/dsh-evolution-core": "^0.18.1"
|
|
31
31
|
},
|
|
32
32
|
"peerDependencies": {
|
|
33
33
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
34
34
|
"@deepseek-ai/dsh-llm": "^0.2.0-rc.1",
|
|
35
35
|
"@deepseek-ai/dsh-session": "^0.2.0-rc.1",
|
|
36
|
-
"@lmzhen/dsh-evolution-io": "^0.
|
|
37
|
-
"@lmzhen/dsh-evolution-state": "^0.
|
|
36
|
+
"@lmzhen/dsh-evolution-io": "^0.18.1",
|
|
37
|
+
"@lmzhen/dsh-evolution-state": "^0.18.1"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@deepseek-ai/dsh-llm": "^0.2.0-rc.1",
|
|
41
41
|
"@deepseek-ai/dsh-session": "^0.2.0-rc.1",
|
|
42
|
-
"@lmzhen/dsh-evolution-core": "^0.
|
|
43
|
-
"@lmzhen/dsh-evolution-io": "^0.
|
|
44
|
-
"@lmzhen/dsh-evolution-state": "^0.
|
|
42
|
+
"@lmzhen/dsh-evolution-core": "^0.18.1",
|
|
43
|
+
"@lmzhen/dsh-evolution-io": "^0.18.1",
|
|
44
|
+
"@lmzhen/dsh-evolution-state": "^0.18.1"
|
|
45
45
|
}
|
|
46
46
|
}
|