@lmzhen/dsh-evolution-curator 0.17.1 → 0.18.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 +37 -63
- package/lib/types/index.d.ts +3 -22
- package/package.json +8 -8
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, SKILL_NAME_RE, buildCuratorRunReport, claimInstance, clampedNumber, computeDedupGroups, computeLifecycleTransitions, computePrefixClusters, computeQualityScores, computeScopeView, contentHash, emptyRecord, entryTarget, evolutionHome, evolutionIoAdapter, foldCuratorFields, isPresent, isUnknown, loadSuppressedNames, loadUsage, markerEntryName, mutateUsage, newSkillLibrary, paramRowId, parseCuratorNominations, parseFrontmatter, policyStageLimits, probeList, probeMtime, readNumberParam, relatedSkillNames, releaseInstance, renderCuratorReportMarkdown, sessionLastEventTime, transactIo, updateSuppressedNames, usageObserved } from "@lmzhen/dsh-evolution-core";
|
|
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, probeMtime, probeText, readNumberParam, relatedSkillNames, releaseInstance, renderCuratorReportMarkdown, reportTime, sessionLastEventTime, 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.
|
|
@@ -80,6 +80,10 @@ var EvolutionCurator = class extends Service {
|
|
|
80
80
|
bootGraceSeconds;
|
|
81
81
|
/** Keys whose supplied value had to be corrected; each warns once (G3.1). */
|
|
82
82
|
clampedKeys = /* @__PURE__ */ new Set();
|
|
83
|
+
/** The clamp warning sink — the LOGIC lives in core's `clampOnce` (S2.9/T3-10), the prefix here. */
|
|
84
|
+
clampWarn = (message) => {
|
|
85
|
+
this.ctx.logger.warn("evolution-curator: " + message);
|
|
86
|
+
};
|
|
83
87
|
lastRun = 0;
|
|
84
88
|
timer;
|
|
85
89
|
/** B-8 (v18): set by the fiber disposer; a triggered autoCheck must not
|
|
@@ -113,9 +117,10 @@ var EvolutionCurator = class extends Service {
|
|
|
113
117
|
ctx.inject(["settings"], (injected) => {
|
|
114
118
|
const settings = injected.settings;
|
|
115
119
|
if (typeof settings?.configure !== "function") return;
|
|
116
|
-
const disposer = settings.configure({ auto: false });
|
|
120
|
+
const disposer = settings.configure({ auto: false }, ctx.fiber);
|
|
117
121
|
if (typeof disposer === "function") ctx.effect(() => disposer, "evolution-curator: settings presentation");
|
|
118
122
|
});
|
|
123
|
+
if (!verifyPromptBundle(PROMPT_BUNDLE)) throw new Error("dsh-evolution prompt bundle integrity check failed; refusing to run the curator");
|
|
119
124
|
this.io = evolutionIoAdapter(() => ctx.evolutionIo.provider());
|
|
120
125
|
this.skills = newSkillLibrary({
|
|
121
126
|
config,
|
|
@@ -137,7 +142,7 @@ var EvolutionCurator = class extends Service {
|
|
|
137
142
|
this.manageUnmanaged = config.manageUnmanaged ?? false;
|
|
138
143
|
this.pruneBuiltins = config.pruneBuiltins ?? false;
|
|
139
144
|
this.referencedSkillNames = new Set(config.referencedSkillNames ?? []);
|
|
140
|
-
this.bootGraceSeconds = this.
|
|
145
|
+
this.bootGraceSeconds = clampOnce(this.clampedKeys, this.clampWarn, "bootGraceSeconds", config.bootGraceSeconds, DEFAULT_CURATOR_BOOT_GRACE_SECONDS, 0, 3600);
|
|
141
146
|
const archiveAtBoot = this.rowNumber("archiveAfterDays", DEFAULT_ARCHIVE_AFTER_DAYS, 1);
|
|
142
147
|
const staleAtBoot = this.rowNumber("staleAfterDays", DEFAULT_STALE_AFTER_DAYS, 1);
|
|
143
148
|
if (archiveAtBoot < staleAtBoot) this.ctx.logger.warn(`evolution-curator: archiveAfterDays (${archiveAtBoot}) < staleAfterDays (${staleAtBoot}); using staleAfterDays as the archive threshold`);
|
|
@@ -163,26 +168,6 @@ var EvolutionCurator = class extends Service {
|
|
|
163
168
|
if (config.autoStart ?? true) this.start();
|
|
164
169
|
}
|
|
165
170
|
/**
|
|
166
|
-
* Clamp one supplied numeric value, warning once per key (G3.1).
|
|
167
|
-
* @param key - the config key, for the warning.
|
|
168
|
-
* @param value - the value as supplied by the deployment or the user.
|
|
169
|
-
* @param fallback - the package default a corrected value falls back to.
|
|
170
|
-
* @param min - the smallest value the engine can act on.
|
|
171
|
-
* @param max - the largest value the carrier can hold, when bounded.
|
|
172
|
-
* @returns the value the engine may use.
|
|
173
|
-
*/
|
|
174
|
-
clampOnce(key, value, fallback, min, max) {
|
|
175
|
-
const result = clampedNumber(value, fallback, max === void 0 ? { min } : {
|
|
176
|
-
min,
|
|
177
|
-
max
|
|
178
|
-
});
|
|
179
|
-
if (value !== void 0 && result !== value && !this.clampedKeys.has(key)) {
|
|
180
|
-
this.clampedKeys.add(key);
|
|
181
|
-
this.ctx.logger.warn(`evolution-curator: ${key} provided an invalid value; falling back to the default`);
|
|
182
|
-
}
|
|
183
|
-
return result;
|
|
184
|
-
}
|
|
185
|
-
/**
|
|
186
171
|
* One live numeric row field at USE time (G1 §8.3): the row config carries the
|
|
187
172
|
* platform-resolved layers (schema default < bundle row < profile override) and the
|
|
188
173
|
* field stays a live reference, so a committed edit applies without a watcher.
|
|
@@ -202,7 +187,7 @@ var EvolutionCurator = class extends Service {
|
|
|
202
187
|
* @returns the value the engine may use.
|
|
203
188
|
*/
|
|
204
189
|
rowNumber(key, fallback, min, max) {
|
|
205
|
-
return this.
|
|
190
|
+
return clampOnce(this.clampedKeys, this.clampWarn, key, this.liveNumber(key), fallback, min, max);
|
|
206
191
|
}
|
|
207
192
|
/**
|
|
208
193
|
* One live boolean row field at USE time.
|
|
@@ -235,20 +220,6 @@ var EvolutionCurator = class extends Service {
|
|
|
235
220
|
healthChurnMinPatches: number("healthChurnMinPatches", DEFAULT_HEALTH_THRESHOLDS.churnMinPatches, 1)
|
|
236
221
|
};
|
|
237
222
|
}
|
|
238
|
-
/**
|
|
239
|
-
* The keys the user set in this row's namespace (G1 §8.4-A).
|
|
240
|
-
*
|
|
241
|
-
* This is the ONLY remaining read of the settings user layer, and it reads KEY
|
|
242
|
-
* NAMES, never values: the platform resolves the user layer into the row config,
|
|
243
|
-
* so a value is needed from `config` — but the precedence rule (user > policy)
|
|
244
|
-
* needs to know WHICH keys the user set, or a deployment policy would silently
|
|
245
|
-
* override the user.
|
|
246
|
-
* @returns the keys present in this row's user layer.
|
|
247
|
-
*/
|
|
248
|
-
userSetKeys() {
|
|
249
|
-
const entry = this.ctx.get("settings")?.describe?.({ redactSecrets: false }).find((item) => item.ns === paramRowId("evolution-curator"));
|
|
250
|
-
return new Set(Object.keys(entry?.user ?? {}));
|
|
251
|
-
}
|
|
252
223
|
/** The policy-snapshot carrier for this group (unmounted service = no policy). */
|
|
253
224
|
policyFields() {
|
|
254
225
|
return this.ctx.get("evolutionPolicy")?.get() ?? {};
|
|
@@ -265,11 +236,8 @@ var EvolutionCurator = class extends Service {
|
|
|
265
236
|
settings() {
|
|
266
237
|
const snapshot = this.policyFields();
|
|
267
238
|
const row = this.rowSettings();
|
|
268
|
-
const userSet = this.
|
|
269
|
-
const pick = (key) =>
|
|
270
|
-
if (userSet.has(key)) return row[key];
|
|
271
|
-
return snapshot[key] ?? row[key];
|
|
272
|
-
};
|
|
239
|
+
const userSet = userSetKeys(this.ctx, paramRowId("evolution-curator"));
|
|
240
|
+
const pick = (key) => pickWithPolicy(userSet, row, snapshot, key);
|
|
273
241
|
const number = (key, min, max) => {
|
|
274
242
|
const lower = row[key];
|
|
275
243
|
return clampedNumber(pick(key), lower, max === void 0 ? { min } : {
|
|
@@ -966,8 +934,9 @@ var EvolutionCurator = class extends Service {
|
|
|
966
934
|
if (!new RegExp(`^${name}-\\d{14}(-[0-9a-z]{1,6})?$`).test(entry)) continue;
|
|
967
935
|
wasBundled = await this.io.exists(join(this.skills.root, ".archive", entry, markerEntryName("bundled")));
|
|
968
936
|
if (wasBundled) {
|
|
969
|
-
const
|
|
970
|
-
if (
|
|
937
|
+
const archivedProbe = await probeText(this.io, join(this.skills.root, ".archive", entry, "SKILL.md"));
|
|
938
|
+
if (isUnknown(archivedProbe)) this.ctx.logger.warn(`evolution-curator: the archived body of "${entry}" could not be read (${archivedProbe.reason}); keeping the bundled marker as the signal`);
|
|
939
|
+
else if (isPresent(archivedProbe) && parseFrontmatter(archivedProbe.value)?.frontmatter.name !== name) wasBundled = false;
|
|
971
940
|
}
|
|
972
941
|
if (wasBundled) break;
|
|
973
942
|
}
|
|
@@ -1137,24 +1106,22 @@ var EvolutionCurator = class extends Service {
|
|
|
1137
1106
|
for (const name of entries.filter((entry) => entry.startsWith("curator-") && entry.endsWith(".json"))) try {
|
|
1138
1107
|
const raw = await this.io.readText(join(reportsRoot, name));
|
|
1139
1108
|
if (raw === null) continue;
|
|
1140
|
-
const
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
if (isPresent(probe)) startedAt = probe.value;
|
|
1145
|
-
}
|
|
1146
|
-
if (!Number.isFinite(startedAt) && typeof parsed.at === "string") startedAt = Date.parse(parsed.at);
|
|
1147
|
-
if (Number.isFinite(startedAt)) (name.startsWith("curator-error-") ? errors : real).push({
|
|
1109
|
+
const startedAt = await reportTime(this.io, join(reportsRoot, name), JSON.parse(raw));
|
|
1110
|
+
const probed = await probeMtime(this.io, join(reportsRoot, name));
|
|
1111
|
+
const mtime = isPresent(probed) ? probed.value : null;
|
|
1112
|
+
if (startedAt !== null) (name.startsWith("curator-error-") ? errors : real).push({
|
|
1148
1113
|
name,
|
|
1149
|
-
startedAt
|
|
1114
|
+
startedAt,
|
|
1115
|
+
mtime
|
|
1150
1116
|
});
|
|
1151
1117
|
else if (!unorderableWarned) {
|
|
1152
1118
|
unorderableWarned = true;
|
|
1153
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`);
|
|
1154
1120
|
}
|
|
1155
1121
|
} catch {}
|
|
1156
|
-
|
|
1157
|
-
|
|
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);
|
|
1158
1125
|
await this.pruneReportList(reportsRoot, real, keep);
|
|
1159
1126
|
await this.pruneReportList(reportsRoot, errors, errorKeep);
|
|
1160
1127
|
}
|
|
@@ -1181,19 +1148,26 @@ var EvolutionCurator = class extends Service {
|
|
|
1181
1148
|
let latest = null;
|
|
1182
1149
|
let probeWarned = false;
|
|
1183
1150
|
for (const name of names) {
|
|
1184
|
-
const
|
|
1185
|
-
|
|
1151
|
+
const path = join(reportsRoot, name);
|
|
1152
|
+
const raw = await this.io.readText(path);
|
|
1153
|
+
if (raw === null) continue;
|
|
1154
|
+
let parsed;
|
|
1155
|
+
try {
|
|
1156
|
+
parsed = JSON.parse(raw);
|
|
1157
|
+
} catch {
|
|
1158
|
+
continue;
|
|
1159
|
+
}
|
|
1160
|
+
const at = await reportTime(this.io, path, parsed);
|
|
1161
|
+
if (at === null) {
|
|
1186
1162
|
if (!probeWarned) {
|
|
1187
1163
|
probeWarned = true;
|
|
1188
|
-
this.ctx.logger.warn(
|
|
1164
|
+
this.ctx.logger.warn("evolution-curator: latestReport found no usable report time (neither a declared startedAt/at nor an mtime probe) - the affected report(s) are not eligible as the latest");
|
|
1189
1165
|
}
|
|
1190
1166
|
continue;
|
|
1191
1167
|
}
|
|
1192
|
-
if (
|
|
1193
|
-
const mtime = probe.value;
|
|
1194
|
-
if (latest === null || mtime > latest.mtime) latest = {
|
|
1168
|
+
if (latest === null || at > latest.at) latest = {
|
|
1195
1169
|
name,
|
|
1196
|
-
|
|
1170
|
+
at
|
|
1197
1171
|
};
|
|
1198
1172
|
}
|
|
1199
1173
|
if (latest === null) return null;
|
package/lib/types/index.d.ts
CHANGED
|
@@ -29,7 +29,7 @@ export interface Config {
|
|
|
29
29
|
/** The due-ness interval. The policy snapshot shadows it in every shipped
|
|
30
30
|
* composition — configure `curatorIntervalHours` there; that name is the
|
|
31
31
|
* canonical id. Deprecated alias (G0/S0.3): still readable, refused by
|
|
32
|
-
* writes;
|
|
32
|
+
* writes; removal was planned for 0.7.0 and is deferred (the alias still ships — see the package README). */
|
|
33
33
|
intervalHours?: number;
|
|
34
34
|
staleAfterDays?: Volatile<number>;
|
|
35
35
|
archiveAfterDays?: Volatile<number>;
|
|
@@ -205,6 +205,8 @@ export declare class EvolutionCurator extends Service {
|
|
|
205
205
|
private readonly bootGraceSeconds;
|
|
206
206
|
/** Keys whose supplied value had to be corrected; each warns once (G3.1). */
|
|
207
207
|
private readonly clampedKeys;
|
|
208
|
+
/** The clamp warning sink — the LOGIC lives in core's `clampOnce` (S2.9/T3-10), the prefix here. */
|
|
209
|
+
private readonly clampWarn;
|
|
208
210
|
private lastRun;
|
|
209
211
|
private timer;
|
|
210
212
|
/** B-8 (v18): set by the fiber disposer; a triggered autoCheck must not
|
|
@@ -232,16 +234,6 @@ export declare class EvolutionCurator extends Service {
|
|
|
232
234
|
* yielding instance's log line names a concrete holder. */
|
|
233
235
|
private readonly instanceOwner;
|
|
234
236
|
constructor(ctx: Context, config?: Config);
|
|
235
|
-
/**
|
|
236
|
-
* Clamp one supplied numeric value, warning once per key (G3.1).
|
|
237
|
-
* @param key - the config key, for the warning.
|
|
238
|
-
* @param value - the value as supplied by the deployment or the user.
|
|
239
|
-
* @param fallback - the package default a corrected value falls back to.
|
|
240
|
-
* @param min - the smallest value the engine can act on.
|
|
241
|
-
* @param max - the largest value the carrier can hold, when bounded.
|
|
242
|
-
* @returns the value the engine may use.
|
|
243
|
-
*/
|
|
244
|
-
private clampOnce;
|
|
245
237
|
/**
|
|
246
238
|
* One live numeric row field at USE time (G1 §8.3): the row config carries the
|
|
247
239
|
* platform-resolved layers (schema default < bundle row < profile override) and the
|
|
@@ -272,17 +264,6 @@ export declare class EvolutionCurator extends Service {
|
|
|
272
264
|
* @returns the row layer of the curator's behaviour knobs.
|
|
273
265
|
*/
|
|
274
266
|
private rowSettings;
|
|
275
|
-
/**
|
|
276
|
-
* The keys the user set in this row's namespace (G1 §8.4-A).
|
|
277
|
-
*
|
|
278
|
-
* This is the ONLY remaining read of the settings user layer, and it reads KEY
|
|
279
|
-
* NAMES, never values: the platform resolves the user layer into the row config,
|
|
280
|
-
* so a value is needed from `config` — but the precedence rule (user > policy)
|
|
281
|
-
* needs to know WHICH keys the user set, or a deployment policy would silently
|
|
282
|
-
* override the user.
|
|
283
|
-
* @returns the keys present in this row's user layer.
|
|
284
|
-
*/
|
|
285
|
-
private userSetKeys;
|
|
286
267
|
/** The policy-snapshot carrier for this group (unmounted service = no policy). */
|
|
287
268
|
private policyFields;
|
|
288
269
|
/**
|
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.18.0",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -26,21 +26,21 @@
|
|
|
26
26
|
],
|
|
27
27
|
"license": "MIT",
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@deepseek-ai/schemastery": "^3.18.
|
|
30
|
-
"@lmzhen/dsh-evolution-core": "^0.
|
|
29
|
+
"@deepseek-ai/schemastery": "^3.18.4",
|
|
30
|
+
"@lmzhen/dsh-evolution-core": "^0.18.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.18.0",
|
|
37
|
+
"@lmzhen/dsh-evolution-state": "^0.18.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.18.0",
|
|
43
|
+
"@lmzhen/dsh-evolution-io": "^0.18.0",
|
|
44
|
+
"@lmzhen/dsh-evolution-state": "^0.18.0"
|
|
45
45
|
}
|
|
46
46
|
}
|