@lmzhen/dsh-evolution-curator 0.1.0-rc.17 → 0.1.0-rc.19
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 +106 -73
- package/lib/types/index.d.ts +18 -0
- package/package.json +7 -7
package/lib/index.js
CHANGED
|
@@ -197,85 +197,23 @@ var EvolutionCurator = class extends Service {
|
|
|
197
197
|
suppressedNames,
|
|
198
198
|
referencedSkillNames: this.referencedSkillNames
|
|
199
199
|
});
|
|
200
|
-
|
|
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
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
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
|
-
try {
|
|
274
|
-
await saveUsage(root, usage, this.io);
|
|
275
|
-
} catch {
|
|
276
|
-
this.ctx.logger.warn("evolution-curator: failed to persist usage sidecar");
|
|
277
|
-
}
|
|
278
|
-
}
|
|
207
|
+
const { archivedSkills, errors } = await this.applyMutations({
|
|
208
|
+
dryRun,
|
|
209
|
+
archiveCandidates,
|
|
210
|
+
nominations,
|
|
211
|
+
treeNames,
|
|
212
|
+
usage,
|
|
213
|
+
bundledNames,
|
|
214
|
+
suppressedNames,
|
|
215
|
+
root
|
|
216
|
+
});
|
|
279
217
|
if (!dryRun) this.lastRun = Date.now();
|
|
280
218
|
const report = buildCuratorRunReport({
|
|
281
219
|
runId,
|
|
@@ -302,6 +240,7 @@ var EvolutionCurator = class extends Service {
|
|
|
302
240
|
}
|
|
303
241
|
const summary = `${dryRun ? "dry-run" : "auto"}: stale:${result.markStale.length} archived:${archivedSkills.length} consolidated:${nominations.consolidations.length}`;
|
|
304
242
|
await stateService?.saveCuratorState({
|
|
243
|
+
schemaVersion: 1,
|
|
305
244
|
lastRunAt: dryRun ? persisted?.lastRunAt ?? this.lastRun : this.lastRun,
|
|
306
245
|
runCount: dryRun ? persisted?.runCount ?? 0 : (persisted?.runCount ?? 0) + 1,
|
|
307
246
|
lastSummary: summary,
|
|
@@ -334,6 +273,100 @@ var EvolutionCurator = class extends Service {
|
|
|
334
273
|
treeNames
|
|
335
274
|
};
|
|
336
275
|
}
|
|
276
|
+
/**
|
|
277
|
+
* F13 six-factor quality scoring, persisted onto the usage records.
|
|
278
|
+
*/
|
|
279
|
+
async scoreTree(usage, treeNames) {
|
|
280
|
+
const supportDirs = /* @__PURE__ */ new Map();
|
|
281
|
+
for (const name of treeNames) supportDirs.set(name, await this.skills.countSupportDirs(name));
|
|
282
|
+
const quality = computeQualityScores({
|
|
283
|
+
usage,
|
|
284
|
+
supportDirs
|
|
285
|
+
});
|
|
286
|
+
for (const [name, score] of quality) {
|
|
287
|
+
const record = usage.get(name);
|
|
288
|
+
if (record) {
|
|
289
|
+
record.quality_score = score.score;
|
|
290
|
+
record.quality_warn = score.warn;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Execute lifecycle archives and consolidation nominations, then persist the
|
|
296
|
+
* suppression and usage sidecars best-effort. A dry-run short-circuits: no
|
|
297
|
+
* file moves and no state persistence — the caller still writes the report.
|
|
298
|
+
*/
|
|
299
|
+
async applyMutations(input) {
|
|
300
|
+
if (input.dryRun) return {
|
|
301
|
+
archivedSkills: [],
|
|
302
|
+
errors: [],
|
|
303
|
+
suppressedChanged: false
|
|
304
|
+
};
|
|
305
|
+
const { archiveCandidates, nominations, treeNames, usage, bundledNames, suppressedNames, root } = input;
|
|
306
|
+
const errors = [];
|
|
307
|
+
const archivedSkills = [];
|
|
308
|
+
let suppressedChanged = false;
|
|
309
|
+
for (const name of archiveCandidates) {
|
|
310
|
+
const archived = await this.skills.archive(name, {
|
|
311
|
+
reason: "Lifecycle: reached archive threshold",
|
|
312
|
+
allowBundled: this.pruneBuiltins
|
|
313
|
+
});
|
|
314
|
+
if (!archived.ok) {
|
|
315
|
+
const record = usage.get(name);
|
|
316
|
+
if (record) record.state = "active";
|
|
317
|
+
errors.push(`${name}: ${archived.message}`);
|
|
318
|
+
} else {
|
|
319
|
+
archivedSkills.push({
|
|
320
|
+
name,
|
|
321
|
+
path: archived.path ?? "",
|
|
322
|
+
reason: "Lifecycle: reached archive threshold"
|
|
323
|
+
});
|
|
324
|
+
if (bundledNames.has(name)) {
|
|
325
|
+
suppressedNames.add(name);
|
|
326
|
+
suppressedChanged = true;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
const alreadyArchived = new Set(archiveCandidates);
|
|
331
|
+
for (const nomination of nominations.consolidations) {
|
|
332
|
+
if (alreadyArchived.has(nomination.from)) continue;
|
|
333
|
+
if (!treeNames.has(nomination.from) || !treeNames.has(nomination.into)) {
|
|
334
|
+
errors.push(`${nomination.from}: consolidation target or source missing from the skill tree`);
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
337
|
+
const consolidated = await this.skills.consolidate(nomination.into, [nomination.from], "background_review");
|
|
338
|
+
if (!consolidated.ok) {
|
|
339
|
+
errors.push(`${nomination.from}: ${consolidated.message}`);
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
342
|
+
const record = usage.get(nomination.from);
|
|
343
|
+
if (record) {
|
|
344
|
+
record.state = "archived";
|
|
345
|
+
record.archived_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
346
|
+
}
|
|
347
|
+
alreadyArchived.add(nomination.from);
|
|
348
|
+
archivedSkills.push({
|
|
349
|
+
name: nomination.from,
|
|
350
|
+
path: consolidated.path ?? "",
|
|
351
|
+
reason: `Consolidated into ${nomination.into}`
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
if (suppressedChanged) try {
|
|
355
|
+
await saveSuppressedNames(root, suppressedNames, this.io);
|
|
356
|
+
} catch {
|
|
357
|
+
this.ctx.logger.warn("evolution-curator: failed to persist suppressed names; archived built-ins may re-enter the lifecycle");
|
|
358
|
+
}
|
|
359
|
+
try {
|
|
360
|
+
await saveUsage(root, usage, this.io);
|
|
361
|
+
} catch {
|
|
362
|
+
this.ctx.logger.warn("evolution-curator: failed to persist usage sidecar");
|
|
363
|
+
}
|
|
364
|
+
return {
|
|
365
|
+
archivedSkills,
|
|
366
|
+
errors,
|
|
367
|
+
suppressedChanged
|
|
368
|
+
};
|
|
369
|
+
}
|
|
337
370
|
recentSessionActive() {
|
|
338
371
|
const agents = this.ctx.get("agents");
|
|
339
372
|
if (!agents) return false;
|
package/lib/types/index.d.ts
CHANGED
|
@@ -46,6 +46,14 @@ export interface CuratorRunOutcome {
|
|
|
46
46
|
/** LLM nominations when the optional review pass is enabled (audit visibility). */
|
|
47
47
|
nominations?: CuratorNominations;
|
|
48
48
|
}
|
|
49
|
+
/** Persisted curator-state record shape (schemaVersion optional for legacy reads). */
|
|
50
|
+
export interface CuratorStateRecordShape {
|
|
51
|
+
schemaVersion?: number;
|
|
52
|
+
lastRunAt: number;
|
|
53
|
+
runCount: number;
|
|
54
|
+
lastSummary: string;
|
|
55
|
+
paused: boolean;
|
|
56
|
+
}
|
|
49
57
|
export declare class EvolutionCurator extends Service {
|
|
50
58
|
static inject: string[];
|
|
51
59
|
static Config: Schema<Config>;
|
|
@@ -97,6 +105,16 @@ export declare class EvolutionCurator extends Service {
|
|
|
97
105
|
* returns the full active tree names for nomination validation.
|
|
98
106
|
*/
|
|
99
107
|
private seedBaseline;
|
|
108
|
+
/**
|
|
109
|
+
* F13 six-factor quality scoring, persisted onto the usage records.
|
|
110
|
+
*/
|
|
111
|
+
private scoreTree;
|
|
112
|
+
/**
|
|
113
|
+
* Execute lifecycle archives and consolidation nominations, then persist the
|
|
114
|
+
* suppression and usage sidecars best-effort. A dry-run short-circuits: no
|
|
115
|
+
* file moves and no state persistence — the caller still writes the report.
|
|
116
|
+
*/
|
|
117
|
+
private applyMutations;
|
|
100
118
|
private recentSessionActive;
|
|
101
119
|
latestReport(): Promise<CuratorRunReport | null>;
|
|
102
120
|
/**
|
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.
|
|
4
|
+
"version": "0.1.0-rc.19",
|
|
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.
|
|
36
|
+
"@lmzhen/dsh-evolution-core": "^0.1.0-rc.19"
|
|
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.
|
|
43
|
-
"@lmzhen/dsh-evolution-state": "^0.1.0-rc.
|
|
42
|
+
"@lmzhen/dsh-evolution-io": "^0.1.0-rc.19",
|
|
43
|
+
"@lmzhen/dsh-evolution-state": "^0.1.0-rc.19"
|
|
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.
|
|
49
|
-
"@lmzhen/dsh-evolution-io": "^0.1.0-rc.
|
|
50
|
-
"@lmzhen/dsh-evolution-state": "^0.1.0-rc.
|
|
48
|
+
"@lmzhen/dsh-evolution-core": "^0.1.0-rc.19",
|
|
49
|
+
"@lmzhen/dsh-evolution-io": "^0.1.0-rc.19",
|
|
50
|
+
"@lmzhen/dsh-evolution-state": "^0.1.0-rc.19"
|
|
51
51
|
}
|
|
52
52
|
}
|