@lmzhen/dsh-evolution-maintenance 0.3.23 → 0.3.25

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
@@ -110,7 +110,11 @@ function validateAndNormalizeMaintainPlan(raw, report, validSignals) {
110
110
  const errors = [];
111
111
  const forcedHuman = [];
112
112
  const protectedNames = /* @__PURE__ */ new Set();
113
- for (const skill of report.skills) if (skill.protected) protectedNames.add(skill.name);
113
+ const factNames = /* @__PURE__ */ new Set();
114
+ for (const skill of report.skills) {
115
+ if (skill.protected) protectedNames.add(skill.name);
116
+ factNames.add(skill.name);
117
+ }
114
118
  if (!isRecord(raw)) return {
115
119
  ok: false,
116
120
  errors: ["plan root: must be an object"],
@@ -137,7 +141,10 @@ function validateAndNormalizeMaintainPlan(raw, report, validSignals) {
137
141
  }
138
142
  if (typeof item.kind !== "string" || !KINDS.has(item.kind)) errors.push(`${path}.kind: invalid`);
139
143
  if (!Array.isArray(item.names) || item.names.length === 0 || !item.names.every(isNonEmptyString)) errors.push(`${path}.names: non-empty string array required`);
140
- else if (item.names.some((nm) => protectedNames.has(nm))) {
144
+ else if (item.names.some((nm) => !factNames.has(nm))) {
145
+ const missing = item.names.find((nm) => !factNames.has(nm));
146
+ errors.push(`${path}.names: references skill "${String(missing)}" not in the facts report — names must come from the scanned skill set`);
147
+ } else if (item.names.some((nm) => protectedNames.has(nm))) {
141
148
  const hit = item.names.find((nm) => protectedNames.has(nm));
142
149
  errors.push(`${path}.names: references protected skill "${String(hit)}" — §7 forbids recommendations for bundled/hub-installed/pinned skills`);
143
150
  }
@@ -348,7 +355,7 @@ async function runMaintain(runtime, options = {}) {
348
355
  abortSignal = signal;
349
356
  const agentOptions = { model: options.model ?? runtime.evolutionPolicy?.get()?.curatorModel ?? "deepseek-v4-pro" };
350
357
  if (options.provider) agentOptions.provider = options.provider;
351
- const runResult = await (await runtime.subagents.start("spawn", {
358
+ const run = await runtime.subagents.start("spawn", {
352
359
  label: "dsh-evolution-maintain",
353
360
  prompt: [{
354
361
  type: "text",
@@ -430,31 +437,40 @@ async function runMaintain(runtime, options = {}) {
430
437
  }
431
438
  }
432
439
  }
433
- })).result;
434
- const raw = runResult?.structured;
435
- if (raw === void 0) {
436
- if (runResult?.stopReason === "aborted") return {
440
+ });
441
+ try {
442
+ const runResult = await run.result;
443
+ const raw = runResult?.structured;
444
+ if (raw === void 0) {
445
+ if (runResult?.stopReason === "aborted") return {
446
+ ok: false,
447
+ error: "Maintenance scan was aborted (the run was cancelled before the subagent produced a plan) — retry when the session is idle; concurrent re-submission cancels the previous scan."
448
+ };
449
+ return {
450
+ ok: false,
451
+ error: "Maintain subagent returned no structured plan (the model did not emit the structured plan, or the run ended without one) — retry; if it repeats, raise --timeout or check the model output shape."
452
+ };
453
+ }
454
+ const validated = validateAndNormalizeMaintainPlan(raw, report, existingSignalIds(report));
455
+ if (!validated.ok) return {
437
456
  ok: false,
438
- error: "Maintenance scan was aborted (the run was cancelled before the subagent produced a plan) — retry when the session is idle; concurrent re-submission cancels the previous scan."
457
+ error: `Maintain plan rejected by validator: ${validated.errors.slice(0, 5).join("; ")}`
439
458
  };
459
+ const runId = randomUUID();
440
460
  return {
441
- ok: false,
442
- error: "Maintain subagent returned no structured plan (the model did not emit the structured plan, or the run ended without one) — retry; if it repeats, raise --timeout or check the model output shape."
461
+ ok: true,
462
+ runId,
463
+ verdict: validated.plan.verdict,
464
+ forcedHuman: validated.forcedHuman,
465
+ text: formatPlan(validated, runId)
443
466
  };
467
+ } finally {
468
+ try {
469
+ await run.dispose?.();
470
+ } catch (disposeError) {
471
+ runtime.logger?.warn(`dsh-evolution-maintenance: subagent dispose failed: ${disposeError instanceof Error ? disposeError.message : String(disposeError)}`);
472
+ }
444
473
  }
445
- const validated = validateAndNormalizeMaintainPlan(raw, report, existingSignalIds(report));
446
- if (!validated.ok) return {
447
- ok: false,
448
- error: `Maintain plan rejected by validator: ${validated.errors.slice(0, 5).join("; ")}`
449
- };
450
- const runId = randomUUID();
451
- return {
452
- ok: true,
453
- runId,
454
- verdict: validated.plan.verdict,
455
- forcedHuman: validated.forcedHuman,
456
- text: formatPlan(validated, runId)
457
- };
458
474
  } catch (error) {
459
475
  const name = typeof error === "object" && error !== null ? error.name : void 0;
460
476
  const message = error instanceof Error ? error.message : String(error);
package/lib/tools.js CHANGED
@@ -16,7 +16,8 @@ const name = "evolution-maintenance-tools";
16
16
  function apply(ctx, rawConfig = {}) {
17
17
  const config = rawConfig;
18
18
  ctx.inject(["tools"], (toolCtx) => {
19
- toolCtx.get("tools").register(defineTool({
19
+ const tools = toolCtx.get("tools");
20
+ toolCtx.effect(() => tools.register(defineTool({
20
21
  name: "maintenance_probe",
21
22
  description: "Read-only deep-dive into maintenance scan signals: library-level group/cluster membership or per-skill detail (line numbers, pointer gaps, narrow shapes, stamp samples). Machine-derived from the same calculators as the facts block — never introduces new evidence ids. Output is JSON detail.",
22
23
  parameters: {
@@ -74,7 +75,7 @@ function apply(ctx, rawConfig = {}) {
74
75
  detail: redacted.split("\n")
75
76
  };
76
77
  }
77
- }));
78
+ })), "evolution-maintenance.tools");
78
79
  });
79
80
  }
80
81
  //#endregion
@@ -9,14 +9,22 @@ import { type SkillLibraryLike } from './drift-scan.ts';
9
9
  export interface MaintainRuntime {
10
10
  /** SkillLibrary-like reader for snapshot assembly. */
11
11
  library: SkillLibraryLike;
12
- /** Subagent spawner (platform `subagents` service — minimal shape for injection). */
12
+ /** Subagent spawner (platform `subagents` service — minimal shape for injection).
13
+ * `dispose` is optional: not every provider exposes it, so the run is only
14
+ * disposed when the platform provides the handle (rc.42 P1-3 parity with review). */
13
15
  subagents: {
14
16
  start(kind: string, options: unknown): Promise<{
15
17
  result: Promise<unknown>;
18
+ dispose?: () => Promise<void>;
16
19
  }>;
17
20
  };
18
21
  /** Parent agent/session handle passed through to the subagent, when available. */
19
22
  parent?: unknown;
23
+ /** Optional logger for dispose-failure traces; when absent the failure is
24
+ * swallowed without surfacing (the orchestrator is a pure function). */
25
+ logger?: {
26
+ warn(message: string): void;
27
+ } | undefined;
20
28
  /** Evolution-policy reader for model routing (same source as the curator:
21
29
  * `policy.get().curatorModel`). Optional — a missing service falls back to
22
30
  * the default model (E-55). `get()` may RESOLVE to undefined (a soft probe
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@lmzhen/dsh-evolution-maintenance",
3
3
  "description": "Deterministic maintenance-scan surface: skill-library snapshot assembly, drift signals and mechanical-facts rendering (design 011) (community build)",
4
- "version": "0.3.23",
4
+ "version": "0.3.25",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -34,7 +34,7 @@
34
34
  ],
35
35
  "license": "MIT",
36
36
  "dependencies": {
37
- "@lmzhen/dsh-evolution-core": "^0.3.23"
37
+ "@lmzhen/dsh-evolution-core": "^0.3.25"
38
38
  },
39
39
  "peerDependencies": {
40
40
  "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2",