@lmzhen/dsh-evolution-curator 0.27.3 → 0.29.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 CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Service } from "@deepseek-ai/cordis";
2
- import { BASELINE_ACTION, 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_REVIEW_TIMEOUT_MS, DEFAULT_SKILL_LIMITS, DEFAULT_STALE_AFTER_DAYS, EvolutionGateSet, INSTANCE_KEYS, MAX_TIMER_DELAY_MS, PROMPT_BUNDLE, SKILL_NAME_RE, authorityFrom, blobPath, buildCuratorRunReport, claimInstance, clampOnce, clampedNumber, computeDedupGroups, computeLifecycleTransitions, computePrefixClusters, computeQualityScores, computeScopeView, contentHash, emptyRecord, entryTarget, evolutionHome, evolutionIoAdapter, foldCuratorFields, historyIndexFile, isPresent, isUnknown, lintSkill, liveBool, loadSuppressedNames, loadUsage, markerEntryName, mutateUsage, newSkillLibrary, paramRowId, parseCuratorNominations, parseFrontmatter, parseHistoryIndex, pickWithPolicy, policyStageLimits, probeList, probeText, readNumberParam, relatedSkillNames, releaseInstance, renderCuratorReportMarkdown, reportTime, reportsSweepLockTarget, resolveUnitMembers, sessionLastEventTime, staleRouteStubs, sweepReports, transactIo, unitMembersAreComplete, unwrapLive, updateSuppressedNames, usageObserved, userSetKeys, verifyPromptBundle, withTreeWrite } from "@lmzhen/dsh-evolution-core";
2
+ import { BASELINE_ACTION, 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_REVIEW_TIMEOUT_MS, DEFAULT_SKILL_LIMITS, DEFAULT_STALE_AFTER_DAYS, EvolutionGateSet, INSTANCE_KEYS, MAX_TIMER_DELAY_MS, PROMPT_BUNDLE, SKILL_NAME_RE, acquireTreeWriteLock, authorityFrom, blobPath, buildCuratorRunReport, claimInstance, clampOnce, clampedNumber, computeDedupGroups, computeLifecycleTransitions, computePrefixClusters, computeQualityScores, computeScopeView, contentHash, emptyRecord, entryTarget, evolutionHome, evolutionIoAdapter, executeUnitUndo, foldCuratorFields, historyIndexFile, isPresent, isUnknown, lintSkill, liveBool, loadSuppressedNames, loadUsage, markerEntryName, mutateUsage, newSkillLibrary, paramRowId, parseCuratorNominations, parseFrontmatter, parseHistoryIndex, pickWithPolicy, planUnitUndoAcross, policyStageLimits, probeList, probeText, readNumberParam, relatedSkillNames, releaseInstance, renderCuratorReportMarkdown, reportTime, reportsSweepLockTarget, resolveUnitMembers, sessionLastEventTime, staleRouteStubs, sweepReports, transactIo, unitMembersAreComplete, unwrapLive, updateSuppressedNames, usageObserved, userSetKeys, verifyPromptBundle, withTreeWrite } from "@lmzhen/dsh-evolution-core";
3
3
  import { randomUUID } from "node:crypto";
4
4
  import { join } from "node:path";
5
5
  import { BlockAssembler, createUserMessage } from "@deepseek-ai/dsh-llm";
@@ -670,6 +670,19 @@ var EvolutionCurator = class extends Service {
670
670
  runClaim = false;
671
671
  mutexDepth = 0;
672
672
  mutexTail = Promise.resolve();
673
+ /** Cross-process tree lock shared with merge runs; curator mutations take it only around apply. */
674
+ async withMergeTreeLock(body) {
675
+ const acquired = await acquireTreeWriteLock({
676
+ io: this.io,
677
+ home: evolutionHome()
678
+ });
679
+ if (!acquired.ok) throw new Error("evolution-curator: merge tree lock unavailable (" + acquired.reason + ")" + (acquired.holder === void 0 ? "" : "; holder pid " + String(acquired.holder.pid)));
680
+ try {
681
+ return await body();
682
+ } finally {
683
+ await acquired.lock.release();
684
+ }
685
+ }
673
686
  acquireMutex() {
674
687
  this.mutexDepth += 1;
675
688
  const prev = this.mutexTail;
@@ -858,7 +871,7 @@ var EvolutionCurator = class extends Service {
858
871
  note: (text) => {
859
872
  this.ctx.logger.info("evolution-curator: " + text);
860
873
  }
861
- }, async () => apply({
874
+ }, async () => this.withMergeTreeLock(() => apply({
862
875
  dryRun,
863
876
  archiveCandidates,
864
877
  nominations: gatedNominations,
@@ -875,7 +888,7 @@ var EvolutionCurator = class extends Service {
875
888
  ]),
876
889
  runStartStates,
877
890
  failedFrom: new Map(result.transitions.filter((t) => t.to === "archived").map((t) => [t.name, t.from]))
878
- }));
891
+ })));
879
892
  const runAborted = errors.some((error) => error.startsWith("run aborted"));
