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

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,29 +170,49 @@ 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 = /* @__PURE__ */ new Set();
175
+ for (const summary of await this.skills.list()) {
176
+ if (!usage.has(summary.name)) usage.set(summary.name, emptyRecord());
177
+ if (await this.skills.isBundled(summary.name)) bundledNames.add(summary.name);
178
+ }
166
179
  const result = computeLifecycleTransitions(usage, {
167
180
  staleAfterDays: lifecycle.staleAfterDays,
168
181
  archiveAfterDays: lifecycle.archiveAfterDays,
169
182
  qualityWarnStaleAfterDays: this.qualityWarnStaleAfterDays,
170
183
  excludeSkillNames: this.excludeSkillNames,
171
- manageUnmanaged: this.manageUnmanaged
184
+ manageUnmanaged: this.manageUnmanaged,
185
+ pruneBuiltins: this.pruneBuiltins,
186
+ bundledNames,
187
+ suppressedNames
172
188
  });
173
189
  const errors = [];
174
190
  const archivedSkills = [];
175
191
  const llmNominations = this.llmReview ? await this.recommend(result.markStale) : [];
176
192
  const archiveCandidates = [...new Set([...result.archive, ...llmNominations])];
193
+ let suppressedChanged = false;
177
194
  for (const name of archiveCandidates) {
178
- const archived = await this.skills.archive(name, "Lifecycle: reached archive threshold");
195
+ const archived = await this.skills.archive(name, {
196
+ reason: "Lifecycle: reached archive threshold",
197
+ allowBundled: this.pruneBuiltins
198
+ });
179
199
  if (!archived.ok) {
180
200
  const record = usage.get(name);
181
201
  if (record) record.state = "active";
182
202
  errors.push(`${name}: ${archived.message}`);
183
- } else archivedSkills.push({
184
- name,
185
- path: archived.path ?? "",
186
- reason: "Lifecycle: reached archive threshold"
187
- });
203
+ } else {
204
+ archivedSkills.push({
205
+ name,
206
+ path: archived.path ?? "",
207
+ reason: "Lifecycle: reached archive threshold"
208
+ });
209
+ if (bundledNames.has(name)) {
210
+ suppressedNames.add(name);
211
+ suppressedChanged = true;
212
+ }
213
+ }
188
214
  }
215
+ if (suppressedChanged) await saveSuppressedNames(root, suppressedNames, this.io);
189
216
  await saveUsage(root, usage, this.io);
190
217
  this.lastRun = Date.now();
191
218
  const report = buildCuratorRunReport({
@@ -281,6 +308,8 @@ var EvolutionCurator = class extends Service {
281
308
  const record = usage.get(name);
282
309
  if (record) record.state = "active";
283
310
  await saveUsage(this.skills.root, usage, this.io);
311
+ const suppressed = new Set(await loadSuppressedNames(this.skills.root, this.io));
312
+ if (suppressed.delete(name)) await saveSuppressedNames(this.skills.root, suppressed, this.io);
284
313
  return result;
285
314
  }
286
315
  };
@@ -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,13 @@ 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>;
70
81
  private recentSessionActive;
71
82
  latestReport(): Promise<CuratorRunReport | null>;
72
83
  /**
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.13",
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.13"
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.13",
43
+ "@lmzhen/dsh-evolution-state": "^0.1.0-rc.13"
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.13",
49
+ "@lmzhen/dsh-evolution-io": "^0.1.0-rc.13",
50
+ "@lmzhen/dsh-evolution-state": "^0.1.0-rc.13"
51
51
  }
52
52
  }