@lmzhen/dsh-evolution-maintenance 0.4.1 → 0.6.0

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, frontmatterCatalogInvalid, isPresent, missingSupportPointers, narrowNameMatches, overlongLines, parseFrontmatter, usageObserved } from "@lmzhen/dsh-evolution-core";
1
+ import { HEALTH_STAMP_RE, MIN_STAMP_BODY_CHARS, bodyCost, computeDedupGroups, computePrefixClusters, duplicateHeadings, frontmatterCatalogInvalid, idleDays, isPresent, missingSupportPointers, narrowNameMatches, overlongLines, parseFrontmatter, resolveCitations, retirementReport, 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.
@@ -28,15 +28,25 @@ async function snapshotFromLibrary(library, options = {}) {
28
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
29
  continue;
30
30
  }
31
+ const supportFiles = options.supportFiles?.get(entry.name);
31
32
  snapshots.push({
32
33
  name: entry.name,
33
34
  body,
34
35
  description: options.descriptions?.get(entry.name),
35
- supportFiles: options.supportFiles?.get(entry.name),
36
+ supportFiles,
37
+ cost: bodyCost(body),
38
+ ...supportFiles === void 0 ? {} : { citations: resolveCitations({
39
+ content: body,
40
+ file: "SKILL.md",
41
+ files: supportFiles
42
+ }) },
36
43
  quality: options.quality?.get(entry.name),
37
44
  ...options.protected?.get(entry.name) !== void 0 ? { protected: options.protected.get(entry.name) } : {},
38
45
  ...options.catalogInvalid?.get(entry.name) === true ? { catalogInvalid: true } : {},
39
- ...options.usageObserved !== void 0 ? { usageObserved: options.usageObserved } : {}
46
+ ...options.usageObserved !== void 0 ? { usageObserved: options.usageObserved } : {},
47
+ ...options.demand?.get(entry.name) === void 0 ? {} : { demand: options.demand.get(entry.name) },
48
+ ...options.liveness?.get(entry.name) === void 0 ? {} : { liveness: options.liveness.get(entry.name) },
49
+ ...options.supportChars?.get(entry.name) === void 0 ? {} : { supportChars: options.supportChars.get(entry.name) }
40
50
  });
41
51
  }
42
52
  return snapshots;
