@lmzhen/dsh-evolution-core 0.1.0-rc.15 → 0.1.0-rc.17

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 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
- if (!await this.io.readText(join(dir, "SKILL.md"))) return {
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
- if (!await this.io.readText(join(dir, "SKILL.md"))) return {
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 };
@@ -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.15",
4
+ "version": "0.1.0-rc.17",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },