@lmzhen/dsh-evolution-maintenance 0.26.15 → 0.26.26

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/lib/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { a as snapshotFromLibrary, i as enrichmentSnapshotOptions, n as computeProbe, r as buildEnrichment, t as PROBE_SIGNALS } from "./probe-Bojb0piw.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, isRecord, redactSecrets, verifyPromptBundle } from "@lmzhen/dsh-evolution-core";
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, STORE_LEDGER, computeDriftSignals, evolutionHome, findDriftSignal, isRecord, redactSecrets, verifyPromptBundle } from "@lmzhen/dsh-evolution-core";
3
3
  import { createHash, randomUUID } from "node:crypto";
4
+ import { join } from "node:path";
4
5
  //#region lib/types/render-facts.js
5
6
  /**
6
7
  * Mechanical-facts rendering for the maintenance subagent (011 §6).
@@ -23,6 +24,19 @@ function renderFacts(report, options) {
23
24
  lines.push(`# skill=${sanitize(skill.name)} (${meta.join(" ")})`);
24
25
  for (const signal of skill.signals) lines.push(...renderSignal(signal, redact));
25
26
  }
27
+ for (const store of options.footprint ?? []) {
28
+ const parts = [
29
+ `${store.used === void 0 || store.skipped !== void 0 ? "[UNKNOWN]" : "[FACT]"} signal=store/${sanitize(store.id)}`,
30
+ `unit=${sanitize(store.unit)}`,
31
+ `bound=${sanitize(store.bound)}`,
32
+ `used=${store.used === void 0 ? "unknown" : String(store.used)}`,
33
+ `lastSweep=${sanitize(store.lastSweep ?? "never")}`,
34
+ `removed=${store.removed === void 0 ? "unknown" : String(store.removed)}`
35
+ ];
36
+ if (store.skipped !== void 0) parts.push(`skipped=${sanitize(store.skipped)}`);
37
+ parts.push(`verdict=${store.used === void 0 || store.skipped !== void 0 ? "unknown" : "fact"}`);
38
+ lines.push(parts.join(" "));
39
+ }
26
40
  lines.push("<<<END FACTS>>>");
27
41
  return lines.join("\n");
28
42
  }
@@ -285,6 +299,124 @@ function validateAndNormalizeMaintainPlan(raw, report, validSignals) {
285
299
  };
286
300
  }
287
301
  //#endregion
302
+ //#region lib/types/footprint.js
303
+ /**
304
+ * G3/3.3: the footprint reading for one maintenance run.
305
+ *
306
+ * One line per REGISTERED store (STORE_LEDGER), never a line per store that happened to be readable: the
307
+ * auditor must be able to tell "measured zero" from "not measured" (I3: not-done must never read as
308
+ * nothing-to-do). This first cut reads the stores whose artifact needs only the home-relative path the
309
+ * runtime already knows; everything else renders UNKNOWN with the reason, so the block is honest today
310
+ * and becomes informative as each reader lands.
311
+ */
312
+ /**
313
+ * Read what can be read, and say so for everything else.
314
+ * @param io - the runtime io, or undefined when the caller has none (every store then reads UNKNOWN).
315
+ * @returns one reading per registered store, in ledger order.
316
+ */
317
+ async function reclamationFootprint(io) {
318
+ const out = [];
319
+ for (const store of STORE_LEDGER) {
320
+ if (io === void 0) {
321
+ out.push({
322
+ id: store.id,
323
+ unit: store.unit,
324
+ bound: store.bound,
325
+ skipped: "the maintenance runtime has no io; nothing was measured"
326
+ });
327
+ continue;
328
+ }
329
+ if (store.id === "reports") {
330
+ try {
331
+ const names = await io.list(join(evolutionHome(), "reports"));
332
+ out.push({
333
+ id: store.id,
334
+ unit: store.unit,
335
+ bound: store.bound,
336
+ used: names.length,
337
+ lastSweep: "see the curator run end",
338
+ removed: void 0
339
+ });
340
+ } catch {
341
+ out.push({
342
+ id: store.id,
343
+ unit: store.unit,
344
+ bound: store.bound,
345
+ skipped: "the reports directory could not be listed"
346
+ });
347
+ }
348
+ continue;
349
+ }
350
+ out.push({
351
+ id: store.id,
352
+ unit: store.unit,
353
+ bound: store.bound,
354
+ skipped: "no reader yet for this store"
355
+ });
356
+ }
357
+ return out;
358
+ }
359
+ /**
360
+ * G3/3.4: the three reachability readings, folded into the footprint channel so the report has ONE shape.
361
+ *
362
+ * Paths come from the caller: the history root and the snapshot root live under the skills root, and the
363
+ * reports directory under the evolution home. A runtime that does not state its skills root gets UNKNOWN
364
+ * for the two rooted domains instead of a guess - the same three-state rule the rest of this file uses.
365
+ * @param io - the runtime io, or undefined when the caller has none.
366
+ * @param skillRoot - the configured skills root, or undefined when the runtime does not state one.
367
+ */
368
+ async function reachabilityFootprint(io, skillRoot) {
369
+ const domains = [
370
+ "history.blobs",
371
+ "reports.files",
372
+ "backups.trees"
373
+ ];
374
+ if (io === void 0) return domains.map((domain) => ({
375
+ id: domain,
376
+ unit: "files",
377
+ bound: "unmeasured",
378
+ skipped: "the maintenance runtime has no io; nothing was measured"
379
+ }));
380
+ const { historyReachability, reportsReachability, backupsReachability } = await import("@lmzhen/dsh-evolution-core");
381
+ const out = [];
382
+ if (skillRoot === void 0) for (const domain of ["history.blobs", "backups.trees"]) out.push({
383
+ id: domain,
384
+ unit: "files",
385
+ bound: "unmeasured",
386
+ skipped: "the runtime states no skills root; the reading was not attempted"
387
+ });
388
+ else {
389
+ const history = await historyReachability(io, join(skillRoot, ".history"));
390
+ out.push({
391
+ id: history.domain,
392
+ unit: history.unit,
393
+ bound: history.baseline,
394
+ used: history.unreachable,
395
+ lastSweep: "this run",
396
+ removed: void 0
397
+ });
398
+ const backups = await backupsReachability(io, join(skillRoot, ".backups"), []);
399
+ out.push({
400
+ id: backups.domain,
401
+ unit: backups.unit,
402
+ bound: backups.baseline,
403
+ used: backups.unreachable,
404
+ lastSweep: "this run",
405
+ removed: void 0
406
+ });
407
+ }
408
+ const reports = await reportsReachability(io, join(evolutionHome(), "reports"), [], []);
409
+ out.push({
410
+ id: reports.domain,
411
+ unit: reports.unit,
412
+ bound: reports.baseline,
413
+ used: reports.unreachable,
414
+ lastSweep: "this run",
415
+ removed: void 0
416
+ });
417
+ return out;
418
+ }
419
+ //#endregion
288
420
  //#region lib/types/orchestrate.js
