@lmzhen/dsh-evolution-maintenance 0.3.73 → 0.3.76

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
@@ -26,3 +26,5 @@ package owns assembly and rendering only.
26
26
  instruction (`MAINTAIN_OUTPUT_INSTRUCTION`, V10 F-16) ship in `evolution-core`
27
27
  `PROMPT_BUNDLE`; the joint-signature mismatch protocol is honored by
28
28
  `renderFacts` callers, not by this package alone.
29
+
30
+ **Runtime invariant:** No companion is published. The platform auto-assembles nothing and the family mounts no `<pkg>/invariant` cordis row, so a companion here would never execute (v37 S2.1 / I-3).
@@ -141,8 +141,14 @@ async function buildEnrichment(ctx, library) {
141
141
  for (const entry of await library.list()) {
142
142
  if (entry.protectionUnknown) protectedMap.set(entry.name, "unknown");
143
143
  else if (entry.protectedBy) protectedMap.set(entry.name, entry.protectedBy);
144
- const body = await library.read(entry.name);
145
- if (body === void 0 || body === null) continue;
144
+ let body;
145
+ try {
146
+ body = await library.read(entry.name);
147
+ } catch (error) {
148
+ ctx.logger.warn(`evolution-maintenance: enrichment skipped unreadable skill "${entry.name}": ${error instanceof Error ? error.message : String(error)}`);
149
+ continue;
150
+ }
151
+ if (typeof body !== "string") continue;
146
152
  const parsed = parseFrontmatter(body);
147
153
  if (frontmatterCatalogInvalid(body)) catalogInvalid.set(entry.name, true);
148
154
  const description = parsed?.frontmatter.description;
package/lib/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { i as snapshotFromLibrary, n as PROBE_SIGNALS, r as computeProbe, t as buildEnrichment } from "./enrichment-2NEuOAZU.js";
1
+ import { i as snapshotFromLibrary, n as PROBE_SIGNALS, r as computeProbe, t as buildEnrichment } from "./enrichment-BDlI9z8k.js";
2
2
  import { AUTHORING_DESCRIPTION_BAR, DEFAULT_CURATOR_MODEL, DEFAULT_HEALTH_THRESHOLDS, DRIFT_MAX_LINE_CHARS, DRIFT_SIGNALS_VERSION, DRIFT_SIGNAL_NOUNS, LOW_QUALITY_THRESHOLD, MAINTAIN_OUTPUT_INSTRUCTION, MAINTAIN_PROMPT, MAX_TIMER_DELAY_MS, 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
@@ -8,18 +8,19 @@ import { createHash, randomUUID } from "node:crypto";
8
8
  * One canonical block: opening tag with version + joint signature, one
9
9
  * `[FACT]`/`[UNKNOWN]` line per signal, closing tag. The template side
10
10
  * renders its own `<MAINTAIN_PROMPT>` header with the same signature, so
11
- * the model can compare the two heads (011 mismatch protocol). All rendered
12
- * value/detail text passes through the optional redactor before it leaves
13
- * the session.
11
+ * the model can compare the two heads (011 mismatch protocol). Every rendered
12
+ * field (value/detail/threshold/skill name) passes through the optional
13
+ * redactor and the delimiter/newline escaping before it leaves the session.
14
14
  */
15
15
  function renderFacts(report, options) {
16
16
  const redact = options.redact ?? redactSecrets;
17
+ const sanitize = sanitizerFor(redact);
17
18
  const lines = [];
18
19
  lines.push(`<<<MECHANICAL_FACTS v=${options.signalsVersion} sig=${options.signature}>>>`);
19
20
  for (const signal of report.library) lines.push(...renderSignal(signal, redact));
20
21
  for (const skill of report.skills) {
21
22
  const meta = [`protected=${skill.protected ?? "none"}`, `catalog=${skill.catalogInvalid === true ? "yaml-invalid" : "visible"}`];
22
- lines.push(`# skill=${skill.name} (${meta.join(" ")})`);
23
+ lines.push(`# skill=${sanitize(skill.name)} (${meta.join(" ")})`);
23
24
  for (const signal of skill.signals) lines.push(...renderSignal(signal, redact));
24
25
  }
25
26
  lines.push("<<<END FACTS>>>");
@@ -27,17 +28,23 @@ function renderFacts(report, options) {
27
28
  }
28
29
  function renderSignal(signal, redact) {
29
30
  const prefix = signal.verdict === "unknown" ? "[UNKNOWN]" : "[FACT]";
30
- const sanitize = (text) => redact(text).replaceAll("<<<END FACTS>>>", "<<<END FACTS (escaped)>>>").replaceAll("\n", " ").replaceAll("\r", " ");
31
+ const sanitize = sanitizerFor(redact);
31
32
  const value = sanitize(signal.value);
32
33
  const parts = [
33
34
  `${prefix} signal=${signal.id}`,
34
35
  `value=${value}`,
35
36
  `verdict=${signal.verdict}`
36
37
  ];
37
- if (signal.threshold !== void 0) parts.push(`threshold=${signal.threshold}`);
38
+ if (signal.threshold !== void 0) parts.push(`threshold=${sanitize(signal.threshold)}`);
38
39
  if (signal.detail !== void 0) parts.push(`detail=${sanitize(signal.detail)}`);
39
40
  return [parts.join(" ")];
40
41
  }
42
+ /** A2-13: a value carrying the closing delimiter (or a raw newline) could end
43
+ * the facts block early and inject instructions, so EVERY piece of rendered
44
+ * model-facing text — value, detail, threshold, skill name — passes here. */
45
+ function sanitizerFor(redact) {
46
+ return (text) => redact(text).replaceAll("<<<END FACTS>>>", "<<<END FACTS (escaped)>>>").replaceAll("\n", " ").replaceAll("\r", " ");
47
+ }
41
48
  //#endregion
42
49
  //#region lib/types/validate-plan.js
43
50
  /**
@@ -346,21 +353,23 @@ function buildMaintainFacts(snapshots, usageObserved, redact) {
346
353
  signature
347
354
  };
348
355
  }
349
- /** V6-38 (0.3.36): a field value carrying its own standalone `Notes:` line is
350
- * indistinguishable from the plan's section header in the RENDERED text, which
351
- * made the rendered plan read as if it ended early. Mark such lines so they
352
- * can never read as the header. (The former text-based recommendation counter
353
- * that motivated this marker is gone — V10-09/F-05 moved the count into
354
- * `MaintainOutcome.recommendationCount` — but the rendered text stays
355
- * unambiguous for the human reader.) */
356
+ /** V6-38: a field value carrying its own standalone `Notes:` line is
357
+ * indistinguishable from the plan's section header in the RENDERED text, and a
358
+ * line shaped like a rendered recommendation (`- [kind] …`) reads as a plan
359
+ * entry that the validated plan does not contain. Mark both while leaving the
360
+ * rest of the field untouched. Notes/undo/override are free text and need this
361
+ * exactly as much as finding/recommendation do. */
356
362
  function sanitizeField(value) {
357
- return value.split("\n").map((line) => line === "Notes:" ? "> Notes: (inside the field above)" : line).join("\n");
363
+ return value.split("\n").map((line) => {
364
+ if (line === "Notes:") return "> Notes: (inside the field above)";
365
+ return /^\s*- \[/.test(line) ? `> (inside the field above) ${line}` : line;
366
+ }).join("\n");
358
367
  }
359
368
  function formatPlan(validated, runId) {
360
369
  const { plan, forcedHuman } = validated;
361
370
  const lines = [];
362
371
  lines.push(`Maintenance scan ${runId}: verdict=${plan.verdict} (${plan.plan.length} recommendations, ${plan.notes.length} notes)`);
363
- const noteLines = plan.notes.length === 0 ? [] : ["Notes:", ...plan.notes.map((note) => `- ${note}`)];
372
+ const noteLines = plan.notes.length === 0 ? [] : ["Notes:", ...plan.notes.map((note) => `- ${sanitizeField(note)}`)];
364
373
  if (plan.verdict === "no_issues") {
365
374
  lines.push("No drift issues detected. Nothing to do.");
366
375
  lines.push(...noteLines);
@@ -376,8 +385,8 @@ function formatPlan(validated, runId) {
376
385
  lines.push(`- [${item.kind}] ${item.names.join(", ")} · rule=${item.rule} · ${flags.join(" ")}`);
377
386
  lines.push(` finding: ${sanitizeField(item.finding)}`);
378
387
  lines.push(` action: ${sanitizeField(item.recommendation)}`);
379
- if (item.undo_path && item.undo_path !== "n/a") lines.push(` undo: ${item.undo_path}`);
380
- if (item.is_override && item.override_reason) lines.push(` override: ${item.override_reason}`);
388
+ if (item.undo_path && item.undo_path !== "n/a") lines.push(` undo: ${sanitizeField(item.undo_path)}`);
389
+ if (item.is_override && item.override_reason) lines.push(` override: ${sanitizeField(item.override_reason)}`);
381
390
  }
382
391
  if (forcedHuman.length > 0) lines.push(`(quality_low gate: forced needs_human for ${[...new Set(forcedHuman)].join(", ")})`);
383
392
  lines.push(...noteLines);
@@ -528,9 +537,9 @@ ${MAINTAIN_OUTPUT_INSTRUCTION}`;
528
537
  });
529
538
  try {
530
539
  const runResult = await run.result;
531
- const raw = runResult?.structured;
540
+ const raw = runResult.structured;
532
541
  if (raw === void 0) {
533
- if (runResult?.stopReason === "aborted") return {
542
+ if (runResult.stopReason === "aborted") return {
534
543
  ok: false,
535
544
  recommendationCount: 0,
536
545
  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."
@@ -558,7 +567,7 @@ ${MAINTAIN_OUTPUT_INSTRUCTION}`;
558
567
  };
559
568
  } finally {
560
569
  try {
561
- await run.dispose?.();
570
+ await run.dispose();
562
571
  } catch (disposeError) {
563
572
  runtime.logger?.warn(`dsh-evolution-maintenance: subagent dispose failed: ${disposeError instanceof Error ? disposeError.message : String(disposeError)}`);
564
573
  }
package/lib/tools.js CHANGED
@@ -1,5 +1,5 @@
1
- import { i as snapshotFromLibrary, n as PROBE_SIGNALS, r as computeProbe, t as buildEnrichment } from "./enrichment-2NEuOAZU.js";
2
- import { SkillLibrary, assertSkillsRootAliasRetired, redactSecrets, resolveRootConfig, resolveSkillsRoot } from "@lmzhen/dsh-evolution-core";
1
+ import { i as snapshotFromLibrary, n as PROBE_SIGNALS, r as computeProbe, t as buildEnrichment } from "./enrichment-BDlI9z8k.js";
2
+ import { assertSkillsRootAliasRetired, newSkillLibrary, redactSecrets, resolveRootConfig } from "@lmzhen/dsh-evolution-core";
3
3
  import z from "@deepseek-ai/schemastery";
4
4
  import { defineTool } from "@deepseek-ai/dsh-tools";
5
5
  //#region lib/types/tools.js
@@ -65,7 +65,10 @@ function apply(ctx, rawConfig = {}) {
65
65
  detail: ["evolution-io registry not mounted"],
66
66
  ...target ? { target } : {}
67
67
  };
68
- const library = new SkillLibrary(resolveSkillsRoot({ root: rootConfig.root }), ioRegistry.provider());
68
+ const library = newSkillLibrary({
69
+ config: rootConfig,
70
+ io: ioRegistry.provider()
71
+ });
69
72
  const enrichment = await buildEnrichment(ctx, library);
70
73
  const probe = computeProbe(signal, target, await snapshotFromLibrary(library, {
71
74
  descriptions: enrichment.descriptions,
@@ -5,21 +5,54 @@
5
5
  * external dependency (injected, fake-able in tests).
6
6
  */
7
7
  import { type DriftReport, type DriftSkillSnapshot } from '@lmzhen/dsh-evolution-core';
8
+ import type { Agent } from '@deepseek-ai/dsh-agent';
8
9
  import { type SkillLibraryLike } from './drift-scan.ts';
10
+ /** I-5 (v37): the platform's `SubagentStartRequest` fields this orchestrator sends.
11
+ * Typed locally (not imported) because the platform type lives behind the subagent row this
12
+ * package does not depend on; the required `parent` and `signal` mirror the platform. */
13
+ export interface SubagentStartRequestLike {
14
+ readonly label: string;
15
+ readonly prompt: readonly {
16
+ readonly type: 'text';
17
+ readonly text: string;
18
+ }[];
19
+ readonly parent: Agent;
20
+ readonly signal: AbortSignal;
21
+ readonly maxDepth: number;
22
+ readonly agentOptions: Record<string, string>;
23
+ readonly persona: string;
24
+ readonly toolFilter: {
25
+ readonly allow: readonly string[];
26
+ };
27
+ readonly outputSchema: Record<string, unknown>;
28
+ }
29
+ /** I-5 (v37): the platform's `SubagentResult` fields the outcome mapping reads. The
30
+ * index signature keeps a REAL platform result assignable (the platform type is
31
+ * wider than these three fields); the fields below are the ones this code relies on. */
32
+ export interface SubagentResultLike {
33
+ readonly structured?: unknown;
34
+ readonly diagnostic?: string;
35
+ readonly stopReason: string;
36
+ }
37
+ /** I-5 (v37): the platform's `SubagentRun` contract the orchestrator owns — `dispose` is
38
+ * NOT optional (the platform declares it required and every exit path here awaits it). */
39
+ export interface SubagentRunLike {
40
+ readonly result: Promise<SubagentResultLike>;
41
+ dispose(): Promise<void>;
42
+ }
9
43
  export interface MaintainRuntime {
10
44
  /** SkillLibrary-like reader for snapshot assembly. */
11
45
  library: SkillLibraryLike;
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). */
46
+ /** Subagent spawner (platform `subagents` service — the request fields this
47
+ * orchestrator sends, typed as the platform requires them: I-5 (v37) tightened the
48
+ * request from `unknown` and `dispose` from optional to the platform's
49
+ * always-present handle, so a future miss fails `tsc` instead of a runtime TypeError. */
15
50
  subagents: {
16
- start(kind: string, options: unknown): Promise<{
17
- result: Promise<unknown>;
18
- dispose?: () => Promise<void>;
19
- }>;
51
+ start(kind: 'spawn', options: SubagentStartRequestLike): Promise<SubagentRunLike>;
20
52
  };
21
- /** Parent agent/session handle passed through to the subagent, when available. */
22
- parent?: unknown;
53
+ /** The spawning agent (platform `SubagentStartRequest.parent`, required: in-process
54
+ * providers derive workspace, lineage and delegation depth from its durable session). */
55
+ parent: Agent;
23
56
  /** Optional logger for dispose-failure traces; when absent the failure is
24
57
  * swallowed without surfacing (the orchestrator is a pure function). */
25
58
  logger?: {
@@ -4,9 +4,9 @@
4
4
  * One canonical block: opening tag with version + joint signature, one
5
5
  * `[FACT]`/`[UNKNOWN]` line per signal, closing tag. The template side
6
6
  * renders its own `<MAINTAIN_PROMPT>` header with the same signature, so
7
- * the model can compare the two heads (011 mismatch protocol). All rendered
8
- * value/detail text passes through the optional redactor before it leaves
9
- * the session.
7
+ * the model can compare the two heads (011 mismatch protocol). Every rendered
8
+ * field (value/detail/threshold/skill name) passes through the optional
9
+ * redactor and the delimiter/newline escaping before it leaves the session.
10
10
  */
11
11
  import { type DriftReport } from '@lmzhen/dsh-evolution-core';
12
12
  export interface RenderFactsOptions {
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.73",
4
+ "version": "0.3.76",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -18,10 +18,6 @@
18
18
  "types": "./lib/types/index.d.ts",
19
19
  "default": "./lib/index.js"
20
20
  },
21
- "./invariant": {
22
- "types": "./lib/types/invariant.d.ts",
23
- "default": "./lib/invariant.js"
24
- },
25
21
  "./tools": {
26
22
  "types": "./lib/types/tools.d.ts",
27
23
  "default": "./lib/tools.js"
@@ -35,15 +31,15 @@
35
31
  "license": "MIT",
36
32
  "dependencies": {
37
33
  "@deepseek-ai/schemastery": "^3.18.1",
38
- "@lmzhen/dsh-evolution-core": "^0.3.73"
34
+ "@lmzhen/dsh-evolution-core": "^0.3.76"
39
35
  },
40
36
  "peerDependencies": {
41
- "@deepseek-ai/dsh-invariants": "^0.1.5-rc.2",
37
+ "@deepseek-ai/dsh-agent": "^0.1.5-rc.2",
42
38
  "@deepseek-ai/cordis": "^4.0.1",
43
39
  "@deepseek-ai/dsh-tools": "^0.1.5-rc.2"
44
40
  },
45
41
  "devDependencies": {
46
- "@deepseek-ai/dsh-invariants": "^0.1.5-rc.2",
42
+ "@deepseek-ai/dsh-agent": "^0.1.5-rc.2",
47
43
  "@deepseek-ai/dsh-tools": "^0.1.5-rc.2"
48
44
  }
49
45
  }
package/lib/invariant.js DELETED
@@ -1,8 +0,0 @@
1
- //#region lib/types/invariant.js
2
- const PACKAGE_NAME = "@lmzhen/dsh-evolution-maintenance";
3
- const name = "evolution-maintenance-invariant";
4
- const inject = ["invariants"];
5
- const install = () => {};
6
- const apply = (ctx) => Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install));
7
- //#endregion
8
- export { apply, inject, name };
@@ -1,5 +0,0 @@
1
- import type { Context } from '@deepseek-ai/cordis';
2
- export declare const name = "evolution-maintenance-invariant";
3
- export declare const inject: string[];
4
- export declare const apply: (ctx: Context) => Promise<() => void>;
5
- //# sourceMappingURL=invariant.d.ts.map