@lmzhen/dsh-evolution-curator 0.3.68 → 0.3.70

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
@@ -408,7 +408,9 @@ var EvolutionCurator = class extends Service {
408
408
  * mover refuses a directory whose writer lock is alive), so a collision
409
409
  * degrades to a recorded failed op + snapshot rollback, never a torn
410
410
  * write. Automatic passes are kept out of the session-active window by the
411
- * min-idle gate; a manual run (`ignoreGates`) bypasses that gate and is
411
+ * min-idle gate checked pre-run AND re-checked at the commit boundary
412
+ * (v28 G4.2: a session activating mid-run no longer slips past it); a
413
+ * manual run (`ignoreGates`) bypasses both checks and is
412
414
  * the one realistic interleave window — documented, accepted (plan v20
413
415
  * C-3①). The same statement lives on evolution-review's `reviewInFlight`.
414
416
  */
@@ -496,14 +498,13 @@ var EvolutionCurator = class extends Service {
496
498
  skipped: "disposed"
497
499
  };
498
500
  }
499
- const snapshotPath = dryRun ? void 0 : await this.snapshotFull("pre-curator-run");
500
501
  const suppressedNames = new Set(await loadSuppressedNames(root, this.io));
501
502
  const gates = new EvolutionGateSet({
502
503
  exclude: this.excludeSkillNames,
503
504
  referenced: this.referencedSkillNames,
504
505
  suppressed: suppressedNames
505
506
  });
506
- const { bundledNames, treeNames } = await this.seedBaseline(usage);
507
+ const { bundledNames, treeNames, resetToActive } = await this.seedBaseline(usage);
507
508
  const contents = /* @__PURE__ */ new Map();