289
421
  /**
290
422
  * Maintain orchestration (011 §3/§7): snapshot → drift signals → facts
@@ -336,7 +468,7 @@ function jointSignature(template, signalsVersion) {
336
468
  * rendered facts block (joint signature + redaction). Shared by `runMaintain`
337
469
  * and the `--facts` preview so the preview can never disagree with the scan.
338
470
  */
339
- function buildMaintainFacts(snapshots, usageObserved, redact) {
471
+ function buildMaintainFacts(snapshots, usageObserved, redact, footprint) {
340
472
  const report = computeDriftSignals(snapshots.map((snapshot) => ({
341
473
  ...snapshot,
342
474
  usageObserved
@@ -348,7 +480,8 @@ function buildMaintainFacts(snapshots, usageObserved, redact) {
348
480
  facts: renderFacts(report, {
349
481
  signalsVersion,
350
482
  signature,
351
- redact
483
+ redact,
484
+ footprint
352
485
  }),
353
486
  signalsVersion,
354
487
  signature
@@ -430,7 +563,8 @@ async function runMaintain(runtime, options = {}) {
430
563
  text: "维护扫描:技能库为空,无需处理。"
431
564
  };
432
565
  }
433
- const { facts, report, signalsVersion, signature } = buildMaintainFacts(snapshots, enrichment?.usageObservedValue, options.redact);
566
+ const footprint = [...await reclamationFootprint(runtime.io), ...await reachabilityFootprint(runtime.io, runtime.skillRoot)];
567
+ const { facts, report, signalsVersion, signature } = buildMaintainFacts(snapshots, enrichment?.usageObservedValue, options.redact, footprint);
434
568
  const template = renderMaintainTemplate(MAINTAIN_PROMPT, PROMPT_BUNDLE_ID, signalsVersion, signature);
435
569
  const probeRegistered = runtime.tools?.get("maintenance_probe") != null;
436
570
  const probeDegraded = options.toolAllow === void 0 && !probeRegistered;
package/lib/tools.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { a as snapshotFromLibrary, i as enrichmentSnapshotOptions, n as computeProbe, r as buildEnrichment, t as PROBE_SIGNALS } from "./probe-Bojb0piw.js";
2
- import { DEFAULT_PROBE_TIMEOUT_MS, MAX_TIMER_DELAY_MS, assertSkillsRootAliasRetired, clampedNumber, newSkillLibrary, redactSecrets, resolveRootConfig } from "@lmzhen/dsh-evolution-core";
2
+ import { DEFAULT_PROBE_TIMEOUT_MS, MAX_TIMER_DELAY_MS, assertSkillsRootAliasRetired, authorityFrom, clampedNumber, 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
@@ -69,6 +69,7 @@ function apply(ctx, rawConfig = {}) {
69
69
  ...target ? { target } : {}
70
70
  };
71
71
  const library = newSkillLibrary({
72
+ authority: authorityFrom({ origin: "foreground" }),
72
73
  config: rootConfig,
73
74
  io: ioRegistry.provider()
74
75
  });
@@ -0,0 +1,30 @@
1
+ import type { StoreFootprint } from './render-facts.ts';
2
+ import type { ReachabilityIo } from '@lmzhen/dsh-evolution-core';
3
+ /**
4
+ * The io surface this reading needs; a runtime without it yields UNKNOWN for every store.
5
+ *
6
+ * It EXTENDS the reachability read surface instead of restating it: this module drives those readings,
7
+ * so the reads they need are the reads it needs. Restating them here is what produced the same
8
+ * "argument not assignable" error three times - the surface now has ONE owner (`ReachabilityIo`) and this
9
+ * adds only the probe the report sweep uses on top.
10
+ */
11
+ export interface FootprintIo extends ReachabilityIo {
12
+ mtime?(path: string): Promise<number | null>;
13
+ }
14
+ /**
15
+ * Read what can be read, and say so for everything else.
16
+ * @param io - the runtime io, or undefined when the caller has none (every store then reads UNKNOWN).
17
+ * @returns one reading per registered store, in ledger order.
18
+ */
19
+ export declare function reclamationFootprint(io: FootprintIo | undefined): Promise<StoreFootprint[]>;
20
+ /**
21
+ * G3/3.4: the three reachability readings, folded into the footprint channel so the report has ONE shape.
22
+ *
23
+ * Paths come from the caller: the history root and the snapshot root live under the skills root, and the
24
+ * reports directory under the evolution home. A runtime that does not state its skills root gets UNKNOWN
25
+ * for the two rooted domains instead of a guess - the same three-state rule the rest of this file uses.
26
+ * @param io - the runtime io, or undefined when the caller has none.
27
+ * @param skillRoot - the configured skills root, or undefined when the runtime does not state one.
28
+ */
29
+ export declare function reachabilityFootprint(io: FootprintIo | undefined, skillRoot: string | undefined): Promise<StoreFootprint[]>;
30
+ //# sourceMappingURL=footprint.d.ts.map
@@ -6,42 +6,37 @@
6
6
  */
7
7
  import { type DriftReport, type DriftSkillSnapshot } from '@lmzhen/dsh-evolution-core';
8
8
  import type { Agent } from '@deepseek-ai/dsh-agent';
9
+ import type { SubagentResult, SubagentRun, SubagentStartRequest } from '@deepseek-ai/dsh-subagent';
9
10
  import { type SkillLibraryLike } from './drift-scan.ts';
10
11
  import { type Enrichment } from './enrichment.ts';
12
+ import { type StoreFootprint } from './render-facts.ts';
13
+ import { type FootprintIo } from './footprint.ts';
11
14
  import { type MaintainPlanItem } from './validate-plan.ts';
12
- /** I-5 (v37): the platform's `SubagentStartRequest` fields this orchestrator sends.
13
- * Typed locally (not imported) because the platform type lives behind the subagent row this
14
- * package does not depend on; the required `parent` and `signal` mirror the platform. */
15
- export interface SubagentStartRequestLike {
16
- readonly label: string;
17
- readonly prompt: readonly {
18
- readonly type: 'text';
19
- readonly text: string;
20
- }[];
21
- readonly parent: Agent;
22
- readonly signal: AbortSignal;
23
- readonly maxDepth: number;
24
- readonly agentOptions: Record<string, string>;
25
- readonly persona: string;
26
- readonly toolFilter: {
15
+ /**
16
+ * I-5 (v37), D-09 (G1/1.8): the platform types are IMPORTED, not copied. Each view below is DERIVED from
17
+ * the platform contract, so a field the platform renames fails the build here instead of drifting.
18
+ *
19
+ * The one thing this layer must add: `persona` and `toolFilter` are honoured at runtime (child-agent.ts
20
+ * installs a `deployment:persona` system-prompt section; the README documents toolFilter as applying the
21
+ * requested child tool restriction) but the CALLER-facing `SubagentStartRequest` does not declare them.
22
+ * They are therefore declared here, once, as an explicit extension instead of a hand-copied whole.
23
+ */
24
+ export type MaintainSubagentStart = SubagentStartRequest & {
25
+ readonly persona?: string | undefined;
26
+ readonly toolFilter?: {
27
27
  readonly allow: readonly string[];
28
- };
29
- readonly outputSchema: Record<string, unknown>;
30
- }
31
- /** I-5 (v37): the platform's `SubagentResult` fields the outcome mapping reads. The
32
- * index signature keeps a REAL platform result assignable (the platform type is
33
- * wider than these three fields); the fields below are the ones this code relies on. */
34
- export interface SubagentResultLike {
35
- readonly structured?: unknown;
36
- readonly diagnostic?: string;
37
- readonly stopReason: string;
38
- }
39
- /** I-5 (v37): the platform's `SubagentRun` contract the orchestrator owns — `dispose` is
40
- * NOT optional (the platform declares it required and every exit path here awaits it). */
41
- export interface SubagentRunLike {
42
- readonly result: Promise<SubagentResultLike>;
43
- dispose(): Promise<void>;
44
- }
28
+ } | undefined;
29
+ };
30
+ /** The `SubagentResult` fields the outcome mapping reads (the platform result is wider). */
31
+ /** The `SubagentResult` fields the outcome mapping reads. `diagnostic` is OPTIONAL here on purpose: the
32
+ * platform type declares it required, but this layer reads it defensively (the mapped outcome must work
33
+ * for a result that carries none), so the view states that narrower truth instead of inheriting the
34
+ * platform requirement. */
35
+ export type MaintainSubagentResult = Pick<SubagentResult, 'structured' | 'stopReason'> & {
36
+ readonly diagnostic?: string | undefined;
37
+ };
38
+ /** The platform run handle the orchestrator owns (`dispose` is required and every exit path awaits it). */
39
+ export type MaintainSubagentRun = SubagentRun;
45
40
  export interface MaintainRuntime {
46
41
  /** SkillLibrary-like reader for snapshot assembly. */
47
42
  library: SkillLibraryLike;
@@ -50,7 +45,7 @@ export interface MaintainRuntime {
50
45
  * request from `unknown` and `dispose` from optional to the platform's
51
46
  * always-present handle, so a future miss fails `tsc` instead of a runtime TypeError. */
52
47
  subagents: {
53
- start(kind: 'spawn', options: SubagentStartRequestLike): Promise<SubagentRunLike>;
48
+ start(kind: 'spawn', options: MaintainSubagentStart): Promise<MaintainSubagentRun>;
54
49
  };
55
50
  /** The spawning agent (platform `SubagentStartRequest.parent`, required: in-process
56
51
  * providers derive workspace, lineage and delegation depth from its durable session). */
@@ -83,6 +78,9 @@ export interface MaintainRuntime {
83
78
  * probe the scan keeps the previous empty-library answer. */
84
79
  rootExists?: (() => Promise<boolean>) | undefined;
85
80
  /** V27 M-02: the root the probe above refers to, for the error message. */
81
+ /** G3/3.3: the runtime io the footprint reading needs. Optional: a caller without it gets UNKNOWN for
82
+ * every registered store instead of an invented zero. */
83
+ io?: FootprintIo | undefined;
86
84
  skillRoot?: string | undefined;
87
85
  }
88
86
  export interface MaintainOptions {
@@ -138,7 +136,10 @@ export interface FactsBundle {
138
136
  * rendered facts block (joint signature + redaction). Shared by `runMaintain`
139
137
  * and the `--facts` preview so the preview can never disagree with the scan.
140
138
  */
141
- export declare function buildMaintainFacts(snapshots: ReadonlyArray<DriftSkillSnapshot>, usageObserved: boolean | undefined, redact: ((text: string) => string) | undefined): FactsBundle;
139
+ export declare function buildMaintainFacts(snapshots: ReadonlyArray<DriftSkillSnapshot>, usageObserved: boolean | undefined, redact: ((text: string) => string) | undefined,
140
+ /** G3/3.3: one reading per registered bounded store, produced at this run's reclamation point.
141
+ * Optional so the preview and any older caller keep the exact block they had. */
142
+ footprint?: readonly StoreFootprint[]): FactsBundle;
142
143
  /** Run one maintenance scan and return display text plus validation metadata. */
143
144
  export declare function runMaintain(runtime: MaintainRuntime, options?: MaintainOptions): Promise<MaintainOutcome>;
144
145
  //# sourceMappingURL=orchestrate.d.ts.map
@@ -9,11 +9,27 @@
9
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
+ /** G3/3.3: what one footprint line reports about one registered store. */
13
+ export interface StoreFootprint {
14
+ readonly id: string;
15
+ readonly unit: string;
16
+ /** The reading, when one exists; undefined renders as UNKNOWN instead of an invented zero. */
17
+ readonly used?: number | undefined;
18
+ readonly bound: string;
19
+ readonly lastSweep?: string | undefined;
20
+ readonly removed?: number | undefined;
21
+ /** Set for a `manual` store that did not run: stated, never implied. */
22
+ readonly skipped?: string | undefined;
23
+ }
12
24
  export interface RenderFactsOptions {
13
25
  /** signals_version — changes whenever the signal set/thresholds change. */
14
26
  signalsVersion: string;
15
27
  /** Joint signature: sha256(template-text + signal-definitions) — shared with MAINTAIN_PROMPT head. */
16
28
  signature: string;
29
+ /** G3/3.3: one reading per registered bounded store (STORE_LEDGER). Absent ⇒ no footprint lines at
30
+ * all, so an older caller keeps the exact block it had; a store with no reading renders as UNKNOWN
31
+ * and a manual-trigger store that did not run says so — "not done" must never read as "nothing to do". */
32
+ footprint?: readonly StoreFootprint[] | undefined;
17
33
  /** Optional redactor; defaults to core `redactSecrets`. */
18
34
  redact?: ((text: string) => string) | undefined;
19
35
  }
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.26.15",
4
+ "version": "0.26.26",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -31,15 +31,17 @@
31
31
  "license": "MIT",
32
32
  "dependencies": {
33
33
  "@deepseek-ai/schemastery": "^3.18.4",
34
- "@lmzhen/dsh-evolution-core": "^0.26.15"
34
+ "@lmzhen/dsh-evolution-core": "^0.26.26"
35
35
  },
36
36
  "peerDependencies": {
37
- "@deepseek-ai/dsh-agent": "^0.2.0-rc.1",
38
37
  "@deepseek-ai/cordis": "^4.0.1",
38
+ "@deepseek-ai/dsh-agent": "^0.2.0-rc.1",
39
+ "@deepseek-ai/dsh-subagent": "^0.2.0-rc.1",
39
40
  "@deepseek-ai/dsh-tools": "^0.2.0-rc.1"
40
41
  },
41
42
  "devDependencies": {
42
43
  "@deepseek-ai/dsh-agent": "^0.2.0-rc.1",
44
+ "@deepseek-ai/dsh-subagent": "^0.2.0-rc.1",
43
45
  "@deepseek-ai/dsh-tools": "^0.2.0-rc.1"
44
46
  }
45
47
  }