@lmzhen/dsh-evolution-curator 0.3.67 → 0.3.69
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 +51 -13
- package/lib/types/index.d.ts +3 -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_REVIEW_MAX_TOKENS, DEFAULT_HEALTH_THRESHOLDS, DEFAULT_MIN_IDLE_HOURS, DEFAULT_STALE_AFTER_DAYS, EvolutionGateSet, SKILL_NAME_RE, SkillLibrary, buildCuratorRunReport, clampedNumber, computeDedupGroups, computeLifecycleTransitions, computePrefixClusters, computeQualityScores, computeScopeView, emptyRecord, evolutionHome, evolutionIoAdapter, foldCuratorFields, loadSuppressedNames, loadUsage, markerEntryName, mutateUsage, parseCuratorNominations, parseFrontmatter, relatedSkillNames, renderCuratorReportMarkdown, resolveSkillsRoot, 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, MAX_TIMER_DELAY_MS, SKILL_NAME_RE, SkillLibrary, buildCuratorRunReport, clampedNumber, computeDedupGroups, computeLifecycleTransitions, computePrefixClusters, computeQualityScores, computeScopeView, emptyRecord, evolutionHome, evolutionIoAdapter, foldCuratorFields, loadSuppressedNames, loadUsage, markerEntryName, mutateUsage, parseCuratorNominations, parseFrontmatter, relatedSkillNames, renderCuratorReportMarkdown, resolveSkillsRoot, 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.
|
|
@@ -17,7 +17,6 @@ const DEFAULT_QUALITY_WARN_STALE_AFTER_DAYS = 7;
|
|
|
17
17
|
* 120s matches the review subagent default; the 32-bit ceiling is Node's
|
|
18
18
|
* timer-delay limit (`AbortSignal.timeout` throws above it). */
|
|
19
19
|
const DEFAULT_CURATOR_REVIEW_TIMEOUT_MS = 12e4;
|
|
20
|
-
const MAX_TIMER_DELAY_MS = 2147483647;
|
|
21
20
|
/**
|
|
22
21
|
* Block LLM-nominated consolidations that would touch a gate-protected name:
|
|
23
22
|
* exclude / referenced / suppressed skills must never merge (neither as the
|
|
@@ -255,7 +254,7 @@ var EvolutionCurator = class extends Service {
|
|
|
255
254
|
if (candidates.length === 0) return empty;
|
|
256
255
|
const llm = this.ctx.get("llm");
|
|
257
256
|
if (!llm) return empty;
|
|
258
|
-
const model = this.ctx.get("evolutionPolicy")?.get()?.curatorModel ??
|
|
257
|
+
const model = this.ctx.get("evolutionPolicy")?.get()?.curatorModel ?? DEFAULT_CURATOR_MODEL;
|
|
259
258
|
const clusters = computePrefixClusters(candidates);
|
|
260
259
|
const clusterLines = clusters.length === 0 ? ["Prefix clusters observed in the candidate list: (none)"] : ["Prefix clusters observed in the candidate list (orientation only — verify against the names above; you may also flag additional clusters):", ...clusters.map((cluster) => `- '${cluster.key}': ${cluster.members.join(", ")}`)];
|
|
261
260
|
const prompt = [
|
|
@@ -409,7 +408,9 @@ var EvolutionCurator = class extends Service {
|
|
|
409
408
|
* mover refuses a directory whose writer lock is alive), so a collision
|
|
410
409
|
* degrades to a recorded failed op + snapshot rollback, never a torn
|
|
411
410
|
* write. Automatic passes are kept out of the session-active window by the
|
|
412
|
-
* min-idle gate
|
|
411
|
+
* min-idle gate — checked pre-run AND re-checked at the commit boundary
|
|
412
|
+
* (v28 G4.2: a session activating mid-run no longer slips past it); a
|
|
413
|
+
* manual run (`ignoreGates`) bypasses both checks and is
|
|
413
414
|
* the one realistic interleave window — documented, accepted (plan v20
|
|
414
415
|
* C-3①). The same statement lives on evolution-review's `reviewInFlight`.
|
|
415
416
|
*/
|
|
@@ -497,14 +498,13 @@ var EvolutionCurator = class extends Service {
|
|
|
497
498
|
skipped: "disposed"
|
|
498
499
|
};
|
|
499
500
|
}
|
|
500
|
-
const snapshotPath = dryRun ? void 0 : await this.snapshotFull("pre-curator-run");
|
|
501
501
|
const suppressedNames = new Set(await loadSuppressedNames(root, this.io));
|
|
502
502
|
const gates = new EvolutionGateSet({
|
|
503
503
|
exclude: this.excludeSkillNames,
|
|
504
504
|
referenced: this.referencedSkillNames,
|
|
505
505
|
suppressed: suppressedNames
|
|
506
506
|
});
|
|
507
|
-
const { bundledNames, treeNames } = await this.seedBaseline(usage);
|
|
507
|
+
const { bundledNames, treeNames, resetToActive } = await this.seedBaseline(usage);
|
|
508
508
|
const contents = /* @__PURE__ */ new Map();
|
|
509
509
|
for (const name of treeNames) {
|
|
510
510
|
const text = await this.skills.read(name);
|
|
@@ -537,6 +537,23 @@ var EvolutionCurator = class extends Service {
|
|
|
537
537
|
};
|
|
538
538
|
const llmNominations = gatedNominations.prunings;
|
|
539
539
|
const archiveCandidates = [...new Set([...result.archive, ...llmNominations])];
|
|
540
|
+
if (!ignoreGates && this.minIdleHours > 0 && this.recentSessionActive()) {
|
|
541
|
+
if (!dryRun) try {
|
|
542
|
+
await mutateUsage(root, this.io, (map) => {
|
|
543
|
+
for (const [name, record] of usage) if (!map.has(name)) map.set(name, { ...record });
|
|
544
|
+
});
|
|
545
|
+
} catch (error) {
|
|
546
|
+
this.ctx.logger.warn(`evolution-curator: failed to persist the seeded usage baseline on a session-blocked run (${error instanceof Error ? error.message : String(error)})`);
|
|
547
|
+
}
|
|
548
|
+
return {
|
|
549
|
+
stale: [],
|
|
550
|
+
archived: [],
|
|
551
|
+
errors: [],
|
|
552
|
+
report: this.skippedReport(runId, startedAt),
|
|
553
|
+
skipped: "active-session"
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
const snapshotPath = dryRun ? void 0 : await this.snapshotFull("pre-curator-run");
|
|
540
557
|
const { archivedSkills, errors, consolidated } = await this.applyMutations({
|
|
541
558
|
dryRun,
|
|
542
559
|
archiveCandidates,
|
|
@@ -547,11 +564,16 @@ var EvolutionCurator = class extends Service {
|
|
|
547
564
|
suppressedNames,
|
|
548
565
|
root,
|
|
549
566
|
recommendPool: new Set(recommendPool),
|
|
550
|
-
stateOwned: new Set([
|
|
567
|
+
stateOwned: new Set([
|
|
568
|
+
...result.transitions.map((t) => t.name),
|
|
569
|
+
...archiveCandidates,
|
|
570
|
+
...resetToActive
|
|
571
|
+
]),
|
|
551
572
|
runStartStates,
|
|
552
573
|
failedFrom: new Map(result.transitions.filter((t) => t.to === "archived").map((t) => [t.name, t.from]))
|
|
553
574
|
});
|
|
554
|
-
|
|
575
|
+
const runAborted = errors.some((error) => error.startsWith("run aborted"));
|
|
576
|
+
if (!dryRun && !runAborted) this.lastRun = Date.now();
|
|
555
577
|
const report = buildCuratorRunReport({
|
|
556
578
|
runId,
|
|
557
579
|
startedAt,
|
|
@@ -566,6 +588,15 @@ var EvolutionCurator = class extends Service {
|
|
|
566
588
|
reason: errors.find((item) => item.startsWith(`${name}:`))?.slice(name.length + 2) ?? "unknown"
|
|
567
589
|
};
|
|
568
590
|
}),
|
|
591
|
+
...(() => {
|
|
592
|
+
const attributed = new Set([...archiveCandidates, ...gatedNominations.consolidations.map((item) => item.from)]);
|
|
593
|
+
const loose = errors.filter((error) => ![...attributed].some((name) => error.startsWith(`${name}:`)));
|
|
594
|
+
const abort = loose.find((error) => error.startsWith("run aborted"));
|
|
595
|
+
return {
|
|
596
|
+
...abort === void 0 ? {} : { aborted: abort.slice(13) },
|
|
597
|
+
...loose.length === 0 ? {} : { unattributed: loose }
|
|
598
|
+
};
|
|
599
|
+
})(),
|
|
569
600
|
consolidated,
|
|
570
601
|
...snapshotPath === void 0 ? {} : { snapshotPath },
|
|
571
602
|
llmReviewEnabled: this.llmReview,
|
|
@@ -586,8 +617,8 @@ var EvolutionCurator = class extends Service {
|
|
|
586
617
|
const pausedNow = current?.paused ?? false;
|
|
587
618
|
return {
|
|
588
619
|
schemaVersion: 1,
|
|
589
|
-
lastRunAt: dryRun ? persisted?.lastRunAt ?? this.lastRun : Date.now(),
|
|
590
|
-
runCount: dryRun ? persisted?.runCount ?? 0 : (current?.runCount ?? 0) + 1,
|
|
620
|
+
lastRunAt: dryRun || runAborted ? persisted?.lastRunAt ?? this.lastRun : Date.now(),
|
|
621
|
+
runCount: dryRun || runAborted ? persisted?.runCount ?? 0 : (current?.runCount ?? 0) + 1,
|
|
591
622
|
lastSummary: summary,
|
|
592
623
|
paused: pausedNow
|
|
593
624
|
};
|
|
@@ -612,16 +643,23 @@ var EvolutionCurator = class extends Service {
|
|
|
612
643
|
async seedBaseline(usage) {
|
|
613
644
|
const bundledNames = /* @__PURE__ */ new Set();
|
|
614
645
|
const treeNames = /* @__PURE__ */ new Set();
|
|
646
|
+
const resetToActive = /* @__PURE__ */ new Set();
|
|
615
647
|
for (const summary of await this.skills.list()) {
|
|
616
648
|
treeNames.add(summary.name);
|
|
617
649
|
if (!usage.has(summary.name)) usage.set(summary.name, emptyRecord());
|
|
618
650
|
if (await this.skills.isBundled(summary.name)) bundledNames.add(summary.name);
|
|
619
651
|
const record = usage.get(summary.name);
|
|
652
|
+
if (record?.state === "archived") {
|
|
653
|
+
record.state = "active";
|
|
654
|
+
record.archived_at = null;
|
|
655
|
+
resetToActive.add(summary.name);
|
|
656
|
+
}
|
|
620
657
|
if (record) record.pinned = await this.skills.isPinned(summary.name);
|
|
621
658
|
}
|
|
622
659
|
return {
|
|
623
660
|
bundledNames,
|
|
624
|
-
treeNames
|
|
661
|
+
treeNames,
|
|
662
|
+
resetToActive
|
|
625
663
|
};
|
|
626
664
|
}
|
|
627
665
|
/**
|
|
@@ -699,6 +737,7 @@ var EvolutionCurator = class extends Service {
|
|
|
699
737
|
record.state = "archived";
|
|
700
738
|
record.archived_at = record.archived_at ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
701
739
|
stateOwned.add(name);
|
|
740
|
+
this.ctx.logger.warn(`evolution-curator: skill "${name}" left the tree without an archive move - its usage record was folded to archived; if the directory is still on disk, repair or remove it manually (it will not re-enter the lifecycle while the record is archived)`);
|
|
702
741
|
}
|
|
703
742
|
let wasBundled = false;
|
|
704
743
|
try {
|
|
@@ -793,8 +832,7 @@ var EvolutionCurator = class extends Service {
|
|
|
793
832
|
});
|
|
794
833
|
archivedSkills.push({
|
|
795
834
|
name: nomination.from,
|
|
796
|
-
|
|
797
|
-
reason: `Consolidated into ${nomination.into}`
|
|
835
|
+
reason: `Consolidated into ${nomination.into} (exact archive path: the evolution/skill-mutated event, archivedPath)`
|
|
798
836
|
});
|
|
799
837
|
}
|
|
800
838
|
if (suppressedChanged) try {
|
package/lib/types/index.d.ts
CHANGED
|
@@ -222,7 +222,9 @@ export declare class EvolutionCurator extends Service {
|
|
|
222
222
|
* mover refuses a directory whose writer lock is alive), so a collision
|
|
223
223
|
* degrades to a recorded failed op + snapshot rollback, never a torn
|
|
224
224
|
* write. Automatic passes are kept out of the session-active window by the
|
|
225
|
-
* min-idle gate
|
|
225
|
+
* min-idle gate — checked pre-run AND re-checked at the commit boundary
|
|
226
|
+
* (v28 G4.2: a session activating mid-run no longer slips past it); a
|
|
227
|
+
* manual run (`ignoreGates`) bypasses both checks and is
|
|
226
228
|
* the one realistic interleave window — documented, accepted (plan v20
|
|
227
229
|
* C-3①). The same statement lives on evolution-review's `reviewInFlight`.
|
|
228
230
|
*/
|
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.69",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -31,20 +31,20 @@
|
|
|
31
31
|
"license": "MIT",
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@deepseek-ai/schemastery": "^3.18.1",
|
|
34
|
-
"@lmzhen/dsh-evolution-core": "^0.3.
|
|
34
|
+
"@lmzhen/dsh-evolution-core": "^0.3.69"
|
|
35
35
|
},
|
|
36
36
|
"peerDependencies": {
|
|
37
37
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
38
38
|
"@deepseek-ai/dsh-invariants": "^0.1.1-rc.2",
|
|
39
39
|
"@deepseek-ai/dsh-llm": "^0.1.1-rc.2",
|
|
40
|
-
"@lmzhen/dsh-evolution-io": "^0.3.
|
|
41
|
-
"@lmzhen/dsh-evolution-state": "^0.3.
|
|
40
|
+
"@lmzhen/dsh-evolution-io": "^0.3.69",
|
|
41
|
+
"@lmzhen/dsh-evolution-state": "^0.3.69"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"@deepseek-ai/dsh-invariants": "^0.1.1-rc.2",
|
|
45
45
|
"@deepseek-ai/dsh-llm": "^0.1.1-rc.2",
|
|
46
|
-
"@lmzhen/dsh-evolution-core": "^0.3.
|
|
47
|
-
"@lmzhen/dsh-evolution-io": "^0.3.
|
|
48
|
-
"@lmzhen/dsh-evolution-state": "^0.3.
|
|
46
|
+
"@lmzhen/dsh-evolution-core": "^0.3.69",
|
|
47
|
+
"@lmzhen/dsh-evolution-io": "^0.3.69",
|
|
48
|
+
"@lmzhen/dsh-evolution-state": "^0.3.69"
|
|
49
49
|
}
|
|
50
50
|
}
|