@lmzhen/dsh-evolution-review 0.1.0-rc.9 → 0.2.0-rc.1

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,7 +1,7 @@
1
1
  import { createHash, randomUUID } from "node:crypto";
2
2
  import z from "@deepseek-ai/schemastery";
3
- import { CallId, createUserMessage } from "@deepseek-ai/dsh-llm";
4
- import { PROMPT_BUNDLE, advanceReview, foldTurn, reviewPrompt, verifyPromptBundle } from "@lmzhen/dsh-evolution-core";
3
+ import { createUserMessage } from "@deepseek-ai/dsh-llm";
4
+ import { COMPLETION_SKILL_REVIEW_PROMPT, DEFAULT_MAX_OPS_PER_PLAN, DEFAULT_MEMORY_CHAR_LIMIT, DEFAULT_REVIEW_MEMORY_INTERVAL, DEFAULT_REVIEW_SKILL_INTERVAL, DEFAULT_SKILL_CONTENT_CHARS, DEFAULT_SKILL_REVIEW_COMPLETION_MIN_TOOL_CALLS, DEFAULT_SKILL_REVIEW_TRIGGER, DEFAULT_USER_CHAR_LIMIT, PROMPT_BUNDLE, SkillLibrary, advanceReview, evolutionIoAdapter, foldTurn, resolveOrigins, reviewPrompt, verifyPromptBundle } from "@lmzhen/dsh-evolution-core";
5
5
  import { validateEvolutionPlan } from "@lmzhen/dsh-evolution-plan-validator";
6
6
  //#region lib/types/redact.js
7
7
  /**
@@ -42,28 +42,34 @@ const inject = ["agents", "tools"];
42
42
  const Config = z.object({
43
43
  reviewEnabled: z.boolean().default(true),
44
44
  reviewMode: z.string().default("subagent"),
45
- memoryInterval: z.number().default(10),
46
- skillInterval: z.number().default(10),
47
- reviewToolAllow: z.array(z.string()).default([
48
- "skill",
49
- "skill_search",
50
- "skill_load"
51
- ]),
45
+ memoryInterval: z.number().default(DEFAULT_REVIEW_MEMORY_INTERVAL),
46
+ skillInterval: z.number().default(DEFAULT_REVIEW_SKILL_INTERVAL),
47
+ reviewToolAllow: z.array(z.string()).default(["skill"]),
52
48
  reviewTimeoutMs: z.number().default(12e4),
53
49
  executionTimeoutMs: z.number().default(3e4),
54
50
  reviewContextMessages: z.number().default(60),
55
51
  reviewMessageChars: z.number().default(2e3),
56
52
  reviewMaxDepth: z.number().default(0),
57
- reviewProvider: z.string()
53
+ reviewProvider: z.string(),
54
+ skillReviewTrigger: z.string().default(DEFAULT_SKILL_REVIEW_TRIGGER),
55
+ skillReviewCompletionMinToolCalls: z.number().default(DEFAULT_SKILL_REVIEW_COMPLETION_MIN_TOOL_CALLS)
58
56
  });
59
57
  function apply(ctx, rawConfig) {
60
58
  if (!verifyPromptBundle(PROMPT_BUNDLE)) throw new Error("dsh-evolution prompt bundle integrity check failed; refusing to schedule review work");
61
59
  const config = rawConfig;
62
60
  const turnStarts = /* @__PURE__ */ new Map();
61
+ const cumulativeToolCalls = /* @__PURE__ */ new Map();
62
+ const completionInjected = /* @__PURE__ */ new Set();
63
63
  const policy = () => ctx.get("evolutionPolicy")?.get();
64
64
  ctx.on("session/event", (session, event) => {
65
65
  if (event.type === "turn/start") turnStarts.set(session.id, session.seq - 1);
66
66
  if (event.type !== "turn/end") return;
67
+ if (turnStarts.size >= COUNTER_SWEEP_THRESHOLD || cumulativeToolCalls.size >= COUNTER_SWEEP_THRESHOLD || completionInjected.size >= COUNTER_SWEEP_THRESHOLD) {
68
+ const isAlive = (id) => ctx.agents.get(id) !== void 0;
69
+ sweepDeadSessionEntries(turnStarts, isAlive);
70
+ sweepDeadSessionEntries(cumulativeToolCalls, isAlive);
71
+ sweepDeadSessionEntries(completionInjected, isAlive);
72
+ }
67
73
  onTurnEnd(session, event);
68
74
  });
