@lmzhen/dsh-evolution-maintenance 0.3.67 → 0.3.69
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.
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { HEALTH_STAMP_RE, MIN_STAMP_BODY_CHARS, computeDedupGroups, computePrefixClusters, duplicateHeadings,
|
|
1
|
+
import { HEALTH_STAMP_RE, MIN_STAMP_BODY_CHARS, computeDedupGroups, computePrefixClusters, duplicateHeadings, frontmatterCatalogInvalid, missingSupportPointers, narrowNameMatches, overlongLines, parseFrontmatter, usageObserved } from "@lmzhen/dsh-evolution-core";
|
|
2
2
|
//#region lib/types/drift-scan.js
|
|
3
3
|
/**
|
|
4
4
|
* Skill-library snapshot assembly for drift scanning.
|
|
@@ -24,7 +24,10 @@ async function snapshotFromLibrary(library, options = {}) {
|
|
|
24
24
|
options.onReadError?.(entry.name, error);
|
|
25
25
|
continue;
|
|
26
26
|
}
|
|
27
|
-
if (body === void 0 || body === null)
|
|
27
|
+
if (body === void 0 || body === null) {
|
|
28
|
+
options.onReadError?.(entry.name, /* @__PURE__ */ new Error("no readable SKILL.md (missing file, or the directory name is not a valid skill name)"));
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
28
31
|
snapshots.push({
|
|
29
32
|
name: entry.name,
|
|
30
33
|
body,
|
|
@@ -141,7 +144,7 @@ async function buildEnrichment(ctx, library) {
|
|
|
141
144
|
const body = await library.read(entry.name);
|
|
142
145
|
if (body === void 0 || body === null) continue;
|
|
143
146
|
const parsed = parseFrontmatter(body);
|
|
144
|
-
if (
|
|
147
|
+
if (frontmatterCatalogInvalid(body)) catalogInvalid.set(entry.name, true);
|
|
145
148
|
const description = parsed?.frontmatter.description;
|
|
146
149
|
if (typeof description === "string" && description.trim().length > 0) descriptions.set(entry.name, description);
|
|
147
150
|
const files = await library.listSupportFiles(entry.name);
|
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-
|
|
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";
|
|
1
|
+
import { i as snapshotFromLibrary, n as PROBE_SIGNALS, r as computeProbe, t as buildEnrichment } from "./enrichment-2NEuOAZU.js";
|
|
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
|
|
5
5
|
/**
|
|
@@ -49,6 +49,16 @@ function renderSignal(signal, redact) {
|
|
|
49
49
|
* and the quality_low=unknown global imposition. Never judges whether a
|
|
50
50
|
* recommendation is right — only whether it is well-formed and traceable.
|
|
51
51
|
*/
|
|
52
|
+
/**
|
|
53
|
+
* V27 D-3: the clause ids the maintain template ITSELF enumerates — the `- A1 …`
|
|
54
|
+
* bullets of its §5 rule catalogue. `rule` is checked against this set (parsed
|
|
55
|
+
* from the shipped template at module load, so a template edit that adds or
|
|
56
|
+
* renames a clause moves the set with it) instead of the former bare
|
|
57
|
+
* non-empty-string check, which let a plan cite a clause number that does not
|
|
58
|
+
* exist and still pass mechanical validation. `self-consistency.spec.ts` pins
|
|
59
|
+
* the parsed set so a template rewording cannot silently empty it.
|
|
60
|
+
*/
|
|
61
|
+
const MAINTAIN_RULE_IDS = new Set([...MAINTAIN_PROMPT.matchAll(/^-\s+([A-D]\d)\s/gm)].map((match) => match[1]));
|
|
52
62
|
const KINDS = new Set([
|
|
53
63
|
"skill-level",
|
|
54
64
|
"relationship-level",
|
|
@@ -176,6 +186,7 @@ function validateAndNormalizeMaintainPlan(raw, report, validSignals) {
|
|
|
176
186
|
}
|
|
177
187
|
}
|
|
178
188
|
if (!isNonEmptyString(item.rule)) errors.push(`${path}.rule: required`);
|
|
189
|
+
else if (!MAINTAIN_RULE_IDS.has(item.rule.trim())) errors.push(`${path}.rule: "${item.rule}" is not a clause id the maintain template defines (${[...MAINTAIN_RULE_IDS].sort().join(", ")})`);
|
|
179
190
|
if (typeof item.finding !== "string" || item.finding.trim().length === 0) errors.push(`${path}.finding: required`);
|
|
180
191
|
if (!isNonEmptyString(item.recommendation)) errors.push(`${path}.recommendation: required`);
|
|
181
192
|
if (!isNonEmptyString(item.semantic_reasoning)) errors.push(`${path}.semantic_reasoning: required`);
|
|
@@ -238,7 +249,7 @@ function validateAndNormalizeMaintainPlan(raw, report, validSignals) {
|
|
|
238
249
|
},
|
|
239
250
|
forcedHuman
|
|
240
251
|
};
|
|
241
|
-
const unknownQualitySkills = new Set(report.skills.filter((skill) => findDriftSignal(skill.signals, "quality_low")?.verdict === "unknown").map((skill) => skill.name));
|
|
252
|
+
const unknownQualitySkills = new Set(report.skills.filter((skill) => findDriftSignal(skill.signals, "quality_low")?.verdict === "unknown").map((skill) => skill.name.trim()));
|
|
242
253
|
for (const item of plan) if (item.names.some((name) => unknownQualitySkills.has(name)) && !item.needs_human) {
|
|
243
254
|
item.needs_human = true;
|
|
244
255
|
forcedHuman.push(...item.names.filter((name) => unknownQualitySkills.has(name)));
|
|
@@ -349,8 +360,10 @@ function formatPlan(validated, runId) {
|
|
|
349
360
|
const { plan, forcedHuman } = validated;
|
|
350
361
|
const lines = [];
|
|
351
362
|
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}`)];
|
|
352
364
|
if (plan.verdict === "no_issues") {
|
|
353
365
|
lines.push("No drift issues detected. Nothing to do.");
|
|
366
|
+
lines.push(...noteLines);
|
|
354
367
|
return lines.join("\n");
|
|
355
368
|
}
|
|
356
369
|
for (const item of plan.plan) {
|
|
@@ -367,10 +380,7 @@ function formatPlan(validated, runId) {
|
|
|
367
380
|
if (item.is_override && item.override_reason) lines.push(` override: ${item.override_reason}`);
|
|
368
381
|
}
|
|
369
382
|
if (forcedHuman.length > 0) lines.push(`(quality_low gate: forced needs_human for ${[...new Set(forcedHuman)].join(", ")})`);
|
|
370
|
-
|
|
371
|
-
lines.push("Notes:");
|
|
372
|
-
for (const note of plan.notes) lines.push(`- ${note}`);
|
|
373
|
-
}
|
|
383
|
+
lines.push(...noteLines);
|
|
374
384
|
return lines.join("\n");
|
|
375
385
|
}
|
|
376
386
|
/** Run one maintenance scan and return display text plus validation metadata. */
|
|
@@ -383,6 +393,7 @@ async function runMaintain(runtime, options = {}) {
|
|
|
383
393
|
error: "dsh-evolution prompt bundle integrity check failed; refusing to run maintain"
|
|
384
394
|
};
|
|
385
395
|
const usageObserved = options.usageObserved ? options.usageObserved() : void 0;
|
|
396
|
+
const readFailures = [];
|
|
386
397
|
const snapshots = await snapshotFromLibrary(runtime.library, {
|
|
387
398
|
supportFiles: options.supportFiles ? options.supportFiles() : void 0,
|
|
388
399
|
descriptions: options.descriptions ? options.descriptions() : void 0,
|
|
@@ -391,16 +402,29 @@ async function runMaintain(runtime, options = {}) {
|
|
|
391
402
|
catalogInvalid: options.catalogInvalid ? options.catalogInvalid() : void 0,
|
|
392
403
|
usageObserved,
|
|
393
404
|
onReadError: (name, error) => {
|
|
405
|
+
readFailures.push(name);
|
|
394
406
|
runtime.logger?.warn(`evolution-maintenance: skipping unreadable skill "${name}": ${error instanceof Error ? error.message : String(error)}`);
|
|
395
407
|
}
|
|
396
408
|
});
|
|
397
|
-
if (snapshots.length === 0)
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
409
|
+
if (snapshots.length === 0) {
|
|
410
|
+
if (readFailures.length > 0) return {
|
|
411
|
+
ok: false,
|
|
412
|
+
recommendationCount: 0,
|
|
413
|
+
error: `maintenance scan could not read ${readFailures.length} listed skill(s) (${readFailures.slice(0, 10).join(", ")}${readFailures.length > 10 ? ", …" : ""}) and read no skill at all — the library was NOT audited; fix the unreadable skills (or their directory names) and run the scan again`
|
|
414
|
+
};
|
|
415
|
+
if (runtime.rootExists && !await runtime.rootExists()) return {
|
|
416
|
+
ok: false,
|
|
417
|
+
recommendationCount: 0,
|
|
418
|
+
error: `maintenance scan found an empty skill library, but the configured skill root does not exist${runtime.skillRoot ? ` (${runtime.skillRoot})` : ""} — point the skillsRoot config at a real directory (a literal \`~\` is not expanded) and run the scan again`
|
|
419
|
+
};
|
|
420
|
+
return {
|
|
421
|
+
ok: true,
|
|
422
|
+
recommendationCount: 0,
|
|
423
|
+
runId: randomUUID(),
|
|
424
|
+
verdict: "no_issues",
|
|
425
|
+
text: "Maintenance scan: empty skill library. Nothing to do."
|
|
426
|
+
};
|
|
427
|
+
}
|
|
404
428
|
const { facts, report, signalsVersion, signature } = buildMaintainFacts(snapshots, usageObserved, options.redact);
|
|
405
429
|
const template = renderMaintainTemplate(MAINTAIN_PROMPT, PROMPT_BUNDLE_ID, signalsVersion, signature);
|
|
406
430
|
const probeRegistered = runtime.tools?.get("maintenance_probe") != null;
|
|
@@ -410,14 +434,14 @@ async function runMaintain(runtime, options = {}) {
|
|
|
410
434
|
|
|
411
435
|
${MAINTAIN_OUTPUT_INSTRUCTION}`;
|
|
412
436
|
const timeoutMs = options.timeoutMs ?? 6e5;
|
|
413
|
-
if (!Number.isSafeInteger(timeoutMs) || timeoutMs <= 0 || timeoutMs >
|
|
437
|
+
if (!Number.isSafeInteger(timeoutMs) || timeoutMs <= 0 || timeoutMs > MAX_TIMER_DELAY_MS) return {
|
|
414
438
|
ok: false,
|
|
415
439
|
recommendationCount: 0,
|
|
416
|
-
error: `maintain: --timeout must be a positive integer in ms, at most
|
|
440
|
+
error: `maintain: --timeout must be a positive integer in ms, at most ${MAX_TIMER_DELAY_MS}; got ${String(timeoutMs)}`
|
|
417
441
|
};
|
|
418
442
|
const signal = options.signal !== void 0 ? AbortSignal.any([AbortSignal.timeout(timeoutMs), options.signal]) : AbortSignal.timeout(timeoutMs);
|
|
419
443
|
abortSignal = signal;
|
|
420
|
-
const agentOptions = { model: options.model ?? runtime.evolutionPolicy?.get()?.curatorModel ??
|
|
444
|
+
const agentOptions = { model: options.model ?? runtime.evolutionPolicy?.get()?.curatorModel ?? DEFAULT_CURATOR_MODEL };
|
|
421
445
|
if (options.provider) agentOptions.provider = options.provider;
|
|
422
446
|
const run = await runtime.subagents.start("spawn", {
|
|
423
447
|
label: "dsh-evolution-maintain",
|
|
@@ -550,4 +574,4 @@ ${MAINTAIN_OUTPUT_INSTRUCTION}`;
|
|
|
550
574
|
}
|
|
551
575
|
}
|
|
552
576
|
//#endregion
|
|
553
|
-
export { PROBE_SIGNALS, buildEnrichment, buildMaintainFacts, computeProbe, renderFacts, renderMaintainTemplate, runMaintain, snapshotFromLibrary, validateAndNormalizeMaintainPlan };
|
|
577
|
+
export { MAINTAIN_RULE_IDS, PROBE_SIGNALS, buildEnrichment, buildMaintainFacts, computeProbe, renderFacts, renderMaintainTemplate, runMaintain, snapshotFromLibrary, validateAndNormalizeMaintainPlan };
|
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 { SkillLibrary, 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-2NEuOAZU.js";
|
|
2
|
+
import { SkillLibrary, assertSkillsRootAliasRetired, redactSecrets, resolveRootConfig, resolveSkillsRoot } 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
|
|
@@ -19,8 +19,8 @@ const Config = z.object({
|
|
|
19
19
|
skillsRoot: z.string().default("")
|
|
20
20
|
});
|
|
21
21
|
function apply(ctx, rawConfig = {}) {
|
|
22
|
+
assertSkillsRootAliasRetired(rawConfig);
|
|
22
23
|
const rootConfig = resolveRootConfig(rawConfig);
|
|
23
|
-
if (rootConfig.usedDeprecatedAlias) ctx.logger.warn("evolution-maintenance-tools: config \"skillsRoot\" is deprecated (E-7); use \"root\" — the alias is honoured until 0.3.65");
|
|
24
24
|
ctx.inject(["tools"], (toolCtx) => {
|
|
25
25
|
const tools = toolCtx.get("tools");
|
|
26
26
|
toolCtx.effect(() => tools.register(defineTool({
|
|
@@ -41,6 +41,14 @@ export interface MaintainRuntime {
|
|
|
41
41
|
tools?: {
|
|
42
42
|
get(name: string): unknown;
|
|
43
43
|
} | undefined;
|
|
44
|
+
/** V27 M-02: does the configured skill root EXIST? The io seam maps a missing
|
|
45
|
+
* directory to an empty listing, so "the scan found no skills" could not be
|
|
46
|
+
* told apart from "the configured root is not there" (an unexpanded `~`, a
|
|
47
|
+
* typo — INSTALL.md routes users to absolute paths). Optional: without the
|
|
48
|
+
* probe the scan keeps the previous empty-library answer. */
|
|
49
|
+
rootExists?: (() => Promise<boolean>) | undefined;
|
|
50
|
+
/** V27 M-02: the root the probe above refers to, for the error message. */
|
|
51
|
+
skillRoot?: string | undefined;
|
|
44
52
|
}
|
|
45
53
|
export interface MaintainOptions {
|
|
46
54
|
timeoutMs?: number;
|
package/lib/types/tools.d.ts
CHANGED
|
@@ -15,8 +15,8 @@ export interface Config {
|
|
|
15
15
|
/** Skill-tree root for probe reads; empty uses the default tree. E-7 (v18):
|
|
16
16
|
* canonical key — the same `root` every other family row reads. */
|
|
17
17
|
root?: string | undefined;
|
|
18
|
-
/**
|
|
19
|
-
*
|
|
18
|
+
/** V27 G2.4 (M-08): RETIRED alias of `root`. Declared so the loader passes it
|
|
19
|
+
* to the load-time gate (which rejects it loudly) instead of dropping it. */
|
|
20
20
|
skillsRoot?: string | undefined;
|
|
21
21
|
}
|
|
22
22
|
export declare const Config: z<Schemastery.ObjectS<{
|
|
@@ -8,6 +8,16 @@
|
|
|
8
8
|
* recommendation is right — only whether it is well-formed and traceable.
|
|
9
9
|
*/
|
|
10
10
|
import { type DriftReport } from '@lmzhen/dsh-evolution-core';
|
|
11
|
+
/**
|
|
12
|
+
* V27 D-3: the clause ids the maintain template ITSELF enumerates — the `- A1 …`
|
|
13
|
+
* bullets of its §5 rule catalogue. `rule` is checked against this set (parsed
|
|
14
|
+
* from the shipped template at module load, so a template edit that adds or
|
|
15
|
+
* renames a clause moves the set with it) instead of the former bare
|
|
16
|
+
* non-empty-string check, which let a plan cite a clause number that does not
|
|
17
|
+
* exist and still pass mechanical validation. `self-consistency.spec.ts` pins
|
|
18
|
+
* the parsed set so a template rewording cannot silently empty it.
|
|
19
|
+
*/
|
|
20
|
+
export declare const MAINTAIN_RULE_IDS: ReadonlySet<string>;
|
|
11
21
|
export type MaintainVerdict = 'issues' | 'no_issues';
|
|
12
22
|
export type MaintainPlanItemKind = 'skill-level' | 'relationship-level' | 'library-level';
|
|
13
23
|
export type MaintainReversibility = 'archive' | 'restructure' | 'patch' | 'rename' | 'none';
|
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.69",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"license": "MIT",
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"@deepseek-ai/schemastery": "^3.18.1",
|
|
38
|
-
"@lmzhen/dsh-evolution-core": "^0.3.
|
|
38
|
+
"@lmzhen/dsh-evolution-core": "^0.3.69"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
41
41
|
"@deepseek-ai/dsh-invariants": "^0.1.1-rc.2",
|
|
@@ -43,6 +43,7 @@
|
|
|
43
43
|
"@deepseek-ai/dsh-tools": "^0.1.1-rc.2"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
|
-
"@deepseek-ai/dsh-invariants": "^0.1.1-rc.2"
|
|
46
|
+
"@deepseek-ai/dsh-invariants": "^0.1.1-rc.2",
|
|
47
|
+
"@deepseek-ai/dsh-tools": "^0.1.1-rc.2"
|
|
47
48
|
}
|
|
48
49
|
}
|