@lmzhen/dsh-evolution-curator 0.18.0 → 0.19.0
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 +29 -60
- package/lib/types/index.d.ts +5 -3
- package/package.json +7 -7
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_DRY_RUN_BANNER, CURATOR_PROMPT, DEFAULT_ARCHIVE_AFTER_DAYS, DEFAULT_CURATOR_BOOT_GRACE_SECONDS, DEFAULT_CURATOR_INTERVAL_HOURS, DEFAULT_CURATOR_MODEL, DEFAULT_CURATOR_REVIEW_MAX_TOKENS, DEFAULT_HEALTH_THRESHOLDS, DEFAULT_MIN_IDLE_HOURS, DEFAULT_SKILL_LIMITS, DEFAULT_STALE_AFTER_DAYS, EvolutionGateSet, INSTANCE_KEYS, MAX_TIMER_DELAY_MS, PROMPT_BUNDLE, SKILL_NAME_RE, buildCuratorRunReport, claimInstance, clampOnce, clampedNumber, computeDedupGroups, computeLifecycleTransitions, computePrefixClusters, computeQualityScores, computeScopeView, contentHash, emptyRecord, entryTarget, evolutionHome, evolutionIoAdapter, foldCuratorFields, isPresent, isUnknown, loadSuppressedNames, loadUsage, markerEntryName, mutateUsage, newSkillLibrary, paramRowId, parseCuratorNominations, parseFrontmatter, pickWithPolicy, policyStageLimits, probeList,
|
|
6
|
+
import { CURATOR_DRY_RUN_BANNER, CURATOR_PROMPT, DEFAULT_ARCHIVE_AFTER_DAYS, DEFAULT_CURATOR_BOOT_GRACE_SECONDS, DEFAULT_CURATOR_INTERVAL_HOURS, DEFAULT_CURATOR_MODEL, DEFAULT_CURATOR_REVIEW_MAX_TOKENS, DEFAULT_HEALTH_THRESHOLDS, DEFAULT_MIN_IDLE_HOURS, DEFAULT_SKILL_LIMITS, DEFAULT_STALE_AFTER_DAYS, EvolutionGateSet, INSTANCE_KEYS, MAX_TIMER_DELAY_MS, PROMPT_BUNDLE, SKILL_NAME_RE, buildCuratorRunReport, claimInstance, clampOnce, clampedNumber, computeDedupGroups, computeLifecycleTransitions, computePrefixClusters, computeQualityScores, computeScopeView, contentHash, emptyRecord, entryTarget, evolutionHome, evolutionIoAdapter, foldCuratorFields, isPresent, isUnknown, loadSuppressedNames, loadUsage, markerEntryName, mutateUsage, newSkillLibrary, paramRowId, parseCuratorNominations, parseFrontmatter, pickWithPolicy, policyStageLimits, probeList, probeText, readNumberParam, relatedSkillNames, releaseInstance, renderCuratorReportMarkdown, reportTime, reportsSweepLockTarget, sessionLastEventTime, sweepReports, transactIo, updateSuppressedNames, usageObserved, userSetKeys, verifyPromptBundle } 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.
|
|
@@ -404,6 +404,7 @@ var EvolutionCurator = class extends Service {
|
|
|
404
404
|
"Return a YAML summary with consolidations and prunings lists. Nominate only actions whose archival/merge is clearly safe."
|
|
405
405
|
].join("\n");
|
|
406
406
|
const settings = this.settings();
|
|
407
|
+
const llmSignal = AbortSignal.timeout(settings.curatorReviewTimeoutMs);
|
|
407
408
|
try {
|
|
408
409
|
const assembler = new BlockAssembler();
|
|
409
410
|
for await (const chunk of llm.stream({
|
|
@@ -421,7 +422,7 @@ var EvolutionCurator = class extends Service {
|
|
|
421
422
|
}
|
|
422
423
|
})],
|
|
423
424
|
maxTokens: settings.curatorReviewMaxTokens,
|
|
424
|
-
signal:
|
|
425
|
+
signal: llmSignal
|
|
425
426
|
})) assembler.push(chunk);
|
|
426
427
|
const parsed = parseCuratorNominations(assembler.blocks().filter((block) => block.type === "text").map((block) => block.text).join("\n"));
|
|
427
428
|
return {
|
|
@@ -430,8 +431,12 @@ var EvolutionCurator = class extends Service {
|
|
|
430
431
|
warnings: parsed.warnings
|
|
431
432
|
};
|
|
432
433
|
} catch (error) {
|
|
433
|
-
|
|
434
|
-
|
|
434
|
+
const failure = llmSignal.aborted ? `LLM nomination pass timed out after ${settings.curatorReviewTimeoutMs} ms — no nominations this run (NOT "nothing to nominate": the pass did not complete)` : `LLM nomination pass failed: ${error instanceof Error ? error.message : String(error)} — no nominations this run (NOT "nothing to nominate": the pass did not complete)`;
|
|
435
|
+
this.ctx.logger.warn(`evolution-curator: ${failure}`);
|
|
436
|
+
return {
|
|
437
|
+
...empty,
|
|
438
|
+
warnings: [failure]
|
|
439
|
+
};
|
|
435
440
|
}
|
|
436
441
|
}
|
|
437
442
|
/** Optional curator-state service (evolution-state-json / storage-domain). */
|
|
@@ -1091,51 +1096,27 @@ var EvolutionCurator = class extends Service {
|
|
|
1091
1096
|
});
|
|
1092
1097
|
}
|
|
1093
1098
|
/** The sweep `retainReports` holds the per-home lock for. A caller MUST hold
|
|
1094
|
-
* that lock: the listing and the deletions are not atomic on their own.
|
|
1099
|
+
* that lock: the listing and the deletions are not atomic on their own.
|
|
1100
|
+
* 0.19.0 (S2): the sweep itself now lives in core (`sweepReports`) — the
|
|
1101
|
+
* maintenance run reports added in 0.19.0 need the same window, and a second
|
|
1102
|
+
* copy of "list, order, delete beyond the window" is the near-duplicate class
|
|
1103
|
+
* this family treats as a defect. The lock and the two windows stay here. */
|
|
1095
1104
|
async sweepReports(keep, errorKeep) {
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
const mtime = isPresent(probed) ? probed.value : null;
|
|
1112
|
-
if (startedAt !== null) (name.startsWith("curator-error-") ? errors : real).push({
|
|
1113
|
-
name,
|
|
1114
|
-
startedAt,
|
|
1115
|
-
mtime
|
|
1116
|
-
});
|
|
1117
|
-
else if (!unorderableWarned) {
|
|
1118
|
-
unorderableWarned = true;
|
|
1119
|
-
this.ctx.logger.warn(`evolution-curator: report "${name}" carries no usable timestamp and this backend has no mtime probe - it is kept outside the retention window`);
|
|
1120
|
-
}
|
|
1121
|
-
} catch {}
|
|
1122
|
-
const newestFirst = (a, b) => b.startedAt - a.startedAt || (b.mtime ?? 0) - (a.mtime ?? 0) || b.name.localeCompare(a.name);
|
|
1123
|
-
real.sort(newestFirst);
|
|
1124
|
-
errors.sort(newestFirst);
|
|
1125
|
-
await this.pruneReportList(reportsRoot, real, keep);
|
|
1126
|
-
await this.pruneReportList(reportsRoot, errors, errorKeep);
|
|
1127
|
-
}
|
|
1128
|
-
/** Remove the oldest reports beyond `keep` (each with its `.md` digest, best-effort). */
|
|
1129
|
-
async pruneReportList(reportsRoot, dated, keep) {
|
|
1130
|
-
for (const oldReport of dated.slice(keep)) {
|
|
1131
|
-
const stem = oldReport.name.replace(/\.json$/, "");
|
|
1132
|
-
try {
|
|
1133
|
-
await this.io.remove(join(reportsRoot, oldReport.name));
|
|
1134
|
-
} catch {}
|
|
1135
|
-
try {
|
|
1136
|
-
await this.io.remove(join(reportsRoot, `${stem}.md`));
|
|
1137
|
-
} catch {}
|
|
1138
|
-
}
|
|
1105
|
+
await sweepReports({
|
|
1106
|
+
io: this.io,
|
|
1107
|
+
dir: join(evolutionHome(), "reports"),
|
|
1108
|
+
buckets: [{
|
|
1109
|
+
prefix: "curator-error-",
|
|
1110
|
+
keep: errorKeep
|
|
1111
|
+
}, {
|
|
1112
|
+
prefix: "curator-",
|
|
1113
|
+
keep
|
|
1114
|
+
}],
|
|
1115
|
+
warn: (message) => {
|
|
1116
|
+
this.ctx.logger.warn(message);
|
|
1117
|
+
},
|
|
1118
|
+
owner: "evolution-curator"
|
|
1119
|
+
});
|
|
1139
1120
|
}
|
|
1140
1121
|
async latestReport() {
|
|
1141
1122
|
const reportsRoot = join(evolutionHome(), "reports");
|
|
@@ -1433,17 +1414,5 @@ var EvolutionCurator = class extends Service {
|
|
|
1433
1414
|
}
|
|
1434
1415
|
}
|
|
1435
1416
|
};
|
|
1436
|
-
/**
|
|
1437
|
-
* v43 FLOW2-1: the per-home lock target for the report retention sweep.
|
|
1438
|
-
* `transactIo` takes the IO write lock on THIS path (minting `<path>.lock` with
|
|
1439
|
-
* the io protocol's `pid:token` body) and holds it for the whole task; nothing
|
|
1440
|
-
* ever writes the target, because the sweep's exclusion is the point. It lives
|
|
1441
|
-
* inside `<home>/reports` next to the directory it protects: the sweep's own
|
|
1442
|
-
* name filter (`curator-*.json`) ignores it, and the lock file is removed on
|
|
1443
|
-
* release.
|
|
1444
|
-
*/
|
|
1445
|
-
function reportsSweepLockTarget() {
|
|
1446
|
-
return join(evolutionHome(), "reports", ".retention");
|
|
1447
|
-
}
|
|
1448
1417
|
//#endregion
|
|
1449
1418
|
export { EvolutionCurator, EvolutionCurator as default, gateConsolidations, validateCuratorSettings };
|
package/lib/types/index.d.ts
CHANGED
|
@@ -450,10 +450,12 @@ export declare class EvolutionCurator extends Service {
|
|
|
450
450
|
*/
|
|
451
451
|
private retainReports;
|
|
452
452
|
/** The sweep `retainReports` holds the per-home lock for. A caller MUST hold
|
|
453
|
-
* that lock: the listing and the deletions are not atomic on their own.
|
|
453
|
+
* that lock: the listing and the deletions are not atomic on their own.
|
|
454
|
+
* 0.19.0 (S2): the sweep itself now lives in core (`sweepReports`) — the
|
|
455
|
+
* maintenance run reports added in 0.19.0 need the same window, and a second
|
|
456
|
+
* copy of "list, order, delete beyond the window" is the near-duplicate class
|
|
457
|
+
* this family treats as a defect. The lock and the two windows stay here. */
|
|
454
458
|
private sweepReports;
|
|
455
|
-
/** Remove the oldest reports beyond `keep` (each with its `.md` digest, best-effort). */
|
|
456
|
-
private pruneReportList;
|
|
457
459
|
latestReport(): Promise<CuratorRunReport | null>;
|
|
458
460
|
/**
|
|
459
461
|
* Read-only lifecycle scope classification: which skills are in scope,
|
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.
|
|
4
|
+
"version": "0.19.0",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -27,20 +27,20 @@
|
|
|
27
27
|
"license": "MIT",
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"@deepseek-ai/schemastery": "^3.18.4",
|
|
30
|
-
"@lmzhen/dsh-evolution-core": "^0.
|
|
30
|
+
"@lmzhen/dsh-evolution-core": "^0.19.0"
|
|
31
31
|
},
|
|
32
32
|
"peerDependencies": {
|
|
33
33
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
34
34
|
"@deepseek-ai/dsh-llm": "^0.2.0-rc.1",
|
|
35
35
|
"@deepseek-ai/dsh-session": "^0.2.0-rc.1",
|
|
36
|
-
"@lmzhen/dsh-evolution-io": "^0.
|
|
37
|
-
"@lmzhen/dsh-evolution-state": "^0.
|
|
36
|
+
"@lmzhen/dsh-evolution-io": "^0.19.0",
|
|
37
|
+
"@lmzhen/dsh-evolution-state": "^0.19.0"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@deepseek-ai/dsh-llm": "^0.2.0-rc.1",
|
|
41
41
|
"@deepseek-ai/dsh-session": "^0.2.0-rc.1",
|
|
42
|
-
"@lmzhen/dsh-evolution-core": "^0.
|
|
43
|
-
"@lmzhen/dsh-evolution-io": "^0.
|
|
44
|
-
"@lmzhen/dsh-evolution-state": "^0.
|
|
42
|
+
"@lmzhen/dsh-evolution-core": "^0.19.0",
|
|
43
|
+
"@lmzhen/dsh-evolution-io": "^0.19.0",
|
|
44
|
+
"@lmzhen/dsh-evolution-state": "^0.19.0"
|
|
45
45
|
}
|
|
46
46
|
}
|