@lmzhen/dsh-evolution-feedback 0.3.73 → 0.3.76

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/README.md CHANGED
@@ -23,3 +23,5 @@ Independent of request-prefix construction. This package does not alter the asse
23
23
 
24
24
  - Persists through the IO seam; quality propagation into skill usage requires the `skillUsage` service.
25
25
  - P1-1 (v15): feedback writes the FEEDBACK-OWNED `feedback_score`/`feedback_warn` usage fields. The lifecycle engine and the scope view read the union `quality_warn || feedback_warn`, so a negative feedback shortens the stale window even though the curator's six-factor run always recomputes `quality_warn` itself.
26
+
27
+ **Runtime invariant:** No companion is published. The platform auto-assembles nothing and the family mounts no `<pkg>/invariant` cordis row, so a companion here would never execute (v37 S2.1 / I-3).
package/lib/index.js CHANGED
@@ -97,7 +97,9 @@ var EvolutionFeedback = class EvolutionFeedback {
97
97
  await migrateFeedbackEvents(io, eventsPath, aggregate, this.warn);
98
98
  } catch {}
99
99
  }
100
- const { events } = await readEvolutionTimeline(io, eventsPath, archiveNames);
100
+ const initial = await readEvolutionTimeline(io, eventsPath, archiveNames);
101
+ const lateArchives = (await listEventArchives(io, eventsPath)).filter((name) => !archiveNames.includes(name));
102
+ const events = lateArchives.length === 0 ? initial.events : (await readEvolutionTimeline(io, eventsPath, [...archiveNames, ...lateArchives])).events;
101
103
  const cache = parseCache(rawCache, this.warn);
102
104
  const maxSeq = events.reduce((max, event) => Math.max(max, event.seq), 0);
103
105
  const floor = events[0]?.seq ?? 0;
@@ -256,10 +258,19 @@ var EvolutionFeedback = class EvolutionFeedback {
256
258
  const { events } = await readEvolutionTimeline(recordIo, eventsPath);
257
259
  const maxSeq = events.reduce((max, event) => Math.max(max, event.seq), 0);
258
260
  if (maxSeq === 0) return;
261
+ let rawCache;
262
+ try {
263
+ rawCache = await recordIo.readText(path);
264
+ } catch {
265
+ return;
266
+ }
267
+ const cache = parseCache(rawCache, this.warn);
268
+ const floor = events[0]?.seq ?? 0;
269
+ const state = cache && cache.lastSeq >= floor - 1 ? foldWithDelta(cache, events, this.warn) : foldFeedbackState(events, this.warn);
259
270
  const body = JSON.stringify({
260
271
  version: CACHE_VERSION,
261
272
  lastSeq: maxSeq,
262
- ...foldFeedbackState(events, this.warn)
273
+ ...state
263
274
  }, null, 2);
264
275
  await recordIo.writeText(path, body);
265
276
  } catch {}
@@ -272,27 +283,44 @@ var EvolutionFeedback = class EvolutionFeedback {
272
283
  });
273
284
  }
274
285
  };
