@lmzhen/dsh-evolution-curator 0.3.77 → 0.3.78
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 +36 -18
- package/lib/types/index.d.ts +7 -0
- package/package.json +7 -7
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_STALE_AFTER_DAYS, EvolutionGateSet, MAX_TIMER_DELAY_MS, SKILL_NAME_RE, buildCuratorRunReport, clampedNumber, computeDedupGroups, computeLifecycleTransitions, computePrefixClusters, computeQualityScores, computeScopeView, emptyRecord, evolutionHome, evolutionIoAdapter, foldCuratorFields, loadSuppressedNames, loadUsage, markerEntryName, mutateUsage, newSkillLibrary, parseCuratorNominations, parseFrontmatter, relatedSkillNames, renderCuratorReportMarkdown, 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_STALE_AFTER_DAYS, EvolutionGateSet, INSTANCE_KEYS, MAX_TIMER_DELAY_MS, SKILL_NAME_RE, buildCuratorRunReport, claimInstance, clampedNumber, computeDedupGroups, computeLifecycleTransitions, computePrefixClusters, computeQualityScores, computeScopeView, emptyRecord, evolutionHome, evolutionIoAdapter, foldCuratorFields, isPresent, isUnknown, loadSuppressedNames, loadUsage, markerEntryName, mutateUsage, newSkillLibrary, parseCuratorNominations, parseFrontmatter, probeList, probeMtime, relatedSkillNames, releaseInstance, renderCuratorReportMarkdown, updateSuppressedNames, usageObserved } 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.
|
|
@@ -92,6 +92,13 @@ var EvolutionCurator = class extends Service {
|
|
|
92
92
|
statelessFirstRunDeferred = false;
|
|
93
93
|
/** P2-5 (v14): one-shot warning that the interval baseline is process-only. */
|
|
94
94
|
statelessStateWarned = false;
|
|
95
|
+
/** B3 / G4: the home this instance claimed, plus whether the claim was
|
|
96
|
+
* granted. A non-owning instance schedules nothing and runs nothing. */
|
|
97
|
+
instanceHome = "";
|
|
98
|
+
holdsInstance = false;
|
|
99
|
+
/** B3 / G4: this instance's identity in the claim — pid + short token, so the
|
|
100
|
+
* yielding instance's log line names a concrete holder. */
|
|
101
|
+
instanceOwner = `evolution-curator[${process.pid}:${randomUUID().slice(0, 8)}]`;
|
|
95
102
|
constructor(ctx, config = {}) {
|
|
96
103
|
super(ctx, "evolutionCurator");
|
|
97
104
|
this.io = evolutionIoAdapter(() => ctx.evolutionIo.provider());
|
|
@@ -101,6 +108,10 @@ var EvolutionCurator = class extends Service {
|
|
|
101
108
|
ctx: this.ctx
|
|
102
109
|
});
|
|
103
110
|
this.enabled = config.enabled ?? true;
|
|
111
|
+
this.instanceHome = evolutionHome();
|
|
112
|
+
const claim = claimInstance(this.instanceHome, INSTANCE_KEYS.curator, this.instanceOwner);
|
|
113
|
+
this.holdsInstance = claim.granted;
|
|
114
|
+
if (!claim.granted) this.ctx.logger.warn(`evolution-curator: this instance YIELDS — ${claim.key} is already held by ${claim.holder}; a second curator over one home would race the report retention sweep. It schedules nothing and every run() returns skipped "instance-held".`);
|
|
104
115
|
const clamped = [];
|
|
105
116
|
const field = (name, value, fallback, min, max) => {
|
|
106
117
|
const result = clampedNumber(value, fallback, max === void 0 ? { min } : {
|
|
@@ -138,6 +149,7 @@ var EvolutionCurator = class extends Service {
|
|
|
138
149
|
return () => {
|
|
139
150
|
this.disposed = true;
|
|
140
151
|
this.stop();
|
|
152
|
+
releaseInstance(this.instanceHome, INSTANCE_KEYS.curator, this.instanceOwner);
|
|
141
153
|
};
|
|
142
154
|
}, "evolution-curator.stop");
|
|
143
155
|
if (config.autoStart ?? true) this.start();
|
|
@@ -151,6 +163,7 @@ var EvolutionCurator = class extends Service {
|
|
|
151
163
|
};
|
|
152
164
|
}
|
|
153
165
|
start() {
|
|
166
|
+
if (!this.holdsInstance) return;
|
|
154
167
|
if (this.isDisposed() || !this.enabled || this.timer) return;
|
|
155
168
|
this.bootCheck = setTimeout(() => {
|
|
156
169
|
this.bootCheck = void 0;
|
|
@@ -372,6 +385,13 @@ var EvolutionCurator = class extends Service {
|
|
|
372
385
|
* skipped with an explicit `already-running` outcome.
|
|
373
386
|
*/
|
|
374
387
|
async run(options = {}) {
|
|
388
|
+
if (!this.holdsInstance) return {
|
|
389
|
+
stale: [],
|
|
390
|
+
archived: [],
|
|
391
|
+
errors: [],
|
|
392
|
+
report: this.skippedReport("instance-held", (/* @__PURE__ */ new Date()).toISOString()),
|
|
393
|
+
skipped: "instance-held"
|
|
394
|
+
};
|
|
375
395
|
if (this.mutexDepth > 0) return {
|
|
376
396
|
stale: [],
|
|
377
397
|
archived: [],
|
|
@@ -905,12 +925,12 @@ var EvolutionCurator = class extends Service {
|
|
|
905
925
|
*/
|
|
906
926
|
async retainReports(keep = 20, errorKeep = 10) {
|
|
907
927
|
const reportsRoot = join(evolutionHome(), "reports");
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
} catch {
|
|
928
|
+
const listed = await probeList(this.io, reportsRoot);
|
|
929
|
+
if (isUnknown(listed)) {
|
|
930
|
+
this.ctx.logger.warn(`evolution-curator: report retention skipped — the reports directory could not be listed (${listed.reason})`);
|
|
912
931
|
return;
|
|
913
932
|
}
|
|
933
|
+
const entries = isPresent(listed) ? listed.value : [];
|
|
914
934
|
const real = [];
|
|
915
935
|
const errors = [];
|
|
916
936
|
let unorderableWarned = false;
|
|
@@ -920,8 +940,8 @@ var EvolutionCurator = class extends Service {
|
|
|
920
940
|
const parsed = JSON.parse(raw);
|
|
921
941
|
let startedAt = typeof parsed.startedAt === "string" ? Date.parse(parsed.startedAt) : NaN;
|
|
922
942
|
if (!Number.isFinite(startedAt)) {
|
|
923
|
-
const
|
|
924
|
-
if (
|
|
943
|
+
const probe = await probeMtime(this.io, join(reportsRoot, name));
|
|
944
|
+
if (isPresent(probe)) startedAt = probe.value;
|
|
925
945
|
}
|
|
926
946
|
if (!Number.isFinite(startedAt) && typeof parsed.at === "string") startedAt = Date.parse(parsed.at);
|
|
927
947
|
if (Number.isFinite(startedAt)) (name.startsWith("curator-error-") ? errors : real).push({
|
|
@@ -952,27 +972,25 @@ var EvolutionCurator = class extends Service {
|
|
|
952
972
|
}
|
|
953
973
|
async latestReport() {
|
|
954
974
|
const reportsRoot = join(evolutionHome(), "reports");
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
} catch (error) {
|
|
959
|
-
this.ctx.logger.warn(`evolution-curator: latestReport could not list the reports directory (${error instanceof Error ? error.message : String(error)}) - answering "no report"`);
|
|
975
|
+
const listed = await probeList(this.io, reportsRoot);
|
|
976
|
+
if (isUnknown(listed)) {
|
|
977
|
+
this.ctx.logger.warn(`evolution-curator: latestReport could not list the reports directory (${listed.reason}) - answering "no report"`);
|
|
960
978
|
return null;
|
|
961
979
|
}
|
|
980
|
+
const names = (isPresent(listed) ? listed.value : []).filter((name) => name.startsWith("curator-") && name.endsWith(".json") && !name.startsWith("curator-error-"));
|
|
962
981
|
let latest = null;
|
|
963
982
|
let probeWarned = false;
|
|
964
983
|
for (const name of names) {
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
mtime = await this.io.mtime?.(join(reportsRoot, name)) ?? null;
|
|
968
|
-
} catch (error) {
|
|
984
|
+
const probe = await probeMtime(this.io, join(reportsRoot, name));
|
|
985
|
+
if (isUnknown(probe)) {
|
|
969
986
|
if (!probeWarned) {
|
|
970
987
|
probeWarned = true;
|
|
971
|
-
this.ctx.logger.warn(`evolution-curator: latestReport could not read a report mtime (${
|
|
988
|
+
this.ctx.logger.warn(`evolution-curator: latestReport could not read a report mtime (${probe.reason}) - the affected report(s) are not eligible as the latest`);
|
|
972
989
|
}
|
|
973
990
|
continue;
|
|
974
991
|
}
|
|
975
|
-
if (
|
|
992
|
+
if (!isPresent(probe)) continue;
|
|
993
|
+
const mtime = probe.value;
|
|
976
994
|
if (latest === null || mtime > latest.mtime) latest = {
|
|
977
995
|
name,
|
|
978
996
|
mtime
|
package/lib/types/index.d.ts
CHANGED
|
@@ -127,6 +127,13 @@ export declare class EvolutionCurator extends Service {
|
|
|
127
127
|
private statelessFirstRunDeferred;
|
|
128
128
|
/** P2-5 (v14): one-shot warning that the interval baseline is process-only. */
|
|
129
129
|
private statelessStateWarned;
|
|
130
|
+
/** B3 / G4: the home this instance claimed, plus whether the claim was
|
|
131
|
+
* granted. A non-owning instance schedules nothing and runs nothing. */
|
|
132
|
+
private instanceHome;
|
|
133
|
+
private holdsInstance;
|
|
134
|
+
/** B3 / G4: this instance's identity in the claim — pid + short token, so the
|
|
135
|
+
* yielding instance's log line names a concrete holder. */
|
|
136
|
+
private readonly instanceOwner;
|
|
130
137
|
constructor(ctx: Context, config?: Config);
|
|
131
138
|
private lifecycle;
|
|
132
139
|
start(): void;
|
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.3.
|
|
4
|
+
"version": "0.3.78",
|
|
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.1",
|
|
30
|
-
"@lmzhen/dsh-evolution-core": "^0.3.
|
|
30
|
+
"@lmzhen/dsh-evolution-core": "^0.3.78"
|
|
31
31
|
},
|
|
32
32
|
"peerDependencies": {
|
|
33
33
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
34
34
|
"@deepseek-ai/dsh-llm": "^0.1.5-rc.2",
|
|
35
35
|
"@deepseek-ai/dsh-session": "^0.1.5-rc.2",
|
|
36
|
-
"@lmzhen/dsh-evolution-io": "^0.3.
|
|
37
|
-
"@lmzhen/dsh-evolution-state": "^0.3.
|
|
36
|
+
"@lmzhen/dsh-evolution-io": "^0.3.78",
|
|
37
|
+
"@lmzhen/dsh-evolution-state": "^0.3.78"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@deepseek-ai/dsh-llm": "^0.1.5-rc.2",
|
|
41
41
|
"@deepseek-ai/dsh-session": "^0.1.5-rc.2",
|
|
42
|
-
"@lmzhen/dsh-evolution-core": "^0.3.
|
|
43
|
-
"@lmzhen/dsh-evolution-io": "^0.3.
|
|
44
|
-
"@lmzhen/dsh-evolution-state": "^0.3.
|
|
42
|
+
"@lmzhen/dsh-evolution-core": "^0.3.78",
|
|
43
|
+
"@lmzhen/dsh-evolution-io": "^0.3.78",
|
|
44
|
+
"@lmzhen/dsh-evolution-state": "^0.3.78"
|
|
45
45
|
}
|
|
46
46
|
}
|