@lmzhen/dsh-evolution-curator 0.18.0 → 0.19.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
@@ -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_SKILL_LIMITS, DEFAULT_STALE_AFTER_DAYS, EvolutionGateSet, INSTANCE_KEYS, MAX_TIMER_DELAY_MS, PROMPT_BUNDLE, SKILL_NAME_RE, buildCuratorRunReport, claimInstance, clampOnce, clampedNumber, computeDedupGroups, computeLifecycleTransitions, computePrefixClusters, computeQualityScores, computeScopeView, contentHash, emptyRecord, entryTarget, evolutionHome, evolutionIoAdapter, foldCuratorFields, isPresent, isUnknown, loadSuppressedNames, loadUsage, markerEntryName, mutateUsage, newSkillLibrary, paramRowId, parseCuratorNominations, parseFrontmatter, pickWithPolicy, policyStageLimits, probeList, probeMtime, probeText, readNumberParam, relatedSkillNames, releaseInstance, renderCuratorReportMarkdown, reportTime, sessionLastEventTime, transactIo, updateSuppressedNames, usageObserved, userSetKeys, verifyPromptBundle } 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_SKILL_LIMITS, DEFAULT_STALE_AFTER_DAYS, EvolutionGateSet, INSTANCE_KEYS, MAX_TIMER_DELAY_MS, PROMPT_BUNDLE, SKILL_NAME_RE, buildCuratorRunReport, claimInstance, clampOnce, clampedNumber, computeDedupGroups, computeLifecycleTransitions, computePrefixClusters, computeQualityScores, computeScopeView, contentHash, emptyRecord, entryTarget, evolutionHome, evolutionIoAdapter, foldCuratorFields, isPresent, isUnknown, loadSuppressedNames, loadUsage, markerEntryName, mutateUsage, newSkillLibrary, paramRowId, parseCuratorNominations, parseFrontmatter, pickWithPolicy, policyStageLimits, probeList, probeText, readNumberParam, relatedSkillNames, releaseInstance, renderCuratorReportMarkdown, reportTime, reportsSweepLockTarget, sessionLastEventTime, sweepReports, transactIo, updateSuppressedNames, usageObserved, userSetKeys, verifyPromptBundle } 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.
@@ -404,6 +404,7 @@ var EvolutionCurator = class extends Service {
404
404
  "Return a YAML summary with consolidations and prunings lists. Nominate only actions whose archival/merge is clearly safe."
405
405
  ].join("\n");
406
406
  const settings = this.settings();
407
+ const llmSignal = AbortSignal.timeout(settings.curatorReviewTimeoutMs);
407
408
  try {
408
409
  const assembler = new BlockAssembler();
409
410
  for await (const chunk of llm.stream({
@@ -421,7 +422,7 @@ var EvolutionCurator = class extends Service {
421
422
  }
422
423
  })],
423
424
  maxTokens: settings.curatorReviewMaxTokens,
424
- signal: AbortSignal.timeout(settings.curatorReviewTimeoutMs)
425
+ signal: llmSignal
425
426
  })) assembler.push(chunk);
426
427
  const parsed = parseCuratorNominations(assembler.blocks().filter((block) => block.type === "text").map((block) => block.text).join("\n"));
427
428
  return {
@@ -430,8 +431,12 @@ var EvolutionCurator = class extends Service {
430
431
  warnings: parsed.warnings
431
432
  };
432
433
  } catch (error) {
433
- this.ctx.logger.warn(`evolution-curator: LLM nomination pass failed: ${error instanceof Error ? error.message : String(error)}`);
434
- return empty;
434
+ const failure = llmSignal.aborted ? `LLM nomination pass timed out after ${settings.curatorReviewTimeoutMs} ms — no nominations this run (NOT "nothing to nominate": the pass did not complete)` : `LLM nomination pass failed: ${error instanceof Error ? error.message : String(error)} — no nominations this run (NOT "nothing to nominate": the pass did not complete)`;
435
+ this.ctx.logger.warn(`evolution-curator: ${failure}`);
436
+ return {
437
+ ...empty,
438
+ warnings: [failure]
439
+ };
435
440
  }
436
441
  }
437
442
  /** Optional curator-state service (evolution-state-json / storage-domain). */
@@ -1091,51 +1096,27 @@ var EvolutionCurator = class extends Service {
1091
1096
  });
1092
1097
  }
