@lmzhen/dsh-evolution-curator 0.1.0-rc.16 → 0.1.0-rc.18

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
@@ -197,81 +197,23 @@ var EvolutionCurator = class extends Service {
197
197
  suppressedNames,
198
198
  referencedSkillNames: this.referencedSkillNames
199
199
  });
200
- const supportDirs = /* @__PURE__ */ new Map();
201
- for (const name of treeNames) supportDirs.set(name, await this.skills.countSupportDirs(name));
202
- const quality = computeQualityScores({
203
- usage,
204
- supportDirs
205
- });
206
- for (const [name, score] of quality) {
207
- const record = usage.get(name);
208
- if (record) {
209
- record.quality_score = score.score;
210
- record.quality_warn = score.warn;
211
- }
212
- }
213
- const errors = [];
214
- const archivedSkills = [];
200
+ await this.scoreTree(usage, treeNames);
215
201
  const nominations = this.llmReview ? await this.recommend(result.markStale, { dryRun }) : {
216
202
  prunings: [],
217
203
  consolidations: []
218
204
  };
219
205
  const llmNominations = nominations.prunings;
220
206
  const archiveCandidates = [...new Set([...result.archive, ...llmNominations])];
221
- let suppressedChanged = false;
222
- if (!dryRun) {
223
- for (const name of archiveCandidates) {
224
- const archived = await this.skills.archive(name, {
225
- reason: "Lifecycle: reached archive threshold",
226
- allowBundled: this.pruneBuiltins
227
- });
228
- if (!archived.ok) {
229
- const record = usage.get(name);
230
- if (record) record.state = "active";
231
- errors.push(`${name}: ${archived.message}`);
232
- } else {
233
- archivedSkills.push({
234
- name,
235
- path: archived.path ?? "",
236
- reason: "Lifecycle: reached archive threshold"
237
- });
238
- if (bundledNames.has(name)) {
239
- suppressedNames.add(name);
240
- suppressedChanged = true;
241
- }
242
- }
243
- }
244
- const alreadyArchived = new Set(archiveCandidates);
245
- for (const nomination of nominations.consolidations) {
246
- if (alreadyArchived.has(nomination.from)) continue;
247
- if (!treeNames.has(nomination.from) || !treeNames.has(nomination.into)) {
248
- errors.push(`${nomination.from}: consolidation target or source missing from the skill tree`);
249
- continue;
250
- }
251
- const consolidated = await this.skills.consolidate(nomination.into, [nomination.from], "background_review");
252
- if (!consolidated.ok) {
253
- errors.push(`${nomination.from}: ${consolidated.message}`);
254
- continue;
255
- }
256
- const record = usage.get(nomination.from);
257
- if (record) {
258
- record.state = "archived";
259
- record.archived_at = (/* @__PURE__ */ new Date()).toISOString();
260
- }
261
- alreadyArchived.add(nomination.from);
262
- archivedSkills.push({
263
- name: nomination.from,
264
- path: consolidated.path ?? "",
265
- reason: `Consolidated into ${nomination.into}`
266
- });
267
- }
268
- if (suppressedChanged) try {
269
- await saveSuppressedNames(root, suppressedNames, this.io);
270
- } catch {
271
- this.ctx.logger.warn("evolution-curator: failed to persist suppressed names; archived built-ins may re-enter the lifecycle");
272
- }
273
- await saveUsage(root, usage, this.io);
274
- }
207
+ const { archivedSkills, errors } = await this.applyMutations({
208
+ dryRun,
209
+ archiveCandidates,
210
+ nominations,
211
+ treeNames,
212
+ usage,
213
+ bundledNames,
214
+ suppressedNames,
215
+ root
216
+ });
275
217
  if (!dryRun) this.lastRun = Date.now();
