@lmzhen/dsh-evolution-feedback 0.1.0-rc.65 → 0.1.0-rc.67

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
@@ -65,7 +65,7 @@ function evolutionIoAdapter(provider) {
65
65
  * changes semantically: the bundle digest is the fail-closed signal for
66
66
  * review workers, so a stale id across deployments must be distinguishable.
67
67
  */
68
- const PROMPT_BUNDLE_ID = "dsh-evolution@6";
68
+ const PROMPT_BUNDLE_ID = "dsh-evolution@7";
69
69
  const MEMORY_REVIEW_PROMPT = `[Auto-review — Memory]
70
70
  Review the conversation above and consider saving to memory if appropriate.
71
71
 
@@ -86,6 +86,8 @@ Signals to look for (any one of these warrants action):
86
86
  • Non-trivial technique, fix, workaround, debugging path, or tool-usage pattern emerged that a future session would benefit from. Capture it.
87
87
  • A skill that got loaded or consulted this session turned out to be wrong, missing a step, or outdated. Patch it NOW.
88
88
 
89
+ Read-before-write (enforced by this channel): update, patch, delete, or write support files ONLY into skills you loaded or read in THIS session — ops on unread skills are dropped; CREATE of a brand-new umbrella is the only exception.
90
+
89
91
  Preference order — prefer the earliest action that fits, but do pick one when a signal above fired:
90
92
  1. UPDATE A CURRENTLY-LOADED SKILL. Look back through the conversation for skills the user loaded or you read. If any of them covers the territory of the new learning, PATCH that one first. It is the skill that was in play, so it's the right one to extend.
91
93
  2. UPDATE AN EXISTING UMBRELLA. If no loaded skill fits but an existing class-level skill does, patch it. Add a subsection, a pitfall, or broaden a trigger.
@@ -133,6 +135,8 @@ Signals that warrant a skill update (any one is enough):
133
135
  • Non-trivial technique, fix, workaround, or debugging path emerged.
134
136
  • A skill that was loaded or consulted turned out wrong, missing, or outdated — patch it now.
135
137
 
138
+ Read-before-write (enforced by this channel): update, patch, delete, or write support files ONLY into skills you loaded or read in THIS session — ops on unread skills are dropped; CREATE of a brand-new umbrella is the only exception.
139
+
136
140
  Preference order for skills — pick the earliest that fits:
137
141
  1. UPDATE A CURRENTLY-LOADED SKILL. Check what skills were loaded or read in the conversation. If one of them covers the learning, PATCH it first. It was in play; it's the right place.
138
142
  2. UPDATE AN EXISTING UMBRELLA. Patch it.
@@ -240,12 +244,12 @@ function sha256(text) {
240
244
  function createPromptBundle(prompts) {
241
245
  const canonical = JSON.stringify({
242
246
  id: PROMPT_BUNDLE_ID,
243
- version: 6,
247
+ version: 7,
244
248
  prompts: Object.fromEntries(Object.entries(prompts).sort())
245
249
  });
246
250
  return Object.freeze({
247
251
  id: PROMPT_BUNDLE_ID,
248
- version: 6,
252
+ version: 7,
249
253
  prompts: Object.freeze({ ...prompts }),
250
254
  sha256: sha256(canonical)
251
255
  });
@@ -281,8 +285,10 @@ var EvolutionFeedback = class {
281
285
  };
282
286
  chain = Promise.resolve();
283
287
  path;
288
+ io;
284
289
  constructor(io, home = process.env.DSH_HOME ?? join(homedir(), ".dsh"), pathOverride) {
285
290
  if (io) this.path = pathOverride ?? join(home, "evolution", "feedback.json");
291
+ this.io = io;
286
292
  }
287
293
  mutate(task) {
288
294
  const run = this.chain.then(task, task);
@@ -311,7 +317,8 @@ var EvolutionFeedback = class {
311
317
  });
312
318
  }
313
319
  record(target, rating, note, kind = "session", io) {
314
- const table = kind === "skill" ? this.state.skills : this.state.sessions;
320
+ const mode = kind === "skill" ? "skills" : "sessions";
321
+ const table = this.state[mode];
315
322
  const current = table[target] ?? {
316
323
  positive: 0,
317
324
  negative: 0
@@ -319,7 +326,31 @@ var EvolutionFeedback = class {
319
326
  current[rating] += 1;
320
327
  if (note !== void 0) current.lastNote = note;
321
328
  table[target] = current;
322
- if (io && this.path) this.flush(io);
329
+ const recordIo = io ?? this.io;
330
+ const path = this.path;
331
+ if (!recordIo || !path) return;
332
+ this.mutate(async () => {
333
+ try {
334
+ await transactIo(recordIo, path, (raw) => {
335
+ if (raw !== null) try {
336
+ JSON.parse(raw);
337
+ } catch {
338
+ return Promise.resolve(raw);
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));
351
+ });
352
+ } catch (error) {}
353
+ });
323
354
  }
324
355
  score(target, kind = "session") {
325
356
  const record = (kind === "skill" ? this.state.skills : this.state.sessions)[target];
@@ -334,16 +365,9 @@ var EvolutionFeedback = class {
334
365
  sessions: { ...this.state.sessions }
335
366
  };
336
367
  }
337
- async flush(io) {
338
- const path = this.path;
339
- if (!path || !io) return;
340
- await this.mutate(async () => {
341
- await transactIo(io, path, (current) => {
342
- const merged = mergeStates(parseState(current), this.state);
343
- this.state = merged;
344
- return Promise.resolve(JSON.stringify(merged, null, 2));
345
- });
346
- });
368
+ /** Await the pending record-task chain (unload safety; rc.66). */
369
+ waitIdle() {
370
+ return this.chain;
347
371
  }
348
372
  };
349
373
  /** Parse a raw feedback sidecar; malformed reads as empty (best-effort). */
@@ -365,25 +389,6 @@ function parseState(raw) {
365
389
  };
366
390
  }
367
391
  }
368
- /** Deep-merge two feedback states: union by target; in-memory values win on
369
- * conflict. NOTE (v3 audit P3 reviewed): additive counting was rejected — a
370
- * restart merge (restore+flush) would double-count the same records, and a
371
- * stateless JSON sidecar cannot distinguish "two processes incrementing the
372
- * same target" from "the same record seen twice". Cross-process same-target
373
- * increments stay a documented limitation; an append-only event log would be
374
- * the real fix. */
375
- function mergeStates(disk, memory) {
376
- return {
377
- skills: {
378
- ...disk.skills,
379
- ...memory.skills
380
- },
381
- sessions: {
382
- ...disk.sessions,
383
- ...memory.sessions
384
- }
385
- };
386
- }
387
392
  const name = "evolution-feedback";
388
393
  const Config = z.object({
389
394
  qualityWarnThreshold: z.number().default(-.25),
@@ -412,8 +417,8 @@ function apply(ctx, rawConfig = {}) {
412
417
  };
413
418
  }
414
419
  ctx.effect(() => () => {
415
- if (io) return feedback.flush(io);
416
- }, "evolution-feedback.flush");
420
+ return feedback.waitIdle();
421
+ }, "evolution-feedback.records");
417
422
  }
418
423
  //#endregion
419
424
  export { Config, EvolutionFeedback, apply, name };
@@ -28,13 +28,15 @@ export declare class EvolutionFeedback {
28
28
  private state;
29
29
  private chain;
30
30
  private readonly path?;
31
+ private readonly io;
31
32
  constructor(io?: IoLike, home?: string, pathOverride?: string);
32
33
  private mutate;
33
34
  restore(io: IoLike): Promise<void>;
34
35
  record(target: string, rating: 'positive' | 'negative', note?: string, kind?: 'skill' | 'session', io?: IoLike): void;
35
36
  score(target: string, kind?: 'skill' | 'session'): number;
36
37
  snapshot(): FeedbackState;
37
- flush(io?: IoLike): Promise<void>;
38
+ /** Await the pending record-task chain (unload safety; rc.66). */
39
+ waitIdle(): Promise<unknown>;
38
40
  }
39
41
  export declare const name = "evolution-feedback";
40
42
  export interface 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.65",
4
+ "version": "0.1.0-rc.67",
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.65",
41
- "@lmzhen/dsh-skill-usage": "^0.1.0-rc.65"
40
+ "@lmzhen/dsh-evolution-io": "^0.1.0-rc.67",
41
+ "@lmzhen/dsh-skill-usage": "^0.1.0-rc.67"
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.65",
46
- "@lmzhen/dsh-evolution-io-node": "^0.1.0-rc.65",
47
- "@lmzhen/dsh-skill-usage": "^0.1.0-rc.65"
45
+ "@lmzhen/dsh-evolution-io": "^0.1.0-rc.67",
46
+ "@lmzhen/dsh-evolution-io-node": "^0.1.0-rc.67",
47
+ "@lmzhen/dsh-skill-usage": "^0.1.0-rc.67"
48
48
  }
49
49
  }