@lmzhen/dsh-evolution-curator 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.
package/README.md CHANGED
@@ -33,7 +33,7 @@ Independent of request-prefix construction. This package does not alter the asse
33
33
  - `archive` never deletes: skills move to `.archive/` with a `.archive-reason` marker.
34
34
  - `restore(name)` (service) / `/evolution skill restore <name>` brings one archived skill back to the active root and resets its usage state.
35
35
  - `consolidate(target, sources)` (service) / `/evolution consolidate` merges source bodies into the target, archives the sources with an absorbed-into marker, and folds their usage records into `archived` state. Both operations snapshot the full state first (`pre-consolidate` / `pre-restore`).
36
- - `restoreSnapshot` (service) / `/evolution restore` rolls the FULL state back to the latest snapshot: active tree, usage/suppression sidecars, `.archive/` and the curator state carried in the snapshot (`curator-state.json`), so the interval gate does not immediately re-fire after a rollback. The restore itself is undoable — the pre-rollback safety snapshot preserves the current tree plus its state.
36
+ - `restoreSnapshot` (service) / `/evolution restore` rolls the state back to the latest snapshot: active tree, usage/suppression sidecars, `.archive/` and the curator state carried in the snapshot (`curator-state.json`), so the interval gate does not immediately re-fire after a rollback. Skills that were skipped at snapshot time (a live writer held their lock; recorded in the manifest's `skipped` list) are NOT restored — the restore result names them, and `.backups` may hold a copy. The restore itself is undoable — the pre-rollback safety snapshot preserves the current tree plus its state.
37
37
 
38
38
  ## Automatic scheduling
39
39
 
package/lib/index.js CHANGED
@@ -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`);
@@ -775,12 +815,6 @@ var EvolutionCurator = class extends Service {
775
815
  } catch {
776
816
  this.ctx.logger.warn("evolution-curator: failed to persist usage sidecar");
777
817
  }
778
- const usageRegistry = this.ctx.get("skillUsage");
779
- try {
780
- await usageRegistry?.invalidate?.();
781
- } catch (error) {
782
- this.ctx.logger.warn(`evolution-curator: skillUsage cache invalidate failed after curation: ${error instanceof Error ? error.message : String(error)}`);
783
- }
784
818
  return {
785
819
  archivedSkills,
786
820
  errors,
@@ -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.65",
4
+ "version": "0.3.67",
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.65"
34
+ "@lmzhen/dsh-evolution-core": "^0.3.67"
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.65",
41
- "@lmzhen/dsh-evolution-state": "^0.3.65"
40
+ "@lmzhen/dsh-evolution-io": "^0.3.67",
41
+ "@lmzhen/dsh-evolution-state": "^0.3.67"
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.65",
47
- "@lmzhen/dsh-evolution-io": "^0.3.65",
48
- "@lmzhen/dsh-evolution-state": "^0.3.65"
46
+ "@lmzhen/dsh-evolution-core": "^0.3.67",
47
+ "@lmzhen/dsh-evolution-io": "^0.3.67",
48
+ "@lmzhen/dsh-evolution-state": "^0.3.67"
49
49
  }
50
50
  }