@lmzhen/dsh-evolution-commands 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,10 +1,9 @@
1
1
  import { createRequire } from "node:module";
2
+ import { DEFAULT_SKILL_LIMITS, MAX_TIMER_DELAY_MS, PARAM_EXPOSURE, PARAM_GROUP_LABELS, absentHostServices, appendEvolutionEvent, applyNamespaceMigration, asRecord, assertSkillsRootAliasRetired, atomicWriteFiles, authorityFrom, buildLearnPrompt, canonicalWriteId, clampedNumber, classifyNamespaceMigration, composePresetEntry, elapsedSince, errorText, eventsFile, evolutionHome, evolutionRoot, isDeprecatedParamId, isPresent, isRecord, isUnknown, liveBool, mergePresetRow, newRunRegistry, newSkillLibrary, paramSettingsId, partitionVersions, planNamespaceMigration, policyStageLimits, presetPatchText, presetRowBlock, presetRowBody, presetRowId, probeText, reportsSweepLockTarget, resolveBasePresetPatch, resolveParamId, resolveRootConfig, scopedPackageName, scopedProbeReport, sweepReports, transactIo } from "@lmzhen/dsh-evolution-core";
2
3
  import z from "@deepseek-ai/schemastery";
3
4
  import { effectiveSessionPolicy } from "@lmzhen/dsh-evolution-approval";
4
5
  import { createUserMessage } from "@deepseek-ai/dsh-llm";
5
- import { DEFAULT_SKILL_LIMITS, MAX_TIMER_DELAY_MS, PARAM_EXPOSURE, PARAM_GROUP_LABELS, absentHostServices, appendEvolutionEvent, applyNamespaceMigration, asRecord, assertSkillsRootAliasRetired, atomicWriteFiles, buildLearnPrompt, canonicalWriteId, clampedNumber, classifyNamespaceMigration, composePresetEntry, elapsedSince, errorText, eventsFile, evolutionHome, evolutionRoot, isDeprecatedParamId, isPresent, isRecord, isUnknown, liveBool, mergePresetRow, newRunRegistry, newSkillLibrary, paramSettingsId, partitionVersions, planNamespaceMigration, policyStageLimits, presetPatchText, presetRowBlock, presetRowBody, presetRowId, probeText, reportsSweepLockTarget, resolveBasePresetPatch, resolveParamId, resolveRootConfig, scopedProbeReport, sweepReports, transactIo } from "@lmzhen/dsh-evolution-core";
6
6
  import { buildEnrichment, buildMaintainFacts, enrichmentSnapshotOptions, runMaintain, snapshotFromLibrary } from "@lmzhen/dsh-evolution-maintenance";
7
- import { preferProvider } from "@lmzhen/dsh-evolution-io";
8
7
  import { existsSync, readFileSync, readdirSync } from "node:fs";
9
8
  import { dirname, join } from "node:path";
10
9
  import { fileURLToPath } from "node:url";
@@ -53,15 +52,18 @@ async function runNamespaceMigration(run) {
53
52
  await settings.update(rowId, patch);
54
53
  };
55
54
  if (settings.describe === void 0 || settings.update === void 0) return NOTHING;
55
+ let seen;
56
56
  for (const source of legacySettingsPaths(run.home)) {
57
57
  const text = await run.io.readText(source);
58
58
  if (text === null) continue;
59
59
  const plan = planNamespaceMigration(text);
60
- if (plan.entries.length === 0) return {
61
- source,
62
- plan,
63
- report: void 0
64
- };
60
+ if (plan.entries.length === 0) {
61
+ seen ??= {
62
+ source,
63
+ plan
64
+ };
65
+ continue;
66
+ }
65
67
  return {
66
68
  source,
67
69
  plan,
@@ -72,7 +74,11 @@ async function runNamespaceMigration(run) {
72
74
  })
73
75
  };
74
76
  }
75
- return NOTHING;
77
+ return seen === void 0 ? NOTHING : {
78
+ source: seen.source,
79
+ plan: seen.plan,
80
+ report: void 0
81
+ };
76
82
  }
