@lmzhen/dsh-evolution-curator 0.3.81 → 0.3.82
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 +35 -3
- package/lib/types/index.d.ts +16 -1
- 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, 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";
|
|
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, transactIo, 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.
|
|
@@ -96,7 +96,9 @@ var EvolutionCurator = class extends Service {
|
|
|
96
96
|
/** P2-5 (v14): one-shot warning that the interval baseline is process-only. */
|
|
97
97
|
statelessStateWarned = false;
|
|
98
98
|
/** B3 / G4: the home this instance claimed, plus whether the claim was
|
|
99
|
-
* granted. A non-owning instance schedules nothing and runs nothing.
|
|
99
|
+
* granted. A non-owning instance schedules nothing and runs nothing. The
|
|
100
|
+
* claim is PER PROCESS (v43 FLOW2-1): another process's holder is invisible
|
|
101
|
+
* here, so `holdsInstance` never proves this instance is alone on the home. */
|
|
100
102
|
instanceHome = "";
|
|
101
103
|
holdsInstance = false;
|
|
102
104
|
/** B3 / G4: this instance's identity in the claim — pid + short token, so the
|
|
@@ -114,7 +116,7 @@ var EvolutionCurator = class extends Service {
|
|
|
114
116
|
this.instanceHome = evolutionHome();
|
|
115
117
|
const claim = claimInstance(this.instanceHome, INSTANCE_KEYS.curator, this.instanceOwner);
|
|
116
118
|
this.holdsInstance = claim.granted;
|
|
117
|
-
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
|
|
119
|
+
if (!claim.granted) this.ctx.logger.warn(`evolution-curator: this instance YIELDS — ${claim.key} is already held by ${claim.holder} IN THIS PROCESS (the claim is a module-scope Map, so it does not exclude another process); a second curator ROW over one home would run a second gate set and a second lifecycle pass over the same tree. It schedules nothing and every run() returns skipped "instance-held"; the report sweep itself is cross-process locked (v43 FLOW2-1).`);
|
|
118
120
|
const clamped = [];
|
|
119
121
|
const field = (name, value, fallback, min, max) => {
|
|
120
122
|
const result = clampedNumber(value, fallback, max === void 0 ? { min } : {
|
|
@@ -934,8 +936,26 @@ var EvolutionCurator = class extends Service {
|
|
|
934
936
|
* (`curator-error-*.json`) are BUDGETED INDEPENDENTLY. Before this they shared
|
|
935
937
|
* one keep-20 window, so after 25 consecutive failures the next successful
|
|
936
938
|
* run's window held only a few real reports alongside the errors.
|
|
939
|
+
*
|
|
940
|
+
* v43 FLOW2-1 (P1): the sweep runs inside the IO backend's cross-process write
|
|
941
|
+
* lock. The inventory used to declare this site as serialized by the per-home
|
|
942
|
+
* instance claim, which is a module-scope Map (core/instance-scope.ts): a
|
|
943
|
+
* second ROW of this process yields, but a second PROCESS over the same home
|
|
944
|
+
* was never excluded — and "list the directory, then delete beyond the window"
|
|
945
|
+
* is exactly the multi-step shape that needs a real lock. A backend without
|
|
946
|
+
* `transact` has no lock at all: the sweep then degrades to the previous
|
|
947
|
+
* best-effort pass, which is safe because every deletion is idempotent and
|
|
948
|
+
* each report name is unique per runId.
|
|
937
949
|
*/
|
|
938
950
|
async retainReports(keep = 20, errorKeep = 10) {
|
|
951
|
+
await transactIo(this.io, reportsSweepLockTarget(), async () => {
|
|
952
|
+
await this.sweepReports(keep, errorKeep);
|
|
953
|
+
return null;
|
|
954
|
+
});
|
|
955
|
+
}
|
|
956
|
+
/** The sweep `retainReports` holds the per-home lock for. A caller MUST hold
|
|
957
|
+
* that lock: the listing and the deletions are not atomic on their own. */
|
|
958
|
+
async sweepReports(keep, errorKeep) {
|
|
939
959
|
const reportsRoot = join(evolutionHome(), "reports");
|
|
940
960
|
const listed = await probeList(this.io, reportsRoot);
|
|
941
961
|
if (isUnknown(listed)) {
|
|
@@ -1167,5 +1187,17 @@ var EvolutionCurator = class extends Service {
|
|
|
1167
1187
|
return result;
|
|
1168
1188
|
}
|
|
1169
1189
|
};
|
|
1190
|
+
/**
|
|
1191
|
+
* v43 FLOW2-1: the per-home lock target for the report retention sweep.
|
|
1192
|
+
* `transactIo` takes the IO write lock on THIS path (minting `<path>.lock` with
|
|
1193
|
+
* the io protocol's `pid:token` body) and holds it for the whole task; nothing
|
|
1194
|
+
* ever writes the target, because the sweep's exclusion is the point. It lives
|
|
1195
|
+
* inside `<home>/reports` next to the directory it protects: the sweep's own
|
|
1196
|
+
* name filter (`curator-*.json`) ignores it, and the lock file is removed on
|
|
1197
|
+
* release.
|
|
1198
|
+
*/
|
|
1199
|
+
function reportsSweepLockTarget() {
|
|
1200
|
+
return join(evolutionHome(), "reports", ".retention");
|
|
1201
|
+
}
|
|
1170
1202
|
//#endregion
|
|
1171
1203
|
export { EvolutionCurator, EvolutionCurator as default, gateConsolidations };
|
package/lib/types/index.d.ts
CHANGED
|
@@ -131,7 +131,9 @@ export declare class EvolutionCurator extends Service {
|
|
|
131
131
|
/** P2-5 (v14): one-shot warning that the interval baseline is process-only. */
|
|
132
132
|
private statelessStateWarned;
|
|
133
133
|
/** B3 / G4: the home this instance claimed, plus whether the claim was
|
|
134
|
-
* granted. A non-owning instance schedules nothing and runs nothing.
|
|
134
|
+
* granted. A non-owning instance schedules nothing and runs nothing. The
|
|
135
|
+
* claim is PER PROCESS (v43 FLOW2-1): another process's holder is invisible
|
|
136
|
+
* here, so `holdsInstance` never proves this instance is alone on the home. */
|
|
135
137
|
private instanceHome;
|
|
136
138
|
private holdsInstance;
|
|
137
139
|
/** B3 / G4: this instance's identity in the claim — pid + short token, so the
|
|
@@ -283,8 +285,21 @@ export declare class EvolutionCurator extends Service {
|
|
|
283
285
|
* (`curator-error-*.json`) are BUDGETED INDEPENDENTLY. Before this they shared
|
|
284
286
|
* one keep-20 window, so after 25 consecutive failures the next successful
|
|
285
287
|
* run's window held only a few real reports alongside the errors.
|
|
288
|
+
*
|
|
289
|
+
* v43 FLOW2-1 (P1): the sweep runs inside the IO backend's cross-process write
|
|
290
|
+
* lock. The inventory used to declare this site as serialized by the per-home
|
|
291
|
+
* instance claim, which is a module-scope Map (core/instance-scope.ts): a
|
|
292
|
+
* second ROW of this process yields, but a second PROCESS over the same home
|
|
293
|
+
* was never excluded — and "list the directory, then delete beyond the window"
|
|
294
|
+
* is exactly the multi-step shape that needs a real lock. A backend without
|
|
295
|
+
* `transact` has no lock at all: the sweep then degrades to the previous
|
|
296
|
+
* best-effort pass, which is safe because every deletion is idempotent and
|
|
297
|
+
* each report name is unique per runId.
|
|
286
298
|
*/
|
|
287
299
|
private retainReports;
|
|
300
|
+
/** The sweep `retainReports` holds the per-home lock for. A caller MUST hold
|
|
301
|
+
* that lock: the listing and the deletions are not atomic on their own. */
|
|
302
|
+
private sweepReports;
|
|
288
303
|
/** Remove the oldest reports beyond `keep` (each with its `.md` digest, best-effort). */
|
|
289
304
|
private pruneReportList;
|
|
290
305
|
latestReport(): Promise<CuratorRunReport | null>;
|
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.82",
|
|
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.82"
|
|
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.82",
|
|
37
|
+
"@lmzhen/dsh-evolution-state": "^0.3.82"
|
|
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.82",
|
|
43
|
+
"@lmzhen/dsh-evolution-io": "^0.3.82",
|
|
44
|
+
"@lmzhen/dsh-evolution-state": "^0.3.82"
|
|
45
45
|
}
|
|
46
46
|
}
|