@lmzhen/dsh-evolution-review 0.3.65 → 0.3.67

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +1 -1
  2. package/lib/index.js +164 -36
  3. package/package.json +10 -10
package/README.md CHANGED
@@ -25,7 +25,7 @@ Independent of request-prefix construction. This package does not alter the asse
25
25
  - Review subagents run as `spawn` children on the deployment default preset rather than inheriting the parent agent's composition (`fork`): a fork child is always promoted by the Anchored Standard bootstrap and its narrowed resident catalog would drop the plain `skill` tool from the review allow-list.
26
26
  - The review request text is redacted for credential-shaped patterns before it reaches the subagent, but redaction is pattern-based and best-effort, not a security boundary.
27
27
  - Read-before-write tracks only reads through the `skill` tool. `skill_manage` has no per-skill read action (`list`/`review` are whole-library, not targeted at one name), so a skill that was only listed via `skill_manage` is not marked as read — a background review may still reject a patch to it until it is actually loaded.
28
- - The completion-channel counters (`cumulativeToolCalls` / `completionInjected`) are in-memory only. A process restart resets them, which is accepted behavior: the completion review is a one-per-session post-task adaptation and a restart is treated as a fresh conversation boundary. The cadence state (`turnsSinceMemory` / `turnsSinceSkill`) is persisted via `ReviewState` and survives restart.
28
+ - The completion-channel counters (`cumulativeToolCalls` / `completionInjected`) are in-memory only. A process restart resets them, which is accepted behavior: the completion review is a one-per-session post-task adaptation and a restart is treated as a fresh conversation boundary. The cadence state (`turnsSinceMemory` / `turnsSinceSkill`) is persisted via `ReviewState` and survives restart — bounded by `REVIEW_STATE_SESSION_CAP` (500, seam constant): the least-recently-active sessions are pruned on save, so a very old session restarting resumes from a fresh cadence baseline rather than an unbounded store.
29
29
  - `evolution/review-scheduled` and `evolution/review-error` are emitted for platform/user wiring only — this family has no in-repo production `ctx.on` consumer for them. They are declared externally owned (the platform side wires consumption), which matches the `EXEMPT_ORPHANS` set in `scripts/verify-event-pairing.mjs`.
30
30
  - When the `evolution-state` service is not mounted, the memory/skill cadence state is not persisted and every turn restarts from a clean `{ turnsSinceMemory: 0, turnsSinceSkill: 0 }` baseline — the review schedule is stateless and re-decided each turn rather than accumulating across the conversation. The loss is surfaced once per process as a logger warning at the first turn/end.
31
31
  - Read-before-write can see the review subagent's own `skill` reads only when the subagent backend exposes `localAgent` (the in-process driver does; out-of-process backends such as ACP and the CLI providers set `localAgent: undefined`). With a remote backend the subagent's reads are invisible, so a plan item patching a skill the subagent itself loaded is dropped as "unread" — the review then falls back to the parent session's reads only. Documented rather than worked around: recovering the child read set needs a `SubagentLike` contract change (v14 P2-6).
package/lib/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { createHash, randomUUID } from "node:crypto";
2
2
  import z from "@deepseek-ai/schemastery";
3
3
  import { createUserMessage } from "@deepseek-ai/dsh-llm";
