@lmzhen/dsh-evolution-maintenance 0.3.51 → 0.3.52

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/README.md CHANGED
@@ -18,7 +18,11 @@ package owns assembly and rendering only.
18
18
  `evolution-maintenance-tools`) is available to maintenance subagents only
19
19
  through the orchestrate `toolFilter` allow-list; it is globally visible to
20
20
  every session as a read-only query (same exposure tier as the `skill`
21
- tool — never a write path).
22
- - The model-visible template (`MAINTAIN_PROMPT`) ships in `evolution-core`
21
+ tool — never a write path). V10 (F-02): orchestrate soft-probes the tools
22
+ registry before spawning and degrades the filter to `skill`-only (declared
23
+ in the subagent prompt) when the tool row is not mounted, instead of failing
24
+ the spawn; an explicit `toolAllow` option bypasses the probe.
25
+ - The model-visible template (`MAINTAIN_PROMPT`) and the subagent output
26
+ instruction (`MAINTAIN_OUTPUT_INSTRUCTION`, V10 F-16) ship in `evolution-core`
23
27
  `PROMPT_BUNDLE`; the joint-signature mismatch protocol is honored by
24
28
  `renderFacts` callers, not by this package alone.
@@ -132,7 +132,7 @@ async function buildEnrichment(ctx, library) {
132
132
  for (const entry of await library.list()) {
133
133
  if (entry.protectedBy) protectedMap.set(entry.name, entry.protectedBy);
134
134
  const body = await library.read(entry.name);
135
- if (body === null) continue;
135
+ if (body === void 0 || body === null) continue;
136
136
  const parsed = parseFrontmatter(body);
137
137
  if (frontmatterYamlUnsafeValues(body).length > 0) catalogInvalid.set(entry.name, true);
138
138
  const description = parsed?.frontmatter.description;
package/lib/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import { i as snapshotFromLibrary, n as PROBE_SIGNALS, r as computeProbe, t as buildEnrichment } from "./enrichment-Di8Y4kev.js";
2
- import { AUTHORING_DESCRIPTION_BAR, DEFAULT_HEALTH_THRESHOLDS, DRIFT_MAX_LINE_CHARS, DRIFT_SIGNALS_VERSION, DRIFT_SIGNAL_NOUNS, LOW_QUALITY_THRESHOLD, MAINTAIN_PROMPT, MIN_STAMP_BODY_CHARS, PROMPT_BUNDLE, PROMPT_BUNDLE_ID, computeDriftSignals, findDriftSignal, redactSecrets, verifyPromptBundle } from "@lmzhen/dsh-evolution-core";
1
+ import { i as snapshotFromLibrary, n as PROBE_SIGNALS, r as computeProbe, t as buildEnrichment } from "./enrichment-B7tEHkM5.js";
2
+ import { AUTHORING_DESCRIPTION_BAR, DEFAULT_HEALTH_THRESHOLDS, DRIFT_MAX_LINE_CHARS, DRIFT_SIGNALS_VERSION, DRIFT_SIGNAL_NOUNS, LOW_QUALITY_THRESHOLD, MAINTAIN_OUTPUT_INSTRUCTION, MAINTAIN_PROMPT, MIN_STAMP_BODY_CHARS, PROMPT_BUNDLE, PROMPT_BUNDLE_ID, computeDriftSignals, findDriftSignal, redactSecrets, verifyPromptBundle } from "@lmzhen/dsh-evolution-core";
3
3
  import { createHash, randomUUID } from "node:crypto";
4
4
  //#region lib/types/render-facts.js
5
5
  /**
@@ -321,9 +321,12 @@ function buildMaintainFacts(snapshots, usageObserved, redact) {
321
321
  };
322
322
  }
323
323
  /** V6-38 (0.3.36): a field value carrying its own standalone `Notes:` line is
324
- * indistinguishable from the plan's section header in the RENDERED text —
325
- * `beforeNotesHeader` cuts at the first standalone `Notes:` and undercounts
326
- * recommendations. Mark such lines so they can never read as the header. */
324
+ * indistinguishable from the plan's section header in the RENDERED text, which
325
+ * made the rendered plan read as if it ended early. Mark such lines so they
326
+ * can never read as the header. (The former text-based recommendation counter
327
+ * that motivated this marker is gone — V10-09/F-05 moved the count into
328
+ * `MaintainOutcome.recommendationCount` — but the rendered text stays
329
+ * unambiguous for the human reader.) */
327
330
  function sanitizeField(value) {
328
331
  return value.split("\n").map((line) => line === "Notes:" ? "> Notes: (inside the field above)" : line).join("\n");
329
332
  }
@@ -361,6 +364,7 @@ async function runMaintain(runtime, options = {}) {
361
364
  try {
362
365
  if (!verifyPromptBundle(PROMPT_BUNDLE)) return {
363
366
  ok: false,
367
+ recommendationCount: 0,
364
368
  error: "dsh-evolution prompt bundle integrity check failed; refusing to run maintain"
365
369
  };
366
370
  const usageObserved = options.usageObserved ? options.usageObserved() : void 0;
@@ -374,18 +378,23 @@ async function runMaintain(runtime, options = {}) {
374
378
  });
375
379
  if (snapshots.length === 0) return {
376
380
  ok: true,
381
+ recommendationCount: 0,
377
382
  runId: randomUUID(),
378
383
  verdict: "no_issues",
379
384
  text: "Maintenance scan: empty skill library. Nothing to do."
380
385
  };
381
386
  const { facts, report, signalsVersion, signature } = buildMaintainFacts(snapshots, usageObserved, options.redact);
382
387
  const template = renderMaintainTemplate(MAINTAIN_PROMPT, PROMPT_BUNDLE_ID, signalsVersion, signature);
383
- const prompt = `${facts}
388
+ const probeRegistered = runtime.tools?.get("maintenance_probe") != null;
389
+ const probeDegraded = options.toolAllow === void 0 && !probeRegistered;
390
+ const toolAllow = options.toolAllow ?? (probeRegistered ? ["skill", "maintenance_probe"] : ["skill"]);
391
+ const prompt = `${facts}${probeDegraded ? "\n\n[tools] maintenance_probe is not registered in this session — the scan degrades to the `skill` tool only; the facts block above remains the complete evidence base. Do not call maintenance_probe." : ""}
384
392
 
385
- 按模板契约输出 JSON 维护计划(verdict/plan/notes);除 skill 工具与维护模板外你无其他工具。`;
393
+ ${MAINTAIN_OUTPUT_INSTRUCTION}`;
386
394
  const timeoutMs = options.timeoutMs ?? 6e5;
387
395
  if (!Number.isSafeInteger(timeoutMs) || timeoutMs <= 0 || timeoutMs > 4294967295) return {
388
396
  ok: false,
397
+ recommendationCount: 0,
389
398
  error: `maintain: --timeout must be a positive integer in ms, at most 4294967295; got ${String(timeoutMs)}`
390
399
  };
391
400
  const signal = AbortSignal.timeout(timeoutMs);
@@ -403,7 +412,7 @@ async function runMaintain(runtime, options = {}) {
403
412
  maxDepth: typeof options.maxDepth === "number" && Number.isFinite(options.maxDepth) && options.maxDepth >= 1 ? options.maxDepth : 1,
404
413
  agentOptions,
405
414
  persona: template,
406
- toolFilter: { allow: [...options.toolAllow ?? ["skill", "maintenance_probe"]] },
415
+ toolFilter: { allow: [...toolAllow] },
407
416
  outputSchema: {
408
417
  type: "object",
409
418
  additionalProperties: false,
@@ -481,16 +490,19 @@ async function runMaintain(runtime, options = {}) {
481
490
  if (raw === void 0) {
482
491
  if (runResult?.stopReason === "aborted") return {
483
492
  ok: false,
493
+ recommendationCount: 0,
484
494
  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."
485
495
  };
486
496
  return {
487
497
  ok: false,
498
+ recommendationCount: 0,
488
499
  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."
489
500
  };
490
501
  }
491
502
  const validated = validateAndNormalizeMaintainPlan(raw, report, existingSignalIds(report));
492
503
  if (!validated.ok) return {
493
504
  ok: false,
505
+ recommendationCount: 0,
494
506
  error: `Maintain plan rejected by validator: ${validated.errors.slice(0, 5).join("; ")}`
495
507
  };
496
508
  const runId = randomUUID();
@@ -499,6 +511,7 @@ async function runMaintain(runtime, options = {}) {
499
511
  runId,
500
512
  verdict: validated.plan.verdict,
501
513
  forcedHuman: validated.forcedHuman,
514
+ recommendationCount: validated.plan.plan.length,
502
515
  text: formatPlan(validated, runId)
503
516
  };
504
517
  } finally {
@@ -513,6 +526,7 @@ async function runMaintain(runtime, options = {}) {
513
526
  const message = error instanceof Error ? error.message : String(error);
514
527
  return {
515
528
  ok: false,
529
+ recommendationCount: 0,
516
530
  error: abortSignal?.aborted === true || name === "AbortError" || message === "This operation was aborted" ? "Maintenance scan was aborted (cancellation or timeout) before a plan was produced — retry, or raise the timeout (evolution-commands maintainTimeoutMs) on a slow/large library." : `Maintenance scan failed: ${message}`
517
531
  };
518
532
  }
package/lib/tools.js CHANGED
@@ -1,4 +1,4 @@
1
- import { i as snapshotFromLibrary, n as PROBE_SIGNALS, r as computeProbe, t as buildEnrichment } from "./enrichment-Di8Y4kev.js";
1
+ import { i as snapshotFromLibrary, n as PROBE_SIGNALS, r as computeProbe, t as buildEnrichment } from "./enrichment-B7tEHkM5.js";
2
2
  import { SkillLibrary, redactSecrets, resolveSkillsRoot } from "@lmzhen/dsh-evolution-core";
3
3
  import { defineTool } from "@deepseek-ai/dsh-tools";
4
4
  //#region lib/types/tools.js
@@ -34,6 +34,13 @@ export interface MaintainRuntime {
34
34
  curatorModel?: string | undefined;
35
35
  } | undefined;
36
36
  } | undefined;
37
+ /** F-02: tools-registry soft probe, wired from the host context
38
+ * (`ctx.get('tools')`) with the platform's `get(name)` accessor shape.
39
+ * Optional — an absent accessor means the tools service is not mounted at
40
+ * all, so no tool beyond `skill` can be assumed registered. */
41
+ tools?: {
42
+ get(name: string): unknown;
43
+ } | undefined;
37
44
  }
38
45
  export interface MaintainOptions {
39
46
  timeoutMs?: number;
@@ -56,6 +63,11 @@ export interface MaintainOutcome {
56
63
  verdict?: 'issues' | 'no_issues' | undefined;
57
64
  text?: string | undefined;
58
65
  forcedHuman?: string[] | undefined;
66
+ /** V10-09 (F-05): structured count of recommendations in the VALIDATED plan
67
+ * (plan entries, never notes). Consumers must read this field instead of
68
+ * parsing the rendered `text` — the text is display-only. 0 on every
69
+ * non-success outcome. */
70
+ recommendationCount: number;
59
71
  }
60
72
  /** Render template-M placeholders from the signal vocabulary (011 single-source rule). */
61
73
  export declare function renderMaintainTemplate(template: string, bundleVersion: string, signalsVersion: string, signature: string): string;
package/package.json CHANGED
@@ -1,14 +1,14 @@
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.51",
4
+ "version": "0.3.52",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
8
8
  "repository": {
9
9
  "type": "git",
10
10
  "url": "git+https://github.com/lmzhen/dsh-evolution.git",
11
- "directory": "packages/dsh-evolution-maintenance"
11
+ "directory": "packages/evolution-maintenance"
12
12
  },
13
13
  "type": "module",
14
14
  "main": "lib/index.js",
@@ -34,7 +34,7 @@
34
34
  ],
35
35
  "license": "MIT",
36
36
  "dependencies": {
37
- "@lmzhen/dsh-evolution-core": "^0.3.51"
37
+ "@lmzhen/dsh-evolution-core": "^0.3.52"
38
38
  },
39
39
  "peerDependencies": {
40
40
  "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2",