77
83
  /**
78
84
  * Read the legacy document and report what is still unmigrated.
@@ -86,11 +92,24 @@ async function runNamespaceMigration(run) {
86
92
  async function readLegacyDocumentState(run) {
87
93
  const settings = run.settings;
88
94
  if (settings?.describe === void 0) return void 0;
95
+ let seen;
89
96
  for (const source of legacySettingsPaths(run.home)) {
90
97
  const text = await run.io.readText(source);
91
98
  if (text === null) continue;
92
99
  const plan = planNamespaceMigration(text);
93
- const read = (rowId) => settings.describe?.({ redactSecrets: false }).find((entry) => entry.ns === rowId);
100
+ if (plan.entries.length === 0) {
101
+ seen ??= {
102
+ source,
103
+ plan
104
+ };
105
+ continue;
106
+ }
107
+ return stateOf(source, plan);
108
+ }
109
+ return seen === void 0 ? void 0 : stateOf(seen.source, seen.plan);
110
+ /** The doctor-facing state of ONE document, judged against the seat. */
111
+ function stateOf(source, plan) {
112
+ const read = (rowId) => settings?.describe?.({ redactSecrets: false }).find((entry) => entry.ns === rowId);
94
113
  const decided = classifyNamespaceMigration(plan, read);
95
114
  return {
96
115
  source,
@@ -657,7 +676,7 @@ function memoryInterpolationIssues(home) {
657
676
  * "unknown" — the doctor never throws.
658
677
  * Platform anchor: `packages/boot/app-boot/src/profile.ts` — a family preset is a ROW in the
659
678
  * profile's own patch layer there (`PROFILE_PATCH_FILENAME`), never a preset directory. */
660
- const AGENT_PRESET_PACKAGE = "@lmzhen/dsh-evolution-agent-preset";
679
+ const AGENT_PRESET_PACKAGE = scopedPackageName("dsh-evolution-agent-preset", import.meta.url);
661
680
  /** Resolve one asset the package declares in its exports map.
662
681
  * @param asset - bases.json or agent.cordis.yml.
663
682
  * @returns the file path, or null when the package is not installed. */
@@ -1368,10 +1387,20 @@ function flagsOf(name) {
1368
1387
  const entry = COMMAND_ENTRIES.find((candidate) => entryName(candidate) === name);
1369
1388
  return entry === void 0 ? [] : entryFlags(entry);
1370
1389
  }
1371
- /** C7 shape, DERIVED: the subcommand name is the first token of `usage` (variants use `|`). */
1390
+ /**
1391
+ * C7 shape, DERIVED: the entry IDENTITY key, and it is HIERARCHICAL (E-08).
1392
+ *
1393
+ * The key used to be the first token alone, and `flagsOf` finds an entry with `.find(...)` - the FIRST
1394
+ * match - so `maintain handoff` / `maintain cancel` and `skill restore` / `skill history` / `skill undo` /
1395
+ * `skill pin` all derived the SAME key as their parent and every flag lookup silently saw the parent entry
1396
+ * only (nine collisions). A bare parent (`maintain [--timeout]`) keeps its one-token key, so parent lookups
1397
+ * still resolve: the one call site that named an entry by a name the key no longer produced was the preset
1398
+ * dispatch branch, and it now asks for the entry own key (`preset install`).
1399
+ */
1372
1400
  function entryName(entry) {
1373
- const [head = ""] = entry.usage.split(" ");
1374
- return head.split("|")[0] ?? "";
1401
+ const [head = "", second = ""] = entry.usage.split(" ");
1402
+ const name = head.split("|")[0] ?? "";
1403
+ return /^[a-z][a-z-]*$/.test(second) && !second.startsWith("--") ? name + " " + second : name;
1375
1404
  }
1376
1405
  /** C7 shape, DERIVED: the flags a `usage` declares, in the form `--flag` (value flags keep their name). */
1377
1406
  function entryFlags(entry) {
@@ -1841,7 +1870,7 @@ function apply(ctx, rawConfig = {}) {
1841
1870
  const ioRegistry = ioCtx.get("evolutionIo");
1842
1871
  if (!ioRegistry) return;
1843
1872
  const registry = newRunRegistry({
1844
- io: preferProvider(ioRegistry, ["fs"]),
1873
+ io: ioRegistry.provider(),
1845
1874
  home: evolutionHome(),
1846
1875
  warn: (message) => {
1847
1876
  ctx.logger.warn(message);
@@ -2072,8 +2101,9 @@ function apply(ctx, rawConfig = {}) {
2072
2101
  const ioRegistry = ctx.get("evolutionIo");
2073
2102
  if (!ioRegistry) return err("未挂载 evolution-io 注册表:无法固定或取消固定技能。");
2074
2103
  const result = await newSkillLibrary({
2104
+ authority: authorityFrom({ origin: "foreground" }),
2075
2105
  config: { root: skillsRootValue },
2076
- io: preferProvider(ioRegistry, ["fs"]),
2106
+ io: ioRegistry.provider(),
2077
2107
  ctx,
2078
2108
  limits: {
2079
2109
  ...DEFAULT_SKILL_LIMITS,
@@ -2149,8 +2179,9 @@ function apply(ctx, rawConfig = {}) {
2149
2179
  const ioRegistry = ctx.get("evolutionIo");
2150
2180
  if (!ioRegistry) return err("未挂载 evolution-io 注册表:维护事实不可用。");
2151
2181
  const library = newSkillLibrary({
2182
+ authority: authorityFrom({ origin: "foreground" }),
2152
2183
  config: { root: skillsRootValue },
2153
- io: preferProvider(ioRegistry, ["fs"])
2184
+ io: ioRegistry.provider()
2154
2185
  });
2155
2186
  const enrichment = await buildEnrichment(ctx, library);
2156
2187
  const readFailures = [];
@@ -2162,7 +2193,7 @@ function apply(ctx, rawConfig = {}) {
2162
2193
  }
2163
2194
  });
2164
2195
  if (snapshots.length === 0 && readFailures.length > 0) return err(`维护扫描读不出 ${readFailures.length} 个已登记技能(${readFailures.slice(0, 10).join(", ")}${readFailures.length > 10 ? ", …" : ""}),且一个技能都没读到:技能库未被审计。请修好读不出的技能(或它们的目录名)后重新扫描`);
2165
- if (snapshots.length === 0 && skillsRootValue !== "" && !await preferProvider(ioRegistry, ["fs"]).exists(skillsRootValue)) return err(`维护扫描发现技能库为空,但配置的技能根目录不存在(${skillsRootValue}):请把 skillsRoot 指向真实目录(字面量 \`~\` 不会被展开),然后重新扫描`);
2196
+ if (snapshots.length === 0 && skillsRootValue !== "" && !await ioRegistry.provider().exists(skillsRootValue)) return err(`维护扫描发现技能库为空,但配置的技能根目录不存在(${skillsRootValue}):请把 skillsRoot 指向真实目录(字面量 \`~\` 不会被展开),然后重新扫描`);
2166
2197
  if (snapshots.length === 0) return ok("维护扫描:技能库为空,无需处理。");
2167
2198
  const { facts } = buildMaintainFacts(snapshots, enrichment.usageObservedValue, void 0);
2168
2199
  return ok(`维护事实(零 token 预览):\n${facts}`);
@@ -2202,8 +2233,9 @@ function apply(ctx, rawConfig = {}) {
2202
2233
  (async () => {
2203
2234
  try {
2204
2235
  const library = newSkillLibrary({
2236
+ authority: authorityFrom({ origin: "foreground" }),
2205
2237
  config: { root: skillsRootValue },
2206
- io: preferProvider(ioRegistry, ["fs"])
2238
+ io: ioRegistry.provider()
2207
2239
  });
2208
2240
  const enrichment = await buildEnrichment(ctx, library);
2209
2241
  const outcome = await runMaintain({
@@ -2215,7 +2247,7 @@ function apply(ctx, rawConfig = {}) {
2215
2247
  logger: { warn: (message) => {
2216
2248
  ctx.logger.warn(message);
2217
2249
  } },
2218
- rootExists: skillsRootValue === "" ? void 0 : () => preferProvider(ioRegistry, ["fs"]).exists(skillsRootValue),
2250
+ rootExists: skillsRootValue === "" ? void 0 : () => ioRegistry.provider().exists(skillsRootValue),
2219
2251
  skillRoot: skillsRootValue
2220
2252
  }, {
2221
2253
  timeoutMs: runTimeoutMs,
@@ -2224,7 +2256,7 @@ function apply(ctx, rawConfig = {}) {
2224
2256
  enrichment: () => enrichment
2225
2257
  });
2226
2258
  const endedAt = Date.now();
2227
- const resultRef = await writeMaintainReport(preferProvider(ioRegistry, ["fs"]), {
2259
+ const resultRef = await writeMaintainReport(ioRegistry.provider(), {
2228
2260
  runId: handle.id,
2229
2261
  ...outcome.ok ? {
2230
2262
  verdict: outcome.verdict,
@@ -2248,7 +2280,7 @@ function apply(ctx, rawConfig = {}) {
2248
2280
  ...outcome.ok ? {} : { failure: handle.signal.aborted ? "cancelled: the scan was stopped before it settled" : outcome.error ?? "Maintenance scan failed." }
2249
2281
  });
2250
2282
  if (outcome.ok) {
2251
- appendEvolutionEvent(preferProvider(ioRegistry, ["fs"]), eventsFile(runRoot), {
2283
+ appendEvolutionEvent(ioRegistry.provider(), eventsFile(runRoot), {
2252
2284
  type: "maintain",
2253
2285
  source: "manual",
2254
2286
  runId: handle.id,
@@ -2341,7 +2373,7 @@ function apply(ctx, rawConfig = {}) {
2341
2373
  if (wanted === void 0 && record.state === "running") return err(`最新一次运行 ${record.id} 仍在进行:它结束后才会出报告;用 /evolution maintain status ${record.id} 查看进度。`);
2342
2374
  const ioRegistry = ctx.get("evolutionIo");
2343
2375
  if (!ioRegistry) return err("未挂载 evolution-io 注册表:无法查看运行结果。");
2344
- const probed = await readRunReport(preferProvider(ioRegistry, ["fs"]), record);
2376
+ const probed = await readRunReport(ioRegistry.provider(), record);
2345
2377
  if (probed.kind === "missing") return err(`运行 ${record.id}(${record.state})没有结果:没有写出报告${record.failure === void 0 ? "" : `(${record.failure})`}。`);
2346
2378
  if (probed.kind === "unknown") return err(`运行 ${record.id} 有报告但读不出来(${probed.reason}):这「不是」没有结果。`);
2347
2379
  return ok(probed.text);
@@ -2386,7 +2418,7 @@ function apply(ctx, rawConfig = {}) {
2386
2418
  if (wanted === void 0 && record.state === "running") return err(`最新一次运行 ${record.id} 仍在进行:它结束后才会出报告;用 /evolution maintain status ${record.id} 查看进度。`);
2387
2419
  const ioRegistry = ctx.get("evolutionIo");
2388
2420
  if (!ioRegistry) return err("未挂载 evolution-io 注册表:无法交付报告。");
2389
- const probed = await readRunReport(preferProvider(ioRegistry, ["fs"]), record);
2421
+ const probed = await readRunReport(ioRegistry.provider(), record);
2390
2422
  if (probed.kind === "missing") return err(`运行 ${record.id}(${record.state})没有结果:没有写出报告${record.failure === void 0 ? "" : `(${record.failure})`}。`);
2391
2423
  if (probed.kind === "unknown") return err(`运行 ${record.id} 有报告但读不出来(${probed.reason}):这「不是」没有结果。`);
2392
2424
  const missingAgent = agentMissing("handoff");
@@ -2413,7 +2445,7 @@ function apply(ctx, rawConfig = {}) {
2413
2445
  }
2414
2446
  if (/^maintain\b/.test(input)) return err("未知的 maintain 参数:应为 `maintain`、`maintain --timeout <ms>` / `maintain --timeout=<ms>`、`maintain status [<id>]`、`maintain report [<id>]`、`maintain handoff [<id>]` 或 `maintain cancel [<id>]`。实际收到:" + input);
2415
2447
  const presetParsed = parseSubcommand(input);
2416
- const presetInstall = isParseFailure(presetParsed) || presetParsed.name !== "preset" ? null : presetParsed.positionals[0] === "install" && presetParsed.positionals.length === 1 && [...presetParsed.flags.keys()].every((flag) => flagsOf("preset").includes("--" + flag)) ? presetParsed : false;
2448
+ const presetInstall = isParseFailure(presetParsed) || presetParsed.name !== "preset" ? null : presetParsed.positionals[0] === "install" && presetParsed.positionals.length === 1 && [...presetParsed.flags.keys()].every((flag) => flagsOf("preset install").includes("--" + flag)) ? presetParsed : false;
2417
2449
  if (presetInstall !== null && presetInstall !== false) {
2418
2450
  {
2419
2451
  const mounted = presetProfileTarget(ctx);
@@ -2525,8 +2557,9 @@ function apply(ctx, rawConfig = {}) {
2525
2557
  if (decision.action === "staged") return ok(`${decision.message}${planRunId ? `\n[audit] plan=${planRunId}` : ""}`);
2526
2558
  }
2527
2559
  const result = await newSkillLibrary({
2560
+ authority: authorityFrom({ origin: "foreground" }),
2528
2561
  config: { root: skillsRootValue },
2529
- io: preferProvider(ioRegistry, ["fs"]),
2562
+ io: ioRegistry.provider(),
2530
2563
  ctx,
2531
2564
  threatExemptLabels: config.threatExemptLabels,
2532
2565
  limits: {
@@ -2783,7 +2816,8 @@ function resolveAgentPresetDir(importMetaUrl) {
2783
2816
  const overlayCandidate = join(dirname(fileURLToPath(importMetaUrl)), "..", "..", "evolution-agent");
2784
2817
  if (existsSync(join(overlayCandidate, "agent.cordis.yml")) && existsSync(join(overlayCandidate, "bases.json"))) return overlayCandidate;
2785
2818
  try {
2786
- return dirname(createRequire(importMetaUrl).resolve("@lmzhen/dsh-evolution-agent-preset/package.json"));
2819
+ const agentPresetPackage = scopedPackageName("dsh-evolution-agent-preset", importMetaUrl);
2820
+ return dirname(createRequire(importMetaUrl).resolve(agentPresetPackage + "/package.json"));
2787
2821
  } catch {
2788
2822
  return overlayCandidate;
2789
2823
  }
@@ -19,7 +19,16 @@ export interface CommandEntry {
19
19
  }
20
20
  /** The flags the TABLE declares for one subcommand (name → `--flag`), DERIVED from its usage. */
21
21
  export declare function flagsOf(name: string): string[];
22
- /** C7 shape, DERIVED: the subcommand name is the first token of `usage` (variants use `|`). */
22
+ /**
23
+ * C7 shape, DERIVED: the entry IDENTITY key, and it is HIERARCHICAL (E-08).
24
+ *
25
+ * The key used to be the first token alone, and `flagsOf` finds an entry with `.find(...)` - the FIRST
26
+ * match - so `maintain handoff` / `maintain cancel` and `skill restore` / `skill history` / `skill undo` /
27
+ * `skill pin` all derived the SAME key as their parent and every flag lookup silently saw the parent entry
28
+ * only (nine collisions). A bare parent (`maintain [--timeout]`) keeps its one-token key, so parent lookups
29
+ * still resolve: the one call site that named an entry by a name the key no longer produced was the preset
30
+ * dispatch branch, and it now asks for the entry own key (`preset install`).
31
+ */
23
32
  export declare function entryName(entry: CommandEntry): string;
24
33
  /** C7 shape, DERIVED: the flags a `usage` declares, in the form `--flag` (value flags keep their name). */
25
34
  export declare function entryFlags(entry: CommandEntry): string[];
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.26.15",
4
+ "version": "0.26.26",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -27,18 +27,17 @@
27
27
  "license": "MIT",
28
28
  "dependencies": {
29
29
  "@deepseek-ai/schemastery": "^3.18.4",
30
- "@lmzhen/dsh-evolution-approval": "^0.26.15",
31
- "@lmzhen/dsh-evolution-core": "^0.26.15",
32
- "@lmzhen/dsh-evolution-maintenance": "^0.26.15"
30
+ "@lmzhen/dsh-evolution-approval": "^0.26.26",
31
+ "@lmzhen/dsh-evolution-core": "^0.26.26",
32
+ "@lmzhen/dsh-evolution-maintenance": "^0.26.26"
33
33
  },
34
34
  "optionalDependencies": {
35
- "@lmzhen/dsh-evolution-agent-preset": "^0.26.15"
35
+ "@lmzhen/dsh-evolution-agent-preset": "^0.26.26"
36
36
  },
37
37
  "peerDependencies": {
38
38
  "@deepseek-ai/cordis": "^4.0.1",
39
39
  "@deepseek-ai/dsh-commands": "^0.2.0-rc.1",
40
- "@deepseek-ai/dsh-llm": "^0.2.0-rc.1",
41
- "@lmzhen/dsh-evolution-io": "^0.26.15"
40
+ "@deepseek-ai/dsh-llm": "^0.2.0-rc.1"
42
41
  },
43
42
  "devDependencies": {
44
43
  "@deepseek-ai/dsh-commands": "^0.2.0-rc.1"