69
75
  async function onTurnEnd(session, event) {
@@ -88,17 +94,52 @@ function apply(ctx, rawConfig) {
88
94
  substantiveMinAgentChars: snapshot?.substantiveMinAgentChars ?? 500
89
95
  });
90
96
  await stateService?.saveReviewState(session.id, state);
91
- if (!kind) return;
92
- if (!await trySubagentReview(session, agent, kind, signal)) agent.inject(createUserMessage({
97
+ const cumulative = (cumulativeToolCalls.get(session.id) ?? 0) + signal.toolCalls;
98
+ cumulativeToolCalls.set(session.id, cumulative);
99
+ if (kind) {
100
+ ctx.emit("evolution/review-scheduled", {
101
+ sessionId: session.id,
102
+ kind,
103
+ toolCalls: signal.toolCalls,
104
+ userChars: signal.userChars,
105
+ assistantChars: signal.assistantChars
106
+ });
107
+ if (!await trySubagentReview(session, agent, kind, signal)) agent.inject(createUserMessage({
108
+ content: [{
109
+ type: "text",
110
+ text: reviewPrompt(kind)
111
+ }],
112
+ source: {
113
+ kind: "plugin",
114
+ plugin: "dsh-evolution-review",
115
+ form: "notice",
116
+ summary: "auto-review"
117
+ }
118
+ }));
119
+ return;
120
+ }
121
+ const trigger = config.skillReviewTrigger;
122
+ if (trigger !== "completion" && trigger !== "both") return;
123
+ if (completionInjected.has(session.id)) return;
124
+ if (!shouldCompletionReview(event.data.reason, cumulative, config.skillReviewCompletionMinToolCalls)) return;
125
+ completionInjected.add(session.id);
126
+ ctx.emit("evolution/review-scheduled", {
127
+ sessionId: session.id,
128
+ kind: "skill",
129
+ toolCalls: signal.toolCalls,
130
+ userChars: signal.userChars,
131
+ assistantChars: signal.assistantChars
132
+ });
133
+ agent.inject(createUserMessage({
93
134
  content: [{
94
135
  type: "text",
95
- text: reviewPrompt(kind)
136
+ text: COMPLETION_SKILL_REVIEW_PROMPT
96
137
  }],
97
138
  source: {
98
139
  kind: "plugin",
99
140
  plugin: "dsh-evolution-review",
100
141
  form: "notice",
101
- summary: "auto-review"
142
+ summary: "completion review"
102
143
  }
103
144
  }));
104
145
  }
@@ -122,7 +163,7 @@ function apply(ctx, rawConfig) {
122
163
  signal: AbortSignal.timeout(config.reviewTimeoutMs),
123
164
  maxDepth: config.reviewMaxDepth,
124
165
  agentOptions,
125
- persona: reviewPrompt(kind),
166
+ persona: reviewPrompt(kind, "plan"),
126
167
  toolFilter: { allow: [...config.reviewToolAllow] },
127
168
  outputSchema: {
128
169
  type: "object",
@@ -140,51 +181,64 @@ function apply(ctx, rawConfig) {
140
181
  }
141
182
  }
142
183
  });
143
- const result = await run.result;
144
- await run.dispose();
145
- if (!result.structured) return true;
146
- const snapshot = policy();
147
- const plan = result.structured;
148
- const policyFingerprint = fingerprintPolicy(snapshot);
149
- const validation = validateEvolutionPlan(plan, {
150
- sessionSeq: session.seq - 1,
151
- maxOpsPerPlan: snapshot?.maxOpsPerPlan ?? 32,
152
- protectedSkillNames: new Set(snapshot?.protectedSkillNames ?? []),
153
- maxMemoryChars: snapshot?.memoryChars ?? 2200,
154
- maxUserChars: snapshot?.userChars ?? 1375,
155
- maxSkillContentChars: snapshot?.skillContentChars ?? 1e5
156
- });
157
- const actions = await executePlan(validation.accepted, agent);
158
- const evidenceQuotes = [...validation.accepted.memoryOps ?? [], ...validation.accepted.skillOps ?? []].reduce((total, op) => total + (Array.isArray(op.evidence) ? op.evidence.length : 0), 0);
159
- session.append("evolution/plan-applied", {
160
- planId: randomUUID(),
161
- policyFingerprint,
162
- memoryApplied: actions.filter((action) => action.startsWith("Memory")).length,
163
- skillApplied: actions.filter((action) => action.startsWith("Skill ")).length,
164
- rejectedOps: validation.rejected.length,
165
- evidenceQuotes,
166
- estimatedInputChars: reviewText.length
167
- });
168
- if (actions.length > 0) agent.inject(createUserMessage({
169
- content: [{
170
- type: "text",
171
- text: `💾 Self-improvement review: ${actions.join(" · ")}`
172
- }],
173
- source: {
174
- kind: "plugin",
175
- plugin: "dsh-evolution-review",
176
- form: "notice",
177
- summary: "self-improvement review"
184
+ try {
185
+ const result = await run.result;
186
+ if (!result.structured) return true;
187
+ const childReads = run.localAgent ? collectReadSkillNames(run.localAgent.session) : /* @__PURE__ */ new Set();
188
+ const snapshot = policy();
189
+ const plan = result.structured;
190
+ const policyFingerprint = fingerprintPolicy(snapshot);
191
+ const validation = validateEvolutionPlan(plan, {
192
+ sessionSeq: session.seq - 1,
193
+ maxOpsPerPlan: snapshot?.maxOpsPerPlan ?? DEFAULT_MAX_OPS_PER_PLAN,
194
+ protectedSkillNames: new Set(snapshot?.protectedSkillNames ?? []),
195
+ maxMemoryChars: snapshot?.memoryChars ?? DEFAULT_MEMORY_CHAR_LIMIT,
196
+ maxUserChars: snapshot?.userChars ?? DEFAULT_USER_CHAR_LIMIT,
197
+ maxSkillContentChars: snapshot?.skillContentChars ?? DEFAULT_SKILL_CONTENT_CHARS
198
+ });
199
+ const acceptedSkillOps = validation.accepted.skillOps ?? [];
200
+ const skippedUnread = filterUnreadSkillOps(acceptedSkillOps, new Set([...collectReadSkillNames(session), ...childReads]));
201
+ const actions = await executePlan(validation.accepted);
202
+ const evidenceQuotes = [...validation.accepted.memoryOps ?? [], ...acceptedSkillOps].reduce((total, op) => total + (Array.isArray(op.evidence) ? op.evidence.length : 0), 0);
203
+ ctx.emit("evolution/plan-applied", {
204
+ sessionId: session.id,
205
+ planId: randomUUID(),
206
+ policyFingerprint,
207
+ memoryApplied: actions.filter((action) => action.startsWith("Memory")).length,
208
+ skillApplied: actions.filter((action) => action.startsWith("Skill ")).length,
209
+ rejectedOps: validation.rejected.length + skippedUnread,
210
+ evidenceQuotes,
211
+ estimatedInputChars: reviewText.length
212
+ });
213
+ if (actions.length > 0) agent.inject(createUserMessage({
214
+ content: [{
215
+ type: "text",
216
+ text: `💾 Self-improvement review: ${actions.join(" · ")}`
217
+ }],
218
+ source: {
219
+ kind: "plugin",
220
+ plugin: "dsh-evolution-review",
221
+ form: "notice",
222
+ summary: "self-improvement review"
223
+ }
224
+ }));
225
+ return true;
226
+ } finally {
227
+ try {
228
+ await run.dispose();
229
+ } catch (disposeError) {
230
+ ctx.logger.warn(`dsh-evolution-review: subagent dispose failed: ${disposeError instanceof Error ? disposeError.message : String(disposeError)}`);
178
231
  }
179
- }));
180
- return true;
181
- } catch {
232
+ }
233
+ } catch (error) {
234
+ ctx.logger.warn(`dsh-evolution-review: subagent review failed: ${error instanceof Error ? error.message : String(error)}`);
182
235
  return false;
183
236
  }
184
237
  }
