@lmzhen/dsh-evolution-curator 0.1.0-rc.12 → 0.1.0-rc.14

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
@@ -3,7 +3,7 @@ import { randomUUID } from "node:crypto";
3
3
  import { join } from "node:path";
4
4
  import { BlockAssembler, createUserMessage } from "@deepseek-ai/dsh-llm";
5
5
  import z from "@deepseek-ai/schemastery";
6
- import { CURATOR_PROMPT, DEFAULT_ARCHIVE_AFTER_DAYS, DEFAULT_CURATOR_INTERVAL_HOURS, DEFAULT_STALE_AFTER_DAYS, SkillLibrary, buildCuratorRunReport, computeLifecycleTransitions, evolutionHome, evolutionIoAdapter, loadUsage, saveUsage } from "@lmzhen/dsh-evolution-core";
6
+ import { CURATOR_PROMPT, DEFAULT_ARCHIVE_AFTER_DAYS, DEFAULT_CURATOR_INTERVAL_HOURS, DEFAULT_MIN_IDLE_HOURS, DEFAULT_STALE_AFTER_DAYS, SkillLibrary, buildCuratorRunReport, computeLifecycleTransitions, emptyRecord, evolutionHome, evolutionIoAdapter, loadSuppressedNames, loadUsage, saveSuppressedNames, saveUsage } from "@lmzhen/dsh-evolution-core";
7
7
  //#region lib/types/index.js
