@lmzhen/dsh-evolution-feedback 0.1.0-rc.67 → 0.1.0-rc.69
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 +290 -51
- package/lib/types/index.d.ts +23 -2
- package/package.json +6 -6
package/lib/index.js
CHANGED
|
@@ -41,6 +41,82 @@ function evolutionIoAdapter(provider) {
|
|
|
41
41
|
}
|
|
42
42
|
};
|
|
43
43
|
}
|
|
44
|
+
function eventsFile(home) {
|
|
45
|
+
return join(home, "evolution", "events.json");
|
|
46
|
+
}
|
|
47
|
+
function isEventRecord(event) {
|
|
48
|
+
return typeof event === "object" && event !== null && typeof event.seq === "number";
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Parse an event log body. A missing file, a whitespace-only file (rc.69:
|
|
52
|
+
* rebuildable, NOT malformed) or a corrupt one reads as empty; corrupt content
|
|
53
|
+
* is still refused on append, never overwritten.
|
|
54
|
+
*/
|
|
55
|
+
function parseEvolutionEvents(raw) {
|
|
56
|
+
if (raw === null || raw.trim() === "") return [];
|
|
57
|
+
try {
|
|
58
|
+
const parsed = JSON.parse(raw);
|
|
59
|
+
if (!Array.isArray(parsed.events)) return [];
|
|
60
|
+
return parsed.events.filter(isEventRecord);
|
|
61
|
+
} catch {
|
|
62
|
+
return [];
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Append one event under the write lock (rc.68): `seq` = current max + 1
|
|
67
|
+
* computed inside the transact, so two processes appending concurrently never
|
|
68
|
+
* collide. A malformed log is refused (bytes preserved) and the append fails.
|
|
69
|
+
* Returns the assigned seq.
|
|
70
|
+
*/
|
|
71
|
+
async function appendEvolutionEvent(io, path, event) {
|
|
72
|
+
let assigned = 0;
|
|
73
|
+
await transactIo(io, path, (current) => {
|
|
74
|
+
if (current !== null && current.trim() !== "") try {
|
|
75
|
+
JSON.parse(current);
|
|
76
|
+
} catch {
|
|
77
|
+
return Promise.resolve(current);
|
|
78
|
+
}
|
|
79
|
+
const events = parseEvolutionEvents(current);
|
|
80
|
+
const maxSeq = events.reduce((max, entry) => Math.max(max, entry.seq), 0);
|
|
81
|
+
const record = {
|
|
82
|
+
...event,
|
|
83
|
+
seq: maxSeq + 1,
|
|
84
|
+
at: (/* @__PURE__ */ new Date()).toISOString()
|
|
85
|
+
};
|
|
86
|
+
assigned = record.seq;
|
|
87
|
+
return Promise.resolve(JSON.stringify({
|
|
88
|
+
version: 1,
|
|
89
|
+
events: [...events, record]
|
|
90
|
+
}, null, 2));
|
|
91
|
+
});
|
|
92
|
+
if (assigned === 0) throw new Error(`evolution event log is malformed and was not touched: ${path}`);
|
|
93
|
+
return assigned;
|
|
94
|
+
}
|
|
95
|
+
/** Read the event log; a missing/whitespace-only file reads as empty,
|
|
96
|
+
* corrupt content is flagged (and refused on append). */
|
|
97
|
+
async function readEvolutionEvents(io, path) {
|
|
98
|
+
const raw = await io.readText(path);
|
|
99
|
+
if (raw === null || raw.trim() === "") return {
|
|
100
|
+
events: [],
|
|
101
|
+
malformed: false
|
|
102
|
+
};
|
|
103
|
+
try {
|
|
104
|
+
const parsed = JSON.parse(raw);
|
|
105
|
+
if (!Array.isArray(parsed.events)) return {
|
|
106
|
+
events: [],
|
|
107
|
+
malformed: true
|
|
108
|
+
};
|
|
109
|
+
return {
|
|
110
|
+
events: parsed.events.filter(isEventRecord),
|
|
111
|
+
malformed: false
|
|
112
|
+
};
|
|
113
|
+
} catch {
|
|
114
|
+
return {
|
|
115
|
+
events: [],
|
|
116
|
+
malformed: true
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
}
|
|
44
120
|
//#endregion
|
|
45
121
|
//#region ../evolution-core/src/prompts.ts
|
|
46
122
|
/**
|
|
@@ -276,8 +352,15 @@ new RegExp(String.raw`ignore\s+${FILLER}(?:previous|above|prior|all)\s+${FILLER}
|
|
|
276
352
|
* Feedback is durable through `ctx.evolutionIo` (when mounted) and skill
|
|
277
353
|
* feedback feeds `quality_score` / `quality_warn` on the usage record, so
|
|
278
354
|
* curator decisions can consume it deterministically.
|
|
355
|
+
*
|
|
356
|
+
* Persistence (rc.68): the EVENTS LOG (`evolution/events.json`, via
|
|
357
|
+
* `evolution-core/evolution-events.ts`) is the single source of truth —
|
|
358
|
+
* every increment appends one event under the write lock. `feedback.json` is
|
|
359
|
+
* a rebuildable BOOT CACHE (`{ version: 2, lastSeq, skills, sessions }`),
|
|
360
|
+
* never the truth; the in-memory state is the optimistic aggregate.
|
|
279
361
|
* @module @lmzhen/dsh-evolution-feedback
|
|
280
362
|
*/
|
|
363
|
+
const CACHE_VERSION = 2;
|
|
281
364
|
var EvolutionFeedback = class {
|
|
282
365
|
state = {
|
|
283
366
|
skills: {},
|
|
@@ -285,9 +368,13 @@ var EvolutionFeedback = class {
|
|
|
285
368
|
};
|
|
286
369
|
chain = Promise.resolve();
|
|
287
370
|
path;
|
|
371
|
+
eventsPath;
|
|
288
372
|
io;
|
|
289
373
|
constructor(io, home = process.env.DSH_HOME ?? join(homedir(), ".dsh"), pathOverride) {
|
|
290
|
-
if (io)
|
|
374
|
+
if (io) {
|
|
375
|
+
this.path = pathOverride ?? join(home, "evolution", "feedback.json");
|
|
376
|
+
this.eventsPath = eventsFile(home);
|
|
377
|
+
}
|
|
291
378
|
this.io = io;
|
|
292
379
|
}
|
|
293
380
|
mutate(task) {
|
|
@@ -297,26 +384,40 @@ var EvolutionFeedback = class {
|
|
|
297
384
|
}
|
|
298
385
|
async restore(io) {
|
|
299
386
|
const path = this.path;
|
|
300
|
-
|
|
387
|
+
const eventsPath = this.eventsPath;
|
|
388
|
+
if (!path || !eventsPath) return;
|
|
301
389
|
await this.mutate(async () => {
|
|
302
|
-
const
|
|
303
|
-
if (
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
390
|
+
const rawEvents = await io.readText(eventsPath);
|
|
391
|
+
if (rawEvents === null || rawEvents.trim() === "") {
|
|
392
|
+
const aggregate = parseAggregate(await io.readText(path));
|
|
393
|
+
if (aggregate) try {
|
|
394
|
+
await migrateFeedbackEvents(io, eventsPath, aggregate);
|
|
395
|
+
} catch {}
|
|
396
|
+
}
|
|
397
|
+
const { events } = await readEvolutionEvents(io, eventsPath);
|
|
398
|
+
const cache = parseCache(await io.readText(path));
|
|
399
|
+
const maxSeq = events.reduce((max, event) => Math.max(max, event.seq), 0);
|
|
400
|
+
const truth = cache ? foldWithDelta(cache, events) : foldFeedbackState(events);
|
|
401
|
+
this.state = {
|
|
402
|
+
skills: {
|
|
403
|
+
...truth.skills,
|
|
404
|
+
...this.state.skills
|
|
405
|
+
},
|
|
406
|
+
sessions: {
|
|
407
|
+
...truth.sessions,
|
|
408
|
+
...this.state.sessions
|
|
409
|
+
}
|
|
410
|
+
};
|
|
411
|
+
if (maxSeq > 0 && (!cache || cache.lastSeq < maxSeq)) try {
|
|
412
|
+
await io.writeText(path, JSON.stringify({
|
|
413
|
+
version: CACHE_VERSION,
|
|
414
|
+
lastSeq: maxSeq,
|
|
415
|
+
...truth
|
|
416
|
+
}, null, 2));
|
|
316
417
|
} catch {}
|
|
317
418
|
});
|
|
318
419
|
}
|
|
319
|
-
record(target, rating, note, kind = "session"
|
|
420
|
+
record(target, rating, note, kind = "session") {
|
|
320
421
|
const mode = kind === "skill" ? "skills" : "sessions";
|
|
321
422
|
const table = this.state[mode];
|
|
322
423
|
const current = table[target] ?? {
|
|
@@ -326,28 +427,17 @@ var EvolutionFeedback = class {
|
|
|
326
427
|
current[rating] += 1;
|
|
327
428
|
if (note !== void 0) current.lastNote = note;
|
|
328
429
|
table[target] = current;
|
|
329
|
-
const recordIo =
|
|
330
|
-
const
|
|
331
|
-
if (!recordIo || !
|
|
430
|
+
const recordIo = this.io;
|
|
431
|
+
const eventsPath = this.eventsPath;
|
|
432
|
+
if (!recordIo || !eventsPath) return;
|
|
332
433
|
this.mutate(async () => {
|
|
333
434
|
try {
|
|
334
|
-
await
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
const diskState = parseState(raw);
|
|
341
|
-
const diskTable = diskState[mode];
|
|
342
|
-
const diskRec = diskTable[target] ?? {
|
|
343
|
-
positive: 0,
|
|
344
|
-
negative: 0
|
|
345
|
-
};
|
|
346
|
-
diskRec[rating] += 1;
|
|
347
|
-
if (note !== void 0) diskRec.lastNote = note;
|
|
348
|
-
diskTable[target] = diskRec;
|
|
349
|
-
this.state = diskState;
|
|
350
|
-
return Promise.resolve(JSON.stringify(diskState, null, 2));
|
|
435
|
+
await appendEvolutionEvent(recordIo, eventsPath, {
|
|
436
|
+
type: "feedback",
|
|
437
|
+
target,
|
|
438
|
+
kind,
|
|
439
|
+
rating,
|
|
440
|
+
note
|
|
351
441
|
});
|
|
352
442
|
} catch (error) {}
|
|
353
443
|
});
|
|
@@ -369,26 +459,175 @@ var EvolutionFeedback = class {
|
|
|
369
459
|
waitIdle() {
|
|
370
460
|
return this.chain;
|
|
371
461
|
}
|
|
462
|
+
/** Rebuild the boot cache from the log truth (rc.68); best-effort. */
|
|
463
|
+
persistCache() {
|
|
464
|
+
const path = this.path;
|
|
465
|
+
const eventsPath = this.eventsPath;
|
|
466
|
+
const recordIo = this.io;
|
|
467
|
+
if (!path || !eventsPath || !recordIo) return Promise.resolve();
|
|
468
|
+
return this.mutate(async () => {
|
|
469
|
+
try {
|
|
470
|
+
const { events } = await readEvolutionEvents(recordIo, eventsPath);
|
|
471
|
+
const maxSeq = events.reduce((max, event) => Math.max(max, event.seq), 0);
|
|
472
|
+
if (maxSeq === 0) return;
|
|
473
|
+
await recordIo.writeText(path, JSON.stringify({
|
|
474
|
+
version: CACHE_VERSION,
|
|
475
|
+
lastSeq: maxSeq,
|
|
476
|
+
...foldFeedbackState(events)
|
|
477
|
+
}, null, 2));
|
|
478
|
+
} catch {}
|
|
479
|
+
});
|
|
480
|
+
}
|
|
372
481
|
};
|
|
373
|
-
/**
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
482
|
+
/** True when `existing` contains the legacy sequence as a contiguous run on
|
|
483
|
+
* its semantic fields (skip case). `seq` and `at` are excluded: after a merge
|
|
484
|
+
* the legacy events carry shifted seqs, and a re-synthesis stamps a different
|
|
485
|
+
* `at` — the semantic identity is type/kind/target/rating/note. A coincidental
|
|
486
|
+
* semantic match of an already-appended user sequence yields the identical
|
|
487
|
+
* aggregation, so the skip is harmless for counts and notes. */
|
|
488
|
+
function containsLegacySequence(existing, expected) {
|
|
489
|
+
if (expected.length === 0) return true;
|
|
490
|
+
for (let start = 0; start <= existing.length - expected.length; start += 1) {
|
|
491
|
+
let match = true;
|
|
492
|
+
for (let offset = 0; offset < expected.length; offset += 1) {
|
|
493
|
+
const a = expected[offset];
|
|
494
|
+
const b = existing[start + offset];
|
|
495
|
+
if (!a || !b || a.type !== b.type || a.kind !== b.kind || a.target !== b.target || a.rating !== b.rating || a.note !== b.note) {
|
|
496
|
+
match = false;
|
|
497
|
+
break;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
if (match) return true;
|
|
501
|
+
}
|
|
502
|
+
return false;
|
|
503
|
+
}
|
|
504
|
+
/**
|
|
505
|
+
* Merge a legacy aggregate into the event log (rc.69): the expected synthetic
|
|
506
|
+
* sequence is APPENDED (seq-shifted) when the log does not already contain it
|
|
507
|
+
* — so a concurrent first writer's events AND the legacy history both
|
|
508
|
+
* survive; when the sequence is already present the migration was completed
|
|
509
|
+
* (by a first writer or by this path) and nothing is re-appended. Idempotent
|
|
510
|
+
* and race-safe (the search runs inside the same transact). Exported for the
|
|
511
|
+
* migration-race regression test.
|
|
512
|
+
*/
|
|
513
|
+
async function migrateFeedbackEvents(io, eventsPath, aggregate) {
|
|
514
|
+
const expected = synthesizeFeedbackEvents(aggregate);
|
|
515
|
+
await transactIo(io, eventsPath, (current) => {
|
|
516
|
+
const existing = parseEvolutionEvents(current);
|
|
517
|
+
if (containsLegacySequence(existing, expected)) return Promise.resolve(current ?? "");
|
|
518
|
+
const maxSeq = existing.reduce((max, event) => Math.max(max, event.seq), 0);
|
|
519
|
+
const merged = [...existing, ...expected.map((event, index) => ({
|
|
520
|
+
...event,
|
|
521
|
+
seq: maxSeq + index + 1
|
|
522
|
+
}))];
|
|
523
|
+
return Promise.resolve(JSON.stringify({
|
|
524
|
+
version: 1,
|
|
525
|
+
events: merged
|
|
526
|
+
}, null, 2));
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
/** Parse a legacy aggregate (v1) or a v2 cache into a plain aggregate state. */
|
|
530
|
+
function parseAggregate(raw) {
|
|
531
|
+
if (raw === null) return null;
|
|
379
532
|
try {
|
|
380
533
|
const parsed = JSON.parse(raw);
|
|
534
|
+
const skills = isRecord(parsed.skills) ? parsed.skills : void 0;
|
|
535
|
+
const sessions = isRecord(parsed.sessions) ? parsed.sessions : void 0;
|
|
536
|
+
if (!skills && !sessions) return null;
|
|
381
537
|
return {
|
|
382
|
-
skills:
|
|
383
|
-
sessions:
|
|
538
|
+
skills: skills ?? {},
|
|
539
|
+
sessions: sessions ?? {}
|
|
384
540
|
};
|
|
385
541
|
} catch {
|
|
542
|
+
return null;
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
function parseCache(raw) {
|
|
546
|
+
if (raw === null) return null;
|
|
547
|
+
try {
|
|
548
|
+
const parsed = JSON.parse(raw);
|
|
549
|
+
if (parsed.version !== CACHE_VERSION || typeof parsed.lastSeq !== "number") return null;
|
|
550
|
+
if (!isRecord(parsed.skills) || !isRecord(parsed.sessions)) return null;
|
|
386
551
|
return {
|
|
387
|
-
|
|
388
|
-
|
|
552
|
+
lastSeq: parsed.lastSeq,
|
|
553
|
+
state: {
|
|
554
|
+
skills: parsed.skills,
|
|
555
|
+
sessions: parsed.sessions
|
|
556
|
+
}
|
|
389
557
|
};
|
|
558
|
+
} catch {
|
|
559
|
+
return null;
|
|
390
560
|
}
|
|
391
561
|
}
|
|
562
|
+
function isRecord(value) {
|
|
563
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
564
|
+
}
|
|
565
|
+
/** Fold all feedback events from zero (the truth view). */
|
|
566
|
+
function foldFeedbackState(events) {
|
|
567
|
+
const state = {
|
|
568
|
+
skills: {},
|
|
569
|
+
sessions: {}
|
|
570
|
+
};
|
|
571
|
+
for (const event of events) applyFeedbackEvent(state, event);
|
|
572
|
+
return state;
|
|
573
|
+
}
|
|
574
|
+
/** Fold events after the cache's lastSeq onto the cached aggregates. */
|
|
575
|
+
function foldWithDelta(cache, events) {
|
|
576
|
+
const state = {
|
|
577
|
+
skills: { ...cache.state.skills },
|
|
578
|
+
sessions: { ...cache.state.sessions }
|
|
579
|
+
};
|
|
580
|
+
for (const event of events) if (event.seq > cache.lastSeq) applyFeedbackEvent(state, event);
|
|
581
|
+
return state;
|
|
582
|
+
}
|
|
583
|
+
function applyFeedbackEvent(state, event) {
|
|
584
|
+
if (event.type !== "feedback") return;
|
|
585
|
+
const target = event.target;
|
|
586
|
+
if (target === void 0 || event.rating === void 0) return;
|
|
587
|
+
const table = event.kind === "skill" ? state.skills : state.sessions;
|
|
588
|
+
const record = table[target] ?? {
|
|
589
|
+
positive: 0,
|
|
590
|
+
negative: 0
|
|
591
|
+
};
|
|
592
|
+
record[event.rating] += 1;
|
|
593
|
+
if (event.note !== void 0) record.lastNote = event.note;
|
|
594
|
+
table[target] = record;
|
|
595
|
+
}
|
|
596
|
+
/** Synthesize one event per aggregate count unit, lastNote on the final event (migration). */
|
|
597
|
+
function synthesizeFeedbackEvents(aggregate) {
|
|
598
|
+
const events = [];
|
|
599
|
+
const at = (/* @__PURE__ */ new Date()).toISOString();
|
|
600
|
+
const emitTarget = (kind, target, record) => {
|
|
601
|
+
const first = events.length + 1;
|
|
602
|
+
for (let index = 0; index < record.positive; index += 1) events.push({
|
|
603
|
+
seq: events.length + 1,
|
|
604
|
+
at,
|
|
605
|
+
type: "feedback",
|
|
606
|
+
kind,
|
|
607
|
+
target,
|
|
608
|
+
rating: "positive"
|
|
609
|
+
});
|
|
610
|
+
for (let index = 0; index < record.negative; index += 1) events.push({
|
|
611
|
+
seq: events.length + 1,
|
|
612
|
+
at,
|
|
613
|
+
type: "feedback",
|
|
614
|
+
kind,
|
|
615
|
+
target,
|
|
616
|
+
rating: "negative"
|
|
617
|
+
});
|
|
618
|
+
if (record.lastNote !== void 0 && events.length >= first) {
|
|
619
|
+
const last = events.length - 1;
|
|
620
|
+
const final = events[last];
|
|
621
|
+
if (final) events[last] = {
|
|
622
|
+
...final,
|
|
623
|
+
note: record.lastNote
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
};
|
|
627
|
+
for (const [target, record] of Object.entries(aggregate.skills)) emitTarget("skill", target, record);
|
|
628
|
+
for (const [target, record] of Object.entries(aggregate.sessions)) emitTarget("session", target, record);
|
|
629
|
+
return events;
|
|
630
|
+
}
|
|
392
631
|
const name = "evolution-feedback";
|
|
393
632
|
const Config = z.object({
|
|
394
633
|
qualityWarnThreshold: z.number().default(-.25),
|
|
@@ -405,8 +644,8 @@ function apply(ctx, rawConfig = {}) {
|
|
|
405
644
|
const skillUsage = ctx.get("skillUsage");
|
|
406
645
|
if (skillUsage) {
|
|
407
646
|
const original = feedback.record.bind(feedback);
|
|
408
|
-
feedback.record = (target, rating, note, kind
|
|
409
|
-
original(target, rating, note, kind ?? "session"
|
|
647
|
+
feedback.record = (target, rating, note, kind) => {
|
|
648
|
+
original(target, rating, note, kind ?? "session");
|
|
410
649
|
if (kind === "skill") {
|
|
411
650
|
const score = feedback.score(target, "skill");
|
|
412
651
|
const warn = score < (rawConfig.qualityWarnThreshold ?? -.25);
|
|
@@ -417,8 +656,8 @@ function apply(ctx, rawConfig = {}) {
|
|
|
417
656
|
};
|
|
418
657
|
}
|
|
419
658
|
ctx.effect(() => () => {
|
|
420
|
-
return feedback.waitIdle();
|
|
659
|
+
return Promise.all([feedback.persistCache(), feedback.waitIdle()]);
|
|
421
660
|
}, "evolution-feedback.records");
|
|
422
661
|
}
|
|
423
662
|
//#endregion
|
|
424
|
-
export { Config, EvolutionFeedback, apply, name };
|
|
663
|
+
export { Config, EvolutionFeedback, apply, migrateFeedbackEvents, name };
|
package/lib/types/index.d.ts
CHANGED
|
@@ -4,6 +4,12 @@
|
|
|
4
4
|
* Feedback is durable through `ctx.evolutionIo` (when mounted) and skill
|
|
5
5
|
* feedback feeds `quality_score` / `quality_warn` on the usage record, so
|
|
6
6
|
* curator decisions can consume it deterministically.
|
|
7
|
+
*
|
|
8
|
+
* Persistence (rc.68): the EVENTS LOG (`evolution/events.json`, via
|
|
9
|
+
* `evolution-core/evolution-events.ts`) is the single source of truth —
|
|
10
|
+
* every increment appends one event under the write lock. `feedback.json` is
|
|
11
|
+
* a rebuildable BOOT CACHE (`{ version: 2, lastSeq, skills, sessions }`),
|
|
12
|
+
* never the truth; the in-memory state is the optimistic aggregate.
|
|
7
13
|
* @module @deepseek-ai/dsh-evolution-feedback
|
|
8
14
|
*/
|
|
9
15
|
import type { Context } from '@deepseek-ai/cordis';
|
|
@@ -28,21 +34,36 @@ export declare class EvolutionFeedback {
|
|
|
28
34
|
private state;
|
|
29
35
|
private chain;
|
|
30
36
|
private readonly path?;
|
|
37
|
+
private readonly eventsPath?;
|
|
31
38
|
private readonly io;
|
|
32
39
|
constructor(io?: IoLike, home?: string, pathOverride?: string);
|
|
33
40
|
private mutate;
|
|
34
41
|
restore(io: IoLike): Promise<void>;
|
|
35
|
-
record(target: string, rating: 'positive' | 'negative', note?: string, kind?: 'skill' | 'session'
|
|
42
|
+
record(target: string, rating: 'positive' | 'negative', note?: string, kind?: 'skill' | 'session'): void;
|
|
36
43
|
score(target: string, kind?: 'skill' | 'session'): number;
|
|
37
44
|
snapshot(): FeedbackState;
|
|
38
45
|
/** Await the pending record-task chain (unload safety; rc.66). */
|
|
39
46
|
waitIdle(): Promise<unknown>;
|
|
47
|
+
/** Rebuild the boot cache from the log truth (rc.68); best-effort. */
|
|
48
|
+
persistCache(): Promise<void>;
|
|
40
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* Merge a legacy aggregate into the event log (rc.69): the expected synthetic
|
|
52
|
+
* sequence is APPENDED (seq-shifted) when the log does not already contain it
|
|
53
|
+
* — so a concurrent first writer's events AND the legacy history both
|
|
54
|
+
* survive; when the sequence is already present the migration was completed
|
|
55
|
+
* (by a first writer or by this path) and nothing is re-appended. Idempotent
|
|
56
|
+
* and race-safe (the search runs inside the same transact). Exported for the
|
|
57
|
+
* migration-race regression test.
|
|
58
|
+
*/
|
|
59
|
+
export declare function migrateFeedbackEvents(io: IoLike, eventsPath: string, aggregate: FeedbackState): Promise<void>;
|
|
41
60
|
export declare const name = "evolution-feedback";
|
|
42
61
|
export interface Config {
|
|
43
62
|
/** Score below which curator receives quality_warn for a skill. */
|
|
44
63
|
qualityWarnThreshold?: number;
|
|
45
|
-
/** Explicit
|
|
64
|
+
/** Explicit boot-cache file path; empty derives $DSH_HOME/evolution/feedback.json.
|
|
65
|
+
* The event log always stays at $DSH_HOME/evolution/events.json (derived from
|
|
66
|
+
* home, never from this override). */
|
|
46
67
|
path?: string;
|
|
47
68
|
}
|
|
48
69
|
export declare const Config: z<Config>;
|
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.1.0-rc.
|
|
4
|
+
"version": "0.1.0-rc.69",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -37,13 +37,13 @@
|
|
|
37
37
|
"peerDependencies": {
|
|
38
38
|
"@deepseek-ai/dsh-invariants": "^0.1.1-rc.2",
|
|
39
39
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
40
|
-
"@lmzhen/dsh-evolution-io": "^0.1.0-rc.
|
|
41
|
-
"@lmzhen/dsh-skill-usage": "^0.1.0-rc.
|
|
40
|
+
"@lmzhen/dsh-evolution-io": "^0.1.0-rc.69",
|
|
41
|
+
"@lmzhen/dsh-skill-usage": "^0.1.0-rc.69"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"@deepseek-ai/dsh-invariants": "^0.1.1-rc.2",
|
|
45
|
-
"@lmzhen/dsh-evolution-io": "^0.1.0-rc.
|
|
46
|
-
"@lmzhen/dsh-evolution-io-node": "^0.1.0-rc.
|
|
47
|
-
"@lmzhen/dsh-skill-usage": "^0.1.0-rc.
|
|
45
|
+
"@lmzhen/dsh-evolution-io": "^0.1.0-rc.69",
|
|
46
|
+
"@lmzhen/dsh-evolution-io-node": "^0.1.0-rc.69",
|
|
47
|
+
"@lmzhen/dsh-skill-usage": "^0.1.0-rc.69"
|
|
48
48
|
}
|
|
49
49
|
}
|