880
893
  if (!dryRun && !runAborted) this.lastRun = Date.now();
881
894
  const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -1632,10 +1645,36 @@ var EvolutionCurator = class extends Service {
1632
1645
  message: "单元 " + unit + " 的成员没解析齐:" + missing + (conflicts === "" ? "" : ";冲突:" + conflicts) + " —— 拒绝用剩余片段拼一个撤销。"
1633
1646
  };
1634
1647
  }
1635
- if (resolved.members.length > 1) return {
1636
- ok: false,
1637
- message: "单元 " + unit + " 跨 " + String(resolved.members.length) + " 个技能(" + resolved.members.map((m) => m.skill).join("、") + ")—— 跨技能整体撤销尚未接线,拒绝只撤销其中一片。"
1638
- };
1648
+ if (resolved.members.length > 1) {
1649
+ const crossMembers = [];
1650
+ for (const slice of resolved.members) {
1651
+ const live = await this.skills.read(slice.skill);
1652
+ crossMembers.push({
1653
+ skill: slice.skill,
1654
+ entries: slice.entries,
1655
+ status: live === null ? "archived" : "live",
1656
+ ...live === null ? { archivedPath: "archive" } : {},
1657
+ ...slice.skill === name && slice.entries.some((entry) => entry.action === "create") ? { createdInUnit: true } : {}
1658
+ });
1659
+ }
1660
+ const result = await executeUnitUndo(planUnitUndoAcross(crossMembers), {
1661
+ readSkill: (skill) => this.skills.read(skill),
1662
+ readSupport: (skill, path) => this.skills.readSupportFile(skill, path),
1663
+ readBlob: async (hash) => {
1664
+ const probe = await probeText(this.io, blobPath(this.skills.root, hash));
1665
+ return isPresent(probe) ? probe.value : null;
1666
+ },
1667
+ restoreFromArchive: (skill) => this.skills.restoreFromArchive(skill, "foreground"),
1668
+ archive: (skill, reason) => this.skills.archive(skill, { reason }, "foreground"),
1669
+ updateSkill: (skill, body, anchor) => this.skills.update(skill, body, "foreground", anchor),
1670
+ writeSupportFile: (skill, path, body, anchor) => this.skills.writeSupportFile(skill, path, body, "foreground", anchor),
1671
+ removeSupportFile: (skill, path, anchor) => this.skills.removeSupportFile(skill, path, "foreground", anchor)
1672
+ });
1673
+ return {
1674
+ ok: result.ok,
1675
+ message: result.message
1676
+ };
1677
+ }
1639
1678
  }
1640
1679
  const blobOf = async (hash) => {
1641
1680
  if (hash === void 0) return null;
@@ -415,6 +415,8 @@ export declare class EvolutionCurator extends Service {
415
415
  private runClaim;
416
416
  private mutexDepth;
417
417
  private mutexTail;
418
+ /** Cross-process tree lock shared with merge runs; curator mutations take it only around apply. */
419
+ private withMergeTreeLock;
418
420
  private acquireMutex;
419
421
  /**
420
422
  * PLAN S4.5 (2026-09-16) (audit P2-16): the manual control-plane entries
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.27.3",
4
+ "version": "0.29.0",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -27,21 +27,21 @@
27
27
  "license": "MIT",
28
28
  "dependencies": {
29
29
  "@deepseek-ai/schemastery": "^3.18.5",
30
- "@lmzhen/dsh-evolution-core": "^0.27.3"
30
+ "@lmzhen/dsh-evolution-core": "^0.29.0"
31
31
  },
32
32
  "peerDependencies": {
33
33
  "@deepseek-ai/cordis": "^4.0.5",
34
34
  "@deepseek-ai/dsh-llm": "^0.2.1-rc.1",
35
35
  "@deepseek-ai/dsh-session": "^0.2.1-rc.1",
36
- "@lmzhen/dsh-evolution-io": "^0.27.3",
37
- "@lmzhen/dsh-evolution-state": "^0.27.3"
36
+ "@lmzhen/dsh-evolution-io": "^0.29.0",
37
+ "@lmzhen/dsh-evolution-state": "^0.29.0"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@deepseek-ai/dsh-llm": "^0.2.1-rc.1",
41
41
  "@deepseek-ai/dsh-session": "^0.2.1-rc.1",
42
- "@lmzhen/dsh-evolution-core": "^0.27.3",
43
- "@lmzhen/dsh-evolution-io": "^0.27.3",
44
- "@lmzhen/dsh-evolution-state": "^0.27.3"
42
+ "@lmzhen/dsh-evolution-core": "^0.29.0",
43
+ "@lmzhen/dsh-evolution-io": "^0.29.0",
44
+ "@lmzhen/dsh-evolution-state": "^0.29.0"
45
45
  },
46
46
  "engines": {
47
47
  "dsh": ">=0.2.1-rc.1 <0.2.2-0"