@lmzhen/dsh-evolution-curator 0.3.62 → 0.3.64

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 CHANGED
@@ -11,6 +11,13 @@ import { CURATOR_DRY_RUN_BANNER, CURATOR_PROMPT, DEFAULT_ARCHIVE_AFTER_DAYS, DEF
11
11
  */
12
12
  /** Quality-warned skills may turn stale after this many idle days (package-private tunable, P2-8). */
13
13
  const DEFAULT_QUALITY_WARN_STALE_AFTER_DAYS = 7;
14
+ /** B-10 (v18): the optional LLM nomination pass gets its own timeout so a
15
+ * hung/unresponsive provider cannot hold the control-plane mutex forever
16
+ * (`run()` never returns; `restore()`/`consolidate()` queue behind it).
17
+ * 120s matches the review subagent default; the 32-bit ceiling is Node's
18
+ * timer-delay limit (`AbortSignal.timeout` throws above it). */
19
+ const DEFAULT_CURATOR_REVIEW_TIMEOUT_MS = 12e4;
20
+ const MAX_TIMER_DELAY_MS = 4294967295;
14
21
  /**
15
22
  * Block LLM-nominated consolidations that would touch a gate-protected name:
16
23
  * exclude / referenced / suppressed skills must never merge (neither as the
@@ -41,6 +48,7 @@ var EvolutionCurator = class extends Service {
41
48
  autoStart: z.boolean().default(true),
42
49
  bootGraceSeconds: z.number().min(0).default(DEFAULT_CURATOR_BOOT_GRACE_SECONDS),
43
50
  curatorReviewMaxTokens: z.number().min(1).default(DEFAULT_CURATOR_REVIEW_MAX_TOKENS),
51
+ curatorReviewTimeoutMs: z.number().min(1).max(MAX_TIMER_DELAY_MS).default(DEFAULT_CURATOR_REVIEW_TIMEOUT_MS),
44
52
  healthSoftBodyChars: z.number().min(1).default(DEFAULT_HEALTH_THRESHOLDS.softBodyChars),
45
53
  healthStampDensityPerKb: z.number().min(1).default(DEFAULT_HEALTH_THRESHOLDS.stampDensityPerKb),
46
54
  healthChurnMinPatches: z.number().min(1).default(DEFAULT_HEALTH_THRESHOLDS.churnMinPatches)
@@ -62,13 +70,23 @@ var EvolutionCurator = class extends Service {
62
70
  referencedSkillNames;
63
71
  bootGraceSeconds;
64
72
  curatorReviewMaxTokens;
73
+ curatorReviewTimeoutMs;
65
74
  healthSoftBodyChars;
66
75
  healthStampDensityPerKb;
67
76
  healthChurnMinPatches;
68
77
  lastRun = 0;
69
78
  timer;
79
+ /** B-8 (v18): set by the fiber disposer; a triggered autoCheck must not
80
+ * keep mutating the tree after the plugin was disposed. */
81
+ disposed = false;
82
+ /** B-8 (v18): read the disposal flag through a method. The only assignment
83
+ * lives in the disposer closure, which TypeScript's flow analysis cannot
84
+ * see, so a direct `this.disposed` read narrows to the literal `false` and
85
+ * the runtime check would be reported as dead code by `no-unnecessary-condition`. */
86
+ isDisposed() {
87
+ return this.disposed;
88
+ }
70
89
  bootCheck;
71
- running = false;
72
90
  /** 0.3.18 (E-18): stateless first-run defer fires ONCE per process — the
73
91
  * in-memory clock is seeded and later due runs must proceed, or the
74
92
  * persisted===null defer repeats forever (no state service to persist). */
@@ -83,14 +101,21 @@ var EvolutionCurator = class extends Service {
83
101
  });
84
102
  this.enabled = config.enabled ?? true;
85
103
  const clamped = [];
