@lmzhen/dsh-evolution-curator 0.23.0 → 0.25.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 +75 -19
- package/lib/types/index.d.ts +25 -1
- package/package.json +7 -7
package/lib/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import { join } from "node:path";
|
|
|
4
4
|
import { BlockAssembler, createUserMessage } from "@deepseek-ai/dsh-llm";
|
|
5
5
|
import z from "@deepseek-ai/schemastery";
|
|
6
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
|
+
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, lintSkill, 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";
|
|
8
8
|
//#region lib/types/index.js
|
|
9
9
|
/**
|
|
10
10
|
* Deterministic skill lifecycle curator with interval gate and archive.
|
|
@@ -33,6 +33,24 @@ function gateConsolidations(consolidations, gates) {
|
|
|
33
33
|
const gateSet = gates instanceof EvolutionGateSet ? gates : new EvolutionGateSet(gates);
|
|
34
34
|
return consolidations.filter((n) => !gateSet.isBlocked(n.from) && !gateSet.isBlocked(n.into));
|
|
35
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* G5 (in-use protection): which skills are POINTED AT by others, and by whom.
|
|
38
|
+
*
|
|
39
|
+
* Pure: derived from the bodies already in hand, using `relatedSkillNames` — the single reader of
|
|
40
|
+
* that frontmatter field — so this map can never disagree with the catalog or the audit. The value
|
|
41
|
+
* is a Set of SOURCES, not a boolean, because a refusal has to be able to name the referrers.
|
|
42
|
+
* @param contents - skill name to its whole SKILL.md text.
|
|
43
|
+
* @returns target name to the set of skills naming it; targets nobody names are absent.
|
|
44
|
+
*/
|
|
45
|
+
function referencedSkillNames(contents) {
|
|
46
|
+
const referencedBy = /* @__PURE__ */ new Map();
|
|
47
|
+
for (const [owner, text] of contents) for (const target of relatedSkillNames(text, owner)) {
|
|
48
|
+
const sources = referencedBy.get(target) ?? /* @__PURE__ */ new Set();
|
|
49
|
+
sources.add(owner);
|
|
50
|
+
referencedBy.set(target, sources);
|
|
51
|
+
}
|
|
52
|
+
return referencedBy;
|
|
53
|
+
}
|
|
36
54
|
var EvolutionCurator = class extends Service {
|
|
37
55
|
static inject = ["evolutionIo"];
|
|
38
56
|
static Config = z.object({
|
|
@@ -609,7 +627,7 @@ var EvolutionCurator = class extends Service {
|
|
|
609
627
|
instanceHeldRefusal(operation) {
|
|
610
628
|
return {
|
|
611
629
|
ok: false,
|
|
612
|
-
message: `instance-held
|
|
630
|
+
message: `instance-held:本 curator 行已把每 home 的占用权让给本进程的另一 curator 行。为保证持有行是唯一的控制面写入方,${operation} 与 run() 一并被拒绝(请在持有它的那一行上运行)。`
|
|
613
631
|
};
|
|
614
632
|
}
|
|
615
633
|
async runCore(options = {}) {
|
|
@@ -691,8 +709,9 @@ var EvolutionCurator = class extends Service {
|
|
|
691
709
|
const text = await this.skills.read(name);
|
|
692
710
|
if (text) contents.set(name, text);
|
|
693
711
|
}
|
|
712
|
+
const referencedBy = referencedSkillNames(contents);
|
|
694
713
|
const protectedNames = await this.protectedNameMap();
|
|
695
|
-
const dedupMembers = [...new Set(computeDedupGroups({ contents }).groups.filter((group) => group.length >= 2).flat())].filter((name) => !protectedNames.has(name));
|
|
714
|
+
const dedupMembers = [...new Set(computeDedupGroups({ contents }).groups.filter((group) => group.length >= 2).flat())].filter((name) => !protectedNames.has(name) && !referencedBy.has(name));
|
|
696
715
|
await this.scoreTree(usage, treeNames, contents);
|
|
697
716
|
const result = computeLifecycleTransitions(usage, {
|
|
698
717
|
staleAfterDays: lifecycle.staleAfterDays,
|
|
@@ -968,7 +987,7 @@ var EvolutionCurator = class extends Service {
|
|
|
968
987
|
continue;
|
|
969
988
|
}
|
|
970
989
|
const archived = await this.skills.archive(name, {
|
|
971
|
-
reason: "
|
|
990
|
+
reason: "生命周期:已达归档阈值",
|
|
972
991
|
allowBundled: this.pruneBuiltins
|
|
973
992
|
});
|
|
974
993
|
if (!archived.ok) {
|
|
@@ -990,7 +1009,7 @@ var EvolutionCurator = class extends Service {
|
|
|
990
1009
|
archivedSkills.push({
|
|
991
1010
|
name,
|
|
992
1011
|
path: archived.path ?? "",
|
|
993
|
-
reason: "
|
|
1012
|
+
reason: "生命周期:已达归档阈值"
|
|
994
1013
|
});
|
|
995
1014
|
alreadyArchived.add(name);
|
|
996
1015
|
if (bundledNames.has(name)) {
|
|
@@ -1035,7 +1054,7 @@ var EvolutionCurator = class extends Service {
|
|
|
1035
1054
|
});
|
|
1036
1055
|
archivedSkills.push({
|
|
1037
1056
|
name: nomination.from,
|
|
1038
|
-
reason:
|
|
1057
|
+
reason: `已合并进 ${nomination.into}(精确归档路径见 evolution/skill-mutated 事件里的 archivedPath)`
|
|
1039
1058
|
});
|
|
1040
1059
|
}
|
|
1041
1060
|
if (suppressedChanged) try {
|
|
@@ -1219,10 +1238,47 @@ var EvolutionCurator = class extends Service {
|
|
|
1219
1238
|
patchCount: record.patch_count,
|
|
1220
1239
|
readCount: record.view_count
|
|
1221
1240
|
} : void 0);
|
|
1222
|
-
if (assessment
|
|
1241
|
+
if (assessment !== null) {
|
|
1242
|
+
rows.push({
|
|
1243
|
+
name: summary.name,
|
|
1244
|
+
verdict: assessment.verdict,
|
|
1245
|
+
dims: assessment.dims,
|
|
1246
|
+
findings: assessment.findings
|
|
1247
|
+
});
|
|
1248
|
+
continue;
|
|
1249
|
+
}
|
|
1250
|
+
const findings = lintSkill({
|
|
1251
|
+
skillName: summary.name,
|
|
1252
|
+
dims: {
|
|
1253
|
+
bodyChars: null,
|
|
1254
|
+
bodyTokens: null,
|
|
1255
|
+
stampDensityPerKb: null,
|
|
1256
|
+
stampHits: null,
|
|
1257
|
+
supportGroups: 0,
|
|
1258
|
+
supportFiles: null,
|
|
1259
|
+
churnPatches: null,
|
|
1260
|
+
churnReads: null
|
|
1261
|
+
},
|
|
1262
|
+
softLineChars: thresholds.softBodyChars,
|
|
1263
|
+
overSoftLine: false,
|
|
1264
|
+
overTwiceSoftLine: false,
|
|
1265
|
+
stampLimitPerKb: thresholds.stampDensityPerKb,
|
|
1266
|
+
churnMinPatches: thresholds.churnMinPatches,
|
|
1267
|
+
readable: false
|
|
1268
|
+
});
|
|
1269
|
+
rows.push({
|
|
1223
1270
|
name: summary.name,
|
|
1224
|
-
verdict:
|
|
1225
|
-
|
|
1271
|
+
verdict: "warn",
|
|
1272
|
+
dims: {
|
|
1273
|
+
bodyChars: null,
|
|
1274
|
+
bodyTokens: null,
|
|
1275
|
+
stampDensityPerKb: null,
|
|
1276
|
+
supportGroups: 0,
|
|
1277
|
+
supportFiles: null,
|
|
1278
|
+
churnPatches: null,
|
|
1279
|
+
churnReads: null
|
|
1280
|
+
},
|
|
1281
|
+
findings
|
|
1226
1282
|
});
|
|
1227
1283
|
}
|
|
1228
1284
|
return rows;
|
|
@@ -1262,7 +1318,7 @@ var EvolutionCurator = class extends Service {
|
|
|
1262
1318
|
const blocked = [...new Set([target, ...sources])].filter((name) => gates.isBlocked(name));
|
|
1263
1319
|
if (blocked.length > 0) return {
|
|
1264
1320
|
ok: false,
|
|
1265
|
-
message:
|
|
1321
|
+
message: `以下技能受保护、无法合并(被排除/被引用/被抑制/受保护的内置):${blocked.join(", ")}`
|
|
1266
1322
|
};
|
|
1267
1323
|
await this.snapshotFull("pre-consolidate");
|
|
1268
1324
|
const result = await this.skills.consolidate(target, sources);
|
|
@@ -1360,46 +1416,46 @@ var EvolutionCurator = class extends Service {
|
|
|
1360
1416
|
const versions = await this.skills.listVersions(name);
|
|
1361
1417
|
if (versions.length === 0) return {
|
|
1362
1418
|
ok: false,
|
|
1363
|
-
message:
|
|
1419
|
+
message: `技能「${name}」还没有记录内容版本 —— 没有可撤销的。`
|
|
1364
1420
|
};
|
|
1365
1421
|
const live = await this.skills.read(name);
|
|
1366
1422
|
if (live === null) return {
|
|
1367
1423
|
ok: false,
|
|
1368
|
-
message:
|
|
1424
|
+
message: `「${name}」不在活动目录树里 —— 撤销恢复的是存活技能的正文;若它已归档,请用 '/evolution skill restore ${name}'。`
|
|
1369
1425
|
};
|
|
1370
1426
|
const liveHash = contentHash(live);
|
|
1371
1427
|
const target = v ?? this.previousVersionOf(versions, liveHash);
|
|
1372
1428
|
if (target === void 0) return {
|
|
1373
1429
|
ok: false,
|
|
1374
|
-
message:
|
|
1430
|
+
message: `「${name}」已处于其记录的内容 —— 没有更早的版本可撤销。`
|
|
1375
1431
|
};
|
|
1376
1432
|
if (!versions.some((candidate) => candidate.v === target)) {
|
|
1377
1433
|
const listed = versions.map((candidate) => `v${candidate.v}`).join(", ");
|
|
1378
1434
|
return {
|
|
1379
1435
|
ok: false,
|
|
1380
|
-
message:
|
|
1436
|
+
message: `技能「${name}」的版本 v${target} 不在已记录的历史里(已记录:${listed === "" ? "无" : listed})。`
|
|
1381
1437
|
};
|
|
1382
1438
|
}
|
|
1383
1439
|
const entry = versions.find((candidate) => candidate.v === target);
|
|
1384
1440
|
if (entry !== void 0 && entryTarget(entry, versions) === "support") return {
|
|
1385
1441
|
ok: false,
|
|
1386
|
-
message:
|
|
1442
|
+
message: `技能「${name}」的版本 v${target} ${entry.path === void 0 ? `由 ${entry.action} 写入,并且` : `由 ${entry.action} 写入到 ${entry.path},并且`} 存的是支撑文件的字节,而不是该技能的 SKILL.md —— 撤销只会恢复正文。 已记录的字节仍留在历史存储里;要恢复它们,请把该 blob 复制回那个文件。`
|
|
1387
1443
|
};
|
|
1388
1444
|
const content = await this.skills.readVersion(name, target);
|
|
1389
1445
|
if (content === null) return {
|
|
1390
1446
|
ok: false,
|
|
1391
|
-
message:
|
|
1447
|
+
message: `技能「${name}」的版本 v${target} 虽在索引里,但它的存储内容读不出来 —— 拒绝瞎猜、不做写入。`
|
|
1392
1448
|
};
|
|
1393
1449
|
const anchor = { sha256: liveHash };
|
|
1394
1450
|
const result = await this.skills.update(name, content, "foreground", anchor);
|
|
1395
1451
|
if (!result.ok) return result;
|
|
1396
1452
|
if (result.noop === true) return {
|
|
1397
1453
|
ok: true,
|
|
1398
|
-
message:
|
|
1454
|
+
message: `「${name}」已持有 v${target} 的内容 —— 未写入任何内容,也没有记录新版本。`
|
|
1399
1455
|
};
|
|
1400
1456
|
return {
|
|
1401
1457
|
ok: true,
|
|
1402
|
-
message: `${result.message}
|
|
1458
|
+
message: `${result.message} (已撤销到 v${target} —— 仅内容:标记、使用计数与策展状态不变,且这次撤销本身就是一个新版本。)`
|
|
1403
1459
|
};
|
|
1404
1460
|
}
|
|
1405
1461
|
/** The content the live bytes replaced: the CHAIN first, the recorded order as the fallback.
|
|
@@ -1426,4 +1482,4 @@ var EvolutionCurator = class extends Service {
|
|
|
1426
1482
|
}
|
|
1427
1483
|
};
|
|
1428
1484
|
//#endregion
|
|
1429
|
-
export { EvolutionCurator, EvolutionCurator as default, gateConsolidations, validateCuratorSettings };
|
|
1485
|
+
export { EvolutionCurator, EvolutionCurator as default, gateConsolidations, referencedSkillNames, validateCuratorSettings };
|
package/lib/types/index.d.ts
CHANGED
|
@@ -148,6 +148,16 @@ export declare function gateConsolidations(consolidations: CuratorConsolidation[
|
|
|
148
148
|
referenced?: ReadonlySet<string>;
|
|
149
149
|
suppressed?: ReadonlySet<string>;
|
|
150
150
|
}): CuratorConsolidation[];
|
|
151
|
+
/**
|
|
152
|
+
* G5 (in-use protection): which skills are POINTED AT by others, and by whom.
|
|
153
|
+
*
|
|
154
|
+
* Pure: derived from the bodies already in hand, using `relatedSkillNames` — the single reader of
|
|
155
|
+
* that frontmatter field — so this map can never disagree with the catalog or the audit. The value
|
|
156
|
+
* is a Set of SOURCES, not a boolean, because a refusal has to be able to name the referrers.
|
|
157
|
+
* @param contents - skill name to its whole SKILL.md text.
|
|
158
|
+
* @returns target name to the set of skills naming it; targets nobody names are absent.
|
|
159
|
+
*/
|
|
160
|
+
export declare function referencedSkillNames(contents: ReadonlyMap<string, string>): Map<string, Set<string>>;
|
|
151
161
|
export declare class EvolutionCurator extends Service {
|
|
152
162
|
static inject: string[];
|
|
153
163
|
static Config: z<Schemastery.ObjectS<NoInfer<{
|
|
@@ -495,7 +505,21 @@ export declare class EvolutionCurator extends Service {
|
|
|
495
505
|
healthView(): Promise<Array<{
|
|
496
506
|
name: string;
|
|
497
507
|
verdict: SkillHealthVerdict;
|
|
498
|
-
|
|
508
|
+
dims: {
|
|
509
|
+
bodyChars: number | null;
|
|
510
|
+
bodyTokens: number | null;
|
|
511
|
+
stampDensityPerKb: number | null;
|
|
512
|
+
supportGroups: number;
|
|
513
|
+
supportFiles: number | null;
|
|
514
|
+
churnPatches: number | null;
|
|
515
|
+
churnReads: number | null;
|
|
516
|
+
};
|
|
517
|
+
findings: Array<{
|
|
518
|
+
severity: 'warn' | 'error';
|
|
519
|
+
rule: string;
|
|
520
|
+
message: string;
|
|
521
|
+
count: number;
|
|
522
|
+
}>;
|
|
499
523
|
}>>;
|
|
500
524
|
/** Whether the library has ANY observed read evidence (C observation-window gate for churn signals). */
|
|
501
525
|
usageObserved(): Promise<boolean>;
|
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.25.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.25.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.25.0",
|
|
37
|
+
"@lmzhen/dsh-evolution-state": "^0.25.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.25.0",
|
|
43
|
+
"@lmzhen/dsh-evolution-io": "^0.25.0",
|
|
44
|
+
"@lmzhen/dsh-evolution-state": "^0.25.0"
|
|
45
45
|
}
|
|
46
46
|
}
|