@lmzhen/dsh-evolution-feedback 0.1.0-rc.68 → 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 +63 -15
- package/lib/types/index.d.ts +13 -2
- package/package.json +6 -6
package/lib/index.js
CHANGED
|
@@ -47,8 +47,13 @@ function eventsFile(home) {
|
|
|
47
47
|
function isEventRecord(event) {
|
|
48
48
|
return typeof event === "object" && event !== null && typeof event.seq === "number";
|
|
49
49
|
}
|
|
50
|
-
|
|
51
|
-
|
|
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 [];
|
|
52
57
|
try {
|
|
53
58
|
const parsed = JSON.parse(raw);
|
|
54
59
|
if (!Array.isArray(parsed.events)) return [];
|
|
@@ -66,12 +71,12 @@ function parseEventList(raw) {
|
|
|
66
71
|
async function appendEvolutionEvent(io, path, event) {
|
|
67
72
|
let assigned = 0;
|
|
68
73
|
await transactIo(io, path, (current) => {
|
|
69
|
-
if (current !== null) try {
|
|
74
|
+
if (current !== null && current.trim() !== "") try {
|
|
70
75
|
JSON.parse(current);
|
|
71
76
|
} catch {
|
|
72
77
|
return Promise.resolve(current);
|
|
73
78
|
}
|
|
74
|
-
const events =
|
|
79
|
+
const events = parseEvolutionEvents(current);
|
|
75
80
|
const maxSeq = events.reduce((max, entry) => Math.max(max, entry.seq), 0);
|
|
76
81
|
const record = {
|
|
77
82
|
...event,
|
|
@@ -87,10 +92,11 @@ async function appendEvolutionEvent(io, path, event) {
|
|
|
87
92
|
if (assigned === 0) throw new Error(`evolution event log is malformed and was not touched: ${path}`);
|
|
88
93
|
return assigned;
|
|
89
94
|
}
|
|
90
|
-
/** Read the event log; a missing file reads as empty,
|
|
95
|
+
/** Read the event log; a missing/whitespace-only file reads as empty,
|
|
96
|
+
* corrupt content is flagged (and refused on append). */
|
|
91
97
|
async function readEvolutionEvents(io, path) {
|
|
92
98
|
const raw = await io.readText(path);
|
|
93
|
-
if (raw === null) return {
|
|
99
|
+
if (raw === null || raw.trim() === "") return {
|
|
94
100
|
events: [],
|
|
95
101
|
malformed: false
|
|
96
102
|
};
|
|
@@ -381,16 +387,11 @@ var EvolutionFeedback = class {
|
|
|
381
387
|
const eventsPath = this.eventsPath;
|
|
382
388
|
if (!path || !eventsPath) return;
|
|
383
389
|
await this.mutate(async () => {
|
|
384
|
-
|
|
390
|
+
const rawEvents = await io.readText(eventsPath);
|
|
391
|
+
if (rawEvents === null || rawEvents.trim() === "") {
|
|
385
392
|
const aggregate = parseAggregate(await io.readText(path));
|
|
386
393
|
if (aggregate) try {
|
|
387
|
-
await
|
|
388
|
-
if (current !== null) return Promise.resolve(current);
|
|
389
|
-
return Promise.resolve(JSON.stringify({
|
|
390
|
-
version: 1,
|
|
391
|
-
events: synthesizeFeedbackEvents(aggregate)
|
|
392
|
-
}, null, 2));
|
|
393
|
-
});
|
|
394
|
+
await migrateFeedbackEvents(io, eventsPath, aggregate);
|
|
394
395
|
} catch {}
|
|
395
396
|
}
|
|
396
397
|
const { events } = await readEvolutionEvents(io, eventsPath);
|
|
@@ -478,6 +479,53 @@ var EvolutionFeedback = class {
|
|
|
478
479
|
});
|
|
479
480
|
}
|
|
480
481
|
};
|
|
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
|
+
}
|
|
481
529
|
/** Parse a legacy aggregate (v1) or a v2 cache into a plain aggregate state. */
|
|
482
530
|
function parseAggregate(raw) {
|
|
483
531
|
if (raw === null) return null;
|
|
@@ -612,4 +660,4 @@ function apply(ctx, rawConfig = {}) {
|
|
|
612
660
|
}, "evolution-feedback.records");
|
|
613
661
|
}
|
|
614
662
|
//#endregion
|
|
615
|
-
export { Config, EvolutionFeedback, apply, name };
|
|
663
|
+
export { Config, EvolutionFeedback, apply, migrateFeedbackEvents, name };
|
package/lib/types/index.d.ts
CHANGED
|
@@ -47,12 +47,23 @@ export declare class EvolutionFeedback {
|
|
|
47
47
|
/** Rebuild the boot cache from the log truth (rc.68); best-effort. */
|
|
48
48
|
persistCache(): Promise<void>;
|
|
49
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>;
|
|
50
60
|
export declare const name = "evolution-feedback";
|
|
51
61
|
export interface Config {
|
|
52
62
|
/** Score below which curator receives quality_warn for a skill. */
|
|
53
63
|
qualityWarnThreshold?: number;
|
|
54
|
-
/** Explicit boot-cache file path; empty derives $DSH_HOME/evolution/feedback.json
|
|
55
|
-
*
|
|
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). */
|
|
56
67
|
path?: string;
|
|
57
68
|
}
|
|
58
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
|
}
|