8
8
  /**
9
9
  * Deterministic skill lifecycle curator with interval gate and archive.
@@ -19,9 +19,10 @@ var EvolutionCurator = class extends Service {
19
19
  llmReview: z.boolean().default(false),
20
20
  curatorProvider: z.string().default("deepseek-official"),
21
21
  qualityWarnStaleAfterDays: z.number().default(7),
22
- minIdleHours: z.number().default(0),
22
+ minIdleHours: z.number().default(DEFAULT_MIN_IDLE_HOURS),
23
23
  excludeSkillNames: z.array(z.string()).default([]),
24
24
  manageUnmanaged: z.boolean().default(false),
25
+ pruneBuiltins: z.boolean().default(false),
25
26
  curatorReviewMaxTokens: z.number().default(2048)
26
27
  });
27
28
  skills;
@@ -36,6 +37,7 @@ var EvolutionCurator = class extends Service {
36
37
  minIdleHours;
37
38
  excludeSkillNames;
38
39
  manageUnmanaged;
40
+ pruneBuiltins;
39
41
  curatorReviewMaxTokens;
40
42
  lastRun = 0;
41
43
  timer;
@@ -50,9 +52,10 @@ var EvolutionCurator = class extends Service {
50
52
  this.llmReview = config.llmReview ?? false;
51
53
  this.curatorProvider = config.curatorProvider ?? "deepseek-official";
52
54
  this.qualityWarnStaleAfterDays = config.qualityWarnStaleAfterDays ?? 7;
53
- this.minIdleHours = config.minIdleHours ?? 0;
55
+ this.minIdleHours = config.minIdleHours ?? DEFAULT_MIN_IDLE_HOURS;
54
56
  this.excludeSkillNames = new Set(config.excludeSkillNames ?? []);
55
57
  this.manageUnmanaged = config.manageUnmanaged ?? false;
58
+ this.pruneBuiltins = config.pruneBuiltins ?? false;
56
59
  this.curatorReviewMaxTokens = config.curatorReviewMaxTokens ?? 2048;
57
60
  this.lastRun = Date.now();
58
61
  this.ctx.effect(() => {
@@ -140,20 +143,24 @@ var EvolutionCurator = class extends Service {
140
143
  failed: []
141
144
  });
142
145
  }
143
- async run() {
146
+ /**
147
+ * Run one curator pass. `ignoreGates` skips the interval and idle gates so an
148
+ * explicit `/evolution curator run` always executes (manual-run semantics).
149
+ */
150
+ async run(options = {}) {
144
151
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
145
152
  const runId = randomUUID();
146
153
  const stateService = this.ctx.get("evolutionState");
147
154
  const lifecycle = this.lifecycle();
148
155
  const persisted = await stateService?.loadCuratorState();
149
- if (persisted && Date.now() - persisted.lastRunAt < lifecycle.intervalHours * 36e5) return {
156
+ if (!options.ignoreGates && persisted && Date.now() - persisted.lastRunAt < lifecycle.intervalHours * 36e5) return {
150
157
  stale: [],
151
158
  archived: [],
152
159
  errors: [],
153
160
  report: this.skippedReport(runId, startedAt),
154
161
  skipped: "interval"
155
162
  };
156
- if (this.minIdleHours > 0 && this.recentSessionActive()) return {
163
+ if (!options.ignoreGates && this.minIdleHours > 0 && this.recentSessionActive()) return {
157
164
  stale: [],
158
165
  archived: [],
159
166
  errors: [],
@@ -163,28 +170,48 @@ var EvolutionCurator = class extends Service {
163
170
  const root = this.skills.root;
164
171
  const snapshotPath = await this.skills.snapshotAll("pre-curator-run");
165
172
  const usage = await loadUsage(root, this.io);
173
+ const suppressedNames = new Set(await loadSuppressedNames(root, this.io));
174
+ const bundledNames = await this.seedBaseline(usage);
166
175
  const result = computeLifecycleTransitions(usage, {
167
176
  staleAfterDays: lifecycle.staleAfterDays,
168
177
  archiveAfterDays: lifecycle.archiveAfterDays,
169
178
  qualityWarnStaleAfterDays: this.qualityWarnStaleAfterDays,
170
179
  excludeSkillNames: this.excludeSkillNames,
171
- manageUnmanaged: this.manageUnmanaged
180
+ manageUnmanaged: this.manageUnmanaged,
181
+ pruneBuiltins: this.pruneBuiltins,
182
+ bundledNames,
183
+ suppressedNames
172
184
  });
173
185
  const errors = [];
174
186
  const archivedSkills = [];
175
187
  const llmNominations = this.llmReview ? await this.recommend(result.markStale) : [];
176
188
  const archiveCandidates = [...new Set([...result.archive, ...llmNominations])];
189
+ let suppressedChanged = false;
177
190
  for (const name of archiveCandidates) {
178
- const archived = await this.skills.archive(name, "Lifecycle: reached archive threshold");
191
+ const archived = await this.skills.archive(name, {
192
+ reason: "Lifecycle: reached archive threshold",
193
+ allowBundled: this.pruneBuiltins
194
+ });
179
195
  if (!archived.ok) {
180
196
  const record = usage.get(name);
181
197
  if (record) record.state = "active";
182
198
  errors.push(`${name}: ${archived.message}`);
183
- } else archivedSkills.push({
184
- name,
185
- path: archived.path ?? "",
186
- reason: "Lifecycle: reached archive threshold"
187
- });
199
+ } else {
200
+ archivedSkills.push({
201
+ name,
202
+ path: archived.path ?? "",
203
+ reason: "Lifecycle: reached archive threshold"
204
+ });
205
+ if (bundledNames.has(name)) {
206
+ suppressedNames.add(name);
207
+ suppressedChanged = true;
208
+ }
209
+ }
210
+ }
211
+ if (suppressedChanged) try {
212
+ await saveSuppressedNames(root, suppressedNames, this.io);
213
+ } catch {
214
+ this.ctx.logger.warn("evolution-curator: failed to persist suppressed names; archived built-ins may re-enter the lifecycle");
188
215
  }
189
216
  await saveUsage(root, usage, this.io);
190
217
  this.lastRun = Date.now();
@@ -224,6 +251,19 @@ var EvolutionCurator = class extends Service {
224
251
  report
225
252
  };
226
253
  }
254
+ /**
255
+ * Seed baseline records for tree skills the sidecar has not seen yet, so
256
+ * their inactivity clock starts now (first-sight defer) and bundled skills
257
+ * become known candidates only when prune-builtins opts them in.
258
+ */
259
+ async seedBaseline(usage) {
260
+ const bundledNames = /* @__PURE__ */ new Set();
261
+ for (const summary of await this.skills.list()) {
262
+ if (!usage.has(summary.name)) usage.set(summary.name, emptyRecord());
263
+ if (await this.skills.isBundled(summary.name)) bundledNames.add(summary.name);
264
+ }
265
+ return bundledNames;
266
+ }
227
267
  recentSessionActive() {
228
268
  const agents = this.ctx.get("agents");
229
269
  if (!agents) return false;
@@ -281,6 +321,12 @@ var EvolutionCurator = class extends Service {
281
321
  const record = usage.get(name);
282
322
  if (record) record.state = "active";
283
323
  await saveUsage(this.skills.root, usage, this.io);
324
+ const suppressed = new Set(await loadSuppressedNames(this.skills.root, this.io));
325
+ if (suppressed.delete(name)) try {
326
+ await saveSuppressedNames(this.skills.root, suppressed, this.io);
327
+ } catch {
328
+ this.ctx.logger.warn(`evolution-curator: failed to persist suppressed names after restoring ${name}`);
329
+ }
284
330
  return result;
285
331
  }
286
332
  };
@@ -27,9 +27,19 @@ export interface Config {
27
27
  excludeSkillNames?: string[];
28
28
  /** Include usage records whose created_by is not 'agent' in lifecycle decisions. */
29
29
  manageUnmanaged?: boolean;
30
+ /** Archive long-unused bundled skills too (with suppression against re-seeds). */
31
+ pruneBuiltins?: boolean;
30
32
  /** Max tokens for the optional LLM nomination pass. */
31
33
  curatorReviewMaxTokens?: number;
32
34
  }
35
+ /** Outcome of one curator run pass. */
36
+ export interface CuratorRunOutcome {
37
+ stale: string[];
38
+ archived: string[];
39
+ errors: string[];
40
+ report: CuratorRunReport;
41
+ skipped?: string;
42
+ }
33
43
  export declare class EvolutionCurator extends Service {
34
44
  static inject: string[];
35
45
  static Config: Schema<Config>;
@@ -45,6 +55,7 @@ export declare class EvolutionCurator extends Service {
45
55
  private readonly minIdleHours;
46
56
  private readonly excludeSkillNames;
47
57
  private readonly manageUnmanaged;
58
+ private readonly pruneBuiltins;
48
59
  private readonly curatorReviewMaxTokens;
49
60
  private lastRun;
50
61
  private timer;
@@ -60,13 +71,19 @@ export declare class EvolutionCurator extends Service {
60
71
  */
61
72
  recommend(candidates: string[]): Promise<string[]>;
62
73
  private skippedReport;
63
- run(): Promise<{
64
- stale: string[];
65
- archived: string[];
66
- errors: string[];
67
- report: CuratorRunReport;
68
- skipped?: string;
69
- }>;
74
+ /**
75
+ * Run one curator pass. `ignoreGates` skips the interval and idle gates so an
76
+ * explicit `/evolution curator run` always executes (manual-run semantics).
77
+ */
78
+ run(options?: {
79
+ ignoreGates?: boolean;
80
+ }): Promise<CuratorRunOutcome>;
81
+ /**
82
+ * Seed baseline records for tree skills the sidecar has not seen yet, so
83
+ * their inactivity clock starts now (first-sight defer) and bundled skills
84
+ * become known candidates only when prune-builtins opts them in.
85
+ */
86
+ private seedBaseline;
70
87
  private recentSessionActive;
71
88
  latestReport(): Promise<CuratorRunReport | null>;
72
89
  /**
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.1.0-rc.12",
4
+ "version": "0.1.0-rc.14",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -33,20 +33,20 @@
33
33
  "license": "MIT",
34
34
  "dependencies": {
35
35
  "@deepseek-ai/schemastery": "^3.18.1",
36
- "@lmzhen/dsh-evolution-core": "^0.1.0-rc.12"
36
+ "@lmzhen/dsh-evolution-core": "^0.1.0-rc.14"
37
37
  },
38
38
  "peerDependencies": {
39
39
  "@deepseek-ai/cordis": "^4.0.1",
40
40
  "@deepseek-ai/dsh-invariants": "^0.1.0-rc.6",
41
41
  "@deepseek-ai/dsh-llm": "^0.1.0-rc.6",
42
- "@lmzhen/dsh-evolution-io": "^0.1.0-rc.12",
43
- "@lmzhen/dsh-evolution-state": "^0.1.0-rc.12"
42
+ "@lmzhen/dsh-evolution-io": "^0.1.0-rc.14",
43
+ "@lmzhen/dsh-evolution-state": "^0.1.0-rc.14"
44
44
  },
45
45
  "devDependencies": {
46
46
  "@deepseek-ai/dsh-invariants": "^0.1.0-rc.6",
47
47
  "@deepseek-ai/dsh-llm": "^0.1.0-rc.6",
48
- "@lmzhen/dsh-evolution-core": "^0.1.0-rc.12",
49
- "@lmzhen/dsh-evolution-io": "^0.1.0-rc.12",
50
- "@lmzhen/dsh-evolution-state": "^0.1.0-rc.12"
48
+ "@lmzhen/dsh-evolution-core": "^0.1.0-rc.14",
49
+ "@lmzhen/dsh-evolution-io": "^0.1.0-rc.14",
50
+ "@lmzhen/dsh-evolution-state": "^0.1.0-rc.14"
51
51
  }
52
52
  }