@lmzhen/dsh-evolution-curator 0.18.1 → 0.19.1

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.
@@ -1096,51 +1096,27 @@ var EvolutionCurator = class extends Service {
1096
1096
  });
1097
1097
  }
1098
1098
  /** The sweep `retainReports` holds the per-home lock for. A caller MUST hold
1099
- * 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. */
1100
1104
  async sweepReports(keep, errorKeep) {
1101
- const reportsRoot = join(evolutionHome(), "reports");
1102
- const listed = await probeList(this.io, reportsRoot);
1103
- if (isUnknown(listed)) {
1104
- this.ctx.logger.warn(`evolution-curator: report retention skipped — the reports directory could not be listed (${listed.reason})`);
1105
- return;
1106
- }
1107
- const entries = isPresent(listed) ? listed.value : [];
1108
- const real = [];
1109
- const errors = [];
1110
- let unorderableWarned = false;
1111
- for (const name of entries.filter((entry) => entry.startsWith("curator-") && entry.endsWith(".json"))) try {
1112
- const raw = await this.io.readText(join(reportsRoot, name));
1113
- if (raw === null) continue;
1114
- const startedAt = await reportTime(this.io, join(reportsRoot, name), JSON.parse(raw));
1115
- const probed = await probeMtime(this.io, join(reportsRoot, name));
1116
- const mtime = isPresent(probed) ? probed.value : null;
1117
- if (startedAt !== null) (name.startsWith("curator-error-") ? errors : real).push({
1118
- name,
1119
- startedAt,
1120
- mtime
1121
- });
1122
- else if (!unorderableWarned) {
1123
- unorderableWarned = true;
1124
- 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`);
1125
- }
1126
- } catch {}
1127
- const newestFirst = (a, b) => b.startedAt - a.startedAt || (b.mtime ?? 0) - (a.mtime ?? 0) || b.name.localeCompare(a.name);
1128
- real.sort(newestFirst);
1129
- errors.sort(newestFirst);
1130
- await this.pruneReportList(reportsRoot, real, keep);
1131
- await this.pruneReportList(reportsRoot, errors, errorKeep);
1132
- }
1133
- /** Remove the oldest reports beyond `keep` (each with its `.md` digest, best-effort). */
1134
- async pruneReportList(reportsRoot, dated, keep) {
1135
- for (const oldReport of dated.slice(keep)) {
1136
- const stem = oldReport.name.replace(/\.json$/, "");
1137
- try {
1138
- await this.io.remove(join(reportsRoot, oldReport.name));
1139
- } catch {}
1140
- try {
1141
- await this.io.remove(join(reportsRoot, `${stem}.md`));
1142
- } catch {}
1143
- }
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
+ });
1144
1120
  }
1145
1121
  async latestReport() {
1146
1122
  const reportsRoot = join(evolutionHome(), "reports");
@@ -1438,17 +1414,5 @@ var EvolutionCurator = class extends Service {
1438
1414
  }
1439
1415
  }
1440
1416
  };
1441
- /**
1442
- * v43 FLOW2-1: the per-home lock target for the report retention sweep.
1443
- * `transactIo` takes the IO write lock on THIS path (minting `<path>.lock` with
1444
- * the io protocol's `pid:token` body) and holds it for the whole task; nothing
1445
- * ever writes the target, because the sweep's exclusion is the point. It lives
1446
- * inside `<home>/reports` next to the directory it protects: the sweep's own
1447
- * name filter (`curator-*.json`) ignores it, and the lock file is removed on
1448
- * release.
1449
- */
1450
- function reportsSweepLockTarget() {
1451
- return join(evolutionHome(), "reports", ".retention");
1452
- }
1453
1417
  //#endregion
1454
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.1",
4
+ "version": "0.19.1",
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.1"
30
+ "@lmzhen/dsh-evolution-core": "^0.19.1"
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.1",
37
- "@lmzhen/dsh-evolution-state": "^0.18.1"
36
+ "@lmzhen/dsh-evolution-io": "^0.19.1",
37
+ "@lmzhen/dsh-evolution-state": "^0.19.1"
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.1",
43
- "@lmzhen/dsh-evolution-io": "^0.18.1",
44
- "@lmzhen/dsh-evolution-state": "^0.18.1"
42
+ "@lmzhen/dsh-evolution-core": "^0.19.1",
43
+ "@lmzhen/dsh-evolution-io": "^0.19.1",
44
+ "@lmzhen/dsh-evolution-state": "^0.19.1"
45
45
  }
46
46
  }