@lmzhen/dsh-evolution-feedback 0.3.70 → 0.3.72

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 z from "@deepseek-ai/schemastery";
2
- import { EVENT_LOG_VERSION, appendEvolutionEvent, clampedNumber, eventsFile, evolutionIoAdapter, evolutionRoot, listEventArchives, parseEvolutionEvents, readEvolutionTimeline, transactIo } from "@lmzhen/dsh-evolution-core";
2
+ import { EVENT_LOG_VERSION, appendEvolutionEvent, clampedNumber, eventsFile, evolutionEventPayloadIssue, evolutionIoAdapter, evolutionRoot, listEventArchives, parseEvolutionEvents, readEvolutionTimeline, transactIo } from "@lmzhen/dsh-evolution-core";
3
3
  import { join } from "node:path";
4
4
  //#region lib/types/index.js
5
5
  /**
@@ -90,14 +90,15 @@ var EvolutionFeedback = class EvolutionFeedback {
90
90
  await this.mutate(async () => {
91
91
  const rawEvents = await io.readText(eventsPath);
92
92
  const archiveNames = await listEventArchives(io, eventsPath);
93
+ const rawCache = await io.readText(path);
93
94
  if ((rawEvents === null || rawEvents.trim() === "") && archiveNames.length === 0) {
94
- const aggregate = parseAggregate(await io.readText(path), this.warn);
95
+ const aggregate = parseAggregate(rawCache, this.warn);
95
96
  if (aggregate) try {
96
- await migrateFeedbackEvents(io, eventsPath, aggregate);
97
+ await migrateFeedbackEvents(io, eventsPath, aggregate, this.warn);
97
98
  } catch {}
98
99
  }
99
- const { events } = await readEvolutionTimeline(io, eventsPath);
100
- const cache = parseCache(await io.readText(path), this.warn);
100
+ const { events } = await readEvolutionTimeline(io, eventsPath, archiveNames);
101
+ const cache = parseCache(rawCache, this.warn);
101
102
  const maxSeq = events.reduce((max, event) => Math.max(max, event.seq), 0);
102
103
  const floor = events[0]?.seq ?? 0;
103
104
  const usableCache = cache && cache.lastSeq >= floor - 1 ? cache : null;
@@ -222,6 +223,14 @@ var EvolutionFeedback = class EvolutionFeedback {
222
223
  if (total === 0) return 0;
223
224
  return (record.positive - record.negative) / total;
224
225
  }
226
+ /**
227
+ * Deep-copy view of the live aggregate.
228
+ *
229
+ * OPT-26 (2026-09): `@internal` — test-support API, NO production consumer
230
+ * in the family (mirrors the explicit posture of evolution-replay's
231
+ * `plansSnapshot()`). External consumers should read the rendered
232
+ * score/warn surfaces instead of relying on this shape.
233
+ */
225
234
  snapshot() {
226
235
  const copyRecords = (table) => {
227
236
  const out = {};
@@ -294,8 +303,13 @@ function containsLegacySequence(existing, expected) {
294
303
  * and race-safe (the search runs inside the same transact). Exported for the
295
304
  * migration-race regression test.
296
305
  */
297
- async function migrateFeedbackEvents(io, eventsPath, aggregate) {
306
+ async function migrateFeedbackEvents(io, eventsPath, aggregate, warn = () => {}) {
298
307
  const expected = synthesizeFeedbackEvents(aggregate);
308
+ const refused = expected.filter((event) => evolutionEventPayloadIssue(event) !== null);
309
+ if (refused.length > 0) {
310
+ warn(`evolution-feedback: migration refused ${refused.length} synthesized event(s) that the event log's payload gate rejects (types: ${[...new Set(refused.map((event) => event.type))].join(", ")}) — history starts empty instead of writing an unreadable log`);
311
+ return;
312
+ }
299
313
  await transactIo(io, eventsPath, (current) => {
300
314
  const existing = parseEvolutionEvents(current);
301
315
  if (containsLegacySequence(existing, expected)) return Promise.resolve(current);
@@ -367,12 +381,28 @@ function parseCache(raw, warn = () => {}) {
367
381
  /** Per-record numeric-domain validation (S6.4): a record whose `positive` or
368
382
  * `negative` is not a finite number >= 0, or whose `lastNote` is not a string,
369
383
  * would fold as NaN into the usage aggregate — skip it with a warn instead of
370
- * corrupting the state. A record with no valid count is dropped entirely. */
384
+ * corrupting the state. A record with no valid count is dropped entirely.
385
+ * OPT-25 (2026-09): a count ABOVE `MAX_MIGRATED_EVENTS_PER_RECORD` is now
386
+ * CLAMPED here with a warn, symmetric with the migration reader's clamp (same
387
+ * file, same threat model — V24-05 covered the migration side only). Before,
388
+ * a poisoned boot-cache count folded as-is into every downstream decision;
389
+ * the clamp bounds the skew to the same ceiling migration accepts. */
371
390
  function sanitizeCacheRecords(input, kind, warn) {
372
391
  const out = {};
373
392
  for (const [target, value] of Object.entries(input)) {
374
393
  const record = sanitizeFeedbackRecord(value, target, kind, warn);
375
- if (record) out[target] = record;
394
+ if (record) {
395
+ let clamped;
396
+ if (record.positive > MAX_MIGRATED_EVENTS_PER_RECORD || record.negative > MAX_MIGRATED_EVENTS_PER_RECORD) {
397
+ warn(`evolution-feedback: cache record for ${kind} "${target}" exceeds the clamp ceiling (${MAX_MIGRATED_EVENTS_PER_RECORD}) — clamping like the migration reader`);
398
+ clamped = {
399
+ positive: Math.min(record.positive, MAX_MIGRATED_EVENTS_PER_RECORD),
400
+ negative: Math.min(record.negative, MAX_MIGRATED_EVENTS_PER_RECORD),
401
+ ...record.lastNote !== void 0 ? { lastNote: record.lastNote } : {}
402
+ };
403
+ }
404
+ out[target] = clamped ?? record;
405
+ }
376
406
  }
377
407
  return out;
378
408
  }
@@ -98,6 +98,14 @@ export declare class EvolutionFeedback {
98
98
  refold(): Promise<void>;
99
99
  record(target: string, rating: 'positive' | 'negative', note?: string, kind?: 'skill' | 'session'): void;
100
100
  score(target: string, kind?: 'skill' | 'session'): number;
101
+ /**
102
+ * Deep-copy view of the live aggregate.
103
+ *
104
+ * OPT-26 (2026-09): `@internal` — test-support API, NO production consumer
105
+ * in the family (mirrors the explicit posture of evolution-replay's
106
+ * `plansSnapshot()`). External consumers should read the rendered
107
+ * score/warn surfaces instead of relying on this shape.
108
+ */
101
109
  snapshot(): FeedbackState;
102
110
  /** Await the pending record-task chain (unload safety; rc.66). */
103
111
  waitIdle(): Promise<unknown>;
@@ -116,7 +124,7 @@ export declare class EvolutionFeedback {
116
124
  * and race-safe (the search runs inside the same transact). Exported for the
117
125
  * migration-race regression test.
118
126
  */
119
- export declare function migrateFeedbackEvents(io: IoLike, eventsPath: string, aggregate: FeedbackState): Promise<void>;
127
+ export declare function migrateFeedbackEvents(io: IoLike, eventsPath: string, aggregate: FeedbackState, warn?: (message: string) => void): Promise<void>;
120
128
  export declare const name = "evolution-feedback";
121
129
  export interface Config {
122
130
  /** Score below which the FEEDBACK pair flips to warned (P1-1, v15: written
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@lmzhen/dsh-evolution-feedback",
3
3
  "description": "Feedback-to-quality scoring for self-evolution (community build)",
4
- "version": "0.3.70",
4
+ "version": "0.3.72",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -31,18 +31,18 @@
31
31
  "license": "MIT",
32
32
  "dependencies": {
33
33
  "@deepseek-ai/schemastery": "^3.18.1",
34
- "@lmzhen/dsh-evolution-core": "^0.3.70"
34
+ "@lmzhen/dsh-evolution-core": "^0.3.72"
35
35
  },
36
36
  "peerDependencies": {
37
37
  "@deepseek-ai/dsh-invariants": "^0.1.5-rc.2",
38
38
  "@deepseek-ai/cordis": "^4.0.1",
39
- "@lmzhen/dsh-evolution-io": "^0.3.70",
40
- "@lmzhen/dsh-skill-usage": "^0.3.70"
39
+ "@lmzhen/dsh-evolution-io": "^0.3.72",
40
+ "@lmzhen/dsh-skill-usage": "^0.3.72"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@deepseek-ai/dsh-invariants": "^0.1.5-rc.2",
44
- "@lmzhen/dsh-evolution-io": "^0.3.70",
45
- "@lmzhen/dsh-evolution-io-node": "^0.3.70",
46
- "@lmzhen/dsh-skill-usage": "^0.3.70"
44
+ "@lmzhen/dsh-evolution-io": "^0.3.72",
45
+ "@lmzhen/dsh-evolution-io-node": "^0.3.72",
46
+ "@lmzhen/dsh-skill-usage": "^0.3.72"
47
47
  }
48
48
  }