@lmzhen/dsh-evolution-curator 0.25.9 → 0.26.26

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,10 +1,9 @@
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
3
  import { randomUUID } from "node:crypto";
3
4
  import { join } from "node:path";
4
5
  import { BlockAssembler, createUserMessage } from "@deepseek-ai/dsh-llm";
5
6
  import z from "@deepseek-ai/schemastery";
6
- import { preferProvider } from "@lmzhen/dsh-evolution-io";
7
- 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, 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 } from "@lmzhen/dsh-evolution-core";
8
7
  //#region lib/types/unit-undo-plan.js
9
8
  /**
10
9
  * G3a: the pure half of `undoUnit` — deciding WHAT to write back, with no io and no host.
@@ -206,8 +205,9 @@ var EvolutionCurator = class extends Service {
206
205
  if (typeof disposer === "function") ctx.effect(() => disposer, "evolution-curator: settings presentation");
207
206
  });
208
207
  if (!verifyPromptBundle(PROMPT_BUNDLE)) throw new Error("dsh-evolution prompt bundle integrity check failed; refusing to run the curator");
209
- this.io = evolutionIoAdapter(() => preferProvider(ctx.evolutionIo, ["fs"]));
208
+ this.io = evolutionIoAdapter(() => ctx.evolutionIo.provider());
210
209
  this.skills = newSkillLibrary({
210
+ authority: authorityFrom({ origin: "background_review" }),
211
211
  config,
212
212
  io: this.io,
213
213
  ctx: this.ctx,
@@ -619,18 +619,18 @@ var EvolutionCurator = class extends Service {
619
619
  report: this.skippedReport("instance-held", (/* @__PURE__ */ new Date()).toISOString()),
620
620
  skipped: "instance-held"
621
621
  };