86
- const field = (name, value, fallback, min) => {
87
- const result = clampedNumber(value, fallback, { min });
104
+ const field = (name, value, fallback, min, max) => {
105
+ const result = clampedNumber(value, fallback, max === void 0 ? { min } : {
106
+ min,
107
+ max
108
+ });
88
109
  if (value !== void 0 && result !== value) clamped.push(name);
89
110
  return result;
90
111
  };
91
112
  this.intervalHours = field("intervalHours", config.intervalHours, DEFAULT_CURATOR_INTERVAL_HOURS, 1);
92
113
  this.staleAfterDays = field("staleAfterDays", config.staleAfterDays, DEFAULT_STALE_AFTER_DAYS, 1);
93
114
  this.archiveAfterDays = field("archiveAfterDays", config.archiveAfterDays, DEFAULT_ARCHIVE_AFTER_DAYS, 1);
115
+ if (this.archiveAfterDays < this.staleAfterDays) {
116
+ this.ctx.logger.warn(`evolution-curator: archiveAfterDays (${this.archiveAfterDays}) < staleAfterDays (${this.staleAfterDays}); using staleAfterDays as the archive threshold`);
117
+ this.archiveAfterDays = this.staleAfterDays;
118
+ }
94
119
  this.llmReview = config.llmReview ?? false;
95
120
  this.curatorProvider = config.curatorProvider ?? "deepseek-official";
96
121
  this.qualityWarnStaleAfterDays = field("qualityWarnStaleAfterDays", config.qualityWarnStaleAfterDays, DEFAULT_QUALITY_WARN_STALE_AFTER_DAYS, 1);
@@ -100,8 +125,9 @@ var EvolutionCurator = class extends Service {
100
125
  this.manageUnmanaged = config.manageUnmanaged ?? false;
101
126
  this.pruneBuiltins = config.pruneBuiltins ?? false;
102
127
  this.referencedSkillNames = new Set(config.referencedSkillNames ?? []);
103
- this.bootGraceSeconds = field("bootGraceSeconds", config.bootGraceSeconds, DEFAULT_CURATOR_BOOT_GRACE_SECONDS, 0);
128
+ this.bootGraceSeconds = field("bootGraceSeconds", config.bootGraceSeconds, DEFAULT_CURATOR_BOOT_GRACE_SECONDS, 0, 3600);
104
129
  this.curatorReviewMaxTokens = field("curatorReviewMaxTokens", config.curatorReviewMaxTokens, DEFAULT_CURATOR_REVIEW_MAX_TOKENS, 1);
130
+ this.curatorReviewTimeoutMs = field("curatorReviewTimeoutMs", config.curatorReviewTimeoutMs, DEFAULT_CURATOR_REVIEW_TIMEOUT_MS, 1, MAX_TIMER_DELAY_MS);
105
131
  this.healthSoftBodyChars = field("healthSoftBodyChars", config.healthSoftBodyChars, DEFAULT_HEALTH_THRESHOLDS.softBodyChars, 1);
106
132
  this.healthStampDensityPerKb = field("healthStampDensityPerKb", config.healthStampDensityPerKb, DEFAULT_HEALTH_THRESHOLDS.stampDensityPerKb, 1);
107
133
  this.healthChurnMinPatches = field("healthChurnMinPatches", config.healthChurnMinPatches, DEFAULT_HEALTH_THRESHOLDS.churnMinPatches, 1);
@@ -109,6 +135,7 @@ var EvolutionCurator = class extends Service {
109
135
  this.lastRun = Date.now();
110
136
  this.ctx.effect(() => {
111
137
  return () => {
138
+ this.disposed = true;
112
139
  this.stop();
113
140
  };
114
141
  }, "evolution-curator.stop");
@@ -123,7 +150,7 @@ var EvolutionCurator = class extends Service {
123
150
  };
124
151
  }
125
152
  start() {
126
- if (!this.enabled || this.timer) return;
153
+ if (this.isDisposed() || !this.enabled || this.timer) return;
127
154
  this.bootCheck = setTimeout(() => {
128
155
  this.bootCheck = void 0;
129
156
  this.autoCheck();
@@ -181,13 +208,16 @@ var EvolutionCurator = class extends Service {
181
208
  * surfaced once instead of silently meaning "never runs".
182
209
  */
183
210
  async autoCheck() {
211
+ if (this.isDisposed()) return;
184
212
  try {
185
213
  const stateService = this.curatorStateService();
186
214
  if (stateService === void 0 && !this.statelessStateWarned) {
187
215
  this.statelessStateWarned = true;
188
- this.ctx.logger.warn("evolution-curator: evolution-state is not mounted — the curation interval baseline is this process's lifetime only (default interval 168h), so automatic curation will not fire again until the process has been alive that long. Mount evolution-state (evolution-host/all bundle) for a durable schedule.");
216
+ this.ctx.logger.warn(`evolution-curator: evolution-state is not mounted — the curation interval baseline is this process's lifetime only (default interval ${DEFAULT_CURATOR_INTERVAL_HOURS}h), so automatic curation will not fire again until the process has been alive that long. Mount evolution-state (evolution-host/all bundle) for a durable schedule.`);
189
217
  }
190
- const last = (await stateService?.loadCuratorState())?.lastRunAt ?? this.lastRun;
218
+ const persisted = await stateService?.loadCuratorState();
219
+ if (this.isDisposed()) return;
220
+ const last = persisted?.lastRunAt ?? this.lastRun;
191
221
  if (Date.now() - last >= this.lifecycle().intervalHours * 36e5) await this.run();
192
222
  } catch (error) {
193
223
  const reason = error instanceof Error ? error.message : String(error);
@@ -256,7 +286,8 @@ var EvolutionCurator = class extends Service {
256
286
  summary: "curator review"
257
287
  }
258
288
  })],
259
- maxTokens: this.curatorReviewMaxTokens
289
+ maxTokens: this.curatorReviewMaxTokens,
290
+ signal: AbortSignal.timeout(this.curatorReviewTimeoutMs)
260
291
  })) assembler.push(chunk);
261
292
  const parsed = parseCuratorNominations(assembler.blocks().filter((block) => block.type === "text").map((block) => block.text).join("\n"));
262
293
  return {
@@ -294,6 +325,14 @@ var EvolutionCurator = class extends Service {
294
325
  * is reversible.
295
326
  */
296
327
  async restoreSnapshot() {
328
+ const release = await this.acquireMutex();
329
+ try {
330
+ return await this.restoreSnapshotCore();
331
+ } finally {
332
+ release();
333
+ }
334
+ }
335
+ async restoreSnapshotCore() {
297
336
  const stateService = this.curatorStateService();
298
337
  const currentState = await stateService?.loadCuratorState();
299
338
  const extras = currentState === null || currentState === void 0 ? [] : [{
@@ -332,18 +371,18 @@ var EvolutionCurator = class extends Service {
332
371
  * skipped with an explicit `already-running` outcome.
333
372
  */
334
373
  async run(options = {}) {
335
- if (this.running) return {
374
+ if (this.mutexDepth > 0) return {
336
375
  stale: [],
337
376
  archived: [],
338
377
  errors: [],
339
378
  report: this.skippedReport("already-running", (/* @__PURE__ */ new Date()).toISOString()),
340
379
  skipped: "already-running"
341
380
  };
342
- this.running = true;
381
+ const release = await this.acquireMutex();
343
382
  try {
344
383
  return await this.runCore(options);
345
384
  } finally {
346
- this.running = false;
385
+ release();
347
386
  try {
348
387
  await this.retainReports();
349
388
  } catch (error) {
@@ -351,6 +390,36 @@ var EvolutionCurator = class extends Service {
351
390
  }
352
391
  }
353
392
  }
393
+ /**
394
+ * P1 (v16): the control-plane mutex — ONE promise chain serializing run(),
395
+ * restore() and consolidate(). Replaces the v15 draft (`running` flag +
396
+ * `runSettled` polling), which could (a) spin forever on an
397
+ * already-resolved promise while a control-plane mutator held the flag
398
+ * (micro-task starvation: the flag's reset lives behind IO the spun loop
399
+ * never lets run) and (b) let two queued waiters wake into the same idle
400
+ * window and mutate concurrently. Here the chain IS the mutex: an entrant
401
+ * increments `mutexDepth` synchronously (so run()'s skip check sees queued
402
+ * work), awaits the previous tail, and the returned release resolves the
403
+ * tail for the next entrant. Double-release is a no-op.
404
+ */
405
+ mutexDepth = 0;
406
+ mutexTail = Promise.resolve();
407
+ acquireMutex() {
408
+ this.mutexDepth += 1;
409
+ const prev = this.mutexTail;
410
+ let releaseMutex;
411
+ this.mutexTail = new Promise((resolve) => {
412
+ releaseMutex = resolve;
413
+ });
414
+ let released = false;
415
+ const release = () => {
416
+ if (released) return;
417
+ released = true;
418
+ this.mutexDepth -= 1;
419
+ releaseMutex();
420
+ };
421
+ return prev.then(() => release);
422
+ }
354
423
  async runCore(options = {}) {
355
424
  const { ignoreGates = false, dryRun = false } = options;
356
425
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -402,6 +471,7 @@ var EvolutionCurator = class extends Service {
402
471
  const root = this.skills.root;
403
472
  const rawUsage = await loadUsage(root, this.io);
404
473
  const usage = dryRun ? new Map([...rawUsage].map(([name, record]) => [name, { ...record }])) : rawUsage;
474
+ const runStartStates = new Map([...usage].map(([name, record]) => [name, record.state]));
405
475
  const snapshotPath = dryRun ? void 0 : await this.snapshotFull("pre-curator-run");
406
476
  const suppressedNames = new Set(await loadSuppressedNames(root, this.io));
407
477
  const gates = new EvolutionGateSet({
@@ -453,6 +523,7 @@ var EvolutionCurator = class extends Service {
453
523
  root,
454
524
  recommendPool: new Set(recommendPool),
455
525
  stateOwned: new Set([...result.transitions.map((t) => t.name), ...archiveCandidates]),
526
+ runStartStates,
456
527
  failedFrom: new Map(result.transitions.filter((t) => t.to === "archived").map((t) => [t.name, t.from]))
457
528
  });
458
529
  if (!dryRun) this.lastRun = Date.now();
@@ -485,16 +556,20 @@ var EvolutionCurator = class extends Service {
485
556
  }
486
557
  const llmHint = !this.llmReview && result.markStale.length > 0 ? " (llmReview: off - deterministic archive only; set llmReview: true for the LLM merge channel)" : "";
487
558
  const summary = `${dryRun ? "dry-run" : "auto"}: stale:${result.markStale.length} archived:${archivedSkills.length} consolidated:${gatedNominations.consolidations.length}${llmHint}`;
488
- await stateService?.transactCuratorState((current) => {
489
- const pausedNow = current?.paused ?? false;
490
- return {
491
- schemaVersion: 1,
492
- lastRunAt: dryRun ? persisted?.lastRunAt ?? this.lastRun : Date.now(),
493
- runCount: dryRun ? persisted?.runCount ?? 0 : (current?.runCount ?? 0) + 1,
494
- lastSummary: summary,
495
- paused: pausedNow
496
- };
497
- });
559
+ try {
560
+ await stateService?.transactCuratorState((current) => {
561
+ const pausedNow = current?.paused ?? false;
562
+ return {
563
+ schemaVersion: 1,
564
+ lastRunAt: dryRun ? persisted?.lastRunAt ?? this.lastRun : Date.now(),
565
+ runCount: dryRun ? persisted?.runCount ?? 0 : (current?.runCount ?? 0) + 1,
566
+ lastSummary: summary,
567
+ paused: pausedNow
568
+ };
569
+ });
570
+ } catch (error) {
571
+ this.ctx.logger.warn(`evolution-curator: failed to persist run bookkeeping: ${error instanceof Error ? error.message : String(error)}`);
572
+ }
498
573
  return {
499
574
  stale: result.markStale,
500
575
  archived: archivedSkills.map((item) => item.name),
@@ -526,6 +601,11 @@ var EvolutionCurator = class extends Service {
526
601
  }
527
602
  /**
528
603
  * F13 six-factor quality scoring, persisted onto the usage records.
604
+ * P1-1 (v15): these are the CURATOR-owned fields (`quality_score`/
605
+ * `quality_warn`) — this method must never touch the feedback-owned
606
+ * `feedback_*` pair (the lifecycle engine reads the union of both warn
607
+ * flags, so overwriting feedback here is what used to make negative
608
+ * feedback decision-irrelevant; field ownership on `UsageRecord`).
529
609
  */
530
610
  async scoreTree(usage, treeNames) {
531
611
  const supportDirs = /* @__PURE__ */ new Map();
@@ -571,6 +651,7 @@ var EvolutionCurator = class extends Service {
571
651
  consolidated: []
572
652
  };
573
653
  const { archiveCandidates, nominations, treeNames, usage, bundledNames, suppressedNames, root, failedFrom } = input;
654
+ const runStartStates = input.runStartStates;
574
655
  const errors = [];
575
656
  const archivedSkills = [];
576
657
  const executedConsolidations = [];
@@ -684,9 +765,11 @@ var EvolutionCurator = class extends Service {
684
765
  this.ctx.logger.warn("evolution-curator: failed to persist suppressed names; archived built-ins may re-enter the lifecycle");
685
766
  }
686
767
  try {
768
+ let skipped = [];
687
769
  await mutateUsage(root, this.io, (disk) => {
688
- foldCuratorFields(disk, usage, stateOwned);
770
+ skipped = foldCuratorFields(disk, usage, stateOwned, runStartStates);
689
771
  });
772
+ if (skipped.length > 0) this.ctx.logger.warn(`evolution-curator: lifecycle fold skipped ${skipped.length} name(s) whose on-disk state moved during the run (a concurrent curator/tool won): ${skipped.join(", ")}`);
690
773
  } catch {
691
774
  this.ctx.logger.warn("evolution-curator: failed to persist usage sidecar");
692
775
  }
@@ -850,7 +933,8 @@ var EvolutionCurator = class extends Service {
850
933
  /** marker info (pinned/bundled/hub-installed) per skill, from the library list. */
851
934
  async protectedNameMap() {
852
935
  const map = /* @__PURE__ */ new Map();
853
- for (const summary of await this.skills.list()) if (summary.protectedBy !== null) map.set(summary.name, summary.protectedBy);
936
+ for (const summary of await this.skills.list()) if (summary.protectionUnknown) map.set(summary.name, "unknown");
937
+ else if (summary.protectedBy !== null) map.set(summary.name, summary.protectedBy);
854
938
  return map;
855
939
  }
856
940
  /**
@@ -859,6 +943,14 @@ var EvolutionCurator = class extends Service {
859
943
  * records into `archived` state. Snapshot-then-mutate, never a hard delete.
860
944
  */
861
945
  async consolidate(target, sources) {
946
+ const release = await this.acquireMutex();
947
+ try {
948
+ return await this.consolidateMutate(target, sources);
949
+ } finally {
950
+ release();
951
+ }
952
+ }
953
+ async consolidateMutate(target, sources) {
862
954
  const suppressedNames = new Set(await loadSuppressedNames(this.skills.root, this.io));
863
955
  const gates = new EvolutionGateSet({
864
956
  exclude: this.excludeSkillNames,
@@ -873,15 +965,19 @@ var EvolutionCurator = class extends Service {
873
965
  await this.snapshotFull("pre-consolidate");
874
966
  const result = await this.skills.consolidate(target, sources);
875
967
  if (!result.ok) return result;
876
- await mutateUsage(this.skills.root, this.io, (disk) => {
877
- for (const source of sources) {
878
- const record = disk.get(source);
879
- if (record) {
880
- record.state = "archived";
881
- record.archived_at = (/* @__PURE__ */ new Date()).toISOString();
968
+ try {
969
+ await mutateUsage(this.skills.root, this.io, (disk) => {
970
+ for (const source of sources) {
971
+ const record = disk.get(source);
972
+ if (record) {
973
+ record.state = "archived";
974
+ record.archived_at = (/* @__PURE__ */ new Date()).toISOString();
975
+ }
882
976
  }
883
- }
884
- });
977
+ });
978
+ } catch (error) {
979
+ this.ctx.logger.warn(`evolution-curator: failed to persist consolidate usage state: ${error instanceof Error ? error.message : String(error)}`);
980
+ }
885
981
  return result;
886
982
  }
887
983
  /**
@@ -889,16 +985,28 @@ var EvolutionCurator = class extends Service {
889
985
  * and reset its usage state, keeping the recoverable-archive invariant.
890
986
  */
891
987
  async restore(name) {
988
+ const release = await this.acquireMutex();
989
+ try {
990
+ return await this.restoreMutate(name);
991
+ } finally {
992
+ release();
993
+ }
994
+ }
995
+ async restoreMutate(name) {
892
996
  await this.snapshotFull("pre-restore");
893
997
  const result = await this.skills.restoreFromArchive(name);
894
998
  if (!result.ok) return result;
895
- await mutateUsage(this.skills.root, this.io, (disk) => {
896
- const record = disk.get(name);
897
- if (record) {
898
- record.state = "active";
899
- record.archived_at = null;
900
- }
901
- });
999
+ try {
1000
+ await mutateUsage(this.skills.root, this.io, (disk) => {
1001
+ const record = disk.get(name);
1002
+ if (record) {
1003
+ record.state = "active";
1004
+ record.archived_at = null;
1005
+ }
1006
+ });
1007
+ } catch (error) {
1008
+ this.ctx.logger.warn(`evolution-curator: failed to persist restore usage state: ${error instanceof Error ? error.message : String(error)}`);
1009
+ }
902
1010
  if (new Set(await loadSuppressedNames(this.skills.root, this.io)).has(name)) try {
903
1011
  await updateSuppressedNames(this.skills.root, this.io, (current) => {
904
1012
  current.delete(name);
@@ -47,6 +47,8 @@ export interface Config {
47
47
  bootGraceSeconds?: number;
48
48
  /** Max tokens for the optional LLM nomination pass. */
49
49
  curatorReviewMaxTokens?: number;
50
+ /** Timeout (ms) for the optional LLM nomination pass (B-10, v18). */
51
+ curatorReviewTimeoutMs?: number;
50
52
  /** Structure-health soft body limit (chars) — see DEFAULT_HEALTH_THRESHOLDS (rc.73 A1). */
51
53
  healthSoftBodyChars?: number;
52
54
  /** Structure-health stamp-density ceiling per KB — see DEFAULT_HEALTH_THRESHOLDS. */
@@ -104,13 +106,21 @@ export declare class EvolutionCurator extends Service {
104
106
  private readonly referencedSkillNames;
105
107
  private readonly bootGraceSeconds;
106
108
  private readonly curatorReviewMaxTokens;
109
+ private readonly curatorReviewTimeoutMs;
107
110
  private readonly healthSoftBodyChars;
108
111
  private readonly healthStampDensityPerKb;
109
112
  private readonly healthChurnMinPatches;
110
113
  private lastRun;
111
114
  private timer;
115
+ /** B-8 (v18): set by the fiber disposer; a triggered autoCheck must not
116
+ * keep mutating the tree after the plugin was disposed. */
117
+ private disposed;
118
+ /** B-8 (v18): read the disposal flag through a method. The only assignment
119
+ * lives in the disposer closure, which TypeScript's flow analysis cannot
120
+ * see, so a direct `this.disposed` read narrows to the literal `false` and
121
+ * the runtime check would be reported as dead code by `no-unnecessary-condition`. */
122
+ private isDisposed;
112
123
  private bootCheck;
113
- private running;
114
124
  /** 0.3.18 (E-18): stateless first-run defer fires ONCE per process — the
115
125
  * in-memory clock is seeded and later due runs must proceed, or the
116
126
  * persisted===null defer repeats forever (no state service to persist). */
@@ -179,6 +189,7 @@ export declare class EvolutionCurator extends Service {
179
189
  content: string;
180
190
  }>;
181
191
  }>;
192
+ private restoreSnapshotCore;
182
193
  private skippedReport;
183
194
  /**
184
195
  * Run one curator pass. `ignoreGates` skips the interval and idle gates so an
@@ -192,6 +203,21 @@ export declare class EvolutionCurator extends Service {
192
203
  ignoreGates?: boolean;
193
204
  dryRun?: boolean;
194
205
  }): Promise<CuratorRunOutcome>;
206
+ /**
207
+ * P1 (v16): the control-plane mutex — ONE promise chain serializing run(),
208
+ * restore() and consolidate(). Replaces the v15 draft (`running` flag +
209
+ * `runSettled` polling), which could (a) spin forever on an
210
+ * already-resolved promise while a control-plane mutator held the flag
211
+ * (micro-task starvation: the flag's reset lives behind IO the spun loop
212
+ * never lets run) and (b) let two queued waiters wake into the same idle
213
+ * window and mutate concurrently. Here the chain IS the mutex: an entrant
214
+ * increments `mutexDepth` synchronously (so run()'s skip check sees queued
215
+ * work), awaits the previous tail, and the returned release resolves the
216
+ * tail for the next entrant. Double-release is a no-op.
217
+ */
218
+ private mutexDepth;
219
+ private mutexTail;
220
+ private acquireMutex;
195
221
  private runCore;
196
222
  /**
197
223
  * Seed baseline records for tree skills the sidecar has not seen yet, so
@@ -202,6 +228,11 @@ export declare class EvolutionCurator extends Service {
202
228
  private seedBaseline;
203
229
  /**
204
230
  * F13 six-factor quality scoring, persisted onto the usage records.
231
+ * P1-1 (v15): these are the CURATOR-owned fields (`quality_score`/
232
+ * `quality_warn`) — this method must never touch the feedback-owned
233
+ * `feedback_*` pair (the lifecycle engine reads the union of both warn
234
+ * flags, so overwriting feedback here is what used to make negative
235
+ * feedback decision-irrelevant; field ownership on `UsageRecord`).
205
236
  */
206
237
  private scoreTree;
207
238
  /**
@@ -262,11 +293,13 @@ export declare class EvolutionCurator extends Service {
262
293
  * records into `archived` state. Snapshot-then-mutate, never a hard delete.
263
294
  */
264
295
  consolidate(target: string, sources: string[]): Promise<SkillActionResult>;
296
+ private consolidateMutate;
265
297
  /**
266
298
  * Control-plane restore: bring one archived skill back to the active root
267
299
  * and reset its usage state, keeping the recoverable-archive invariant.
268
300
  */
269
301
  restore(name: string): Promise<SkillActionResult>;
302
+ private restoreMutate;
270
303
  }
271
304
  export default EvolutionCurator;
272
305
  //# sourceMappingURL=index.d.ts.map
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.3.62",
4
+ "version": "0.3.64",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -31,20 +31,20 @@
31
31
  "license": "MIT",
32
32
  "dependencies": {
33
33
  "@deepseek-ai/schemastery": "^3.18.1",
34
- "@lmzhen/dsh-evolution-core": "^0.3.62"
34
+ "@lmzhen/dsh-evolution-core": "^0.3.64"
35
35
  },
36
36
  "peerDependencies": {
37
37
  "@deepseek-ai/cordis": "^4.0.1",
38
38
  "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2",
39
39
  "@deepseek-ai/dsh-llm": "^0.1.1-rc.2",
40
- "@lmzhen/dsh-evolution-io": "^0.3.62",
41
- "@lmzhen/dsh-evolution-state": "^0.3.62"
40
+ "@lmzhen/dsh-evolution-io": "^0.3.64",
41
+ "@lmzhen/dsh-evolution-state": "^0.3.64"
42
42
  },
43
43
  "devDependencies": {
44
44
  "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2",
45
45
  "@deepseek-ai/dsh-llm": "^0.1.1-rc.2",
46
- "@lmzhen/dsh-evolution-core": "^0.3.62",
47
- "@lmzhen/dsh-evolution-io": "^0.3.62",
48
- "@lmzhen/dsh-evolution-state": "^0.3.62"
46
+ "@lmzhen/dsh-evolution-core": "^0.3.64",
47
+ "@lmzhen/dsh-evolution-io": "^0.3.64",
48
+ "@lmzhen/dsh-evolution-state": "^0.3.64"
49
49
  }
50
50
  }