@lmzhen/dsh-evolution-curator 0.22.0 → 0.24.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 +18 -23
- package/lib/types/index.d.ts +5 -0
- package/package.json +7 -7
package/lib/index.js
CHANGED
|
@@ -3,7 +3,8 @@ 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 {
|
|
6
|
+
import { preferProvider } from "@lmzhen/dsh-evolution-io";
|
|
7
|
+
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_REVIEW_TIMEOUT_MS, 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, liveBool, loadSuppressedNames, loadUsage, markerEntryName, mutateUsage, newSkillLibrary, paramRowId, parseCuratorNominations, parseFrontmatter, pickWithPolicy, policyStageLimits, probeList, probeText, readNumberParam, relatedSkillNames, releaseInstance, renderCuratorReportMarkdown, reportTime, reportsSweepLockTarget, sessionLastEventTime, sweepReports, transactIo, unwrapLive, updateSuppressedNames, usageObserved, userSetKeys, verifyPromptBundle } from "@lmzhen/dsh-evolution-core";
|
|
7
8
|
//#region lib/types/index.js
|
|
8
9
|
/**
|
|
9
10
|
* Deterministic skill lifecycle curator with interval gate and archive.
|
|
@@ -11,12 +12,6 @@ import { CURATOR_DRY_RUN_BANNER, CURATOR_PROMPT, DEFAULT_ARCHIVE_AFTER_DAYS, DEF
|
|
|
11
12
|
*/
|
|
12
13
|
/** Quality-warned skills may turn stale after this many idle days (package-private tunable, P2-8). */
|
|
13
14
|
const DEFAULT_QUALITY_WARN_STALE_AFTER_DAYS = 7;
|
|
14
|
-
/** B-10 (v18): the optional LLM nomination pass gets its own timeout so a
|
|
15
|
-
* hung/unresponsive provider cannot hold the control-plane mutex forever
|
|
16
|
-
* (`run()` never returns; `restore()`/`consolidate()` queue behind it).
|
|
17
|
-
* 120s matches the review subagent default; the 32-bit ceiling is Node's
|
|
18
|
-
* timer-delay limit (`AbortSignal.timeout` throws above it). */
|
|
19
|
-
const DEFAULT_CURATOR_REVIEW_TIMEOUT_MS = 12e4;
|
|
20
15
|
/**
|
|
21
16
|
* The cross-field rule the schema cannot express (A2-17): an archive threshold
|
|
22
17
|
* below the stale threshold makes the engine reactivate stale records instead of
|
|
@@ -59,7 +54,7 @@ var EvolutionCurator = class extends Service {
|
|
|
59
54
|
autoStart: z.boolean().default(true),
|
|
60
55
|
bootGraceSeconds: z.number().min(0).default(DEFAULT_CURATOR_BOOT_GRACE_SECONDS),
|
|
61
56
|
curatorReviewMaxTokens: z.number().min(1).default(DEFAULT_CURATOR_REVIEW_MAX_TOKENS).volatile(),
|
|
62
|
-
curatorReviewTimeoutMs: z.number().min(1).max(MAX_TIMER_DELAY_MS).default(
|
|
57
|
+
curatorReviewTimeoutMs: z.number().min(1).max(MAX_TIMER_DELAY_MS).default(DEFAULT_REVIEW_TIMEOUT_MS).volatile(),
|
|
63
58
|
healthSoftBodyChars: z.number().min(1).default(DEFAULT_HEALTH_THRESHOLDS.softBodyChars).volatile(),
|
|
64
59
|
healthStampDensityPerKb: z.number().min(1).default(DEFAULT_HEALTH_THRESHOLDS.stampDensityPerKb).volatile(),
|
|
65
60
|
healthChurnMinPatches: z.number().min(1).default(DEFAULT_HEALTH_THRESHOLDS.churnMinPatches).volatile()
|
|
@@ -121,7 +116,7 @@ var EvolutionCurator = class extends Service {
|
|
|
121
116
|
if (typeof disposer === "function") ctx.effect(() => disposer, "evolution-curator: settings presentation");
|
|
122
117
|
});
|
|
123
118
|
if (!verifyPromptBundle(PROMPT_BUNDLE)) throw new Error("dsh-evolution prompt bundle integrity check failed; refusing to run the curator");
|
|
124
|
-
this.io = evolutionIoAdapter(() => ctx.evolutionIo
|
|
119
|
+
this.io = evolutionIoAdapter(() => preferProvider(ctx.evolutionIo, ["fs"]));
|
|
125
120
|
this.skills = newSkillLibrary({
|
|
126
121
|
config,
|
|
127
122
|
io: this.io,
|
|
@@ -230,7 +225,7 @@ var EvolutionCurator = class extends Service {
|
|
|
230
225
|
minIdleFailOpen: this.rowBoolean("minIdleFailOpen", true),
|
|
231
226
|
llmReview: this.rowBoolean("llmReview", false),
|
|
232
227
|
curatorReviewMaxTokens: number("curatorReviewMaxTokens", DEFAULT_CURATOR_REVIEW_MAX_TOKENS, 1),
|
|
233
|
-
curatorReviewTimeoutMs: number("curatorReviewTimeoutMs",
|
|
228
|
+
curatorReviewTimeoutMs: number("curatorReviewTimeoutMs", DEFAULT_REVIEW_TIMEOUT_MS, 1, MAX_TIMER_DELAY_MS),
|
|
234
229
|
healthSoftBodyChars: number("healthSoftBodyChars", DEFAULT_HEALTH_THRESHOLDS.softBodyChars, 1),
|
|
235
230
|
healthStampDensityPerKb: number("healthStampDensityPerKb", DEFAULT_HEALTH_THRESHOLDS.stampDensityPerKb, 1),
|
|
236
231
|
healthChurnMinPatches: number("healthChurnMinPatches", DEFAULT_HEALTH_THRESHOLDS.churnMinPatches, 1)
|
|
@@ -614,7 +609,7 @@ var EvolutionCurator = class extends Service {
|
|
|
614
609
|
instanceHeldRefusal(operation) {
|
|
615
610
|
return {
|
|
616
611
|
ok: false,
|
|
617
|
-
message: `instance-held
|
|
612
|
+
message: `instance-held:本 curator 行已把每 home 的占用权让给本进程的另一 curator 行。为保证持有行是唯一的控制面写入方,${operation} 与 run() 一并被拒绝(请在持有它的那一行上运行)。`
|
|
618
613
|
};
|
|
619
614
|
}
|
|
620
615
|
async runCore(options = {}) {
|
|
@@ -973,7 +968,7 @@ var EvolutionCurator = class extends Service {
|
|
|
973
968
|
continue;
|
|
974
969
|
}
|
|
975
970
|
const archived = await this.skills.archive(name, {
|
|
976
|
-
reason: "
|
|
971
|
+
reason: "生命周期:已达归档阈值",
|
|
977
972
|
allowBundled: this.pruneBuiltins
|
|
978
973
|
});
|
|
979
974
|
if (!archived.ok) {
|
|
@@ -995,7 +990,7 @@ var EvolutionCurator = class extends Service {
|
|
|
995
990
|
archivedSkills.push({
|
|
996
991
|
name,
|
|
997
992
|
path: archived.path ?? "",
|
|
998
|
-
reason: "
|
|
993
|
+
reason: "生命周期:已达归档阈值"
|
|
999
994
|
});
|
|
1000
995
|
alreadyArchived.add(name);
|
|
1001
996
|
if (bundledNames.has(name)) {
|
|
@@ -1040,7 +1035,7 @@ var EvolutionCurator = class extends Service {
|
|
|
1040
1035
|
});
|
|
1041
1036
|
archivedSkills.push({
|
|
1042
1037
|
name: nomination.from,
|
|
1043
|
-
reason:
|
|
1038
|
+
reason: `已合并进 ${nomination.into}(精确归档路径见 evolution/skill-mutated 事件里的 archivedPath)`
|
|
1044
1039
|
});
|
|
1045
1040
|
}
|
|
1046
1041
|
if (suppressedChanged) try {
|
|
@@ -1267,7 +1262,7 @@ var EvolutionCurator = class extends Service {
|
|
|
1267
1262
|
const blocked = [...new Set([target, ...sources])].filter((name) => gates.isBlocked(name));
|
|
1268
1263
|
if (blocked.length > 0) return {
|
|
1269
1264
|
ok: false,
|
|
1270
|
-
message:
|
|
1265
|
+
message: `以下技能受保护、无法合并(被排除/被引用/被抑制/受保护的内置):${blocked.join(", ")}`
|
|
1271
1266
|
};
|
|
1272
1267
|
await this.snapshotFull("pre-consolidate");
|
|
1273
1268
|
const result = await this.skills.consolidate(target, sources);
|
|
@@ -1365,46 +1360,46 @@ var EvolutionCurator = class extends Service {
|
|
|
1365
1360
|
const versions = await this.skills.listVersions(name);
|
|
1366
1361
|
if (versions.length === 0) return {
|
|
1367
1362
|
ok: false,
|
|
1368
|
-
message:
|
|
1363
|
+
message: `技能「${name}」还没有记录内容版本 —— 没有可撤销的。`
|
|
1369
1364
|
};
|
|
1370
1365
|
const live = await this.skills.read(name);
|
|
1371
1366
|
if (live === null) return {
|
|
1372
1367
|
ok: false,
|
|
1373
|
-
message:
|
|
1368
|
+
message: `「${name}」不在活动目录树里 —— 撤销恢复的是存活技能的正文;若它已归档,请用 '/evolution skill restore ${name}'。`
|
|
1374
1369
|
};
|
|
1375
1370
|
const liveHash = contentHash(live);
|
|
1376
1371
|
const target = v ?? this.previousVersionOf(versions, liveHash);
|
|
1377
1372
|
if (target === void 0) return {
|
|
1378
1373
|
ok: false,
|
|
1379
|
-
message:
|
|
1374
|
+
message: `「${name}」已处于其记录的内容 —— 没有更早的版本可撤销。`
|
|
1380
1375
|
};
|
|
1381
1376
|
if (!versions.some((candidate) => candidate.v === target)) {
|
|
1382
1377
|
const listed = versions.map((candidate) => `v${candidate.v}`).join(", ");
|
|
1383
1378
|
return {
|
|
1384
1379
|
ok: false,
|
|
1385
|
-
message:
|
|
1380
|
+
message: `技能「${name}」的版本 v${target} 不在已记录的历史里(已记录:${listed === "" ? "无" : listed})。`
|
|
1386
1381
|
};
|
|
1387
1382
|
}
|
|
1388
1383
|
const entry = versions.find((candidate) => candidate.v === target);
|
|
1389
1384
|
if (entry !== void 0 && entryTarget(entry, versions) === "support") return {
|
|
1390
1385
|
ok: false,
|
|
1391
|
-
message:
|
|
1386
|
+
message: `技能「${name}」的版本 v${target} ${entry.path === void 0 ? `由 ${entry.action} 写入,并且` : `由 ${entry.action} 写入到 ${entry.path},并且`} 存的是支撑文件的字节,而不是该技能的 SKILL.md —— 撤销只会恢复正文。 已记录的字节仍留在历史存储里;要恢复它们,请把该 blob 复制回那个文件。`
|
|
1392
1387
|
};
|
|
1393
1388
|
const content = await this.skills.readVersion(name, target);
|
|
1394
1389
|
if (content === null) return {
|
|
1395
1390
|
ok: false,
|
|
1396
|
-
message:
|
|
1391
|
+
message: `技能「${name}」的版本 v${target} 虽在索引里,但它的存储内容读不出来 —— 拒绝瞎猜、不做写入。`
|
|
1397
1392
|
};
|
|
1398
1393
|
const anchor = { sha256: liveHash };
|
|
1399
1394
|
const result = await this.skills.update(name, content, "foreground", anchor);
|
|
1400
1395
|
if (!result.ok) return result;
|
|
1401
1396
|
if (result.noop === true) return {
|
|
1402
1397
|
ok: true,
|
|
1403
|
-
message:
|
|
1398
|
+
message: `「${name}」已持有 v${target} 的内容 —— 未写入任何内容,也没有记录新版本。`
|
|
1404
1399
|
};
|
|
1405
1400
|
return {
|
|
1406
1401
|
ok: true,
|
|
1407
|
-
message: `${result.message}
|
|
1402
|
+
message: `${result.message} (已撤销到 v${target} —— 仅内容:标记、使用计数与策展状态不变,且这次撤销本身就是一个新版本。)`
|
|
1408
1403
|
};
|
|
1409
1404
|
}
|
|
1410
1405
|
/** The content the live bytes replaced: the CHAIN first, the recorded order as the fallback.
|
package/lib/types/index.d.ts
CHANGED
|
@@ -17,6 +17,11 @@ import { EvolutionGateSet, type SkillLibrary } from '@lmzhen/dsh-evolution-core'
|
|
|
17
17
|
import { type CuratorConsolidation, type CuratorNominations, type CuratorRunReport, type ScopeView, type SkillActionResult, type SkillHealthVerdict } from '@lmzhen/dsh-evolution-core';
|
|
18
18
|
import type { SkillVersion } from '@lmzhen/dsh-evolution-core';
|
|
19
19
|
import type { CuratorStateRecord } from '@lmzhen/dsh-evolution-state';
|
|
20
|
+
/** B-10 (v18): the optional LLM nomination pass gets its own timeout so a
|
|
21
|
+
* hung/unresponsive provider cannot hold the control-plane mutex forever
|
|
22
|
+
* (`run()` never returns; `restore()`/`consolidate()` queue behind it).
|
|
23
|
+
* 120s matches the review subagent default; the 32-bit ceiling is Node's
|
|
24
|
+
* timer-delay limit (`AbortSignal.timeout` throws above it). */
|
|
20
25
|
declare module '@deepseek-ai/cordis' {
|
|
21
26
|
interface Context {
|
|
22
27
|
evolutionCurator: EvolutionCurator;
|
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.24.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.24.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.24.0",
|
|
37
|
+
"@lmzhen/dsh-evolution-state": "^0.24.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.24.0",
|
|
43
|
+
"@lmzhen/dsh-evolution-io": "^0.24.0",
|
|
44
|
+
"@lmzhen/dsh-evolution-state": "^0.24.0"
|
|
45
45
|
}
|
|
46
46
|
}
|