@@ -60,6 +70,8 @@ const PROBE_SIGNALS = [
60
70
  "dup_heading",
61
71
  "overlong_line",
62
72
  "pointer_missing",
73
+ "citation_resolution",
74
+ "demand",
63
75
  "narrow_name",
64
76
  "description_chars",
65
77
  "quality_low"
@@ -110,6 +122,25 @@ function computeProbe(signal, target, snapshots) {
110
122
  case "dup_heading": return result(signal, duplicateHeadings(body).map((d) => `${d.heading} x${d.count}`), target);
111
123
  case "overlong_line": return result(signal, overlongLines(body).map((line) => `line ${line.lineNo}: ${line.chars} chars`), target);
112
124
  case "pointer_missing": return result(signal, missingSupportPointers(body, snapshot.supportFiles ?? []).map((path) => `no body reference: ${path}`), target);
125
+ case "citation_resolution": {
126
+ const citations = snapshot.citations;
127
+ if (citations === void 0) return result(signal, ["not-scanned (support files not enumerated)"], target);
128
+ if (citations.truncated) return result(signal, [`scan truncated at ${citations.refs.length} refs`], target);
129
+ const cited = citations.refs.filter((ref) => ref.kind === "citation");
130
+ const dangling = citations.dangling.map((ref) => `${ref.target ?? ref.raw}:${ref.line}`);
131
+ const unverified = citations.unverified.map((ref) => `${ref.target ?? ref.raw}:${ref.line}`);
132
+ return result(signal, dangling.length > 0 ? dangling.map((entry) => `dangling ${entry}`) : [`dangling=0 of ${cited.length} (foreign=${citations.foreign.length}${unverified.length === 0 ? "" : `, unverified=${unverified.length}: ${unverified.slice(0, 3).join(", ")}`})`], target);
133
+ }
134
+ case "demand": {
135
+ if (!snapshot.supportFiles || snapshot.supportFiles.length === 0) return result(signal, ["not-enumerated (support files unknown)"], target);
136
+ if (snapshot.usageObserved !== true) return result(signal, ["window-closed (no observed reads yet)"], target);
137
+ const demand = snapshot.demand ?? {};
138
+ const cold = snapshot.supportFiles.filter((path) => (demand[path] ?? 0) === 0);
139
+ const retire = retirementReport(snapshot, snapshot.supportFiles);
140
+ const coldLines = cold.length === 0 ? [`cold=0 of ${snapshot.supportFiles.length}`] : cold.map((path) => `never read: ${path}`);
141
+ const retireLines = retire.candidates.length === 0 ? [`retire: ${retire.status}`] : retire.candidates.map((candidate) => `retire candidate: ${candidate.path} (idle ${candidate.idleDays}d)`);
142
+ return result(signal, [...coldLines, ...retireLines], target);
143
+ }
113
144
  case "narrow_name": return result(signal, narrowNameMatches(snapshot.name).map((shape) => `narrow shape: ${shape}`), target);
114
145
  case "description_chars": {
115
146
  const desc = snapshot.description;
@@ -143,6 +174,10 @@ async function buildEnrichment(ctx, library) {
143
174
  const quality = /* @__PURE__ */ new Map();
144
175
  const protectedMap = /* @__PURE__ */ new Map();
145
176
  const catalogInvalid = /* @__PURE__ */ new Map();
177
+ const demand = /* @__PURE__ */ new Map();
178
+ const liveness = /* @__PURE__ */ new Map();
179
+ const supportChars = /* @__PURE__ */ new Map();
180
+ const now = /* @__PURE__ */ new Date();
146
181
  for (const entry of await library.list()) {
147
182
  if (entry.protectionUnknown) protectedMap.set(entry.name, "unknown");
148
183
  else if (entry.protectedBy) protectedMap.set(entry.name, entry.protectedBy);
@@ -162,6 +197,11 @@ async function buildEnrichment(ctx, library) {
162
197
  if (isPresent(files) && files.value.length > 0) supportFiles.set(entry.name, files.value);
163
198
  const record = usageMap?.get(entry.name);
164
199
  if (typeof record?.quality_score === "number") quality.set(entry.name, record.quality_score);
200
+ const reads = record?.support_reads;
201
+ if (reads !== void 0 && Object.keys(reads).length > 0) demand.set(entry.name, reads);
202
+ if (record !== void 0) liveness.set(entry.name, { idleDays: idleDays(record, now) });
203
+ const big = await library.supportFileChars(entry.name);
204
+ if (big !== null && Object.keys(big).length > 0) supportChars.set(entry.name, big);
165
205
  }
166
206
  return {
167
207
  descriptions,
@@ -169,7 +209,10 @@ async function buildEnrichment(ctx, library) {
169
209
  quality,
170
210
  usageObservedValue,
171
211
  protected: protectedMap,
172
- catalogInvalid
212
+ catalogInvalid,
213
+ demand,
214
+ liveness,
215
+ supportChars
173
216
  };
174
217
  }
175
218
  //#endregion
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-31K43lVa.js";
1
+ import { i as snapshotFromLibrary, n as PROBE_SIGNALS, r as computeProbe, t as buildEnrichment } from "./enrichment-CjZmhRI9.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
@@ -414,6 +414,9 @@ async function runMaintain(runtime, options = {}) {
414
414
  protected: options.protected ? options.protected() : void 0,
415
415
  catalogInvalid: options.catalogInvalid ? options.catalogInvalid() : void 0,
416
416
  usageObserved,
417
+ ...options.demand === void 0 ? {} : { demand: options.demand() },
418
+ ...options.liveness === void 0 ? {} : { liveness: options.liveness() },
419
+ ...options.supportChars === void 0 ? {} : { supportChars: options.supportChars() },
417
420
  onReadError: (name, error) => {
418
421
  readFailures.push(name);
419
422
  runtime.logger?.warn(`evolution-maintenance: skipping unreadable skill "${name}": ${error instanceof Error ? error.message : String(error)}`);
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-31K43lVa.js";
1
+ import { i as snapshotFromLibrary, n as PROBE_SIGNALS, r as computeProbe, t as buildEnrichment } from "./enrichment-CjZmhRI9.js";
2
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";
@@ -34,7 +34,7 @@ function apply(ctx, rawConfig = {}) {
34
34
  },
35
35
  target: {
36
36
  type: "string",
37
- description: "Skill name for skill-level signals (required for stamp_density/body_size/dup_heading/overlong_line/pointer_missing/narrow_name/description_chars/quality_low)."
37
+ description: "Skill name for skill-level signals (required for stamp_density/body_size/dup_heading/overlong_line/pointer_missing/citation_resolution/demand/narrow_name/description_chars/quality_low)."
38
38
  }
39
39
  },
40
40
  output: {
@@ -76,7 +76,8 @@ function apply(ctx, rawConfig = {}) {
76
76
  quality: enrichment.quality,
77
77
  protected: enrichment.protected,
78
78
  catalogInvalid: enrichment.catalogInvalid,
79
- usageObserved: enrichment.usageObservedValue
79
+ usageObserved: enrichment.usageObservedValue,
80
+ demand: enrichment.demand
80
81
  }));
81
82
  const redacted = redactSecrets(probe.detail.join("\n"));
82
83
  return {
@@ -7,7 +7,7 @@
7
7
  * when absent they stay `undefined`, which the signal layer reports as
8
8
  * `unknown` (never a fabricated verdict).
9
9
  */
10
- import type { DriftSkillSnapshot } from '@lmzhen/dsh-evolution-core';
10
+ import { type DriftSkillSnapshot, type SkillLiveness } from '@lmzhen/dsh-evolution-core';
11
11
  /** Minimal SkillLibrary surface needed for snapshot assembly. */
12
12
  export interface SkillLibraryLike {
13
13
  list(): Promise<ReadonlyArray<{
@@ -34,6 +34,12 @@ export interface SnapshotOptions {
34
34
  * the snapshot (E-36): without it the probe always answered 'unknown' while
35
35
  * the facts block reported the enrichment value. */
36
36
  usageObserved?: boolean | undefined;
37
+ /** Per-support-file read counts per skill (design §5.5). */
38
+ demand?: ReadonlyMap<string, Readonly<Record<string, number>>> | undefined;
39
+ /** Idle age per skill (design §5.6). */
40
+ liveness?: ReadonlyMap<string, SkillLiveness> | undefined;
41
+ /** Support-file char counts for files that can exceed the cap (design §16.6). */
42
+ supportChars?: ReadonlyMap<string, Readonly<Record<string, number>>> | undefined;
37
43
  }
38
44
  /**
39
45
  * Assemble drift snapshots from a library reader and optional enrichment.
@@ -7,7 +7,7 @@
7
7
  * enrichment at all).
8
8
  */
9
9
  import type { Context } from '@deepseek-ai/cordis';
10
- import { type SkillLibrary } from '@lmzhen/dsh-evolution-core';
10
+ import { type SkillLibrary, type SkillLiveness } from '@lmzhen/dsh-evolution-core';
11
11
  export interface Enrichment {
12
12
  descriptions: ReadonlyMap<string, string>;
13
13
  supportFiles: ReadonlyMap<string, readonly string[]>;
@@ -17,6 +17,16 @@ export interface Enrichment {
17
17
  protected: ReadonlyMap<string, string>;
18
18
  /** Skills whose frontmatter the strict-YAML platform catalog cannot load. */
19
19
  catalogInvalid: ReadonlyMap<string, boolean>;
20
+ /** Per-support-file read counts (design §5.5) — present only where the usage
21
+ * sidecar recorded at least one read, so absence stays "no evidence". */
22
+ demand: ReadonlyMap<string, Readonly<Record<string, number>>>;
23
+ /** Idle age per skill (design §5.6) — present only for skills with a usage
24
+ * record, so a skill the sidecar never saw stays "no age evidence". */
25
+ liveness: ReadonlyMap<string, SkillLiveness>;
26
+ /** Support-file char counts for files big enough to possibly exceed the cap
27
+ * (design §16.6) — present only where at least one such file exists, so the
28
+ * common measured-and-clean case adds no field. */
29
+ supportChars: ReadonlyMap<string, Readonly<Record<string, number>>>;
20
30
  }
21
31
  export declare function buildEnrichment(ctx: Context, library: SkillLibrary): Promise<Enrichment>;
22
32
  //# sourceMappingURL=enrichment.d.ts.map
@@ -4,7 +4,7 @@
4
4
  * text. Deterministic parts stay pure; the subagent call is the only
5
5
  * external dependency (injected, fake-able in tests).
6
6
  */
7
- import { type DriftReport, type DriftSkillSnapshot } from '@lmzhen/dsh-evolution-core';
7
+ import { type DriftReport, type DriftSkillSnapshot, type SkillLiveness } from '@lmzhen/dsh-evolution-core';
8
8
  import type { Agent } from '@deepseek-ai/dsh-agent';
9
9
  import { type SkillLibraryLike } from './drift-scan.ts';
10
10
  /** I-5 (v37): the platform's `SubagentStartRequest` fields this orchestrator sends.
@@ -98,6 +98,12 @@ export interface MaintainOptions {
98
98
  protected?: () => ReadonlyMap<string, string>;
99
99
  catalogInvalid?: () => ReadonlyMap<string, boolean>;
100
100
  usageObserved?: () => boolean | undefined;
101
+ /** Per-support-file read counts per skill (design §5.5). */
102
+ demand?: () => ReadonlyMap<string, Readonly<Record<string, number>>>;
103
+ /** Idle age per skill (design §5.6). */
104
+ liveness?: () => ReadonlyMap<string, SkillLiveness>;
105
+ /** Support-file char counts for files that can exceed the cap (design §16.6). */
106
+ supportChars?: () => ReadonlyMap<string, Readonly<Record<string, number>>>;
101
107
  }
102
108
  export interface MaintainOutcome {
103
109
  ok: boolean;
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.4.1",
4
+ "version": "0.6.0",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -31,7 +31,7 @@
31
31
  "license": "MIT",
32
32
  "dependencies": {
33
33
  "@deepseek-ai/schemastery": "^3.18.1",
34
- "@lmzhen/dsh-evolution-core": "^0.4.1"
34
+ "@lmzhen/dsh-evolution-core": "^0.6.0"
35
35
  },
36
36
  "peerDependencies": {
37
37
  "@deepseek-ai/dsh-agent": "^0.1.5-rc.2",