508
509
  for (const name of treeNames) {
509
510
  const text = await this.skills.read(name);
@@ -536,6 +537,23 @@ var EvolutionCurator = class extends Service {
536
537
  };
537
538
  const llmNominations = gatedNominations.prunings;
538
539
  const archiveCandidates = [...new Set([...result.archive, ...llmNominations])];
540
+ if (!ignoreGates && this.minIdleHours > 0 && this.recentSessionActive()) {
541
+ if (!dryRun) try {
542
+ await mutateUsage(root, this.io, (map) => {
543
+ for (const [name, record] of usage) if (!map.has(name)) map.set(name, { ...record });
544
+ });
545
+ } catch (error) {
546
+ this.ctx.logger.warn(`evolution-curator: failed to persist the seeded usage baseline on a session-blocked run (${error instanceof Error ? error.message : String(error)})`);
547
+ }
548
+ return {
549
+ stale: [],
550
+ archived: [],
551
+ errors: [],
552
+ report: this.skippedReport(runId, startedAt),
553
+ skipped: "active-session"
554
+ };
555
+ }
556
+ const snapshotPath = dryRun ? void 0 : await this.snapshotFull("pre-curator-run");
539
557
  const { archivedSkills, errors, consolidated } = await this.applyMutations({
540
558
  dryRun,
541
559
  archiveCandidates,
@@ -546,11 +564,16 @@ var EvolutionCurator = class extends Service {
546
564
  suppressedNames,
547
565
  root,
548
566
  recommendPool: new Set(recommendPool),
549
- stateOwned: new Set([...result.transitions.map((t) => t.name), ...archiveCandidates]),
567
+ stateOwned: new Set([
568
+ ...result.transitions.map((t) => t.name),
569
+ ...archiveCandidates,
570
+ ...resetToActive
571
+ ]),
550
572
  runStartStates,
551
573
  failedFrom: new Map(result.transitions.filter((t) => t.to === "archived").map((t) => [t.name, t.from]))
552
574
  });
553
- if (!dryRun) this.lastRun = Date.now();
575
+ const runAborted = errors.some((error) => error.startsWith("run aborted"));
576
+ if (!dryRun && !runAborted) this.lastRun = Date.now();
554
577
  const report = buildCuratorRunReport({
555
578
  runId,
556
579
  startedAt,
@@ -594,8 +617,8 @@ var EvolutionCurator = class extends Service {
594
617
  const pausedNow = current?.paused ?? false;
595
618
  return {
596
619
  schemaVersion: 1,
597
- lastRunAt: dryRun ? persisted?.lastRunAt ?? this.lastRun : Date.now(),
598
- runCount: dryRun ? persisted?.runCount ?? 0 : (current?.runCount ?? 0) + 1,
620
+ lastRunAt: dryRun || runAborted ? persisted?.lastRunAt ?? this.lastRun : Date.now(),
621
+ runCount: dryRun || runAborted ? persisted?.runCount ?? 0 : (current?.runCount ?? 0) + 1,
599
622
  lastSummary: summary,
600
623
  paused: pausedNow
601
624
  };
@@ -620,16 +643,23 @@ var EvolutionCurator = class extends Service {
620
643
  async seedBaseline(usage) {
621
644
  const bundledNames = /* @__PURE__ */ new Set();
622
645
  const treeNames = /* @__PURE__ */ new Set();
646
+ const resetToActive = /* @__PURE__ */ new Set();
623
647
  for (const summary of await this.skills.list()) {
624
648
  treeNames.add(summary.name);
625
649
  if (!usage.has(summary.name)) usage.set(summary.name, emptyRecord());
626
650
  if (await this.skills.isBundled(summary.name)) bundledNames.add(summary.name);
627
651
  const record = usage.get(summary.name);
652
+ if (record?.state === "archived") {
653
+ record.state = "active";
654
+ record.archived_at = null;
655
+ resetToActive.add(summary.name);
656
+ }
628
657
  if (record) record.pinned = await this.skills.isPinned(summary.name);
629
658
  }
630
659
  return {
631
660
  bundledNames,
632
- treeNames
661
+ treeNames,
662
+ resetToActive
633
663
  };
634
664
  }
635
665
  /**
@@ -707,6 +737,7 @@ var EvolutionCurator = class extends Service {
707
737
  record.state = "archived";
708
738
  record.archived_at = record.archived_at ?? (/* @__PURE__ */ new Date()).toISOString();
709
739
  stateOwned.add(name);
740
+ this.ctx.logger.warn(`evolution-curator: skill "${name}" left the tree without an archive move - its usage record was folded to archived; if the directory is still on disk, repair or remove it manually (it will not re-enter the lifecycle while the record is archived)`);
710
741
  }
711
742
  let wasBundled = false;
712
743
  try {
@@ -801,8 +832,7 @@ var EvolutionCurator = class extends Service {
801
832
  });
802
833
  archivedSkills.push({
803
834
  name: nomination.from,
804
- path: join(this.skills.root, ".archive", nomination.from),
805
- reason: `Consolidated into ${nomination.into}`
835
+ reason: `Consolidated into ${nomination.into} (exact archive path: the evolution/skill-mutated event, archivedPath)`
806
836
  });
807
837
  }
808
838
  if (suppressedChanged) try {
@@ -835,7 +865,7 @@ var EvolutionCurator = class extends Service {
835
865
  if (!agents) return !this.minIdleFailOpen;
836
866
  let latest = 0;
837
867
  for (const agent of agents.list()) {
838
- const events = agent.session.events;
868
+ const events = agent.session.snapshotEvents();
839
869
  const last = events.length === 0 ? 0 : events[events.length - 1]?.time ?? 0;
840
870
  latest = Math.max(latest, last);
841
871
  }
@@ -222,7 +222,9 @@ export declare class EvolutionCurator extends Service {
222
222
  * mover refuses a directory whose writer lock is alive), so a collision
223
223
  * degrades to a recorded failed op + snapshot rollback, never a torn
224
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
225
+ * min-idle gate checked pre-run AND re-checked at the commit boundary
226
+ * (v28 G4.2: a session activating mid-run no longer slips past it); a
227
+ * manual run (`ignoreGates`) bypasses both checks and is
226
228
  * the one realistic interleave window — documented, accepted (plan v20
227
229
  * C-3①). The same statement lives on evolution-review's `reviewInFlight`.
228
230
  */
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.68",
4
+ "version": "0.3.70",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -31,20 +31,22 @@
31
31
  "license": "MIT",
32
32
  "dependencies": {
33
33
  "@deepseek-ai/schemastery": "^3.18.1",
34
- "@lmzhen/dsh-evolution-core": "^0.3.68"
34
+ "@lmzhen/dsh-evolution-core": "^0.3.70"
35
35
  },
36
36
  "peerDependencies": {
37
37
  "@deepseek-ai/cordis": "^4.0.1",
38
- "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2",
39
- "@deepseek-ai/dsh-llm": "^0.1.1-rc.2",
40
- "@lmzhen/dsh-evolution-io": "^0.3.68",
41
- "@lmzhen/dsh-evolution-state": "^0.3.68"
38
+ "@deepseek-ai/dsh-invariants": "^0.1.5-rc.2",
39
+ "@deepseek-ai/dsh-llm": "^0.1.5-rc.2",
40
+ "@deepseek-ai/dsh-session": "^0.1.5-rc.2",
41
+ "@lmzhen/dsh-evolution-io": "^0.3.70",
42
+ "@lmzhen/dsh-evolution-state": "^0.3.70"
42
43
  },
43
44
  "devDependencies": {
44
- "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2",
45
- "@deepseek-ai/dsh-llm": "^0.1.1-rc.2",
46
- "@lmzhen/dsh-evolution-core": "^0.3.68",
47
- "@lmzhen/dsh-evolution-io": "^0.3.68",
48
- "@lmzhen/dsh-evolution-state": "^0.3.68"
45
+ "@deepseek-ai/dsh-invariants": "^0.1.5-rc.2",
46
+ "@deepseek-ai/dsh-llm": "^0.1.5-rc.2",
47
+ "@deepseek-ai/dsh-session": "^0.1.5-rc.2",
48
+ "@lmzhen/dsh-evolution-core": "^0.3.70",
49
+ "@lmzhen/dsh-evolution-io": "^0.3.70",
50
+ "@lmzhen/dsh-evolution-state": "^0.3.70"
49
51
  }
50
52
  }