@lmzhen/dsh-evolution-curator 0.3.64 → 0.3.66

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
@@ -17,7 +17,7 @@ const DEFAULT_QUALITY_WARN_STALE_AFTER_DAYS = 7;
17
17
  * 120s matches the review subagent default; the 32-bit ceiling is Node's
18
18
  * timer-delay limit (`AbortSignal.timeout` throws above it). */
19
19
  const DEFAULT_CURATOR_REVIEW_TIMEOUT_MS = 12e4;
20
- const MAX_TIMER_DELAY_MS = 4294967295;
20
+ const MAX_TIMER_DELAY_MS = 2147483647;
21
21
  /**
22
22
  * Block LLM-nominated consolidations that would touch a gate-protected name:
23
23
  * exclude / referenced / suppressed skills must never merge (neither as the
@@ -401,6 +401,17 @@ var EvolutionCurator = class extends Service {
401
401
  * increments `mutexDepth` synchronously (so run()'s skip check sees queued
402
402
  * work), awaits the previous tail, and the returned release resolves the
403
403
  * tail for the next entrant. Double-release is a no-op.
404
+ *
405
+ * v20 (C-3) cross-layer note: this chain serializes CURATOR operations
406
+ * only. The review subagent channel writes through its own SkillLibrary
407
+ * and shares NO mutex with this service; the cross-layer exclusion is
408
+ * skill-store's per-file write lock + the F-17 marker probe (a destructive
409
+ * mover refuses a directory whose writer lock is alive), so a collision
410
+ * degrades to a recorded failed op + snapshot rollback, never a torn
411
+ * write. Automatic passes are kept out of the session-active window by the
412
+ * min-idle gate; a manual run (`ignoreGates`) bypasses that gate and is
413
+ * the one realistic interleave window — documented, accepted (plan v20
414
+ * C-3①). The same statement lives on evolution-review's `reviewInFlight`.
404
415
  */
405
416
  mutexDepth = 0;
406
417
  mutexTail = Promise.resolve();
@@ -449,13 +460,17 @@ var EvolutionCurator = class extends Service {
449
460
  skipped: "active-session"
450
461
  };