185
- async function executePlan(plan, parent) {
238
+ async function executePlan(plan) {
186
239
  const memory = ctx.get("memory");
187
240
  const approval = ctx.get("evolutionApproval");
241
+ const origins = resolveOrigins(void 0, true);
188
242
  const actions = [];
189
243
  for (const op of plan.memoryOps ?? []) {
190
244
  if (!Array.isArray(op.evidence) || op.evidence.length === 0) continue;
@@ -202,46 +256,177 @@ function apply(ctx, rawConfig) {
202
256
  ...op,
203
257
  evidence: op.evidence
204
258
  };
205
- if ((approval ? await runApproved("skill", `skill ${op.action ?? "patch"} ${op.name}`, {
259
+ const runnerArgs = {
206
260
  operation: args,
207
- origin: "background_review"
208
- }, args) : await executeSkillTool(parent, args))?.ok) actions.push(`Skill ${op.name} ${op.action ?? "patch"}`);
261
+ origin: origins.library
262
+ };
263
+ if ((approval ? await runApproved("skill", `skill ${op.action ?? "patch"} ${op.name}`, runnerArgs, runnerArgs) : await executeSkillDirect(args))?.ok) actions.push(`Skill ${op.name} ${op.action ?? "patch"}`);
209
264
  }
210
265
  return actions;
211
266
  async function runApproved(kind, summary, stored, runnerArgs) {
212
267
  if (!approval) return void 0;
268
+ if (approval.isEnabled === true && !approval.hasRunner(kind)) {
269
+ ctx.logger.warn(`dsh-evolution-review: approval enabled but no replay runner registered for kind "${kind}" - skipping write (${summary})`);
270
+ return {
271
+ ok: false,
272
+ message: `Approval is enabled but no replay runner is registered for kind "${kind}"; write skipped (mount the tool that provides it, or disable approval).`
273
+ };
274
+ }
213
275
  const decision = await approval.request({
214
276
  kind,
215
277
  summary,
216
278
  args: stored,
217
- origin: "background_review"
279
+ origin: origins.approval
218
280
  });
219
281
  if (decision.action === "staged") return {
220
282
  ok: false,
221
283
  message: decision.message
222
284
  };
285
+ if (approval.isEnabled === false) return await runnerDirect(kind, runnerArgs);
223
286
  return await approval.run(kind, runnerArgs);
224
287
  }
225
- async function executeSkillTool(agent, args) {
226
- return (await ctx.tools.execute({
227
- callId: CallId(`evolution-${randomUUID()}`),
228
- name: "skill_manage",
229
- arguments: args,
230
- agent,
231
- signal: AbortSignal.timeout(config.executionTimeoutMs)
232
- })).isError ? {
288
+ /** Direct execution for the approval-disabled case (parallel to executeSkillDirect). */
289
+ async function runnerDirect(kind, args) {
290
+ if (kind === "memory") {
291
+ const memory = ctx.get("memory");
292
+ const op = args;
293
+ if (!memory?.applyBatch) return void 0;
294
+ return await memory.applyBatch(op.target === "user" ? "user" : "memory", [{
295
+ action: op.action ?? "add",
296
+ facts: op.facts ?? op.content,
297
+ old_text: op.old_text
298
+ }]);
299
+ }
300
+ const wrapped = args ?? {};
301
+ if (!wrapped.operation) return void 0;
302
+ return await executeSkillDirect(wrapped.operation);
303
+ }
304
+ /**
305
+ * Approval-disabled path: execute the skill op through SkillLibrary with an
306
+ * EXPLICIT background_review origin. Going through ctx.tools.execute would
307
+ * make tool-skill-manage infer origin from the parent agent's header
308
+ * (not 'subagent'), silently escaping the .hermes-managed marker and the
309
+ * pinned write guard.
310
+ */
311
+ async function executeSkillDirect(skillArgs) {
312
+ const io = ctx.get("evolutionIo");
313
+ if (!io) return {
233
314
  ok: false,
234
- message: "skill_manage execution failed"
235
- } : {
236
- ok: true,
237
- message: "skill_manage executed"
315
+ message: "evolution-io service not mounted"
316
+ };
317
+ const library = new SkillLibrary(void 0, evolutionIoAdapter(() => io.provider()), void 0, (event) => {
318
+ ctx.emit("evolution/skill-mutated", event);
319
+ });
320
+ const op = skillArgs;
321
+ const name = op.name ?? "";
322
+ const origin = origins.library;
323
+ if (op.action === "create") {
324
+ const created = await library.create(name, op.content ?? "", origin);
325
+ if (created.ok) await ctx.get("skillUsage")?.markAgentCreated?.(name);
326
+ return created;
327
+ }
328
+ if (op.action === "edit" || op.action === "update") return await library.update(name, op.content ?? "", origin);
329
+ if (op.action === "patch") return await library.patch(name, op.old_string ?? "", op.new_string ?? "", op.file_path ?? "", false, origin);
330
+ if (op.action === "delete") {
331
+ const into = (op.absorbed_into ?? "").trim();
332
+ if (!into || !await library.read(into)) return {
333
+ ok: false,
334
+ message: "delete requires an existing absorbed_into target"
335
+ };
336
+ const archived = await library.archive(name, { absorbedInto: into });
337
+ if (archived.ok) await ctx.get("skillUsage")?.markArchived?.(name);
338
+ return archived;
339
+ }
340
+ if (op.action === "write_file") return await library.writeSupportFile(name, op.file_path ?? "", op.file_content ?? op.content ?? "", origin);
341
+ if (op.action === "remove_file") return await library.removeSupportFile(name, op.file_path ?? "", origin);
342
+ if (op.action === "restructure") {
343
+ const moves = (op.restructure ?? []).filter((move) => move !== null).map((move) => ({
344
+ heading: move.heading ?? "",
345
+ toFile: move.to_file ?? ""
346
+ }));
347
+ return await library.restructure(name, moves, origin);
348
+ }
349
+ return {
350
+ ok: false,
351
+ message: `Unknown skill action "${op.action ?? ""}"`
238
352
  };
239
353
  }
240
354
  }
241
355
  ctx.effect(() => () => {
242
356
  turnStarts.clear();
357
+ cumulativeToolCalls.clear();
358
+ completionInjected.clear();
243
359
  }, "dsh-evolution-review.cleanup");
244
360
  }
361
+ /** Completion-channel decision: task finished normally AND the session is proven long. */
362
+ function shouldCompletionReview(reason, sessionToolCalls, minToolCalls) {
363
+ return reason?.kind === "completed" && sessionToolCalls >= minToolCalls;
364
+ }
365
+ /** Skill names this session loaded (read-before-write source for the background review). */
366
+ function collectReadSkillNames(session) {
367
+ const names = /* @__PURE__ */ new Set();
368
+ for (const event of session.events) {
369
+ if (event.type !== "tool/call") continue;
370
+ if (event.data.name !== "skill" && event.data.name !== "skill_load") continue;
371
+ const raw = event.data.arguments;
372
+ let parsed = {};
373
+ if (typeof raw === "string") try {
374
+ parsed = JSON.parse(raw);
375
+ } catch {
376
+ continue;
377
+ }
378
+ else parsed = raw ?? {};
379
+ const name = typeof parsed.name === "string" ? parsed.name : typeof parsed.skill === "string" ? parsed.skill : "";
380
+ if (name) names.add(name);
381
+ }
382
+ return names;
383
+ }
384
+ /** Map/set size that triggers a dead-session counter sweep (bounded, not a hard cap). */
385
+ const COUNTER_SWEEP_THRESHOLD = 128;
386
+ /**
387
+ * Remove every entry whose session is no longer live (rc.42 audit P1-10):
388
+ * `turnStarts` / `cumulativeToolCalls` / `completionInjected` are keyed by
389
+ * SessionId with no platform session-end hook to prune against, so they grew
390
+ * unbounded over a long-lived host. Works for maps and sets; returns the
391
+ * number of removed entries.
392
+ */
393
+ function sweepDeadSessionEntries(entries, isAlive) {
394
+ let removed = 0;
395
+ for (const id of [...entries.keys()]) if (!isAlive(id)) {
396
+ entries.delete(id);
397
+ removed += 1;
398
+ }
399
+ return removed;
400
+ }
401
+ /**
402
+ * Drop mutating ops whose target was not read this session, in place.
403
+ * Create is exempt (no read required to author a new skill). Covers the same
404
+ * mutating surface Hermes guards (edit/patch/write_file/remove_file), so a
405
+ * background review cannot blind-touch support files or edits of skills it
406
+ * never loaded. Returns the count of dropped ops so the plan event can report
407
+ * them as rejected.
408
+ */
409
+ function filterUnreadSkillOps(ops, readNames) {
410
+ const READ_REQUIRED = [
411
+ "edit",
412
+ "update",
413
+ "patch",
414
+ "delete",
415
+ "write_file",
416
+ "remove_file",
417
+ "restructure"
418
+ ];
419
+ let dropped = 0;
420
+ for (let index = ops.length - 1; index >= 0; index -= 1) {
421
+ const op = ops[index];
422
+ if (!op) continue;
423
+ if (op.action !== void 0 && READ_REQUIRED.includes(op.action) && op.name && !readNames.has(op.name)) {
424
+ ops.splice(index, 1);
425
+ dropped += 1;
426
+ }
427
+ }
428
+ return dropped;
429
+ }
245
430
  function fingerprintPolicy(snapshot) {
246
431
  try {
247
432
  return createHash("sha256").update(JSON.stringify(snapshot)).digest("hex").slice(0, 12);
@@ -256,13 +441,31 @@ function buildReviewRequest(session, kind, signal, maxMessages, maxMessageChars)
256
441
  const text = message.content.map((block) => block.type === "text" ? block.text : "").join(" ").trim();
257
442
  if (text) messages.push(`${message.role.toUpperCase()}: ${text.slice(0, maxMessageChars)}`);
258
443
  }
444
+ const toolLines = [];
445
+ const events = session.events;
446
+ for (let index = events.length - 1; index >= 0 && toolLines.length < 12; index -= 1) {
447
+ const event = events[index];
448
+ if (event?.type === "tool/call") {
449
+ const data = event.data;
450
+ const argsRaw = typeof data?.arguments === "string" ? data.arguments : JSON.stringify(data?.arguments ?? {});
451
+ toolLines.push(`[call] ${data?.name ?? "?"} ${argsRaw.slice(0, 500)}`);
452
+ } else if (event?.type === "tool/result") {
453
+ const data = event.data;
454
+ const output = typeof data?.output === "string" ? data.output : "";
455
+ const failure = data?.error ? " [ERROR]" : "";
456
+ toolLines.push(`[result]${failure} ${output.slice(0, 500)}`);
457
+ }
458
+ }
459
+ toolLines.reverse();
259
460
  return [
260
461
  `Review kind: ${kind}`,
261
462
  `Signals: ${signal.toolCalls} tool calls, ${signal.userChars} user chars, ${signal.assistantChars} assistant chars.`,
463
+ `Recent tool activity (${toolLines.length}):`,
464
+ ...toolLines,
262
465
  "Return ONLY the structured JSON plan. Evidence is mandatory for every op.",
263
466
  "",
264
467
  ...messages
265
468
  ].join("\n");
266
469
  }
267
470
  //#endregion
268
- export { Config, apply, inject, name };
471
+ export { Config, apply, filterUnreadSkillOps, inject, name, shouldCompletionReview, sweepDeadSessionEntries };
@@ -11,7 +11,11 @@ export interface Config {
11
11
  reviewMode?: string;
12
12
  memoryInterval?: number;
13
13
  skillInterval?: number;
14
- /** Tools the one-shot review subagent may use. Defaults include the Anchored Standard discovery pair. */
14
+ /**
15
+ * Tools the one-shot review subagent may use. Only actually-existing tools
16
+ * may be listed (the DSH tool catalog has `skill`; the `skill_search` /
17
+ * `skill_load` discovery pair does not exist on this platform).
18
+ */
15
19
  reviewToolAllow?: string[];
16
20
  reviewTimeoutMs?: number;
17
21
  executionTimeoutMs?: number;
@@ -20,7 +24,35 @@ export interface Config {
20
24
  reviewMaxDepth?: number;
21
25
  /** LLM provider for review subagents. Omit to inherit the deployment default route. */
22
26
  reviewProvider?: string;
27
+ /** Skill-review trigger: cadence (interval) | completion (once after a proven-long task) | both. */
28
+ skillReviewTrigger?: string;
29
+ /** Cumulative session tool calls before a session counts as proven-long for the completion channel. */
30
+ skillReviewCompletionMinToolCalls?: number;
23
31
  }
24
32
  export declare const Config: z<Config>;
25
33
  export declare function apply(ctx: Context, rawConfig: Config): void;
34
+ /** Completion-channel decision: task finished normally AND the session is proven long. */
35
+ export declare function shouldCompletionReview(reason: {
36
+ kind?: string;
37
+ } | undefined, sessionToolCalls: number, minToolCalls: number): boolean;
38
+ /**
39
+ * Remove every entry whose session is no longer live (rc.42 audit P1-10):
40
+ * `turnStarts` / `cumulativeToolCalls` / `completionInjected` are keyed by
41
+ * SessionId with no platform session-end hook to prune against, so they grew
42
+ * unbounded over a long-lived host. Works for maps and sets; returns the
43
+ * number of removed entries.
44
+ */
45
+ export declare function sweepDeadSessionEntries<K>(entries: Map<K, unknown> | Set<K>, isAlive: (id: K) => boolean): number;
46
+ /**
47
+ * Drop mutating ops whose target was not read this session, in place.
48
+ * Create is exempt (no read required to author a new skill). Covers the same
49
+ * mutating surface Hermes guards (edit/patch/write_file/remove_file), so a
50
+ * background review cannot blind-touch support files or edits of skills it
51
+ * never loaded. Returns the count of dropped ops so the plan event can report
52
+ * them as rejected.
53
+ */
54
+ export declare function filterUnreadSkillOps(ops: Array<{
55
+ action?: string;
56
+ name?: string;
57
+ }>, readNames: ReadonlySet<string>): number;
26
58
  //# sourceMappingURL=index.d.ts.map
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@lmzhen/dsh-evolution-review",
3
3
  "description": "Background review orchestration (community build)",
4
- "version": "0.1.0-rc.9",
4
+ "version": "0.2.0-rc.1",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -33,27 +33,29 @@
33
33
  "license": "MIT",
34
34
  "dependencies": {
35
35
  "@deepseek-ai/schemastery": "^3.18.1",
36
- "@lmzhen/dsh-evolution-core": "^0.1.0-rc.9",
37
- "@lmzhen/dsh-evolution-plan-validator": "^0.1.0-rc.9"
36
+ "@lmzhen/dsh-evolution-core": "^0.2.0-rc.1",
37
+ "@lmzhen/dsh-evolution-plan-validator": "^0.2.0-rc.1"
38
38
  },
39
39
  "peerDependencies": {
40
40
  "@deepseek-ai/cordis": "^4.0.1",
41
- "@deepseek-ai/dsh-agent": "^0.1.0-rc.6",
42
- "@deepseek-ai/dsh-invariants": "^0.1.0-rc.6",
43
- "@deepseek-ai/dsh-llm": "^0.1.0-rc.6",
44
- "@deepseek-ai/dsh-session": "^0.1.0-rc.6",
45
- "@deepseek-ai/dsh-tools": "^0.1.0-rc.6",
46
- "@lmzhen/dsh-evolution-state": "^0.1.0-rc.9"
41
+ "@deepseek-ai/dsh-agent": "^0.1.1-rc.2",
42
+ "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2",
43
+ "@deepseek-ai/dsh-llm": "^0.1.1-rc.2",
44
+ "@deepseek-ai/dsh-session": "^0.1.1-rc.2",
45
+ "@deepseek-ai/dsh-tools": "^0.1.1-rc.2",
46
+ "@lmzhen/dsh-evolution-state": "^0.2.0-rc.1"
47
47
  },
48
48
  "devDependencies": {
49
- "@deepseek-ai/dsh-agent": "^0.1.0-rc.6",
50
- "@deepseek-ai/dsh-agent-loop-testkit": "^0.1.0-rc.6",
51
- "@deepseek-ai/dsh-invariants": "^0.1.0-rc.6",
52
- "@deepseek-ai/dsh-llm": "^0.1.0-rc.6",
53
- "@deepseek-ai/dsh-session": "^0.1.0-rc.6",
54
- "@deepseek-ai/dsh-tools": "^0.1.0-rc.6",
55
- "@lmzhen/dsh-evolution-core": "^0.1.0-rc.9",
56
- "@lmzhen/dsh-evolution-plan-validator": "^0.1.0-rc.9",
57
- "@lmzhen/dsh-evolution-state": "^0.1.0-rc.9"
49
+ "@deepseek-ai/dsh-agent": "^0.1.1-rc.2",
50
+ "@deepseek-ai/dsh-agent-loop-testkit": "^0.1.1-rc.2",
51
+ "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2",
52
+ "@deepseek-ai/dsh-llm": "^0.1.1-rc.2",
53
+ "@deepseek-ai/dsh-session": "^0.1.1-rc.2",
54
+ "@deepseek-ai/dsh-session-persistence": "^0.1.1-rc.2",
55
+ "@deepseek-ai/dsh-session-persistence-jsonl": "^0.1.1-rc.2",
56
+ "@deepseek-ai/dsh-tools": "^0.1.1-rc.2",
57
+ "@lmzhen/dsh-evolution-core": "^0.2.0-rc.1",
58
+ "@lmzhen/dsh-evolution-plan-validator": "^0.2.0-rc.1",
59
+ "@lmzhen/dsh-evolution-state": "^0.2.0-rc.1"
58
60
  }
59
61
  }