@lmzhen/dsh-evolution-commands 0.3.73 → 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 CHANGED
@@ -19,7 +19,7 @@ The injection goes through the agent's waking primitive (`followup`, falling bac
19
19
  to `inject` when the host lacks it), **called on the agent instance**: the
20
20
  platform's `Agent.followup` is a prototype method (`this.send(...)`), so a
21
21
  detached reference throws and queues nothing (0.3.73 fix; the same shape is
22
- pinned by `evolution-host/tests/wake-delivery-guard.spec.ts`).
22
+ pinned by rule N13b in `packages/scripts/verify-arch-guards.mjs`, which masks comments and string literals before matching and self-tests its detector at startup).
23
23
 
24
24
  #### KV Cache effect
25
25
 
@@ -29,3 +29,5 @@ Independent of request-prefix construction. This package does not alter the asse
29
29
 
30
30
 
31
31
  - No known durable consumer gaps at this time. Runtime contracts are covered by package and boundary tests.
32
+
33
+ **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).
package/lib/index.js CHANGED
@@ -2,7 +2,7 @@ import { createRequire } from "node:module";
2
2
  import z from "@deepseek-ai/schemastery";
3
3
  import { effectiveSessionPolicy } from "@lmzhen/dsh-evolution-approval";
4
4
  import { createUserMessage } from "@deepseek-ai/dsh-llm";
5
- import { MAX_TIMER_DELAY_MS, SkillLibrary, appendEvolutionEvent, assertSkillsRootAliasRetired, buildLearnPrompt, clampedNumber, composePresetComposition, eventsFile, evolutionRoot, resolveRootConfig, resolveSkillsRoot } from "@lmzhen/dsh-evolution-core";
5
+ import { MAX_TIMER_DELAY_MS, appendEvolutionEvent, assertSkillsRootAliasRetired, buildLearnPrompt, clampedNumber, composePresetComposition, eventsFile, evolutionRoot, newSkillLibrary, resolveRootConfig } from "@lmzhen/dsh-evolution-core";
6
6
  import { buildEnrichment, buildMaintainFacts, runMaintain, snapshotFromLibrary } from "@lmzhen/dsh-evolution-maintenance";