451
462
  if (!ignoreGates && persisted === null && (stateService !== void 0 || !this.statelessFirstRunDeferred)) {
452
- if (stateService) await stateService.saveCuratorState({
453
- schemaVersion: 1,
454
- lastRunAt: Date.now(),
455
- runCount: 0,
456
- lastSummary: "first-run-deferred",
457
- paused: false
458
- });
463
+ if (stateService) try {
464
+ await stateService.saveCuratorState({
465
+ schemaVersion: 1,
466
+ lastRunAt: Date.now(),
467
+ runCount: 0,
468
+ lastSummary: "first-run-deferred",
469
+ paused: false
470
+ });
471
+ } catch (error) {
472
+ this.ctx.logger.warn(`evolution-curator: failed to persist the first-run baseline: ${error instanceof Error ? error.message : String(error)}`);
473
+ }
459
474
  else {
460
475
  this.lastRun = Date.now();
461
476
  this.statelessFirstRunDeferred = true;
@@ -472,6 +487,16 @@ var EvolutionCurator = class extends Service {
472
487
  const rawUsage = await loadUsage(root, this.io);
473
488
  const usage = dryRun ? new Map([...rawUsage].map(([name, record]) => [name, { ...record }])) : rawUsage;
474
489
  const runStartStates = new Map([...usage].map(([name, record]) => [name, record.state]));
490
+ if (this.isDisposed()) {
491
+ this.ctx.logger.warn("evolution-curator: run aborted before the archive phase (plugin disposed mid-run)");
492
+ return {
493
+ stale: [],
494
+ archived: [],
495
+ errors: ["run aborted: evolution-curator was disposed mid-run"],
496
+ report: this.skippedReport(runId, startedAt),
497
+ skipped: "disposed"
498
+ };
499
+ }
475
500
  const snapshotPath = dryRun ? void 0 : await this.snapshotFull("pre-curator-run");
476
501
  const suppressedNames = new Set(await loadSuppressedNames(root, this.io));
477
502
  const gates = new EvolutionGateSet({
@@ -658,6 +683,15 @@ var EvolutionCurator = class extends Service {
658
683
  let suppressedChanged = false;
659
684
  const suppressedAdded = /* @__PURE__ */ new Set();
660
685
  const stateOwned = new Set(input.stateOwned ?? []);
686
+ if (this.isDisposed()) {
687
+ this.ctx.logger.warn("evolution-curator: run aborted at the archive gate (plugin disposed mid-run)");
688
+ return {
689
+ archivedSkills: [],
690
+ errors: ["run aborted: evolution-curator was disposed mid-run"],
691
+ suppressedChanged: false,
692
+ consolidated: []
693
+ };
694
+ }
661
695
  for (const name of archiveCandidates) {
662
696
  if (!treeNames.has(name)) {
663
697
  const record = usage.get(name);
@@ -725,7 +759,13 @@ var EvolutionCurator = class extends Service {
725
759
  }
726
760
  }
727
761
  const alreadyArchived = new Set(archiveCandidates);
728
- for (const nomination of nominations.consolidations) {
762
+ let consolidationDisposed = false;
763
+ if (this.isDisposed()) {
764
+ consolidationDisposed = true;
765
+ errors.push("run aborted: evolution-curator was disposed mid-run — consolidation skipped; archives that landed above are still accounted");
766
+ this.ctx.logger.warn("evolution-curator: consolidation phase skipped (plugin disposed mid-run); archive accounts above are preserved");
767
+ }
768
+ for (const nomination of consolidationDisposed ? [] : nominations.consolidations) {
729
769
  if (alreadyArchived.has(nomination.from)) continue;
730
770
  if (!treeNames.has(nomination.from) || !treeNames.has(nomination.into)) {
731
771
  errors.push(`${nomination.from}: consolidation target or source missing from the skill tree`);
@@ -768,7 +808,9 @@ var EvolutionCurator = class extends Service {
768
808
  let skipped = [];
769
809
  await mutateUsage(root, this.io, (disk) => {
770
810
  skipped = foldCuratorFields(disk, usage, stateOwned, runStartStates);
771
- });
811
+ }, { onQuarantine: (message) => {
812
+ this.ctx.logger.warn(`evolution-curator: ${message}`);
813
+ } });
772
814
  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(", ")}`);
773
815
  } catch {
774
816
  this.ctx.logger.warn("evolution-curator: failed to persist usage sidecar");
@@ -974,7 +1016,9 @@ var EvolutionCurator = class extends Service {
974
1016
  record.archived_at = (/* @__PURE__ */ new Date()).toISOString();
975
1017
  }
976
1018
  }
977
- });
1019
+ }, { onQuarantine: (message) => {
1020
+ this.ctx.logger.warn(`evolution-curator: ${message}`);
1021
+ } });
978
1022
  } catch (error) {
979
1023
  this.ctx.logger.warn(`evolution-curator: failed to persist consolidate usage state: ${error instanceof Error ? error.message : String(error)}`);
980
1024
  }
@@ -1003,7 +1047,9 @@ var EvolutionCurator = class extends Service {
1003
1047
  record.state = "active";
1004
1048
  record.archived_at = null;
1005
1049
  }
1006
- });
1050
+ }, { onQuarantine: (message) => {
1051
+ this.ctx.logger.warn(`evolution-curator: ${message}`);
1052
+ } });
1007
1053
  } catch (error) {
1008
1054
  this.ctx.logger.warn(`evolution-curator: failed to persist restore usage state: ${error instanceof Error ? error.message : String(error)}`);
1009
1055
  }
@@ -214,6 +214,17 @@ export declare class EvolutionCurator extends Service {
214
214
  * increments `mutexDepth` synchronously (so run()'s skip check sees queued
215
215
  * work), awaits the previous tail, and the returned release resolves the
216
216
  * tail for the next entrant. Double-release is a no-op.
217
+ *
218
+ * v20 (C-3) cross-layer note: this chain serializes CURATOR operations
219
+ * only. The review subagent channel writes through its own SkillLibrary
220
+ * and shares NO mutex with this service; the cross-layer exclusion is
221
+ * skill-store's per-file write lock + the F-17 marker probe (a destructive
222
+ * mover refuses a directory whose writer lock is alive), so a collision
223
+ * degrades to a recorded failed op + snapshot rollback, never a torn
224
+ * write. Automatic passes are kept out of the session-active window by the
225
+ * min-idle gate; a manual run (`ignoreGates`) bypasses that gate and is
226
+ * the one realistic interleave window — documented, accepted (plan v20
227
+ * C-3①). The same statement lives on evolution-review's `reviewInFlight`.
217
228
  */
218
229
  private mutexDepth;
219
230
  private mutexTail;
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.64",
4
+ "version": "0.3.66",
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.64"
34
+ "@lmzhen/dsh-evolution-core": "^0.3.66"
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.64",
41
- "@lmzhen/dsh-evolution-state": "^0.3.64"
40
+ "@lmzhen/dsh-evolution-io": "^0.3.66",
41
+ "@lmzhen/dsh-evolution-state": "^0.3.66"
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.64",
47
- "@lmzhen/dsh-evolution-io": "^0.3.64",
48
- "@lmzhen/dsh-evolution-state": "^0.3.64"
46
+ "@lmzhen/dsh-evolution-core": "^0.3.66",
47
+ "@lmzhen/dsh-evolution-io": "^0.3.66",
48
+ "@lmzhen/dsh-evolution-state": "^0.3.66"
49
49
  }
50
50
  }