@lmzhen/dsh-evolution-core 0.1.0-rc.15 → 0.1.0-rc.16
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 +218 -3
- package/lib/types/index.d.ts +2 -0
- package/lib/types/mutations.d.ts +22 -0
- package/lib/types/quality.d.ts +57 -0
- package/lib/types/skill-store.d.ts +7 -0
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -928,6 +928,39 @@ var MemoryStore = class {
|
|
|
928
928
|
}
|
|
929
929
|
};
|
|
930
930
|
//#endregion
|
|
931
|
+
//#region lib/types/mutations.js
|
|
932
|
+
/**
|
|
933
|
+
* Curator/author audit trail: `.mutations.json` records every skill mutation
|
|
934
|
+
* with before/after content hashes so any automated edit is reviewable and
|
|
935
|
+
* replayable. Best-effort persistence, mirroring the usage sidecar posture.
|
|
936
|
+
* @module @lmzhen/dsh-evolution-core
|
|
937
|
+
*/
|
|
938
|
+
const DEFAULT_MUTATION_CAP = 500;
|
|
939
|
+
function mutationsFile(root) {
|
|
940
|
+
return join(root, ".mutations.json");
|
|
941
|
+
}
|
|
942
|
+
function contentHash(content) {
|
|
943
|
+
return createHash("sha256").update(content).digest("hex");
|
|
944
|
+
}
|
|
945
|
+
async function loadMutations(root, io = nodeEvolutionIo()) {
|
|
946
|
+
const raw = await io.readText(mutationsFile(root));
|
|
947
|
+
if (raw === null) return [];
|
|
948
|
+
try {
|
|
949
|
+
const parsed = JSON.parse(raw);
|
|
950
|
+
if (!Array.isArray(parsed)) return [];
|
|
951
|
+
return parsed.filter((entry) => typeof entry === "object" && entry !== null && typeof entry.skillName === "string" && typeof entry.action === "string");
|
|
952
|
+
} catch {
|
|
953
|
+
return [];
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
/** Append one record, trim to `cap`, and write atomically. */
|
|
957
|
+
async function recordMutation(root, io, record, cap = 500) {
|
|
958
|
+
const existing = await loadMutations(root, io);
|
|
959
|
+
existing.push(record);
|
|
960
|
+
const trimmed = existing.length > cap ? existing.slice(existing.length - cap) : existing;
|
|
961
|
+
await io.writeText(mutationsFile(root), JSON.stringify(trimmed, null, 2));
|
|
962
|
+
}
|
|
963
|
+
//#endregion
|
|
931
964
|
//#region lib/types/prompts.js
|
|
932
965
|
/**
|
|
933
966
|
* Review and curation prompts adapted from Hermes Agent
|
|
@@ -1099,6 +1132,143 @@ Quality bar:
|
|
|
1099
1132
|
- No router/index/hub skills that only point at other skills.
|
|
1100
1133
|
- References go in \`references/\`, templates in \`templates/\`.`;
|
|
1101
1134
|
//#endregion
|
|
1135
|
+
//#region lib/types/quality.js
|
|
1136
|
+
/**
|
|
1137
|
+
* Quality scoring and near-duplicate detection for the curated skill library.
|
|
1138
|
+
*
|
|
1139
|
+
* Pure functions over data inputs so the scoring policy is unit-testable and
|
|
1140
|
+
* the same math feeds the usage sidecar, the `skill_manage review` surface and
|
|
1141
|
+
* the learning graph. Weights follow the Hermes/hermes-claw six-factor model;
|
|
1142
|
+
* mutation maturity is a documented DSH approximation (single per-month patch
|
|
1143
|
+
* trend ratio replaces the claw timestamp-trend formula, since DSH usage
|
|
1144
|
+
* records only carry the last patched timestamp).
|
|
1145
|
+
* @module @lmzhen/dsh-evolution-core
|
|
1146
|
+
*/
|
|
1147
|
+
const QUALITY_WEIGHTS = {
|
|
1148
|
+
usageFrequency: .25,
|
|
1149
|
+
stability: .2,
|
|
1150
|
+
recency: .2,
|
|
1151
|
+
references: .1,
|
|
1152
|
+
mutationMaturity: .2,
|
|
1153
|
+
richness: .05
|
|
1154
|
+
};
|
|
1155
|
+
/** Score below which a skill is flagged for review. */
|
|
1156
|
+
const LOW_QUALITY_THRESHOLD = .3;
|
|
1157
|
+
function clamp01(value) {
|
|
1158
|
+
return Math.max(0, Math.min(1, value));
|
|
1159
|
+
}
|
|
1160
|
+
function daysBetween(from, now) {
|
|
1161
|
+
return Math.max(0, (now.getTime() - new Date(from).getTime()) / 864e5);
|
|
1162
|
+
}
|
|
1163
|
+
function computeQualityScores(input) {
|
|
1164
|
+
const now = input.now ?? /* @__PURE__ */ new Date();
|
|
1165
|
+
const scores = /* @__PURE__ */ new Map();
|
|
1166
|
+
for (const [name, record] of input.usage) {
|
|
1167
|
+
const ageDays = Math.max(1, daysBetween(record.created_at, now));
|
|
1168
|
+
const idleDays = daysBetween(latestActivityAt(record) ?? record.created_at, now);
|
|
1169
|
+
const patchCount = record.patch_count;
|
|
1170
|
+
const useCount = record.use_count;
|
|
1171
|
+
const usageFrequency = clamp01(useCount / ageDays);
|
|
1172
|
+
const stability = useCount === 0 ? 1 : clamp01(1 - patchCount / useCount);
|
|
1173
|
+
const recency = idleDays < 30 ? 1 : clamp01(1 - (idleDays - 30) / 150);
|
|
1174
|
+
const references = clamp01((input.referenceCounts?.get(name) ?? 0) / 3);
|
|
1175
|
+
const mutationMaturity = patchCount === 0 ? .3 : patchCount === 1 ? .4 : clamp01((patchCount - 1) / Math.max(1, ageDays / 30));
|
|
1176
|
+
const richness = clamp01((input.supportDirs?.get(name) ?? 0) * .175);
|
|
1177
|
+
const factors = {
|
|
1178
|
+
usageFrequency,
|
|
1179
|
+
stability,
|
|
1180
|
+
recency,
|
|
1181
|
+
references,
|
|
1182
|
+
mutationMaturity,
|
|
1183
|
+
richness
|
|
1184
|
+
};
|
|
1185
|
+
const score = usageFrequency * QUALITY_WEIGHTS.usageFrequency + stability * QUALITY_WEIGHTS.stability + recency * QUALITY_WEIGHTS.recency + references * QUALITY_WEIGHTS.references + mutationMaturity * QUALITY_WEIGHTS.mutationMaturity + richness * QUALITY_WEIGHTS.richness;
|
|
1186
|
+
scores.set(name, {
|
|
1187
|
+
score,
|
|
1188
|
+
factors,
|
|
1189
|
+
warn: score < LOW_QUALITY_THRESHOLD
|
|
1190
|
+
});
|
|
1191
|
+
}
|
|
1192
|
+
return scores;
|
|
1193
|
+
}
|
|
1194
|
+
function normalize(content) {
|
|
1195
|
+
return content.toLowerCase().replace(/\s+/g, " ").trim();
|
|
1196
|
+
}
|
|
1197
|
+
function contentHash$1(content) {
|
|
1198
|
+
return createHash("sha256").update(normalize(content)).digest("hex");
|
|
1199
|
+
}
|
|
1200
|
+
function tokenize(content) {
|
|
1201
|
+
return new Set(normalize(content).split(/[^a-z0-9\u4e00-\u9fff]+/).filter(Boolean));
|
|
1202
|
+
}
|
|
1203
|
+
function jaccard(a, b) {
|
|
1204
|
+
if (a.size === 0 || b.size === 0) return 0;
|
|
1205
|
+
let intersection = 0;
|
|
1206
|
+
for (const token of a) if (b.has(token)) intersection += 1;
|
|
1207
|
+
return intersection / (a.size + b.size - intersection);
|
|
1208
|
+
}
|
|
1209
|
+
/**
|
|
1210
|
+
* Two-phase near-duplicate clustering: exact normalized-hash groups first,
|
|
1211
|
+
* then token-Jaccard edges at {@link DEDUP_SIMILARITY_THRESHOLD} with a token
|
|
1212
|
+
* ratio guard, union-find across the whole set.
|
|
1213
|
+
*/
|
|
1214
|
+
function computeDedupGroups(input) {
|
|
1215
|
+
const threshold = input.threshold ?? .95;
|
|
1216
|
+
const names = [...input.contents.keys()];
|
|
1217
|
+
const hashes = /* @__PURE__ */ new Map();
|
|
1218
|
+
for (const name of names) {
|
|
1219
|
+
const hash = contentHash$1(input.contents.get(name) ?? "");
|
|
1220
|
+
const bucket = hashes.get(hash);
|
|
1221
|
+
if (bucket) bucket.push(name);
|
|
1222
|
+
else hashes.set(hash, [name]);
|
|
1223
|
+
}
|
|
1224
|
+
const parent = /* @__PURE__ */ new Map();
|
|
1225
|
+
const find = (x) => {
|
|
1226
|
+
const root = parent.get(x) ?? x;
|
|
1227
|
+
if (root !== x) parent.set(x, find(root));
|
|
1228
|
+
return parent.get(x) ?? x;
|
|
1229
|
+
};
|
|
1230
|
+
const union = (a, b) => {
|
|
1231
|
+
const [ra, rb] = [find(a), find(b)];
|
|
1232
|
+
if (ra !== rb) parent.set(rb, ra);
|
|
1233
|
+
};
|
|
1234
|
+
for (const [hash, bucketNames] of hashes) {
|
|
1235
|
+
const first = bucketNames[0];
|
|
1236
|
+
if (first === void 0 || bucketNames.length === 1) continue;
|
|
1237
|
+
for (let index = 1; index < bucketNames.length; index += 1) {
|
|
1238
|
+
const peer = bucketNames[index];
|
|
1239
|
+
if (peer) union(first, peer);
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
const tokens = /* @__PURE__ */ new Map();
|
|
1243
|
+
const tokenSet = (name) => {
|
|
1244
|
+
let set = tokens.get(name);
|
|
1245
|
+
if (!set) {
|
|
1246
|
+
set = tokenize(input.contents.get(name) ?? "");
|
|
1247
|
+
tokens.set(name, set);
|
|
1248
|
+
}
|
|
1249
|
+
return set;
|
|
1250
|
+
};
|
|
1251
|
+
for (let index = 0; index < names.length; index += 1) {
|
|
1252
|
+
const a = names[index];
|
|
1253
|
+
if (a === void 0) continue;
|
|
1254
|
+
for (let other = index + 1; other < names.length; other += 1) {
|
|
1255
|
+
const b = names[other];
|
|
1256
|
+
if (b === void 0) continue;
|
|
1257
|
+
const [ta, tb] = [tokenSet(a), tokenSet(b)];
|
|
1258
|
+
if (Math.max(ta.size, tb.size) / Math.max(1, Math.min(ta.size, tb.size)) > 5) continue;
|
|
1259
|
+
if (jaccard(ta, tb) >= threshold) union(a, b);
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
const groups = /* @__PURE__ */ new Map();
|
|
1263
|
+
for (const name of names) {
|
|
1264
|
+
const root = find(name);
|
|
1265
|
+
const group = groups.get(root);
|
|
1266
|
+
if (group) group.push(name);
|
|
1267
|
+
else groups.set(root, [name]);
|
|
1268
|
+
}
|
|
1269
|
+
return [...groups.values()].filter((group) => group.length > 1);
|
|
1270
|
+
}
|
|
1271
|
+
//#endregion
|
|
1102
1272
|
//#region lib/types/signals.js
|
|
1103
1273
|
/**
|
|
1104
1274
|
* Deterministic review signal gate.
|
|
@@ -1314,6 +1484,41 @@ var SkillLibrary = class {
|
|
|
1314
1484
|
const dir = skillDir(this.root, name);
|
|
1315
1485
|
return await this.io.exists(markerPath(dir, "bundled"));
|
|
1316
1486
|
}
|
|
1487
|
+
/** Count non-empty support subdirectories (richness input for quality scoring). */
|
|
1488
|
+
async countSupportDirs(name) {
|
|
1489
|
+
const dir = skillDir(this.root, name);
|
|
1490
|
+
let entries;
|
|
1491
|
+
try {
|
|
1492
|
+
entries = await this.io.list(dir);
|
|
1493
|
+
} catch {
|
|
1494
|
+
return 0;
|
|
1495
|
+
}
|
|
1496
|
+
let count = 0;
|
|
1497
|
+
for (const subdir of SUPPORT_DIRS) {
|
|
1498
|
+
if (!entries.includes(subdir)) continue;
|
|
1499
|
+
try {
|
|
1500
|
+
if ((await this.io.list(join(dir, subdir))).some((file) => file !== ".gitkeep")) count += 1;
|
|
1501
|
+
} catch {}
|
|
1502
|
+
}
|
|
1503
|
+
return count;
|
|
1504
|
+
}
|
|
1505
|
+
/** Best-effort audit trail entry; never blocks the mutation. */
|
|
1506
|
+
async audit(skillName, action, before, after, summary) {
|
|
1507
|
+
try {
|
|
1508
|
+
await recordMutation(this.root, this.io, {
|
|
1509
|
+
skillName,
|
|
1510
|
+
action,
|
|
1511
|
+
...before === null ? {} : { beforeHash: contentHash(before) },
|
|
1512
|
+
...after === null ? {} : { afterHash: contentHash(after) },
|
|
1513
|
+
summary,
|
|
1514
|
+
at: (/* @__PURE__ */ new Date()).toISOString()
|
|
1515
|
+
});
|
|
1516
|
+
} catch {}
|
|
1517
|
+
}
|
|
1518
|
+
/** Recent mutation audit records (read-only inspection surface). */
|
|
1519
|
+
async listMutations() {
|
|
1520
|
+
return await loadMutations(this.root, this.io);
|
|
1521
|
+
}
|
|
1317
1522
|
async create(name, content, origin) {
|
|
1318
1523
|
const normalized = name.trim();
|
|
1319
1524
|
if (!SKILL_NAME_RE.test(normalized) || normalized.length > this.limits.maxNameLength) return {
|
|
@@ -1337,6 +1542,7 @@ var SkillLibrary = class {
|
|
|
1337
1542
|
};
|
|
1338
1543
|
await this.io.writeText(join(dir, "SKILL.md"), content.trimEnd() + "\n");
|
|
1339
1544
|
if (origin === "background_review") await this.io.writeText(markerPath(dir, "hermes-managed"), "");
|
|
1545
|
+
await this.audit(normalized, "create", null, content, "created");
|
|
1340
1546
|
return {
|
|
1341
1547
|
ok: true,
|
|
1342
1548
|
message: `Skill "${normalized}" created.`,
|
|
@@ -1345,7 +1551,8 @@ var SkillLibrary = class {
|
|
|
1345
1551
|
}
|
|
1346
1552
|
async update(name, content, origin = "foreground") {
|
|
1347
1553
|
const dir = skillDir(this.root, name);
|
|
1348
|
-
|
|
1554
|
+
const md = await this.io.readText(join(dir, "SKILL.md"));
|
|
1555
|
+
if (!md) return {
|
|
1349
1556
|
ok: false,
|
|
1350
1557
|
message: `Skill "${name}" not found.`
|
|
1351
1558
|
};
|
|
@@ -1365,6 +1572,7 @@ var SkillLibrary = class {
|
|
|
1365
1572
|
message: threat
|
|
1366
1573
|
};
|
|
1367
1574
|
await this.io.writeText(join(dir, "SKILL.md"), content.trimEnd() + "\n");
|
|
1575
|
+
await this.audit(name, "update", md, content, "updated");
|
|
1368
1576
|
return {
|
|
1369
1577
|
ok: true,
|
|
1370
1578
|
message: `Skill "${name}" updated.`,
|
|
@@ -1425,6 +1633,7 @@ var SkillLibrary = class {
|
|
|
1425
1633
|
message: threat
|
|
1426
1634
|
};
|
|
1427
1635
|
await this.io.writeText(target, patched.trimEnd() + "\n");
|
|
1636
|
+
await this.audit(name, "patch", md, patched, `patched ${patchLabel}`);
|
|
1428
1637
|
return {
|
|
1429
1638
|
ok: true,
|
|
1430
1639
|
message: `Skill "${name}" patched (${patchLabel}).`,
|
|
@@ -1433,7 +1642,8 @@ var SkillLibrary = class {
|
|
|
1433
1642
|
}
|
|
1434
1643
|
async archive(name, options = {}) {
|
|
1435
1644
|
const dir = skillDir(this.root, name);
|
|
1436
|
-
|
|
1645
|
+
const md = await this.io.readText(join(dir, "SKILL.md"));
|
|
1646
|
+
if (!md) return {
|
|
1437
1647
|
ok: false,
|
|
1438
1648
|
message: `Skill "${name}" not found.`
|
|
1439
1649
|
};
|
|
@@ -1459,6 +1669,7 @@ var SkillLibrary = class {
|
|
|
1459
1669
|
}
|
|
1460
1670
|
const reason = options.reason ?? (options.absorbedInto ? `Consolidated into ${options.absorbedInto}` : "Archived by self-evolution curator");
|
|
1461
1671
|
await this.io.writeText(join(dest, ".archive-reason"), `${(/* @__PURE__ */ new Date()).toISOString()}: ${reason}\n`);
|
|
1672
|
+
await this.audit(name, "archive", md, null, reason);
|
|
1462
1673
|
return {
|
|
1463
1674
|
ok: true,
|
|
1464
1675
|
message: `Skill "${name}" archived to .archive.`,
|
|
@@ -1613,7 +1824,9 @@ var SkillLibrary = class {
|
|
|
1613
1824
|
message: threat
|
|
1614
1825
|
};
|
|
1615
1826
|
const target = join(dir, ...filePath.replace(/\\/g, "/").split("/").filter(Boolean));
|
|
1827
|
+
const existing = await this.io.readText(target).catch(() => null);
|
|
1616
1828
|
await this.io.writeText(target, content);
|
|
1829
|
+
await this.audit(name, "write_file", existing, content, `wrote ${filePath}`);
|
|
1617
1830
|
return {
|
|
1618
1831
|
ok: true,
|
|
1619
1832
|
message: `Support file "${filePath}" written to "${name}".`,
|
|
@@ -1641,7 +1854,9 @@ var SkillLibrary = class {
|
|
|
1641
1854
|
ok: false,
|
|
1642
1855
|
message: `File "${filePath}" not found in skill "${name}".`
|
|
1643
1856
|
};
|
|
1857
|
+
const before = await this.io.readText(target).catch(() => null);
|
|
1644
1858
|
await this.io.remove(target);
|
|
1859
|
+
await this.audit(name, "remove_file", before, null, `removed ${filePath}`);
|
|
1645
1860
|
return {
|
|
1646
1861
|
ok: true,
|
|
1647
1862
|
message: `Support file "${filePath}" removed from "${name}".`,
|
|
@@ -1769,4 +1984,4 @@ var JsonState = class JsonState {
|
|
|
1769
1984
|
}
|
|
1770
1985
|
};
|
|
1771
1986
|
//#endregion
|
|
1772
|
-
export { COMBINED_REVIEW_PROMPT, COMPLETION_SKILL_REVIEW_PROMPT, CURATOR_DRY_RUN_BANNER, CURATOR_PROMPT, DEFAULT_ARCHIVE_AFTER_DAYS, DEFAULT_CURATOR_INTERVAL_HOURS, DEFAULT_MAX_OPS_PER_PLAN, DEFAULT_MEMORY_CHAR_LIMIT, DEFAULT_MIN_IDLE_HOURS, DEFAULT_REVIEW_MEMORY_INTERVAL, DEFAULT_REVIEW_SKILL_INTERVAL, DEFAULT_SKILL_CONTENT_CHARS, DEFAULT_SKILL_LIMITS, DEFAULT_SKILL_REVIEW_COMPLETION_MIN_TOOL_CALLS, DEFAULT_SKILL_REVIEW_TRIGGER, DEFAULT_STALE_AFTER_DAYS, DEFAULT_SUBSTANTIVE_MIN_AGENT_CHARS, DEFAULT_SUBSTANTIVE_MIN_TOOL_CALLS, DEFAULT_SUBSTANTIVE_MIN_USER_CHARS, DEFAULT_USER_CHAR_LIMIT, DSH_AUTHORING_STANDARDS, ENTRY_DELIMITER, JsonState, MAX_DESCRIPTION_LENGTH, MAX_SKILL_CONTENT_CHARS, MAX_SKILL_FILE_BYTES, MAX_SKILL_NAME_LENGTH, MEMORY_REVIEW_PROMPT, MemoryStore, PROMPT_BUNDLE, PROMPT_BUNDLE_ID, PROTECTED_BUILTIN_SKILLS, SKILL_NAME_RE, SKILL_REVIEW_PROMPT, SUPPORT_DIRS, SkillLibrary, advanceReview, buildCuratorRunReport, bumpPatch, bumpUse, bumpView, computeLifecycleTransitions, emptyRecord, evaluateThreat, evolutionHome, evolutionIoAdapter, foldTurn, getRecord, latestActivityAt, loadSuppressedNames, loadUsage, markAgentCreated, memoryRoot, nodeEvolutionIo, observeEvent, parseCuratorNominations, parseFrontmatter, reviewPrompt, saveSuppressedNames, saveUsage, scanContentThreats, scanMemoryThreats, scanThreats, skillsRoot, suppressedFile, usageFile, validateFrontmatter, verifyPromptBundle };
|
|
1987
|
+
export { COMBINED_REVIEW_PROMPT, COMPLETION_SKILL_REVIEW_PROMPT, CURATOR_DRY_RUN_BANNER, CURATOR_PROMPT, DEFAULT_ARCHIVE_AFTER_DAYS, DEFAULT_CURATOR_INTERVAL_HOURS, DEFAULT_MAX_OPS_PER_PLAN, DEFAULT_MEMORY_CHAR_LIMIT, DEFAULT_MIN_IDLE_HOURS, DEFAULT_MUTATION_CAP, DEFAULT_REVIEW_MEMORY_INTERVAL, DEFAULT_REVIEW_SKILL_INTERVAL, DEFAULT_SKILL_CONTENT_CHARS, DEFAULT_SKILL_LIMITS, DEFAULT_SKILL_REVIEW_COMPLETION_MIN_TOOL_CALLS, DEFAULT_SKILL_REVIEW_TRIGGER, DEFAULT_STALE_AFTER_DAYS, DEFAULT_SUBSTANTIVE_MIN_AGENT_CHARS, DEFAULT_SUBSTANTIVE_MIN_TOOL_CALLS, DEFAULT_SUBSTANTIVE_MIN_USER_CHARS, DEFAULT_USER_CHAR_LIMIT, DSH_AUTHORING_STANDARDS, ENTRY_DELIMITER, JsonState, LOW_QUALITY_THRESHOLD, MAX_DESCRIPTION_LENGTH, MAX_SKILL_CONTENT_CHARS, MAX_SKILL_FILE_BYTES, MAX_SKILL_NAME_LENGTH, MEMORY_REVIEW_PROMPT, MemoryStore, PROMPT_BUNDLE, PROMPT_BUNDLE_ID, PROTECTED_BUILTIN_SKILLS, QUALITY_WEIGHTS, SKILL_NAME_RE, SKILL_REVIEW_PROMPT, SUPPORT_DIRS, SkillLibrary, advanceReview, buildCuratorRunReport, bumpPatch, bumpUse, bumpView, computeDedupGroups, computeLifecycleTransitions, computeQualityScores, contentHash, emptyRecord, evaluateThreat, evolutionHome, evolutionIoAdapter, foldTurn, getRecord, latestActivityAt, loadMutations, loadSuppressedNames, loadUsage, markAgentCreated, memoryRoot, mutationsFile, nodeEvolutionIo, observeEvent, parseCuratorNominations, parseFrontmatter, recordMutation, reviewPrompt, saveSuppressedNames, saveUsage, scanContentThreats, scanMemoryThreats, scanThreats, skillsRoot, suppressedFile, usageFile, validateFrontmatter, verifyPromptBundle };
|
package/lib/types/index.d.ts
CHANGED
|
@@ -11,7 +11,9 @@ export * from './curator.ts';
|
|
|
11
11
|
export * from './events.ts';
|
|
12
12
|
export * from './io.ts';
|
|
13
13
|
export * from './memory-store.ts';
|
|
14
|
+
export * from './mutations.ts';
|
|
14
15
|
export * from './prompts.ts';
|
|
16
|
+
export * from './quality.ts';
|
|
15
17
|
export * from './signals.ts';
|
|
16
18
|
export * from './skill-store.ts';
|
|
17
19
|
export * from './state-store.ts';
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Curator/author audit trail: `.mutations.json` records every skill mutation
|
|
3
|
+
* with before/after content hashes so any automated edit is reviewable and
|
|
4
|
+
* replayable. Best-effort persistence, mirroring the usage sidecar posture.
|
|
5
|
+
* @module @deepseek-ai/dsh-evolution-core
|
|
6
|
+
*/
|
|
7
|
+
import { type EvolutionIoLike } from './io.ts';
|
|
8
|
+
export interface MutationRecord {
|
|
9
|
+
skillName: string;
|
|
10
|
+
action: string;
|
|
11
|
+
beforeHash?: string;
|
|
12
|
+
afterHash?: string;
|
|
13
|
+
summary: string;
|
|
14
|
+
at: string;
|
|
15
|
+
}
|
|
16
|
+
export declare const DEFAULT_MUTATION_CAP = 500;
|
|
17
|
+
export declare function mutationsFile(root: string): string;
|
|
18
|
+
export declare function contentHash(content: string): string;
|
|
19
|
+
export declare function loadMutations(root: string, io?: EvolutionIoLike): Promise<MutationRecord[]>;
|
|
20
|
+
/** Append one record, trim to `cap`, and write atomically. */
|
|
21
|
+
export declare function recordMutation(root: string, io: EvolutionIoLike, record: MutationRecord, cap?: number): Promise<void>;
|
|
22
|
+
//# sourceMappingURL=mutations.d.ts.map
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Quality scoring and near-duplicate detection for the curated skill library.
|
|
3
|
+
*
|
|
4
|
+
* Pure functions over data inputs so the scoring policy is unit-testable and
|
|
5
|
+
* the same math feeds the usage sidecar, the `skill_manage review` surface and
|
|
6
|
+
* the learning graph. Weights follow the Hermes/hermes-claw six-factor model;
|
|
7
|
+
* mutation maturity is a documented DSH approximation (single per-month patch
|
|
8
|
+
* trend ratio replaces the claw timestamp-trend formula, since DSH usage
|
|
9
|
+
* records only carry the last patched timestamp).
|
|
10
|
+
* @module @deepseek-ai/dsh-evolution-core
|
|
11
|
+
*/
|
|
12
|
+
import type { UsageMap } from './usage.ts';
|
|
13
|
+
export interface QualityFactors {
|
|
14
|
+
/** 0.25 — use_count per day of age, capped at 1. */
|
|
15
|
+
usageFrequency: number;
|
|
16
|
+
/** 0.20 — 1 − patch/use (zero use = stable). */
|
|
17
|
+
stability: number;
|
|
18
|
+
/** 0.20 — 1 under 30 idle days, linear decay to 0 at 180. */
|
|
19
|
+
recency: number;
|
|
20
|
+
/** 0.10 — in-degree / 3 (graph references), capped at 1. */
|
|
21
|
+
references: number;
|
|
22
|
+
/** 0.20 — patch cadence maturity (DSH approximation of the trend formula). */
|
|
23
|
+
mutationMaturity: number;
|
|
24
|
+
/** 0.05 — non-empty support subdirectories × 0.175, capped at 1. */
|
|
25
|
+
richness: number;
|
|
26
|
+
}
|
|
27
|
+
export interface QualityScore {
|
|
28
|
+
score: number;
|
|
29
|
+
factors: QualityFactors;
|
|
30
|
+
warn: boolean;
|
|
31
|
+
}
|
|
32
|
+
export declare const QUALITY_WEIGHTS: {
|
|
33
|
+
readonly usageFrequency: 0.25;
|
|
34
|
+
readonly stability: 0.2;
|
|
35
|
+
readonly recency: 0.2;
|
|
36
|
+
readonly references: 0.1;
|
|
37
|
+
readonly mutationMaturity: 0.2;
|
|
38
|
+
readonly richness: 0.05;
|
|
39
|
+
};
|
|
40
|
+
/** Score below which a skill is flagged for review. */
|
|
41
|
+
export declare const LOW_QUALITY_THRESHOLD = 0.3;
|
|
42
|
+
export declare function computeQualityScores(input: {
|
|
43
|
+
usage: UsageMap;
|
|
44
|
+
referenceCounts?: ReadonlyMap<string, number>;
|
|
45
|
+
supportDirs?: ReadonlyMap<string, number>;
|
|
46
|
+
now?: Date;
|
|
47
|
+
}): Map<string, QualityScore>;
|
|
48
|
+
/**
|
|
49
|
+
* Two-phase near-duplicate clustering: exact normalized-hash groups first,
|
|
50
|
+
* then token-Jaccard edges at {@link DEDUP_SIMILARITY_THRESHOLD} with a token
|
|
51
|
+
* ratio guard, union-find across the whole set.
|
|
52
|
+
*/
|
|
53
|
+
export declare function computeDedupGroups(input: {
|
|
54
|
+
contents: ReadonlyMap<string, string>;
|
|
55
|
+
threshold?: number;
|
|
56
|
+
}): string[][];
|
|
57
|
+
//# sourceMappingURL=quality.d.ts.map
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* move to `.archive/` — never a hard delete.
|
|
8
8
|
*/
|
|
9
9
|
import { type EvolutionIoLike } from './io.ts';
|
|
10
|
+
import { type MutationRecord } from './mutations.ts';
|
|
10
11
|
export interface SkillLimits {
|
|
11
12
|
maxNameLength: number;
|
|
12
13
|
maxDescriptionLength: number;
|
|
@@ -63,6 +64,12 @@ export declare class SkillLibrary {
|
|
|
63
64
|
isManaged(name: string): Promise<boolean>;
|
|
64
65
|
/** Whether the skill carries the bundled marker (curator prune-builtins eligibility). */
|
|
65
66
|
isBundled(name: string): Promise<boolean>;
|
|
67
|
+
/** Count non-empty support subdirectories (richness input for quality scoring). */
|
|
68
|
+
countSupportDirs(name: string): Promise<number>;
|
|
69
|
+
/** Best-effort audit trail entry; never blocks the mutation. */
|
|
70
|
+
private audit;
|
|
71
|
+
/** Recent mutation audit records (read-only inspection surface). */
|
|
72
|
+
listMutations(): Promise<MutationRecord[]>;
|
|
66
73
|
create(name: string, content: string, origin: 'foreground' | 'background_review'): Promise<SkillActionResult>;
|
|
67
74
|
update(name: string, content: string, origin?: WriteOrigin): Promise<SkillActionResult>;
|
|
68
75
|
patch(name: string, oldString: string, newString: string, filePath?: string, replaceAll?: boolean, origin?: WriteOrigin): Promise<SkillActionResult>;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lmzhen/dsh-evolution-core",
|
|
3
3
|
"description": "Shared stores, prompts, signals and lifecycle logic for the dsh-evolution plugin family (community build)",
|
|
4
|
-
"version": "0.1.0-rc.
|
|
4
|
+
"version": "0.1.0-rc.16",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|