1093
1098
  /** The sweep `retainReports` holds the per-home lock for. A caller MUST hold
1094
- * that lock: the listing and the deletions are not atomic on their own. */
1099
+ * that lock: the listing and the deletions are not atomic on their own.
1100
+ * 0.19.0 (S2): the sweep itself now lives in core (`sweepReports`) — the
1101
+ * maintenance run reports added in 0.19.0 need the same window, and a second
1102
+ * copy of "list, order, delete beyond the window" is the near-duplicate class
1103
+ * this family treats as a defect. The lock and the two windows stay here. */
1095
1104
  async sweepReports(keep, errorKeep) {
1096
- const reportsRoot = join(evolutionHome(), "reports");
1097
- const listed = await probeList(this.io, reportsRoot);
1098
- if (isUnknown(listed)) {
1099
- this.ctx.logger.warn(`evolution-curator: report retention skipped — the reports directory could not be listed (${listed.reason})`);
1100
- return;
1101
- }
1102
- const entries = isPresent(listed) ? listed.value : [];
1103
- const real = [];
1104
- const errors = [];
1105
- let unorderableWarned = false;
1106
- for (const name of entries.filter((entry) => entry.startsWith("curator-") && entry.endsWith(".json"))) try {
1107
- const raw = await this.io.readText(join(reportsRoot, name));
1108
- if (raw === null) continue;
1109
- const startedAt = await reportTime(this.io, join(reportsRoot, name), JSON.parse(raw));
1110
- const probed = await probeMtime(this.io, join(reportsRoot, name));
1111
- const mtime = isPresent(probed) ? probed.value : null;
1112
- if (startedAt !== null) (name.startsWith("curator-error-") ? errors : real).push({
1113
- name,
1114
- startedAt,
1115
- mtime
1116
- });
1117
- else if (!unorderableWarned) {
1118
- unorderableWarned = true;
1119
- this.ctx.logger.warn(`evolution-curator: report "${name}" carries no usable timestamp and this backend has no mtime probe - it is kept outside the retention window`);
1120
- }
1121
- } catch {}
1122
- const newestFirst = (a, b) => b.startedAt - a.startedAt || (b.mtime ?? 0) - (a.mtime ?? 0) || b.name.localeCompare(a.name);
1123
- real.sort(newestFirst);
1124
- errors.sort(newestFirst);
1125
- await this.pruneReportList(reportsRoot, real, keep);
1126
- await this.pruneReportList(reportsRoot, errors, errorKeep);
1127
- }
1128
- /** Remove the oldest reports beyond `keep` (each with its `.md` digest, best-effort). */
1129
- async pruneReportList(reportsRoot, dated, keep) {
1130
- for (const oldReport of dated.slice(keep)) {
1131
- const stem = oldReport.name.replace(/\.json$/, "");
1132
- try {
1133
- await this.io.remove(join(reportsRoot, oldReport.name));
1134
- } catch {}
1135
- try {
1136
- await this.io.remove(join(reportsRoot, `${stem}.md`));
1137
- } catch {}
1138
- }
1105
+ await sweepReports({
1106
+ io: this.io,
1107
+ dir: join(evolutionHome(), "reports"),
1108
+ buckets: [{
1109
+ prefix: "curator-error-",
1110
+ keep: errorKeep
1111
+ }, {
1112
+ prefix: "curator-",
1113
+ keep
1114
+ }],
1115
+ warn: (message) => {
1116
+ this.ctx.logger.warn(message);
1117
+ },
1118
+ owner: "evolution-curator"
1119
+ });
1139
1120
  }
1140
1121
  async latestReport() {
1141
1122
  const reportsRoot = join(evolutionHome(), "reports");
@@ -1433,17 +1414,5 @@ var EvolutionCurator = class extends Service {
1433
1414
  }
1434
1415
  }
1435
1416
  };
1436
- /**
1437
- * v43 FLOW2-1: the per-home lock target for the report retention sweep.
1438
- * `transactIo` takes the IO write lock on THIS path (minting `<path>.lock` with
1439
- * the io protocol's `pid:token` body) and holds it for the whole task; nothing
1440
- * ever writes the target, because the sweep's exclusion is the point. It lives
1441
- * inside `<home>/reports` next to the directory it protects: the sweep's own
1442
- * name filter (`curator-*.json`) ignores it, and the lock file is removed on
1443
- * release.
1444
- */
1445
- function reportsSweepLockTarget() {
1446
- return join(evolutionHome(), "reports", ".retention");
1447
- }
1448
1417
  //#endregion
1449
1418
  export { EvolutionCurator, EvolutionCurator as default, gateConsolidations, validateCuratorSettings };
@@ -450,10 +450,12 @@ export declare class EvolutionCurator extends Service {
450
450
  */
451
451
  private retainReports;
452
452
  /** The sweep `retainReports` holds the per-home lock for. A caller MUST hold
453
- * that lock: the listing and the deletions are not atomic on their own. */
453
+ * that lock: the listing and the deletions are not atomic on their own.
454
+ * 0.19.0 (S2): the sweep itself now lives in core (`sweepReports`) — the
455
+ * maintenance run reports added in 0.19.0 need the same window, and a second
456
+ * copy of "list, order, delete beyond the window" is the near-duplicate class
457
+ * this family treats as a defect. The lock and the two windows stay here. */
454
458
  private sweepReports;
455
- /** Remove the oldest reports beyond `keep` (each with its `.md` digest, best-effort). */
456
- private pruneReportList;
457
459
  latestReport(): Promise<CuratorRunReport | null>;
458
460
  /**
459
461
  * Read-only lifecycle scope classification: which skills are in scope,
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.18.0",
4
+ "version": "0.19.0",
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.18.0"
30
+ "@lmzhen/dsh-evolution-core": "^0.19.0"
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.18.0",
37
- "@lmzhen/dsh-evolution-state": "^0.18.0"
36
+ "@lmzhen/dsh-evolution-io": "^0.19.0",
37
+ "@lmzhen/dsh-evolution-state": "^0.19.0"
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.18.0",
43
- "@lmzhen/dsh-evolution-io": "^0.18.0",
44
- "@lmzhen/dsh-evolution-state": "^0.18.0"
42
+ "@lmzhen/dsh-evolution-core": "^0.19.0",
43
+ "@lmzhen/dsh-evolution-io": "^0.19.0",
44
+ "@lmzhen/dsh-evolution-state": "^0.19.0"
45
45
  }
46
46
  }