275
- /** True when `existing` contains the legacy sequence as a contiguous run on
276
- * its semantic fields (skip case). `seq` and `at` are excluded: after a merge
277
- * the legacy events carry shifted seqs, and a re-synthesis stamps a different
278
- * `at` — the semantic identity is type/kind/target/rating/note. A coincidental
279
- * semantic match of an already-appended user sequence yields the identical
280
- * aggregation, so the skip is harmless for counts and notes. */
281
- function containsLegacySequence(existing, expected) {
286
+ /** Semantic identity of one event: `seq` and `at` are excluded (a merge
287
+ * shifts seqs, a re-synthesis stamps a different `at`), so type/kind/target/
288
+ * rating/note is everything a fold can see. */
289
+ function sameSemantics(a, b) {
290
+ return a.type === b.type && a.kind === b.kind && a.target === b.target && a.rating === b.rating && a.note === b.note;
291
+ }
292
+ /** True when `existing` already carries the WHOLE legacy batch (the skip case).
293
+ * P1-8 (v37): counted as a MULTISET, never as a contiguous run — the
294
+ * synthesizer emits "all positives, then all negatives" per target while the
295
+ * real log is in temporal order, so the run-based gate read an already-migrated
296
+ * log as unmigrated and re-appended the whole history (3 events became 6, and
297
+ * every further boot doubled it again). */
298
+ function containsLegacyMultiset(existing, expected) {
282
299
  if (expected.length === 0) return true;
283
- for (let start = 0; start <= existing.length - expected.length; start += 1) {
284
- let match = true;
285
- for (let offset = 0; offset < expected.length; offset += 1) {
286
- const a = expected[offset];
287
- const b = existing[start + offset];
288
- if (!a || !b || a.type !== b.type || a.kind !== b.kind || a.target !== b.target || a.rating !== b.rating || a.note !== b.note) {
289
- match = false;
290
- break;
291
- }
292
- }
293
- if (match) return true;
300
+ const pool = [...existing];
301
+ for (const event of expected) {
302
+ const at = pool.findIndex((candidate) => sameSemantics(candidate, event));
303
+ if (at < 0) return false;
304
+ pool.splice(at, 1);
305
+ }
306
+ return true;
307
+ }
308
+ /** True when the body the migration transact just read may be written over:
309
+ * absent or blank (the append path's own "missing" definition), or a v1 body
310
+ * this reader can actually interpret. `parseEvolutionEvents` maps a corrupt or
311
+ * future-version body to an EMPTY timeline, so without this gate a read failure
312
+ * would be folded as "nothing was ever migrated" and the synthesized log would
313
+ * overwrite bytes the append path refuses to touch. */
314
+ function isReadableEventBody(current) {
315
+ if (current === null || current.trim() === "") return true;
316
+ try {
317
+ const parsed = JSON.parse(current);
318
+ if (!isRecord(parsed)) return false;
319
+ if (parsed.version !== void 0 && parsed.version !== EVENT_LOG_VERSION) return false;
320
+ return Array.isArray(parsed.events);
321
+ } catch {
322
+ return false;
294
323
  }
295
- return false;
296
324
  }
297
325
  /**
298
326
  * Merge a legacy aggregate into the event log (rc.69): the expected synthetic
@@ -311,8 +339,12 @@ async function migrateFeedbackEvents(io, eventsPath, aggregate, warn = () => {})
311
339
  return;
312
340
  }
313
341
  await transactIo(io, eventsPath, (current) => {
342
+ if (!isReadableEventBody(current)) {
343
+ warn("evolution-feedback: migration left unreadable event-log bytes untouched (neither migrated nor overwritten)");
344
+ return Promise.resolve(current);
345
+ }
314
346
  const existing = parseEvolutionEvents(current);
315
- if (containsLegacySequence(existing, expected)) return Promise.resolve(current);
347
+ if (containsLegacyMultiset(existing, expected)) return Promise.resolve(current);
316
348
  const maxSeq = existing.reduce((max, event) => Math.max(max, event.seq), 0);
317
349
  const merged = [...existing, ...expected.map((event, index) => ({
318
350
  ...event,
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.73",
4
+ "version": "0.3.76",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -18,10 +18,6 @@
18
18
  "types": "./lib/types/index.d.ts",
19
19
  "default": "./lib/index.js"
20
20
  },
21
- "./invariant": {
22
- "types": "./lib/types/invariant.d.ts",
23
- "default": "./lib/invariant.js"
24
- },
25
21
  "./package.json": "./package.json"
26
22
  },
27
23
  "files": [
@@ -31,18 +27,16 @@
31
27
  "license": "MIT",
32
28
  "dependencies": {
33
29
  "@deepseek-ai/schemastery": "^3.18.1",
34
- "@lmzhen/dsh-evolution-core": "^0.3.73"
30
+ "@lmzhen/dsh-evolution-core": "^0.3.76"
35
31
  },
36
32
  "peerDependencies": {
37
- "@deepseek-ai/dsh-invariants": "^0.1.5-rc.2",
38
33
  "@deepseek-ai/cordis": "^4.0.1",
39
- "@lmzhen/dsh-evolution-io": "^0.3.73",
40
- "@lmzhen/dsh-skill-usage": "^0.3.73"
34
+ "@lmzhen/dsh-evolution-io": "^0.3.76",
35
+ "@lmzhen/dsh-skill-usage": "^0.3.76"
41
36
  },
42
37
  "devDependencies": {
43
- "@deepseek-ai/dsh-invariants": "^0.1.5-rc.2",
44
- "@lmzhen/dsh-evolution-io": "^0.3.73",
45
- "@lmzhen/dsh-evolution-io-node": "^0.3.73",
46
- "@lmzhen/dsh-skill-usage": "^0.3.73"
38
+ "@lmzhen/dsh-evolution-io": "^0.3.76",
39
+ "@lmzhen/dsh-evolution-io-node": "^0.3.76",
40
+ "@lmzhen/dsh-skill-usage": "^0.3.76"
47
41
  }
48
42
  }
package/lib/invariant.js DELETED
@@ -1,8 +0,0 @@
1
- //#region lib/types/invariant.js
2
- const PACKAGE_NAME = "@lmzhen/dsh-evolution-feedback";
3
- const name = "evolution-feedback-invariant";
4
- const inject = ["invariants"];
5
- const install = () => {};
6
- const apply = (ctx) => Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install));
7
- //#endregion
8
- export { apply, inject, name };
@@ -1,5 +0,0 @@
1
- import type { Context } from '@deepseek-ai/cordis';
2
- export declare const name = "evolution-feedback-invariant";
3
- export declare const inject: string[];
4
- export declare const apply: (ctx: Context) => Promise<() => void>;
5
- //# sourceMappingURL=invariant.d.ts.map