@lmzhen/dsh-evolution-maintenance 0.3.74 → 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 +2 -0
- package/lib/{enrichment-2NEuOAZU.js → enrichment-BDlI9z8k.js} +8 -2
- package/lib/index.js +30 -21
- package/lib/tools.js +6 -3
- package/lib/types/orchestrate.d.ts +42 -9
- package/lib/types/render-facts.d.ts +3 -3
- package/package.json +4 -8
- package/lib/invariant.js +0 -8
- package/lib/types/invariant.d.ts +0 -5
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
|
-
|
|
145
|
-
|
|
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-
|
|
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).
|
|
12
|
-
* value/detail
|
|
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 = (
|
|
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
|
|
350
|
-
* indistinguishable from the plan's section header in the RENDERED text,
|
|
351
|
-
*
|
|
352
|
-
*
|
|
353
|
-
*
|
|
354
|
-
*
|
|
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) =>
|
|
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
|
|
540
|
+
const raw = runResult.structured;
|
|
532
541
|
if (raw === void 0) {
|
|
533
|
-
if (runResult
|
|
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-
|
|
2
|
-
import {
|
|
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 =
|
|
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 —
|
|
13
|
-
*
|
|
14
|
-
*
|
|
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:
|
|
17
|
-
result: Promise<unknown>;
|
|
18
|
-
dispose?: () => Promise<void>;
|
|
19
|
-
}>;
|
|
51
|
+
start(kind: 'spawn', options: SubagentStartRequestLike): Promise<SubagentRunLike>;
|
|
20
52
|
};
|
|
21
|
-
/**
|
|
22
|
-
|
|
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).
|
|
8
|
-
* value/detail
|
|
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.
|
|
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.
|
|
34
|
+
"@lmzhen/dsh-evolution-core": "^0.3.76"
|
|
39
35
|
},
|
|
40
36
|
"peerDependencies": {
|
|
41
|
-
"@deepseek-ai/dsh-
|
|
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-
|
|
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 };
|
package/lib/types/invariant.d.ts
DELETED