7
7
  import { copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
8
8
  import { dirname, join } from "node:path";
@@ -46,8 +46,17 @@ function collectEvolutionBundles(home, onReadError) {
46
46
  }
47
47
  for (const profile of profileEntries) {
48
48
  if (!profile.isDirectory()) continue;
49
+ if (profile.name === "node_modules" || profile.name.startsWith(".")) continue;
50
+ let raw;
49
51
  try {
50
- const manifest = JSON.parse(readFileSync(join(profilesDir, profile.name, "package.json"), "utf8"));
52
+ raw = readFileSync(join(profilesDir, profile.name, "package.json"), "utf8");
53
+ } catch (error) {
54
+ if (error?.code === "ENOENT") continue;
55
+ onReadError?.(error, "profile-manifest");
56
+ continue;
57
+ }
58
+ try {
59
+ const manifest = JSON.parse(raw);
51
60
  for (const name of manifest.dsh?.profile?.bundles ?? []) if (EVOLUTION_BUNDLE_TAILS.has(tailOf(name))) bundles.push(name);
52
61
  } catch (error) {
53
62
  onReadError?.(error, "profile-manifest");
@@ -66,6 +75,33 @@ function envIssues() {
66
75
  if (query && !SESSION_QUERY_MODES.has(query)) issues.push(`DSH_EVOLUTION_SESSION_QUERY="${query}" is not one of startup|first-search|never — the patch normalizes it to 'startup' silently; set it to a listed mode.`);
67
76
  return issues;
68
77
  }
78
+ /** S0.2 (v37 P0-1): memory files that still carry the platform's `{{` prompt
79
+ * variable syntax. The platform interpolates EVERY `systemPrompt.context` text
80
+ * once per model step and throws on an unknown/malformed reference, so one such
81
+ * entry used to fail every turn of every session under this home. Current
82
+ * builds neutralize the injected text (`MemoryStore.renderContext`), which is
83
+ * why this is a residual-risk report instead of a hard failure: an older
84
+ * still-running process is still bricked, and an operator may prefer to clean
85
+ * the file. Read-only; an unreadable file is skipped (the doctor never throws).
86
+ * @param home - resolved DSH_HOME.
87
+ * @returns one message per affected memory file.
88
+ */
89
+ function memoryInterpolationIssues(home) {
90
+ const issues = [];
91
+ for (const file of ["MEMORY.md", "USER.md"]) {
92
+ const path = join(home, "memories", file);
93
+ if (!existsSync(path)) continue;
94
+ let raw;
95
+ try {
96
+ raw = readFileSync(path, "utf8");
97
+ } catch {
98
+ continue;
99
+ }
100
+ const hits = raw.split("{{").length - 1;
101
+ if (hits > 0) issues.push(`${file}: ${hits} "{{" occurrence(s) — the platform interpolates this syntax in prompt context and throws on an unknown reference, so a build without the render-time neutralization fails every later turn; rewrite the affected entries`);
102
+ }
103
+ return issues;
104
+ }
69
105
  /**
70
106
  * Diagnose the deployment. `ctx` supplies service presence (a bare stub with
71
107
  * only `get` is enough); `home` defaults to the evolution root so tests can
@@ -110,8 +146,10 @@ async function diagnose(ctx, options = {}) {
110
146
  }
111
147
  const installForm = full ? "full" : preset ? "preset" : layered ? "layered" : host ? "host" : presetDirInstalled ? "preset-only" : "none";
112
148
  const mountConflicts = conflicts.filter((row) => !row.includes("DEGRADED"));
149
+ const undecidable = mountConflicts.length !== conflicts.length;
113
150
  const actions = [];
114
151
  if (mountConflicts.length > 0) actions.push("Resolve the conflict first: keep exactly one of evolution-all / evolution-host / evolution-preset / layered.");
152
+ else if (undecidable) actions.push("The install form is UNDECIDABLE (see the DEGRADED row above): an unreadable profiles directory or profile manifest hides whatever is installed, so this report must not add or remove a bundle. Fix the reported read failure, then re-run /evolution doctor.");
115
153
  else if (installForm === "none") actions.push("Install the default full bundle: dsh plugin --profile web add @lmzhen/dsh-evolution-all");
116
154
  else if (installForm === "preset-only") actions.push("The Evolution preset is delivered but no profile mounts an evolution bundle — re-add @lmzhen/dsh-evolution-host for the layered layout (do NOT add all on top of the preset: that double-mounts the model rows), or remove .agent-presets/evolution if the layered layout is no longer wanted.");
117
155
  else if (installForm === "layered") actions.push("Model tools follow the Evolution preset per session; add @lmzhen/dsh-evolution-all instead if every session should have them.");
@@ -119,12 +157,15 @@ async function diagnose(ctx, options = {}) {
119
157
  if (env.length > 0) actions.push("Fix the DSH_EVOLUTION_* variable listed above.");
120
158
  if (services.review && !services.curator) actions.push("Curator service is not mounted — automatic curation is off; verify the host/all bundle row set is complete.");
121
159
  if (pendingCount === null && services.approval) actions.push("Approval service is mounted but pending listing failed — check the evolution state service.");
160
+ const memoryIssues = memoryInterpolationIssues(home);
161
+ if (memoryIssues.length > 0) actions.push("Rewrite the memory entries listed above (or run a build with the render-time neutralization) — they broke prompt assembly on older builds.");
122
162
  if ((executingCount ?? 0) > 0) actions.push(`${executingCount} staged write(s) are EXECUTING (an approve crashed mid-run — or one is still in flight). Inspect with /evolution pending: if you started the approve, verify the landed write and do not reject it; only reject after verifying no write is intended.`);
123
163
  return {
124
164
  installForm,
125
165
  bundles,
126
166
  conflicts,
127
167
  envIssues: env,
168
+ memoryIssues,
128
169
  services,
129
170
  pendingCount,
130
171
  executingCount,
@@ -141,6 +182,7 @@ function renderDoctorText(report) {
141
182
  ];
142
183
  if (report.conflicts.length > 0) lines.push("conflicts:", ...report.conflicts.map((line) => ` ! ${line}`));
143
184
  if (report.envIssues.length > 0) lines.push("env:", ...report.envIssues.map((line) => ` ! ${line}`));
185
+ if (report.memoryIssues.length > 0) lines.push("memory:", ...report.memoryIssues.map((line) => ` ! ${line}`));
144
186
  if (report.actions.length > 0) lines.push("next steps:", ...report.actions.map((line) => ` → ${line}`));
145
187
  return lines.join("\n");
146
188
  }
@@ -200,8 +242,8 @@ const COMMAND_ENTRIES = [
200
242
  summary: "run a maintenance scan (--facts: 0-token preview)"
201
243
  },
202
244
  {
203
- usage: "preset install",
204
- summary: "generate the Evolution agent preset into the user root"
245
+ usage: "preset install [--base <name>]",
246
+ summary: "generate the Evolution agent preset into the user root (bases from the agent package's bases.json)"
205
247
  },
206
248
  {
207
249
  usage: "restructure <name> \"<heading>\" <to_file> [--plan <runId>]",
@@ -350,11 +392,15 @@ function apply(ctx, rawConfig = {}) {
350
392
  const reportAny = report;
351
393
  const nameOf = (item) => typeof item === "object" && item !== null && typeof item.name === "string" ? item.name : "?";
352
394
  const reasonOf = (item) => typeof item === "object" && item !== null && typeof item.reason === "string" ? item.reason : "?";
395
+ const aborted = typeof reportAny.aborted === "string" ? reportAny.aborted : void 0;
396
+ const unattributed = Array.isArray(reportAny.unattributed) ? reportAny.unattributed.length : 0;
353
397
  return ok([
354
398
  `runId=${reportAny.runId}`,
355
399
  `startedAt=${reportAny.startedAt}`,
356
400
  `archived=${reportAny.archived.map(nameOf).join(", ") || "(none)"}`,
357
- `failed=${reportAny.failed.map((item) => `${nameOf(item)}: ${reasonOf(item)}`).join(", ") || "(none)"}`
401
+ `failed=${reportAny.failed.map((item) => `${nameOf(item)}: ${reasonOf(item)}`).join(", ") || "(none)"}`,
402
+ ...aborted === void 0 ? [] : [`aborted=${aborted}`],
403
+ ...unattributed === 0 ? [] : [`unattributed=${unattributed}`]
358
404
  ].join("\n"));
359
405
  }
360
406
  if (input === "restore" || input.startsWith("restore ")) {
@@ -433,7 +479,10 @@ function apply(ctx, rawConfig = {}) {
433
479
  if (input === "maintain --facts") {
434
480
  const ioRegistry = ctx.get("evolutionIo");
435
481
  if (!ioRegistry) return err("Evolution IO registry not mounted — maintenance facts unavailable.");
436
- const library = new SkillLibrary(resolveSkillsRoot({ root: skillsRootValue }), ioRegistry.provider());
482
+ const library = newSkillLibrary({
483
+ config: { root: skillsRootValue },
484
+ io: ioRegistry.provider()
485
+ });
437
486
  const enrichment = await buildEnrichment(ctx, library);
438
487
  const { facts } = buildMaintainFacts(await snapshotFromLibrary(library, {
439
488
  descriptions: enrichment.descriptions,
@@ -458,7 +507,10 @@ function apply(ctx, rawConfig = {}) {
458
507
  if (!subagents) return err("Subagents service not mounted — maintenance scan unavailable.");
459
508
  try {
460
509
  maintainInFlightSince = Date.now();
461
- const library = new SkillLibrary(resolveSkillsRoot({ root: skillsRootValue }), ioRegistry.provider());
510
+ const library = newSkillLibrary({
511
+ config: { root: skillsRootValue },
512
+ io: ioRegistry.provider()
513
+ });
462
514
  const enrichment = await buildEnrichment(ctx, library);
463
515
  const outcome = await runMaintain({
464
516
  library,
@@ -501,7 +553,9 @@ function apply(ctx, rawConfig = {}) {
501
553
  }
502
554
  }
503
555
  if (/^maintain\b/.test(input)) return err("Unknown maintain arguments: expected bare `maintain`, `maintain --timeout <ms>` or `maintain --timeout=<ms>` (a positive integer). Got: " + input);
504
- if (input === "preset install") {
556
+ const presetInstall = /^preset install(?: --base (\S+))?$/.exec(input);
557
+ if (presetInstall !== null || /^preset\b/.test(input)) {
558
+ if (presetInstall === null) return err("Unknown preset arguments: expected `preset install` or `preset install --base <name>` (the names live in the agent package's bases.json). Got: " + input);
505
559
  {
506
560
  let installedBundles;
507
561
  try {
@@ -515,26 +569,30 @@ function apply(ctx, rawConfig = {}) {
515
569
  const presetBundle = installedBundles.find((name) => name === "dsh-evolution-preset" || name.endsWith("/dsh-evolution-preset"));
516
570
  if (allBundle || presetBundle) return err(`Refusing to install the layered Evolution preset: "${allBundle ?? presetBundle}" is installed in this home — its model rows (tool-memory / tool-skill-manage / tool-session-query / skill-catalog) would double-mount with the preset's and the profile fails loud at startup. Keep ONE: remove the ${allBundle ? "evolution-all bundle" : "evolution-preset bundle"} before installing the layered preset (see /evolution doctor).`);
517
571
  }
518
- const target = join(evolutionRoot(), ".agent-presets", "evolution");
519
572
  try {
520
573
  const source = resolveAgentPresetDir(import.meta.url);
574
+ const table = readAgentPresetBases(source);
575
+ const requested = presetInstall[1] ?? table.defaultBase;
576
+ const base = table.entries.find((entry) => entry.name === requested);
577
+ if (base === void 0) return err(`Unknown agent-preset base "${requested}" — ${join(source, "bases.json")} lists ${table.entries.map((entry) => entry.name).join(", ")}`);
578
+ const target = join(evolutionRoot(), ".agent-presets", base.id);
521
579
  const deltaPath = join(source, "agent.cordis.yml");
522
- const presetPath = join(source, "preset.yml");
580
+ const presetPath = join(source, base.metadata);
523
581
  if (!existsSync(deltaPath) || !existsSync(presetPath)) return err(`Preset file missing from ${source} — is the dsh-evolution-agent-preset package installed?`);
524
582
  const registry = ctx.get("agentPresets");
525
583
  if (!registry) return err("Agent preset registry not mounted — cannot compose the Evolution preset against the runtime standard.");
526
- const composition = composePresetComposition(await registry.read("standard"), readFileSync(deltaPath, "utf8"));
584
+ const composition = composePresetComposition(await registry.read(base.name), readFileSync(deltaPath, "utf8"));
527
585
  mkdirSync(target, { recursive: true });
528
586
  atomicWriteFiles(target, [{
529
587
  name: "agent.cordis.yml",
530
588
  content: composition
531
589
  }, {
532
- name: "preset.yml",
590
+ name: base.metadata,
533
591
  content: readFileSync(presetPath)
534
592
  }], void 0, (message) => {
535
593
  ctx.logger.warn(message);
536
594
  });
537
- return ok(`Evolution agent preset installed to ${target} (runtime standard + delta). Restart the session switcher to select it.`);
595
+ return ok(`Evolution agent preset installed to ${target} (runtime ${base.name} + delta). Restart the session switcher to select it.`);
538
596
  } catch (error) {
539
597
  return err(`Preset install failed: ${error instanceof Error ? error.message : String(error)}`);
540
598
  }
@@ -577,9 +635,12 @@ function apply(ctx, rawConfig = {}) {
577
635
  });
578
636
  if (decision.action === "staged") return ok(`${decision.message}${planRunId ? `\n[audit] plan=${planRunId}` : ""}`);
579
637
  }
580
- const result = await new SkillLibrary(resolveSkillsRoot({ root: skillsRootValue }), ioRegistry.provider(), void 0, (event) => {
581
- ctx.emit("evolution/skill-mutated", event);
582
- }, void 0, config.threatExemptLabels ?? []).restructure(name, [{
638
+ const result = await newSkillLibrary({
639
+ config: { root: skillsRootValue },
640
+ io: ioRegistry.provider(),
641
+ ctx,
642
+ threatExemptLabels: config.threatExemptLabels
643
+ }).restructure(name, [{
583
644
  heading,
584
645
  toFile
585
646
  }], "foreground");
@@ -615,6 +676,31 @@ function apply(ctx, rawConfig = {}) {
615
676
  * name (`evolution-agent` vs `@lmzhen/dsh-evolution-agent-preset` — the rc.44
616
677
  * dir≠name trap), so BOTH sibling shapes are probed.
617
678
  */
679
+ /** The agent-preset base table (evolution-agent/bases.json). The installer
680
+ * (install-layered.mjs) reads the SAME file — the literal that used to live in
681
+ * each of them is what kept this path on `standard` while the installer knew
682
+ * `ptc` too. Fails loud: a missing table is an incomplete install, never a
683
+ * silent fallback to the default base. */
684
+ function readAgentPresetBases(source) {
685
+ const path = join(source, "bases.json");
686
+ const parsed = JSON.parse(readFileSync(path, "utf8"));
687
+ if (!Array.isArray(parsed.bases) || parsed.bases.length === 0) throw new Error(`${path} carries no bases[]`);
688
+ const entries = parsed.bases.map((raw) => {
689
+ const entry = raw;
690
+ if (typeof entry.name !== "string" || typeof entry.id !== "string" || typeof entry.metadata !== "string") throw new Error(`${path} entry ${JSON.stringify(raw)} needs name/id/metadata strings`);
691
+ return {
692
+ name: entry.name,
693
+ id: entry.id,
694
+ metadata: entry.metadata
695
+ };
696
+ });
697
+ const declared = parsed.default;
698
+ if (typeof declared !== "string" || !entries.some((entry) => entry.name === declared)) throw new Error(`${path} default ${JSON.stringify(declared)} is not one of ${entries.map((entry) => entry.name).join(", ")}`);
699
+ return {
700
+ defaultBase: declared,
701
+ entries
702
+ };
703
+ }
618
704
  function resolveAgentPresetDir(importMetaUrl) {
619
705
  const overlayCandidate = join(dirname(fileURLToPath(importMetaUrl)), "..", "..", "evolution-agent");
620
706
  if (existsSync(join(overlayCandidate, "agent.cordis.yml")) && existsSync(join(overlayCandidate, "preset.yml"))) return overlayCandidate;
@@ -14,6 +14,12 @@ export interface DoctorReport {
14
14
  bundles: string[];
15
15
  conflicts: string[];
16
16
  envIssues: string[];
17
+ /** S0.2 (v37 P0-1): memory files carrying the platform's `{{` prompt-variable
18
+ * syntax. Residual-risk detector — current builds neutralize the INJECTED text
19
+ * (`MemoryStore.renderContext`), but a still-running older build fails every
20
+ * pre-step of every session under this home, and an operator may want to clean
21
+ * the file regardless. */
22
+ memoryIssues: string[];
17
23
  services: {
18
24
  review: boolean;
19
25
  curator: boolean;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@lmzhen/dsh-evolution-commands",
3
3
  "description": "Human commands for the evolution family (/evolution pending|curator|maintain|doctor) (community build)",
4
- "version": "0.3.73",
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
  "./package.json": "./package.json"
26
22
  },
27
23
  "files": [
@@ -31,21 +27,19 @@
31
27
  "license": "MIT",
32
28
  "dependencies": {
33
29
  "@deepseek-ai/schemastery": "^3.18.1",
34
- "@lmzhen/dsh-evolution-approval": "^0.3.73",
35
- "@lmzhen/dsh-evolution-core": "^0.3.73",
36
- "@lmzhen/dsh-evolution-maintenance": "^0.3.73"
30
+ "@lmzhen/dsh-evolution-approval": "^0.3.76",
31
+ "@lmzhen/dsh-evolution-core": "^0.3.76",
32
+ "@lmzhen/dsh-evolution-maintenance": "^0.3.76"
37
33
  },
38
34
  "optionalDependencies": {
39
- "@lmzhen/dsh-evolution-agent-preset": "^0.3.73"
35
+ "@lmzhen/dsh-evolution-agent-preset": "^0.3.76"
40
36
  },
41
37
  "peerDependencies": {
42
38
  "@deepseek-ai/cordis": "^4.0.1",
43
39
  "@deepseek-ai/dsh-commands": "^0.1.5-rc.2",
44
- "@deepseek-ai/dsh-invariants": "^0.1.5-rc.2",
45
40
  "@deepseek-ai/dsh-llm": "^0.1.5-rc.2"
46
41
  },
47
42
  "devDependencies": {
48
- "@deepseek-ai/dsh-commands": "^0.1.5-rc.2",
49
- "@deepseek-ai/dsh-invariants": "^0.1.5-rc.2"
43
+ "@deepseek-ai/dsh-commands": "^0.1.5-rc.2"
50
44
  }
51
45
  }
package/lib/invariant.js DELETED
@@ -1,8 +0,0 @@
1
- //#region lib/types/invariant.js
2
- const PACKAGE_NAME = "@lmzhen/dsh-evolution-commands";
3
- const name = "evolution-commands-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 };
@@ -1,5 +0,0 @@
1
- import type { Context } from '@deepseek-ai/cordis';
2
- export declare const name = "evolution-commands-invariant";
3
- export declare const inject: string[];
4
- export declare const apply: (ctx: Context) => Promise<() => void>;
5
- //# sourceMappingURL=invariant.d.ts.map