4
- import { COMPLETION_SKILL_REVIEW_PROMPT, DEFAULT_MAX_OPS_PER_PLAN, DEFAULT_MEMORY_CHAR_LIMIT, DEFAULT_REVIEW_CONTEXT_MESSAGES, DEFAULT_REVIEW_MEMORY_INTERVAL, DEFAULT_REVIEW_MESSAGE_CHARS, DEFAULT_REVIEW_SKILL_INTERVAL, DEFAULT_REVIEW_TIMEOUT_MS, DEFAULT_SKILL_CONTENT_CHARS, DEFAULT_SKILL_REVIEW_COMPLETION_MIN_TOOL_CALLS, DEFAULT_SKILL_REVIEW_TRIGGER, DEFAULT_USER_CHAR_LIMIT, PROMPT_BUNDLE, SkillLibrary, advanceReview, clampedNumber, evolutionIoAdapter, foldTurn, redactSecrets, resolveOrigins, resolveRootConfig, resolveSkillsRoot, reviewPrompt, verifyPromptBundle } from "@lmzhen/dsh-evolution-core";
4
+ import { COMPLETION_SKILL_REVIEW_PROMPT, DEFAULT_MAX_OPS_PER_PLAN, DEFAULT_MEMORY_CHAR_LIMIT, DEFAULT_REVIEW_CONTEXT_MESSAGES, DEFAULT_REVIEW_MEMORY_INTERVAL, DEFAULT_REVIEW_MESSAGE_CHARS, DEFAULT_REVIEW_SKILL_INTERVAL, DEFAULT_REVIEW_TIMEOUT_MS, DEFAULT_SKILL_CONTENT_CHARS, DEFAULT_SKILL_REVIEW_COMPLETION_MIN_TOOL_CALLS, DEFAULT_SKILL_REVIEW_TRIGGER, DEFAULT_USER_CHAR_LIMIT, PROMPT_BUNDLE, SkillLibrary, advanceReview, clampedNumber, contentHash, evolutionIoAdapter, foldTurn, redactSecrets, resolveOrigins, resolveRootConfig, resolveSkillsRoot, reviewPrompt, verifyPromptBundle } from "@lmzhen/dsh-evolution-core";
5
5
  import { validateEvolutionPlan } from "@lmzhen/dsh-evolution-plan-validator";
6
6
  //#region lib/types/index.js
7
7
  /**
@@ -93,8 +93,18 @@ function apply(ctx, rawConfig = {}) {
93
93
  const cadenceResetWarned = /* @__PURE__ */ new Set();
94
94
  let reviewInFlight = false;
95
95
  const policy = () => ctx.get("evolutionPolicy")?.get();