276
218
  const report = buildCuratorRunReport({
277
219
  runId,
@@ -281,7 +223,7 @@ var EvolutionCurator = class extends Service {
281
223
  llmNominations,
282
224
  archiveCandidates,
283
225
  archived: archivedSkills,
284
- failed: archiveCandidates.filter((name) => errors.some((error) => error.startsWith(`${name}:`))).map((name) => {
226
+ failed: [...new Set([...archiveCandidates, ...nominations.consolidations.map((item) => item.from)])].filter((name) => errors.some((error) => error.startsWith(`${name}:`))).map((name) => {
285
227
  return {
286
228
  name,
287
229
  reason: errors.find((item) => item.startsWith(`${name}:`))?.slice(name.length + 2) ?? "unknown"
@@ -330,6 +272,100 @@ var EvolutionCurator = class extends Service {
330
272
  treeNames
331
273
  };
332
274
  }
275
+ /**
276
+ * F13 six-factor quality scoring, persisted onto the usage records.
277
+ */
278
+ async scoreTree(usage, treeNames) {
279
+ const supportDirs = /* @__PURE__ */ new Map();
280
+ for (const name of treeNames) supportDirs.set(name, await this.skills.countSupportDirs(name));
281
+ const quality = computeQualityScores({
282
+ usage,
283
+ supportDirs
284
+ });
285
+ for (const [name, score] of quality) {
286
+ const record = usage.get(name);
287
+ if (record) {
288
+ record.quality_score = score.score;
289
+ record.quality_warn = score.warn;
290
+ }
291
+ }
292
+ }
293
+ /**
294
+ * Execute lifecycle archives and consolidation nominations, then persist the
295
+ * suppression and usage sidecars best-effort. A dry-run short-circuits: no
296
+ * file moves and no state persistence — the caller still writes the report.
297
+ */
298
+ async applyMutations(input) {
299
+ if (input.dryRun) return {
300
+ archivedSkills: [],
301
+ errors: [],
302
+ suppressedChanged: false
303
+ };
304
+ const { archiveCandidates, nominations, treeNames, usage, bundledNames, suppressedNames, root } = input;
305
+ const errors = [];
306
+ const archivedSkills = [];
307
+ let suppressedChanged = false;
308
+ for (const name of archiveCandidates) {
309
+ const archived = await this.skills.archive(name, {
310
+ reason: "Lifecycle: reached archive threshold",
311
+ allowBundled: this.pruneBuiltins
312
+ });
313
+ if (!archived.ok) {
314
+ const record = usage.get(name);
315
+ if (record) record.state = "active";
316
+ errors.push(`${name}: ${archived.message}`);
317
+ } else {
318
+ archivedSkills.push({
319
+ name,
320
+ path: archived.path ?? "",
321
+ reason: "Lifecycle: reached archive threshold"
322
+ });
323
+ if (bundledNames.has(name)) {
324
+ suppressedNames.add(name);
325
+ suppressedChanged = true;
326
+ }
327
+ }
328
+ }
329
+ const alreadyArchived = new Set(archiveCandidates);
330
+ for (const nomination of nominations.consolidations) {
331
+ if (alreadyArchived.has(nomination.from)) continue;
332
+ if (!treeNames.has(nomination.from) || !treeNames.has(nomination.into)) {
333
+ errors.push(`${nomination.from}: consolidation target or source missing from the skill tree`);
334
+ continue;
335
+ }
336
+ const consolidated = await this.skills.consolidate(nomination.into, [nomination.from], "background_review");
337
+ if (!consolidated.ok) {
338
+ errors.push(`${nomination.from}: ${consolidated.message}`);
339
+ continue;
340
+ }
341
+ const record = usage.get(nomination.from);
342
+ if (record) {
343
+ record.state = "archived";
344
+ record.archived_at = (/* @__PURE__ */ new Date()).toISOString();
345
+ }
346
+ alreadyArchived.add(nomination.from);
347
+ archivedSkills.push({
348
+ name: nomination.from,
349
+ path: consolidated.path ?? "",
350
+ reason: `Consolidated into ${nomination.into}`
351
+ });
352
+ }
353
+ if (suppressedChanged) try {
354
+ await saveSuppressedNames(root, suppressedNames, this.io);
355
+ } catch {
356
+ this.ctx.logger.warn("evolution-curator: failed to persist suppressed names; archived built-ins may re-enter the lifecycle");
357
+ }
358
+ try {
359
+ await saveUsage(root, usage, this.io);
360
+ } catch {
361
+ this.ctx.logger.warn("evolution-curator: failed to persist usage sidecar");
362
+ }
363
+ return {
364
+ archivedSkills,
365
+ errors,
366
+ suppressedChanged
367
+ };
368
+ }
333
369
  recentSessionActive() {
334
370
  const agents = this.ctx.get("agents");
335
371
  if (!agents) return false;
@@ -97,6 +97,16 @@ export declare class EvolutionCurator extends Service {
97
97
  * returns the full active tree names for nomination validation.
98
98
  */
99
99
  private seedBaseline;
100
+ /**
101
+ * F13 six-factor quality scoring, persisted onto the usage records.
102
+ */
103
+ private scoreTree;
104
+ /**
105
+ * Execute lifecycle archives and consolidation nominations, then persist the
106
+ * suppression and usage sidecars best-effort. A dry-run short-circuits: no
107
+ * file moves and no state persistence — the caller still writes the report.
108
+ */
109
+ private applyMutations;
100
110
  private recentSessionActive;
101
111
  latestReport(): Promise<CuratorRunReport | null>;
102
112
  /**
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.16",
4
+ "version": "0.1.0-rc.18",
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.16"
36
+ "@lmzhen/dsh-evolution-core": "^0.1.0-rc.18"
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.16",
43
- "@lmzhen/dsh-evolution-state": "^0.1.0-rc.16"
42
+ "@lmzhen/dsh-evolution-io": "^0.1.0-rc.18",
43
+ "@lmzhen/dsh-evolution-state": "^0.1.0-rc.18"
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.16",
49
- "@lmzhen/dsh-evolution-io": "^0.1.0-rc.16",
50
- "@lmzhen/dsh-evolution-state": "^0.1.0-rc.16"
48
+ "@lmzhen/dsh-evolution-core": "^0.1.0-rc.18",
49
+ "@lmzhen/dsh-evolution-io": "^0.1.0-rc.18",
50
+ "@lmzhen/dsh-evolution-state": "^0.1.0-rc.18"
51
51
  }
52
52
  }