@lmzhen/dsh-evolution-maintenance 0.3.66 → 0.3.68
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`);
|
|
@@ -224,6 +235,10 @@ function validateAndNormalizeMaintainPlan(raw, report, validSignals) {
|
|
|
224
235
|
const uncovered = [...overSignals].filter((id) => !covered.has(id) && !notesText.includes(id));
|
|
225
236
|
if (uncovered.length > 0) errors.push(`completeness: over signal(s) not covered by any plan evidence nor named in a note (${uncovered.join(", ")}) — §3 requires each over signal to be addressed or explicitly explained away`);
|
|
226
237
|
}
|
|
238
|
+
else if (verdict === "no_issues" && overSignals.size > 0) {
|
|
239
|
+
const uncovered = [...overSignals].filter((id) => !notesText.includes(id));
|
|
240
|
+
if (uncovered.length > 0) errors.push(`completeness: verdict=no_issues while the facts block carries over signal(s) not named in any note (${uncovered.join(", ")}) — each signal must be explicitly explained away in notes (§3/§7)`);
|
|
241
|
+
}
|
|
227
242
|
if (errors.length > 0) return {
|
|
228
243
|
ok: false,
|
|
229
244
|
errors,
|
|
@@ -234,7 +249,7 @@ function validateAndNormalizeMaintainPlan(raw, report, validSignals) {
|
|
|
234
249
|
},
|
|
235
250
|
forcedHuman
|
|
236
251
|
};
|
|
237
|
-
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()));
|
|
238
253
|
for (const item of plan) if (item.names.some((name) => unknownQualitySkills.has(name)) && !item.needs_human) {
|
|
239
254
|
item.needs_human = true;
|
|
240
255
|
forcedHuman.push(...item.names.filter((name) => unknownQualitySkills.has(name)));
|
|
@@ -345,8 +360,10 @@ function formatPlan(validated, runId) {
|
|
|
345
360
|
const { plan, forcedHuman } = validated;
|
|
346
361
|
const lines = [];
|
|
347
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}`)];
|
|
348
364
|
if (plan.verdict === "no_issues") {
|
|
349
365
|
lines.push("No drift issues detected. Nothing to do.");
|
|
366
|
+
lines.push(...noteLines);
|
|
350
367
|
return lines.join("\n");
|
|
351
368
|
}
|
|
352
369
|
for (const item of plan.plan) {
|
|
@@ -363,10 +380,7 @@ function formatPlan(validated, runId) {
|
|
|
363
380
|
if (item.is_override && item.override_reason) lines.push(` override: ${item.override_reason}`);
|
|
364
381
|
}
|
|
365
382
|
if (forcedHuman.length > 0) lines.push(`(quality_low gate: forced needs_human for ${[...new Set(forcedHuman)].join(", ")})`);
|
|
366
|
-
|
|
367
|
-
lines.push("Notes:");
|
|
368
|
-
for (const note of plan.notes) lines.push(`- ${note}`);
|
|
369
|
-
}
|
|
383
|
+
lines.push(...noteLines);
|
|
370
384
|
return lines.join("\n");
|
|
371
385
|
}
|
|
372
386
|
/** Run one maintenance scan and return display text plus validation metadata. */
|
|
@@ -379,6 +393,7 @@ async function runMaintain(runtime, options = {}) {
|
|
|
379
393
|
error: "dsh-evolution prompt bundle integrity check failed; refusing to run maintain"
|
|
380
394
|
};
|
|
381
395
|
const usageObserved = options.usageObserved ? options.usageObserved() : void 0;
|
|
396
|
+
const readFailures = [];
|
|
382
397
|
const snapshots = await snapshotFromLibrary(runtime.library, {
|
|
383
398
|
supportFiles: options.supportFiles ? options.supportFiles() : void 0,
|
|
384
399
|
descriptions: options.descriptions ? options.descriptions() : void 0,
|
|
@@ -387,16 +402,29 @@ async function runMaintain(runtime, options = {}) {
|
|
|
387
402
|
catalogInvalid: options.catalogInvalid ? options.catalogInvalid() : void 0,
|
|
388
403
|
usageObserved,
|
|
389
404
|
onReadError: (name, error) => {
|
|
405
|
+
readFailures.push(name);
|
|
390
406
|
runtime.logger?.warn(`evolution-maintenance: skipping unreadable skill "${name}": ${error instanceof Error ? error.message : String(error)}`);
|
|
391
407
|
}
|
|
392
408
|
});
|
|
393
|
-
if (snapshots.length === 0)
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
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
|
+
}
|
|
400
428
|
const { facts, report, signalsVersion, signature } = buildMaintainFacts(snapshots, usageObserved, options.redact);
|
|
401
429
|
const template = renderMaintainTemplate(MAINTAIN_PROMPT, PROMPT_BUNDLE_ID, signalsVersion, signature);
|
|
402
430
|
const probeRegistered = runtime.tools?.get("maintenance_probe") != null;
|
|
@@ -406,14 +434,14 @@ async function runMaintain(runtime, options = {}) {
|
|
|
406
434
|
|
|
407
435
|
${MAINTAIN_OUTPUT_INSTRUCTION}`;
|
|
408
436
|
const timeoutMs = options.timeoutMs ?? 6e5;
|
|
409
|
-
if (!Number.isSafeInteger(timeoutMs) || timeoutMs <= 0 || timeoutMs >
|
|
437
|
+
if (!Number.isSafeInteger(timeoutMs) || timeoutMs <= 0 || timeoutMs > MAX_TIMER_DELAY_MS) return {
|
|
410
438
|
ok: false,
|
|
411
439
|
recommendationCount: 0,
|
|
412
|
-
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)}`
|
|
413
441
|
};
|
|
414
442
|
const signal = options.signal !== void 0 ? AbortSignal.any([AbortSignal.timeout(timeoutMs), options.signal]) : AbortSignal.timeout(timeoutMs);
|
|
415
443
|
abortSignal = signal;
|
|
416
|
-
const agentOptions = { model: options.model ?? runtime.evolutionPolicy?.get()?.curatorModel ??
|
|
444
|
+
const agentOptions = { model: options.model ?? runtime.evolutionPolicy?.get()?.curatorModel ?? DEFAULT_CURATOR_MODEL };
|
|
417
445
|
if (options.provider) agentOptions.provider = options.provider;
|
|
418
446
|
const run = await runtime.subagents.start("spawn", {
|
|
419
447
|
label: "dsh-evolution-maintain",
|
|
@@ -546,4 +574,4 @@ ${MAINTAIN_OUTPUT_INSTRUCTION}`;
|
|
|
546
574
|
}
|
|
547
575
|
}
|
|
548
576
|
//#endregion
|
|
549
|
-
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.68",
|
|
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.68"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
41
41
|
"@deepseek-ai/dsh-invariants": "^0.1.1-rc.2",
|