96
+ const reviewStateLocks = /* @__PURE__ */ new Map();
97
+ async function withReviewStateLock(id, task) {
98
+ const next = (reviewStateLocks.get(id) ?? Promise.resolve()).catch(() => {}).then(task);
99
+ reviewStateLocks.set(id, next);
100
+ try {
101
+ return await next;
102
+ } finally {
103
+ if (reviewStateLocks.get(id) === next) reviewStateLocks.delete(id);
104
+ }
105
+ }
96
106
  ctx.on("session/event", (session, event) => {
97
- if (event.type === "turn/start") turnStarts.set(session.id, session.seq - 1);
107
+ if (event.type === "turn/start" && session.header.origin !== "subagent") turnStarts.set(session.id, session.seq - 1);
98
108
  if (event.type !== "turn/end") return;
99
109
  if (turnStarts.size >= COUNTER_SWEEP_THRESHOLD || cumulativeToolCalls.size >= COUNTER_SWEEP_THRESHOLD || completionInjected.size >= COUNTER_SWEEP_THRESHOLD || pendingCadenceReviews.size >= COUNTER_SWEEP_THRESHOLD || skipNextCadenceFire.size >= COUNTER_SWEEP_THRESHOLD || cadenceResetWarned.size >= COUNTER_SWEEP_THRESHOLD) {
100
110
  const isAlive = (id) => ctx.agents.get(id) !== void 0;
@@ -132,24 +142,32 @@ function apply(ctx, rawConfig = {}) {
132
142
  statelessReviewStateWarned = true;
133
143
  ctx.logger.warn("dsh-evolution-review: evolution-state service not mounted — memory/skill review cadence is not persisted and resets every turn (see README Known Limitations).");
134
144
  }
135
- const state = await stateService?.loadReviewState(session.id) ?? {
145
+ const snapshot = policy();
146
+ const skipFire = skipNextCadenceFire.get(session.id) ?? false;
147
+ if (skipFire) skipNextCadenceFire.delete(session.id);
148
+ let state = {
136
149
  turnsSinceMemory: 0,
137
150
  turnsSinceSkill: 0,
138
151
  lastTurn: -1
139
152
  };
140
- const snapshot = policy();
141
- const skipFire = skipNextCadenceFire.get(session.id) ?? false;
142
- if (skipFire) skipNextCadenceFire.delete(session.id);
143
- const rawKind = advanceReview(state, event.data.turn, signal, {
144
- memoryInterval: snapshot?.reviewMemoryInterval ?? config.memoryInterval,
145
- skillInterval: snapshot?.reviewSkillInterval ?? config.skillInterval,
146
- substantiveMinToolCalls: snapshot?.substantiveMinToolCalls ?? 3,
147
- substantiveMinUserChars: snapshot?.substantiveMinUserChars ?? 200,
148
- substantiveMinAgentChars: snapshot?.substantiveMinAgentChars ?? 500,
149
- resetOnFire: false
153
+ const advanced = { kind: null };
154
+ await withReviewStateLock(session.id, async () => {
155
+ state = await stateService?.loadReviewState(session.id) ?? {
156
+ turnsSinceMemory: 0,
157
+ turnsSinceSkill: 0,
158
+ lastTurn: -1
159
+ };
160
+ advanced.kind = advanceReview(state, event.data.turn, signal, {
161
+ memoryInterval: snapshot?.reviewMemoryInterval ?? config.memoryInterval,
162
+ skillInterval: snapshot?.reviewSkillInterval ?? config.skillInterval,
163
+ substantiveMinToolCalls: snapshot?.substantiveMinToolCalls ?? 3,
164
+ substantiveMinUserChars: snapshot?.substantiveMinUserChars ?? 200,
165
+ substantiveMinAgentChars: snapshot?.substantiveMinAgentChars ?? 500,
166
+ resetOnFire: false
167
+ });
168
+ await stateService?.saveReviewState(session.id, state);
150
169
  });
151
- const kind = skipFire ? null : rawKind;
152
- await stateService?.saveReviewState(session.id, state);
170
+ const kind = skipFire ? null : advanced.kind;
153
171
  const cumulative = (cumulativeToolCalls.get(session.id) ?? 0) + signal.toolCalls;
154
172
  cumulativeToolCalls.set(session.id, cumulative);
155
173
  if (event.data.reason.kind === "completed") {
@@ -158,29 +176,60 @@ function apply(ctx, rawConfig = {}) {
158
176
  pendingCadenceReviews.delete(session.id);
159
177
  if ((policy()?.reviewMode ?? config.reviewMode) === "inject") try {
160
178
  deliverMessage(agent, reviewPrompt(pendingKind), "auto-review");
179
+ try {
180
+ ctx.emit("evolution/review-scheduled", {
181
+ sessionId: session.id,
182
+ kind: pendingKind,
183
+ toolCalls: signal.toolCalls,
184
+ userChars: signal.userChars,
185
+ assistantChars: signal.assistantChars,
186
+ channel: "inject"
187
+ });
188
+ } catch (emitError) {
189
+ ctx.logger.warn(`dsh-evolution-review: review-scheduled emit failed: ${emitError instanceof Error ? emitError.message : String(emitError)}`);
190
+ }
161
191
  } catch (injectError) {
162
192
  ctx.logger.warn(`dsh-evolution-review: deferred review inject failed: ${injectError instanceof Error ? injectError.message : String(injectError)}`);
163
193
  }
164
- else if (await trySubagentReview(session, agent, pendingKind, signal)) try {
165
- ctx.emit("evolution/review-scheduled", {
166
- sessionId: session.id,
167
- kind: pendingKind,
168
- toolCalls: signal.toolCalls,
169
- userChars: signal.userChars,
170
- assistantChars: signal.assistantChars
171
- });
172
- } catch (emitError) {
173
- ctx.logger.warn(`dsh-evolution-review: review-scheduled emit failed: ${emitError instanceof Error ? emitError.message : String(emitError)}`);
174
- }
175
- else try {
176
- deliverMessage(agent, reviewPrompt(pendingKind), "auto-review");
177
- } catch (injectError) {
178
- ctx.logger.warn(`dsh-evolution-review: deferred review inject failed: ${injectError instanceof Error ? injectError.message : String(injectError)}`);
194
+ else {
195
+ const reviewOutcome = await trySubagentReview(session, agent, pendingKind, signal);
196
+ if (reviewOutcome === true) try {
197
+ ctx.emit("evolution/review-scheduled", {
198
+ sessionId: session.id,
199
+ kind: pendingKind,
200
+ toolCalls: signal.toolCalls,
201
+ userChars: signal.userChars,
202
+ assistantChars: signal.assistantChars,
203
+ channel: "subagent"
204
+ });
205
+ } catch (emitError) {
206
+ ctx.logger.warn(`dsh-evolution-review: review-scheduled emit failed: ${emitError instanceof Error ? emitError.message : String(emitError)}`);
207
+ }
208
+ else if (reviewOutcome !== "deferred") try {
209
+ deliverMessage(agent, reviewPrompt(pendingKind), "auto-review");
210
+ try {
211
+ ctx.emit("evolution/review-scheduled", {
212
+ sessionId: session.id,
213
+ kind: pendingKind,
214
+ toolCalls: signal.toolCalls,
215
+ userChars: signal.userChars,
216
+ assistantChars: signal.assistantChars,
217
+ channel: "inject"
218
+ });
219
+ } catch (emitError) {
220
+ ctx.logger.warn(`dsh-evolution-review: review-scheduled emit failed: ${emitError instanceof Error ? emitError.message : String(emitError)}`);
221
+ }
222
+ } catch (injectError) {
223
+ ctx.logger.warn(`dsh-evolution-review: deferred review inject failed: ${injectError instanceof Error ? injectError.message : String(injectError)}`);
224
+ }
179
225
  }
180
- state.turnsSinceMemory = 0;
181
- state.turnsSinceSkill = 0;
182
226
  try {
183
- await stateService?.saveReviewState(session.id, state);
227
+ await withReviewStateLock(session.id, async () => {
228
+ const fresh = await stateService?.loadReviewState(session.id) ?? state;
229
+ fresh.turnsSinceMemory = 0;
230
+ fresh.turnsSinceSkill = 0;
231
+ await stateService?.saveReviewState(session.id, fresh);
232
+ });
184
233
  } catch (resetError) {
185
234
  if (!cadenceResetWarned.has(session.id)) {
186
235
  cadenceResetWarned.add(session.id);
@@ -204,6 +253,22 @@ function apply(ctx, rawConfig = {}) {
204
253
  if (completionInjected.has(session.id)) return;
205
254
  if (!shouldCompletionReview(event.data.reason, cumulative, config.skillReviewCompletionMinToolCalls)) return;
206
255
  completionInjected.add(session.id);
256
+ if (reviewInFlight) {
257
+ if (deferredFallbackReviews.length < DEFERRED_REVIEW_CAP) deferredFallbackReviews.push({
258
+ agent,
259
+ sessionId: session.id,
260
+ kind: "skill",
261
+ prompt: COMPLETION_SKILL_REVIEW_PROMPT,
262
+ label: "completion review",
263
+ channel: "completion",
264
+ counts: signal
265
+ });
266
+ else {
267
+ completionInjected.delete(session.id);
268
+ ctx.logger.warn(`dsh-evolution-review: deferred-review queue at cap (${DEFERRED_REVIEW_CAP}) — dropped one deferred completion review prompt; the completion gate re-arms for the next completed turn`);
269
+ }
270
+ return;
271
+ }
207
272
  try {
208
273
  deliverMessage(agent, COMPLETION_SKILL_REVIEW_PROMPT, "completion review");
209
274
  } catch (injectError) {
@@ -217,7 +282,8 @@ function apply(ctx, rawConfig = {}) {
217
282
  kind: "skill",
218
283
  toolCalls: signal.toolCalls,
219
284
  userChars: signal.userChars,
220
- assistantChars: signal.assistantChars
285
+ assistantChars: signal.assistantChars,
286
+ channel: "completion"
221
287
  });
222
288
  } catch (emitError) {
223
289
  ctx.logger.warn(`dsh-evolution-review: review-scheduled emit failed: ${emitError instanceof Error ? emitError.message : String(emitError)}`);
@@ -246,11 +312,40 @@ function apply(ctx, rawConfig = {}) {
246
312
  skipNextCadenceFire.set(agent.session.id, true);
247
313
  } else agent.inject(message);
248
314
  };
315
+ const withTimeout = (promise, ms, label) => new Promise((resolve, reject) => {
316
+ const timer = setTimeout(() => {
317
+ reject(/* @__PURE__ */ new Error(`dsh-evolution-review: ${label} timed out after ${ms}ms`));
318
+ }, ms);
319
+ promise.then((value) => {
320
+ clearTimeout(timer);
321
+ resolve(value);
322
+ }, (error) => {
323
+ clearTimeout(timer);
324
+ reject(error instanceof Error ? error : new Error(String(error)));
325
+ });
326
+ });
327
+ const deferredFallbackReviews = [];
328
+ const DEFERRED_REVIEW_CAP = 16;
329
+ ctx.effect(() => () => {
330
+ deferredFallbackReviews.length = 0;
331
+ }, "dsh-evolution-review.deferred-drain");
249
332
  async function trySubagentReview(session, agent, kind, signal) {
250
333
  if ((policy()?.reviewMode ?? config.reviewMode) === "inject") return false;
251
334
  const subagents = ctx.get("subagents");
252
335
  if (!subagents) return false;
253
- if (reviewInFlight) return false;
336
+ if (reviewInFlight) {
337
+ if (deferredFallbackReviews.length < DEFERRED_REVIEW_CAP) deferredFallbackReviews.push({
338
+ agent,
339
+ sessionId: session.id,
340
+ kind,
341
+ prompt: reviewPrompt(kind),
342
+ label: "auto-review",
343
+ channel: "inject",
344
+ counts: signal
345
+ });
346
+ else ctx.logger.warn(`dsh-evolution-review: deferred-review queue at cap (${DEFERRED_REVIEW_CAP}) — dropping one fallback review prompt`);
347
+ return "deferred";
348
+ }
254
349
  reviewInFlight = true;
255
350
  try {
256
351
  const snapshot = policy();
@@ -296,7 +391,7 @@ function apply(ctx, rawConfig = {}) {
296
391
  });
297
392
  const acceptedSkillOps = validation.accepted.skillOps ?? [];
298
393
  const skippedUnread = filterUnreadSkillOps(acceptedSkillOps, new Set([...collectReadSkillNames(session), ...childReads]));
299
- const executed = await executePlan(validation.accepted, session);
394
+ const executed = await withTimeout(executePlan(validation.accepted, session), config.reviewTimeoutMs, "review plan execution");
300
395
  const actions = executed.actions;
301
396
  const evidenceQuotes = [...validation.accepted.memoryOps ?? [], ...acceptedSkillOps].reduce((total, op) => total + (Array.isArray(op.evidence) ? op.evidence.length : 0), 0);
302
397
  if (actions.length > 0) {
@@ -347,15 +442,43 @@ function apply(ctx, rawConfig = {}) {
347
442
  }
348
443
  } catch (error) {
349
444
  ctx.logger.warn(`dsh-evolution-review: subagent review failed: ${error instanceof Error ? error.message : String(error)}`);
445
+ if (error instanceof Error && error.message.includes("plan execution timed out")) ctx.logger.warn("dsh-evolution-review: plan execution abandoned on timeout — any late write it lands has NO plan-applied record and races the fallback inject; inspect the skill tree and usage sidecar");
350
446
  return false;
351
447
  } finally {
352
448
  reviewInFlight = false;
449
+ const deferred = deferredFallbackReviews.splice(0);
450
+ for (const { agent: waitingAgent, sessionId: entrySession, kind: waitingKind, prompt, label, channel, counts: entryCounts } of deferred) {
451
+ try {
452
+ deliverMessage(waitingAgent, prompt, label);
453
+ } catch (injectError) {
454
+ if (channel === "completion") completionInjected.delete(entrySession);
455
+ ctx.logger.warn(`dsh-evolution-review: deferred review inject failed: ${injectError instanceof Error ? injectError.message : String(injectError)}`);
456
+ continue;
457
+ }
458
+ try {
459
+ ctx.emit("evolution/review-scheduled", {
460
+ sessionId: entrySession,
461
+ kind: waitingKind,
462
+ toolCalls: entryCounts.toolCalls,
463
+ userChars: entryCounts.userChars,
464
+ assistantChars: entryCounts.assistantChars,
465
+ channel
466
+ });
467
+ } catch (emitError) {
468
+ ctx.logger.warn(`dsh-evolution-review: review-scheduled emit failed: ${emitError instanceof Error ? emitError.message : String(emitError)}`);
469
+ }
470
+ }
353
471
  }
354
472
  }
355
473
  async function executePlan(plan, session) {
356
474
  const sessionId = session?.id;
357
475
  const memory = ctx.get("memory");
358
476
  const approval = ctx.get("evolutionApproval");
477
+ const hashLibrary = (() => {
478
+ const io = ctx.get("evolutionIo");
479
+ if (!io) return null;
480
+ return new SkillLibrary(resolveSkillsRoot({ root: rootConfig.root }), evolutionIoAdapter(() => io.provider()));
481
+ })();
359
482
  const origins = resolveOrigins(void 0, true);
360
483
  const actions = [];
361
484
  const failedOps = [];
@@ -390,6 +513,10 @@ function apply(ctx, rawConfig = {}) {
390
513
  ...op,
391
514
  evidence: op.evidence
392
515
  };
516
+ if ((args.action === "update" || args.action === "edit") && hashLibrary && typeof args.name === "string" && args.name !== "") {
517
+ const stageCurrent = await hashLibrary.read(args.name).catch(() => null);
518
+ if (stageCurrent !== null) args.staged_from_sha256 = contentHash(stageCurrent);
519
+ }
393
520
  const runnerArgs = {
394
521
  operation: args,
395
522
  origin: origins.library
@@ -516,6 +643,7 @@ function apply(ctx, rawConfig = {}) {
516
643
  pendingCadenceWarned.clear();
517
644
  skipNextCadenceFire.clear();
518
645
  cadenceResetWarned.clear();
646
+ reviewStateLocks.clear();
519
647
  }, "dsh-evolution-review.cleanup");
520
648
  }
521
649
  /** Completion-channel decision: task finished normally AND the session is proven long. */
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@lmzhen/dsh-evolution-review",
3
3
  "description": "Background review orchestration (community build)",
4
- "version": "0.3.65",
4
+ "version": "0.3.67",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -31,9 +31,9 @@
31
31
  "license": "MIT",
32
32
  "dependencies": {
33
33
  "@deepseek-ai/schemastery": "^3.18.1",
34
- "@lmzhen/dsh-evolution-approval": "^0.3.65",
35
- "@lmzhen/dsh-evolution-core": "^0.3.65",
36
- "@lmzhen/dsh-evolution-plan-validator": "^0.3.65"
34
+ "@lmzhen/dsh-evolution-approval": "^0.3.67",
35
+ "@lmzhen/dsh-evolution-core": "^0.3.67",
36
+ "@lmzhen/dsh-evolution-plan-validator": "^0.3.67"
37
37
  },
38
38
  "peerDependencies": {
39
39
  "@deepseek-ai/cordis": "^4.0.1",
@@ -42,8 +42,8 @@
42
42
  "@deepseek-ai/dsh-llm": "^0.1.1-rc.2",
43
43
  "@deepseek-ai/dsh-session": "^0.1.1-rc.2",
44
44
  "@deepseek-ai/dsh-tools": "^0.1.1-rc.2",
45
- "@lmzhen/dsh-evolution-state": "^0.3.65",
46
- "@lmzhen/dsh-evolution-policy": "^0.3.65"
45
+ "@lmzhen/dsh-evolution-state": "^0.3.67",
46
+ "@lmzhen/dsh-evolution-policy": "^0.3.67"
47
47
  },
48
48
  "devDependencies": {
49
49
  "@deepseek-ai/dsh-agent": "^0.1.1-rc.2",
@@ -54,9 +54,9 @@
54
54
  "@deepseek-ai/dsh-session-persistence": "^0.1.1-rc.2",
55
55
  "@deepseek-ai/dsh-session-persistence-jsonl": "^0.1.1-rc.2",
56
56
  "@deepseek-ai/dsh-tools": "^0.1.1-rc.2",
57
- "@lmzhen/dsh-evolution-approval": "^0.3.65",
58
- "@lmzhen/dsh-evolution-core": "^0.3.65",
59
- "@lmzhen/dsh-evolution-plan-validator": "^0.3.65",
60
- "@lmzhen/dsh-evolution-state": "^0.3.65"
57
+ "@lmzhen/dsh-evolution-approval": "^0.3.67",
58
+ "@lmzhen/dsh-evolution-core": "^0.3.67",
59
+ "@lmzhen/dsh-evolution-plan-validator": "^0.3.67",
60
+ "@lmzhen/dsh-evolution-state": "^0.3.67"
61
61
  }
62
62
  }