622
- if (this.mutexDepth > 0) return {
622
+ if (this.runClaim || this.mutexDepth > 0) return {
623
623
  stale: [],
624
624
  archived: [],
625
625
  errors: [],
626
626
  report: this.skippedReport("already-running", (/* @__PURE__ */ new Date()).toISOString()),
627
627
  skipped: "already-running"
628
628
  };
629
- const release = await this.acquireMutex();
629
+ this.runClaim = true;
630
630
  try {
631
631
  return await this.runCore(options);
632
632
  } finally {
633
- release();
633
+ this.runClaim = false;
634
634
  try {
635
635
  await this.retainReports();
636
636
  } catch (error) {
@@ -663,6 +663,11 @@ var EvolutionCurator = class extends Service {
663
663
  * the one realistic interleave window — documented, accepted (plan v20
664
664
  * C-3①). The same statement lives on evolution-review's `reviewInFlight`.
665
665
  */
666
+ /** G1/1.5 (S7-09): the synchronous run claim. The mutex is taken later now (around the mutation only),
667
+ * so this is what makes the `already-running` answer atomic with respect to other entrants. It is a
668
+ * CLAIM, not a wait: no polling, so it cannot reintroduce the v15 micro-task starvation the promise
669
+ * chain replaced. */
670
+ runClaim = false;
666
671
  mutexDepth = 0;
667
672
  mutexTail = Promise.resolve();
668
673
  acquireMutex() {
@@ -776,14 +781,13 @@ var EvolutionCurator = class extends Service {
776
781
  suppressed: suppressedNames
777
782
  });
778
783
  const { bundledNames, treeNames, resetToActive } = await this.seedBaseline(usage);
784
+ const listed = await this.skills.list({ withContent: true });
779
785
  const contents = /* @__PURE__ */ new Map();
780
- for (const name of treeNames) {
781
- const text = await this.skills.read(name);
782
- if (text) contents.set(name, text);
783
- }
786
+ for (const summary of listed) if (summary.content) contents.set(summary.name, summary.content);
784
787
  const referencedBy = referencedSkillNames(contents);
785
788
  const protectedNames = await this.protectedNameMap();
786
- const dedupMembers = [...new Set(computeDedupGroups({ contents }).groups.filter((group) => group.length >= 2).flat())].filter((name) => !protectedNames.has(name) && !referencedBy.has(name));
789
+ const dedupScan = computeDedupGroups({ contents });
790
+ const dedupMembers = [...new Set(dedupScan.groups.filter((group) => group.length >= 2).flat())].filter((name) => !protectedNames.has(name) && !referencedBy.has(name));
787
791
  await this.scoreTree(usage, treeNames, contents);
788
792
  const result = computeLifecycleTransitions(usage, {
789
793
  staleAfterDays: lifecycle.staleAfterDays,
@@ -842,7 +846,19 @@ var EvolutionCurator = class extends Service {
842
846
  skipped: "active-session"
843
847
  };
844
848
  }
845
- const { archivedSkills, errors, consolidated } = await this.applyMutations({
849
+ const apply = this.applyMutations.bind(this);
850
+ const { archivedSkills, errors, consolidated } = await withTreeWrite(() => this.acquireMutex(), {
851
+ names: [...treeNames],
852
+ read: async (name) => {
853
+ return (await this.skills.list({ withContent: true })).find((entry) => entry.name === name)?.content;
854
+ },
855
+ writeSkill: async (name, content) => {
856
+ await this.skills.update(name, content, "background_review");
857
+ },
858
+ note: (text) => {
859
+ this.ctx.logger.info("evolution-curator: " + text);
860
+ }
861
+ }, async () => apply({
846
862
  dryRun,
847
863
  archiveCandidates,
848
864
  nominations: gatedNominations,
@@ -859,7 +875,7 @@ var EvolutionCurator = class extends Service {
859
875
  ]),
860
876
  runStartStates,
861
877
  failedFrom: new Map(result.transitions.filter((t) => t.to === "archived").map((t) => [t.name, t.from]))
862
- });
878
+ }));
863
879
  const runAborted = errors.some((error) => error.startsWith("run aborted"));
864
880
  if (!dryRun && !runAborted) this.lastRun = Date.now();
865
881
  const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -891,7 +907,8 @@ var EvolutionCurator = class extends Service {
891
907
  consolidated,
892
908
  ...snapshotPath === void 0 ? {} : { snapshotPath },
893
909
  llmReviewEnabled: settings.llmReview,
894
- ...gatedNominations.warnings.length > 0 ? { nominationsWarnings: gatedNominations.warnings } : {}
910
+ ...gatedNominations.warnings.length > 0 ? { nominationsWarnings: gatedNominations.warnings } : {},
911
+ ...dedupScan.truncated ? { scanWarnings: ["dedup scan truncated at the pair-comparison budget - the candidate pool is a PARTIAL result: the budget is spent in name order, so the tail was never examined"] } : {}
895
912
  });
896
913
  report.routeStubs = await (async () => {
897
914
  const history = join(this.skills.root, ".history", "skills");
@@ -408,6 +408,11 @@ export declare class EvolutionCurator extends Service {
408
408
  * the one realistic interleave window — documented, accepted (plan v20
409
409
  * C-3①). The same statement lives on evolution-review's `reviewInFlight`.
410
410
  */
411
+ /** G1/1.5 (S7-09): the synchronous run claim. The mutex is taken later now (around the mutation only),
412
+ * so this is what makes the `already-running` answer atomic with respect to other entrants. It is a
413
+ * CLAIM, not a wait: no polling, so it cannot reintroduce the v15 micro-task starvation the promise
414
+ * chain replaced. */
415
+ private runClaim;
411
416
  private mutexDepth;
412
417
  private mutexTail;
413
418
  private acquireMutex;
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.25.9",
4
+ "version": "0.26.26",
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.4",
30
- "@lmzhen/dsh-evolution-core": "^0.25.9"
30
+ "@lmzhen/dsh-evolution-core": "^0.26.26"
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.25.9",
37
- "@lmzhen/dsh-evolution-state": "^0.25.9"
36
+ "@lmzhen/dsh-evolution-io": "^0.26.26",
37
+ "@lmzhen/dsh-evolution-state": "^0.26.26"
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.25.9",
43
- "@lmzhen/dsh-evolution-io": "^0.25.9",
44
- "@lmzhen/dsh-evolution-state": "^0.25.9"
42
+ "@lmzhen/dsh-evolution-core": "^0.26.26",
43
+ "@lmzhen/dsh-evolution-io": "^0.26.26",
44
+ "@lmzhen/dsh-evolution-state": "^0.26.26"
45
45
  }
46
46
  }