@lmzhen/dsh-evolution-commands 0.26.26 → 0.27.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.
package/lib/index.js CHANGED
@@ -1,12 +1,470 @@
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
+ import { DEFAULT_SKILL_LIMITS, DIAGNOSTICS_FACT_KEYS, DIAGNOSTICS_RUNTIME_KEYS, DIAGNOSTICS_VIEW_SCHEMA_VERSION, EVOLUTION_PRESET_IDS, MAINTAINER_PRESET_ID, MAX_TIMER_DELAY_MS, PARAM_EXPOSURE, PARAM_GROUP_LABELS, PLATFORM_LINE, REVIEWER_PRESET_ID, absentHostServices, appendEvolutionEvent, applyNamespaceMigration, asRecord, assertSkillsRootAliasRetired, atomicWriteFiles, authorityFrom, buildLearnPrompt, canonicalWriteId, clampedNumber, classifyNamespaceMigration, classifySessionEvent, createSubagentChannel, elapsedSince, errorText, eventsFile, evolutionHome, evolutionRoot, hostSurface, isDeprecatedParamId, isPresent, isRecord, isUnknown, liveBool, newRunRegistry, newSkillLibrary, paramSettingsId, partitionVersions, planNamespaceMigration, policyStageLimits, probeMtime, probeText, reportsSweepLockTarget, resolveParamId, resolveRootConfig, scopedPackageName, scopedProbeReport, sweepReports, transactIo } from "@lmzhen/dsh-evolution-core";
3
+ import { HOST_ROUTES, json, methodNotAllowed, registerHostRoutes } from "@lmzhen/dsh-evolution-core/http-routes";
4
+ import { existsSync, readFileSync, readdirSync } from "node:fs";
5
+ import { dirname, join } from "node:path";
6
+ import { fileURLToPath } from "node:url";
3
7
  import z from "@deepseek-ai/schemastery";
4
8
  import { effectiveSessionPolicy } from "@lmzhen/dsh-evolution-approval";
5
9
  import { createUserMessage } from "@deepseek-ai/dsh-llm";
10
+ import { composePresetEntry, mergePresetRow, presetPatchText, presetRowBlock, presetRowBody, presetRowId, resolveBasePresetPatch } from "@lmzhen/dsh-evolution-core/composition";
6
11
  import { buildEnrichment, buildMaintainFacts, enrichmentSnapshotOptions, runMaintain, snapshotFromLibrary } from "@lmzhen/dsh-evolution-maintenance";
7
- import { existsSync, readFileSync, readdirSync } from "node:fs";
8
- import { dirname, join } from "node:path";
9
- import { fileURLToPath } from "node:url";
12
+ //#region lib/types/command-fallback.js
13
+ /**
14
+ * G6/W8: the command-face fallback for every Host data-plane surface.
15
+ *
16
+ * The browser panels depend on the platform's `connection` carrier (exact Fetch routes). When that
17
+ * carrier is absent (headless host, E-325) or a panel answer is missing, the operator is owed the
18
+ * equivalent command — the SAME use case, not a different implementation. This table is the ONE
19
+ * home of those pairs; the INSTALL runbook and the client `fallbacks.generated.ts` are generated
20
+ * from it (W7), and a spec pins its key set to HOST_ROUTES' four surfaces.
21
+ *
22
+ * Not exported from the package: this is a command-side contract, not a plugin API (W8 §2).
23
+ * @module @lmzhen/dsh-evolution-commands/command-fallback
24
+ */
25
+ /**
26
+ * One entry per Host surface. The command column names the same service methods the routes call,
27
+ * so the answers (and refusal sentences) match the panel.
28
+ */
29
+ const COMMAND_FALLBACKS = {
30
+ approval: { command: "/evolution pending … /evolution approve <id> … /evolution reject <id> … /evolution release <id>" },
31
+ "skill-history": { command: "/evolution restore <name> [v](查看:/evolution skills history <name>)" },
32
+ "skill-health": { command: "/evolution skills refresh(健康行:/evolution doctor)" },
33
+ diagnostics: {
34
+ command: "/evolution doctor",
35
+ platformCli: "dsh --json /evolution doctor"
36
+ }
37
+ };
38
+ //#endregion
39
+ //#region lib/types/doctor-view.js
40
+ const FACT_HOMES = {
41
+ "platform-line": "README.md(兼容线声明)",
42
+ "family-version": "README.md(家族版本)",
43
+ "install-form": "packages/INSTALL.md(安装形态与验证状态)",
44
+ "preset-ids": "evolution-core/README.md(preset id 冻结表)",
45
+ "readonly-contract": "evolution-review/README.md(只读契约)",
46
+ "upgrade-order": "packages/INSTALL.md(升级 runbook)",
47
+ "fallback-map": "packages/INSTALL.md(命令兜底表)"
48
+ };
49
+ const FACT_LABELS = {
50
+ "platform-line": "平台兼容线",
51
+ "family-version": "家族版本",
52
+ "install-form": "安装形态",
53
+ "preset-ids": "受限 preset id",
54
+ "readonly-contract": "只读契约",
55
+ "upgrade-order": "升级顺序",
56
+ "fallback-map": "命令兜底"
57
+ };
58
+ const RUNTIME_LABELS = {
59
+ "subagent-channel": "子代理通道",
60
+ "restricted-preset": "受限 preset",
61
+ "host-surface": "Host 数据面",
62
+ "session-fold": "会话事件 fold 单入口",
63
+ "platform-services": "平台服务探测",
64
+ runs: "运行登记",
65
+ "platform-pairing": "平台/家族配对",
66
+ "install-form-detected": "实际安装形态"
67
+ };
68
+ function factsOf(inputs) {
69
+ const values = {
70
+ "platform-line": inputs.platformLine.version + "(floor " + inputs.platformLine.floor + ")",
71
+ "family-version": inputs.familyVersion,
72
+ "install-form": inputs.form.id + "(" + inputs.form.confidence + ";证据:" + inputs.form.evidence.join("、") + ")",
73
+ "preset-ids": REVIEWER_PRESET_ID + " / " + MAINTAINER_PRESET_ID + "(家族 preset 集合:" + EVOLUTION_PRESET_IDS.join(", ") + ")",
74
+ "readonly-contract": REVIEWER_PRESET_ID + " 的工具集由 restrictor allow 集合决定;维护行同构(" + MAINTAINER_PRESET_ID + ")",
75
+ "upgrade-order": "先升平台 → 再升家族 → 最后发布(见 INSTALL runbook)",
76
+ "fallback-map": Object.keys(COMMAND_FALLBACKS).join("、") + "(每面的命令见 INSTALL 命令兜底表)"
77
+ };
78
+ return DIAGNOSTICS_FACT_KEYS.map((key) => ({
79
+ key,
80
+ label: FACT_LABELS[key] ?? key,
81
+ value: values[key] ?? "unknown",
82
+ home: FACT_HOMES[key] ?? "README.md"
83
+ })).sort((a, b) => a.key.localeCompare(b.key));
84
+ }
85
+ function channelRow(channels) {
86
+ const unavailable = [channels.review, channels.maintenance].filter((state) => state.kind === "unavailable");
87
+ if (unavailable.length === 0) return {
88
+ key: "subagent-channel",
89
+ label: RUNTIME_LABELS["subagent-channel"] ?? "子代理通道",
90
+ status: "ok",
91
+ detail: "reviewer/maintainer 通道均可用(spawn provider + agentPreset 能力)"
92
+ };
93
+ const reasons = unavailable.map((state) => state.code + ": " + state.reason);
94
+ return {
95
+ key: "subagent-channel",
96
+ label: RUNTIME_LABELS["subagent-channel"] ?? "子代理通道",
97
+ status: "warn",
98
+ detail: reasons.join(";"),
99
+ remedy: "显式子代理运行会标记为不可用(不会静默退回 inject);运行 /evolution doctor 查看修复指引。"
100
+ };
101
+ }
102
+ function presetRow(presets) {
103
+ const entries = [presets.reviewer, presets.maintainer];
104
+ if (entries.every((entry) => entry === void 0)) return {
105
+ key: "restricted-preset",
106
+ label: RUNTIME_LABELS["restricted-preset"] ?? "受限 preset",
107
+ status: "unknown",
108
+ detail: "agentPresets 未挂载或未暴露 list(),无法探测受限 preset。",
109
+ remedy: "挂载 agentPresets(随平台 Web 组合)后运行 /evolution doctor。"
110
+ };
111
+ const broken = entries.filter((entry) => entry !== void 0 && entry.broken !== void 0);
112
+ const missing = entries.filter((entry) => entry === void 0 || !entry.registered);
113
+ if (broken.length > 0) return {
114
+ key: "restricted-preset",
115
+ label: RUNTIME_LABELS["restricted-preset"] ?? "受限 preset",
116
+ status: "fail",
117
+ detail: errorText("e-324-restricted-preset-unavailable", { a1: broken.map((entry) => entry.broken ?? "").join(";") }),
118
+ remedy: "重载插件行使 ensureRestrictedPreset 重新注册;运行 /evolution doctor 查看 preset id 与 allow 集合。"
119
+ };
120
+ if (missing.length > 0) return {
121
+ key: "restricted-preset",
122
+ label: RUNTIME_LABELS["restricted-preset"] ?? "受限 preset",
123
+ status: "warn",
124
+ detail: "受限 preset 尚未注册(首次显式子代理运行时创建)。",
125
+ remedy: "运行一次审阅/维护,或在挂载后运行 /evolution doctor。"
126
+ };
127
+ return {
128
+ key: "restricted-preset",
129
+ label: RUNTIME_LABELS["restricted-preset"] ?? "受限 preset",
130
+ status: "ok",
131
+ detail: REVIEWER_PRESET_ID + " / " + MAINTAINER_PRESET_ID + " 均已注册"
132
+ };
133
+ }
134
+ function hostRow(host, count) {
135
+ if (!host.mounted) {
136
+ const fallback = COMMAND_FALLBACKS.approval.command.split(" … ")[0] ?? "";
137
+ return {
138
+ key: "host-surface",
139
+ label: RUNTIME_LABELS["host-surface"] ?? "Host 数据面",
140
+ status: "warn",
141
+ detail: errorText("e-325-host-data-plane-unavailable") + "(" + (host.reason ?? "unknown") + ")",
142
+ remedy: "改用命令面完成同等操作:" + fallback + " 等;运行 /evolution doctor 查看已挂载服务。"
143
+ };
144
+ }
145
+ const remote = host.deploymentAllowsRemote === true ? "允许远程权限(平台准入策略原样报告)" : "仅本机权限(平台准入策略原样报告)";
146
+ const mounted = count === void 0 ? "已挂载" : "已挂载 " + String(count.mounted) + "/" + String(count.total);
147
+ return {
148
+ key: "host-surface",
149
+ label: RUNTIME_LABELS["host-surface"] ?? "Host 数据面",
150
+ status: "ok",
151
+ detail: mounted + ";" + remote
152
+ };
153
+ }
154
+ function pairingRow(inputs) {
155
+ switch (inputs.platformPairing ?? "unknown") {
156
+ case "in-window": return {
157
+ key: "platform-pairing",
158
+ label: RUNTIME_LABELS["platform-pairing"] ?? "平台/家族配对",
159
+ status: "ok",
160
+ detail: "宿主版本 " + String(inputs.hostVersion) + " 在兼容窗口 " + inputs.platformLine.version + " 内"
161
+ };
162
+ case "host-newer": return {
163
+ key: "platform-pairing",
164
+ label: RUNTIME_LABELS["platform-pairing"] ?? "平台/家族配对",
165
+ status: "warn",
166
+ detail: "宿主 patch 线已更新(" + String(inputs.hostVersion) + ",家族声明 " + inputs.platformLine.version + ")。",
167
+ remedy: "按升级 runbook 先升平台线再升家族。"
168
+ };
169
+ case "out-of-window": return {
170
+ key: "platform-pairing",
171
+ label: RUNTIME_LABELS["platform-pairing"] ?? "平台/家族配对",
172
+ status: "fail",
173
+ detail: "宿主版本 " + String(inputs.hostVersion) + " 落在兼容窗口(floor " + inputs.platformLine.floor + ")外。",
174
+ remedy: "先升/降平台到兼容窗口,再重载家族。"
175
+ };
176
+ case "unknown": return {
177
+ key: "platform-pairing",
178
+ label: RUNTIME_LABELS["platform-pairing"] ?? "平台/家族配对",
179
+ status: "unknown",
180
+ detail: inputs.hostVersion === void 0 ? "读不到宿主版本。" : "无法判定版本窗口。",
181
+ remedy: "在宿主会话里运行 /evolution doctor。"
182
+ };
183
+ }
184
+ }
185
+ function runtimeOf(inputs) {
186
+ const missing = inputs.missingServices ?? [];
187
+ const fold = inputs.sessionFoldClassifierPresent !== false;
188
+ return [
189
+ channelRow(inputs.channels),
190
+ presetRow(inputs.presets),
191
+ hostRow(inputs.host, inputs.hostRouteCount),
192
+ fold ? {
193
+ key: "session-fold",
194
+ label: RUNTIME_LABELS["session-fold"] ?? "会话事件 fold 单入口",
195
+ status: "ok",
196
+ detail: "classifySessionEvent/isIgnorableEvent 存在:model 消费,plugin-record/unknown 忽略。"
197
+ } : {
198
+ key: "session-fold",
199
+ label: RUNTIME_LABELS["session-fold"] ?? "会话事件 fold 单入口",
200
+ status: "fail",
201
+ detail: "分类器缺失,无法证明单一 fold 入口。",
202
+ remedy: "升级家族(classifySessionEvent 是 core 根导出)。"
203
+ },
204
+ missing.length === 0 ? {
205
+ key: "platform-services",
206
+ label: RUNTIME_LABELS["platform-services"] ?? "平台服务探测",
207
+ status: "ok",
208
+ detail: "探测表中的服务均已挂载"
209
+ } : {
210
+ key: "platform-services",
211
+ label: RUNTIME_LABELS["platform-services"] ?? "平台服务探测",
212
+ status: "warn",
213
+ detail: "缺少:" + missing.join("、"),
214
+ remedy: "安装行(evolution-host/evolution-all)或检查 profile 行。"
215
+ },
216
+ inputs.runs.orphaned > 0 ? {
217
+ key: "runs",
218
+ label: RUNTIME_LABELS.runs ?? "运行登记",
219
+ status: "warn",
220
+ detail: "running=" + String(inputs.runs.running) + ",orphaned=" + String(inputs.runs.orphaned),
221
+ remedy: "运行 /evolution runs 收敛遗留运行。"
222
+ } : {
223
+ key: "runs",
224
+ label: RUNTIME_LABELS.runs ?? "运行登记",
225
+ status: "ok",
226
+ detail: "running=" + String(inputs.runs.running) + ",orphaned=0"
227
+ },
228
+ pairingRow(inputs),
229
+ inputs.form.confidence === "declared" ? {
230
+ key: "install-form-detected",
231
+ label: RUNTIME_LABELS["install-form-detected"] ?? "实际安装形态",
232
+ status: "ok",
233
+ detail: inputs.form.id + "(声明)"
234
+ } : {
235
+ key: "install-form-detected",
236
+ label: RUNTIME_LABELS["install-form-detected"] ?? "实际安装形态",
237
+ status: "warn",
238
+ detail: inputs.form.id + "(推断:" + inputs.form.evidence.join("、") + ")",
239
+ remedy: "在 profile 里显式声明安装行,核对 INSTALL 的形态表。"
240
+ }
241
+ ].sort((a, b) => a.key.localeCompare(b.key));
242
+ }
243
+ /**
244
+ * Build the frozen DiagnosticsView. Pure; never throws.
245
+ * @param inputs - everything the 7+8 rows read (assembleDoctorInputs probes the live host).
246
+ */
247
+ function buildDoctorView(inputs) {
248
+ return {
249
+ schemaVersion: DIAGNOSTICS_VIEW_SCHEMA_VERSION,
250
+ generatedAt: inputs.now.toISOString(),
251
+ facts: factsOf(inputs),
252
+ runtime: runtimeOf(inputs)
253
+ };
254
+ }
255
+ /** A safe view for a host whose inputs could not be assembled: every row names the failure. */
256
+ function unavailableDoctorView(now, reason) {
257
+ const facts = DIAGNOSTICS_FACT_KEYS.map((key) => ({
258
+ key,
259
+ label: FACT_LABELS[key] ?? key,
260
+ value: "unknown",
261
+ home: FACT_HOMES[key] ?? "README.md"
262
+ }));
263
+ const runtime = DIAGNOSTICS_RUNTIME_KEYS.map((key) => ({
264
+ key,
265
+ label: RUNTIME_LABELS[key] ?? key,
266
+ status: "unknown",
267
+ detail: "无法装配 doctor 输入:" + reason,
268
+ remedy: "运行 /evolution doctor 重试;若持续失败,检查宿主日志。"
269
+ }));
270
+ return {
271
+ schemaVersion: DIAGNOSTICS_VIEW_SCHEMA_VERSION,
272
+ generatedAt: now.toISOString(),
273
+ facts: facts.sort((a, b) => a.key.localeCompare(b.key)),
274
+ runtime: runtime.sort((a, b) => a.key.localeCompare(b.key))
275
+ };
276
+ }
277
+ /** The family version this build carries (nearest package.json above the running module). */
278
+ function familyVersion() {
279
+ try {
280
+ let dir = dirname(fileURLToPath(import.meta.url));
281
+ for (let hops = 0; hops < 32; hops += 1) {
282
+ const manifest = join(dir, "package.json");
283
+ if (existsSync(manifest)) {
284
+ const parsed = JSON.parse(readFileSync(manifest, "utf8"));
285
+ if (typeof parsed.version === "string") return parsed.version;
286
+ }
287
+ const parent = dirname(dir);
288
+ if (parent === dir) break;
289
+ dir = parent;
290
+ }
291
+ } catch {}
292
+ return "unknown";
293
+ }
294
+ /** The soft service probe list for the platform-services row. */
295
+ const SERVICES_PROBED = [
296
+ "connection",
297
+ "agentPresets",
298
+ "subagents",
299
+ "commands",
300
+ "settings"
301
+ ];
302
+ /** The host's own version, read from the installed platform manifest (soft: absent = unknown). */
303
+ function hostRuntimeVersion() {
304
+ for (const packageName of ["@deepseek-ai/dsh-app-boot", "@deepseek-ai/dsh"]) try {
305
+ const manifest = createRequire(import.meta.url).resolve(packageName + "/package.json");
306
+ const parsed = JSON.parse(readFileSync(manifest, "utf8"));
307
+ if (typeof parsed.version === "string" && parsed.version !== "") return parsed.version;
308
+ } catch {}
309
+ }
310
+ /** The family bundles the platform reports (soft probe: a host without the manager answers []). */
311
+ async function installedFamilyBundles(ctx) {
312
+ const listed = await ctx.get("pluginManager")?.listBundles?.();
313
+ if (!Array.isArray(listed)) return [];
314
+ const names = [];
315
+ for (const entry of listed) {
316
+ if (typeof entry !== "object" || entry === null) continue;
317
+ const name = entry.name;
318
+ if (typeof name === "string" && name.includes("dsh-evolution-")) names.push(name);
319
+ }
320
+ return names;
321
+ }
322
+ /**
323
+ * Assemble the view inputs through the live Context's soft probes.
324
+ *
325
+ * Every probe is optional: an absent service becomes a row value (unknown/warn), never a throw. The
326
+ * channel probe uses W5's own `createSubagentChannel` state machinery, so the doctor reports the
327
+ * SAME availability the review/maintenance runs would see.
328
+ * @param ctx - the host context.
329
+ * @param extras - call-site facts the context cannot answer (runs, host version, install form).
330
+ */
331
+ async function assembleDoctorInputs(ctx, extras = {}) {
332
+ const get = (name) => ctx.get(name);
333
+ const channel = createSubagentChannel(ctx);
334
+ const probeChannel = async (kind) => {
335
+ try {
336
+ return await channel.prepare({
337
+ kind,
338
+ allow: ["skill"]
339
+ });
340
+ } catch (error) {
341
+ return {
342
+ kind: "unavailable",
343
+ code: "E-323",
344
+ reason: error instanceof Error ? error.message : String(error)
345
+ };
346
+ }
347
+ };
348
+ const [review, maintenance] = await Promise.all([probeChannel("reviewer"), probeChannel("maintainer")]);
349
+ const presetsService = get("agentPresets");
350
+ let listed = [];
351
+ try {
352
+ const value = await presetsService?.list?.();
353
+ if (Array.isArray(value)) listed = value;
354
+ } catch {
355
+ listed = [];
356
+ }
357
+ const presetOf = (id) => {
358
+ if (listed.length === 0) return void 0;
359
+ const entry = listed.find((candidate) => candidate.id === id);
360
+ if (entry === void 0) return {
361
+ id,
362
+ registered: false
363
+ };
364
+ return {
365
+ id,
366
+ registered: true,
367
+ ...typeof entry.broken === "string" ? { broken: entry.broken } : {}
368
+ };
369
+ };
370
+ const bundles = await installedFamilyBundles(ctx);
371
+ const hasAll = bundles.some((name) => name.endsWith("dsh-evolution-all"));
372
+ const hasPreset = bundles.some((name) => name.endsWith("dsh-evolution-preset"));
373
+ const hasHost = bundles.some((name) => name.endsWith("dsh-evolution-host"));
374
+ const hasFamilyPreset = listed.some((entry) => typeof entry.id === "string" && EVOLUTION_PRESET_IDS.includes(entry.id));
375
+ const detectedForm = hasAll || hasPreset ? {
376
+ id: "M1",
377
+ evidence: [hasAll ? "bundle dsh-evolution-all installed" : "bundle dsh-evolution-preset installed"],
378
+ confidence: "declared"
379
+ } : hasHost ? hasFamilyPreset ? {
380
+ id: "M4",
381
+ evidence: ["bundle dsh-evolution-host installed", "family agent preset registered"],
382
+ confidence: "declared"
383
+ } : {
384
+ id: "M3",
385
+ evidence: ["bundle dsh-evolution-host installed"],
386
+ confidence: "declared"
387
+ } : {
388
+ id: "M3",
389
+ evidence: ["no family bundle was reported by the platform manager"],
390
+ confidence: "inferred"
391
+ };
392
+ const platformVersion = hostRuntimeVersion();
393
+ const missingServices = SERVICES_PROBED.filter((name) => get(name) === void 0);
394
+ const host = hostSurface(ctx);
395
+ const hostMounted = host.available;
396
+ return {
397
+ now: extras.now ?? /* @__PURE__ */ new Date(),
398
+ platformLine: {
399
+ version: PLATFORM_LINE.version,
400
+ floor: PLATFORM_LINE.floor
401
+ },
402
+ familyVersion: familyVersion(),
403
+ hostVersion: extras.hostVersion ?? platformVersion,
404
+ form: extras.form ?? detectedForm,
405
+ probes: {
406
+ agentPresets: get("agentPresets") !== void 0,
407
+ connection: get("connection") !== void 0
408
+ },
409
+ channels: {
410
+ review,
411
+ maintenance
412
+ },
413
+ presets: {
414
+ reviewer: presetOf(REVIEWER_PRESET_ID),
415
+ maintainer: presetOf(MAINTAINER_PRESET_ID)
416
+ },
417
+ host: {
418
+ mounted: hostMounted,
419
+ ...hostMounted ? {} : { reason: host.reason },
420
+ ...host.available ? { deploymentAllowsRemote: host.allowsRemoteAuthorities } : {}
421
+ },
422
+ runs: extras.runs ?? {
423
+ running: 0,
424
+ orphaned: 0
425
+ },
426
+ missingServices,
427
+ sessionFoldClassifierPresent: typeof classifySessionEvent === "function",
428
+ platformPairing: extras.platformPairing ?? (platformVersion === void 0 ? "unknown" : platformVersion === PLATFORM_LINE.version ? "in-window" : "host-newer")
429
+ };
430
+ }
431
+ //#endregion
432
+ //#region lib/types/diagnostics-route.js
433
+ /**
434
+ * G6/W8: the diagnostics Host route — the browser face of the SAME DiagnosticsView the CLI prints.
435
+ *
436
+ * Registration belongs to W6 (`registerHostRoutes` is called by the commands plugin's apply), so
437
+ * this module only supplies the handler. A failed input assembly is NOT a transport error: the
438
+ * handler answers an all-unknown view naming the failure, because doctor must never throw (W8 §6).
439
+ * @module @lmzhen/dsh-evolution-commands/diagnostics-route
440
+ */
441
+ /**
442
+ * Build the diagnostics route over one input producer.
443
+ * @param getInputs - assembles the live view inputs (soft probes; may reject).
444
+ * @returns the exact Fetch route for `HOST_ROUTES.diagnostics.view`.
445
+ */
446
+ function makeDiagnosticsRoutes(getInputs) {
447
+ return [{
448
+ path: HOST_ROUTES.diagnostics.view,
449
+ methods: ["GET"],
450
+ requestBody: "buffered",
451
+ fetch: async (request) => {
452
+ if (request.method !== "GET") return methodNotAllowed(["GET"]);
453
+ try {
454
+ return json(200, {
455
+ ok: true,
456
+ data: buildDoctorView(await getInputs())
457
+ });
458
+ } catch (error) {
459
+ return json(200, {
460
+ ok: true,
461
+ data: unavailableDoctorView(/* @__PURE__ */ new Date(), error instanceof Error ? error.message : String(error))
462
+ });
463
+ }
464
+ }
465
+ }];
466
+ }
467
+ //#endregion
10
468
  //#region lib/types/migration.js
11
469
  /**
12
470
  * G3 (caller half): find the legacy settings document and run the namespace migration.
@@ -644,33 +1102,6 @@ function envIssues() {
644
1102
  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.`);
645
1103
  return issues;
646
1104
  }
647
- /** S0.2 (v37 P0-1): memory files that still carry the platform's `{{` prompt
648
- * variable syntax. The platform interpolates EVERY `systemPrompt.context` text
649
- * once per model step and throws on an unknown/malformed reference, so one such
650
- * entry used to fail every turn of every session under this home. Current
651
- * builds neutralize the injected text (`MemoryStore.renderContext`), which is
652
- * why this is a residual-risk report instead of a hard failure: an older
653
- * still-running process is still bricked, and an operator may prefer to clean
654
- * the file. Read-only; an unreadable file is skipped (the doctor never throws).
655
- * @param home - resolved DSH_HOME.
656
- * @returns one message per affected memory file.
657
- */
658
- function memoryInterpolationIssues(home) {
659
- const issues = [];
660
- for (const file of ["MEMORY.md", "USER.md"]) {
661
- const path = join(home, "memories", file);
662
- if (!existsSync(path)) continue;
663
- let raw;
664
- try {
665
- raw = readFileSync(path, "utf8");
666
- } catch {
667
- continue;
668
- }
669
- const hits = raw.split("{{").length - 1;
670
- 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`);
671
- }
672
- return issues;
673
- }
674
1105
  /** G3-② (B2): the family delta container. An optionalDependency of this
675
1106
  * package, so resolution may legitimately fail; every failure degrades to
676
1107
  * "unknown" — the doctor never throws.
@@ -1014,20 +1445,19 @@ async function diagnose(ctx, options = {}) {
1014
1445
  if (presetIssueRows.length > 0) actions.push("Resolve the agent-preset finding above: a broken preset composes no session, and a row two presets declare is resolved by layer order (keep the declaration in one preset).");
1015
1446
  const legacy = await legacyMigrationRow(ctx, home);
1016
1447
  if (legacy.state === "pending") actions.push(`The legacy settings document still holds ${legacy.pending.length} family value(s) — /evolution migrate moves them onto their rows (the document itself is left alone, so nothing is lost)`);
1017
- const memoryIssues = memoryInterpolationIssues(home);
1018
1448
  const budgetIssues = memoryBudgetIssues(ctx);
1019
1449
  const paramIssues = paramDivergences(platform);
1020
1450
  const queryIssues = await sessionQueryIssues(ctx);
1021
1451
  if (queryIssues.length > 0) actions.push("Session search is degraded: isolate the session named above (or wait for the platform fix described in the family maintenance notes) before retrying the same query");
1022
1452
  if (paramIssues.length > 0) actions.push("Review the parameter divergences above: /evolution params shows every row with its source, /evolution policy set writes a user value, and a declared-but-unreachable row needs its owning plugin row mounted in this profile.");
1023
1453
  if (budgetIssues.length > 0) actions.push("Align the memory budget: leave memory-files memoryCharLimit/userCharLimit UNSET so the store follows evolution-policy, or set both surfaces to the same value (the review plans against the policy value while the store enforces its own)");
1024
- 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.");
1025
1454
  if (scopedProbe.rows !== null && scopedProbe.rows.length > 0 && scopedProbe.verdict === "never-hit") actions.push("The session-scoped rows are mounted but the family-tool probe has never matched in this process — review injection and skill-usage telemetry skipped every session observed. HOST-ONLY install: the model rows (tool-memory / tool-skill-manage) sit in evolution-host devDependencies, so no session carries them; install a bundle that mounts them (see INSTALL.md). VARIANT install: only a session on the Evolution preset matches, so open one — a session on a platform original preset is the intended skip, not a fault. Either way, check that no profile overlay disables those two rows.");
1026
1455
  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. For a verified orphan (this build: S2-P2-22), /evolution release <id> returns it to the pending window instead.`);
1027
1456
  for (const row of presetFreshness) {
1028
1457
  if (row.status !== "differs") continue;
1029
1458
  actions.push(`Preset row for base "${row.base}" in ${row.destination} DIFFERS from a fresh generation — it is an install-time snapshot; re-run the installer (/evolution preset install) to regenerate`);
1030
1459
  }
1460
+ const view = buildDoctorView(await assembleDoctorInputs(ctx));
1031
1461
  return {
1032
1462
  installForm,
1033
1463
  deploymentForm,
@@ -1035,7 +1465,6 @@ async function diagnose(ctx, options = {}) {
1035
1465
  foreignProfiles: homeProfiles(home).filter(({ profile, dir }) => mounted === void 0 || profile !== mounted.profile && dir !== mounted.dir).map(({ profile }) => profile),
1036
1466
  conflicts,
1037
1467
  envIssues: env,
1038
- memoryIssues,
1039
1468
  budgetIssues,
1040
1469
  paramIssues,
1041
1470
  queryIssues,
@@ -1051,7 +1480,8 @@ async function diagnose(ctx, options = {}) {
1051
1480
  presetIssues: presetIssueRows,
1052
1481
  dormantBundles,
1053
1482
  serviceSource: reviewRow === void 0 ? "bundles" : "platform",
1054
- actions
1483
+ actions,
1484
+ view
1055
1485
  };
1056
1486
  }
1057
1487
  /**
@@ -1194,13 +1624,35 @@ function renderDoctorText(report) {
1194
1624
  if (report.foreignProfiles.length > 0) lines.push("profiles-foreign: " + report.foreignProfiles.join(", ") + " — not part of this plane: /evolution preset install ignores them, while the bundles (all profiles) line above counts them");
1195
1625
  if (report.conflicts.length > 0) lines.push("conflicts:", ...report.conflicts.map((line) => ` ! ${line}`));
1196
1626
  if (report.envIssues.length > 0) lines.push("env:", ...report.envIssues.map((line) => ` ! ${line}`));
1197
- if (report.memoryIssues.length > 0) lines.push("memory:", ...report.memoryIssues.map((line) => ` ! ${line}`));
1198
1627
  if (report.budgetIssues.length > 0) lines.push("memory budget:", ...report.budgetIssues.map((line) => ` ! ${line}`));
1199
1628
  if (report.paramIssues.length > 0) lines.push("parameters:", ...report.paramIssues.map((line) => ` ! ${line}`));
1200
1629
  if (report.queryIssues.length > 0) lines.push("session search:", ...report.queryIssues.map((line) => ` ! ${line}`));
1201
1630
  if (report.actions.length > 0) lines.push("next steps:", ...report.actions.map((line) => ` → ${line}`));
1202
1631
  return lines.filter((line) => line !== null).join("\n");
1203
1632
  }
1633
+ /**
1634
+ * G6/W8 tail: render the frozen DiagnosticsView as the doctor's diagnostics section.
1635
+ *
1636
+ * This is the ONE text face of the 7+8 rows: the same object `--json` and the diagnostics route
1637
+ * answer, so the three readers cannot disagree. Legacy install/conflict sections keep their own
1638
+ * renderer until the remaining W8-S7 rewrite lands.
1639
+ * @param view - the assembled view.
1640
+ * @returns the section text (ends with a newline).
1641
+ */
1642
+ function renderDiagnosticsText(view) {
1643
+ const lines = [];
1644
+ lines.push("诊断(schema v" + String(view.schemaVersion) + ",生成于 " + view.generatedAt + "):");
1645
+ lines.push(" 事实:");
1646
+ for (const fact of view.facts) lines.push(" - " + fact.label + ":" + fact.value + "(出处:" + fact.home + ")");
1647
+ lines.push(" 运行时:");
1648
+ for (const row of view.runtime) {
1649
+ const status = row.status === "ok" ? "正常" : row.status === "warn" ? "注意" : row.status === "fail" ? "异常" : "未知";
1650
+ const detail = row.detail === void 0 ? "" : ":" + row.detail;
1651
+ const remedy = row.remedy === void 0 ? "" : ";下一步:" + row.remedy;
1652
+ lines.push(" - [" + status + "] " + row.label + detail + remedy);
1653
+ }
1654
+ return lines.join("\n") + "\n";
1655
+ }
1204
1656
  //#endregion
1205
1657
  //#region lib/types/params.js
1206
1658
  /**
@@ -1888,6 +2340,22 @@ function apply(ctx, rawConfig = {}) {
1888
2340
  runs = void 0;
1889
2341
  }, "evolution-commands.run-registry");
1890
2342
  });
2343
+ /** G6/W8: the run-registry snapshot the diagnostics `runs` row reads (one owner: the registry). */
2344
+ const doctorRuns = () => {
2345
+ try {
2346
+ const running = (runs?.runs() ?? []).filter((record) => record.state === "running").length;
2347
+ const active = runs?.inFlight();
2348
+ return {
2349
+ running,
2350
+ orphaned: Math.max(0, running - (active === void 0 ? 0 : 1))
2351
+ };
2352
+ } catch {
2353
+ return {
2354
+ running: 0,
2355
+ orphaned: 0
2356
+ };
2357
+ }
2358
+ };
1891
2359
  ctx.inject(["commands"], (commandCtx) => {
1892
2360
  const commands = commandCtx.commands;
1893
2361
  const commandBody = { run: async (invocation) => {
@@ -2210,9 +2678,8 @@ function apply(ctx, rawConfig = {}) {
2210
2678
  if (!synced.ok) ctx.logger.warn(`evolution-commands: ${synced.note ?? "run index unreadable"} — the in-flight guard cannot see other processes' runs`);
2211
2679
  else if (synced.note !== void 0) ctx.logger.warn(`evolution-commands: ${synced.note}`);
2212
2680
  const ioRegistry = ctx.get("evolutionIo");
2213
- const subagents = ctx.get("subagents");
2214
2681
  if (!ioRegistry) return err("未挂载 evolution-io 注册表:维护扫描不可用。");
2215
- if (!subagents) return err("未挂载子智能体服务:维护扫描不可用。");
2682
+ const channel = createSubagentChannel(ctx);
2216
2683
  const running = registry.inFlight();
2217
2684
  if (running) {
2218
2685
  const elapsed = Math.max(1, Math.round((Date.now() - running.startedAt) / 1e3));
@@ -2240,7 +2707,7 @@ function apply(ctx, rawConfig = {}) {
2240
2707
  const enrichment = await buildEnrichment(ctx, library);
2241
2708
  const outcome = await runMaintain({
2242
2709
  library,
2243
- subagents,
2710
+ channel,
2244
2711
  parent: invocation.agent,
2245
2712
  evolutionPolicy: { get: () => ctx.get("evolutionPolicy")?.get() },
2246
2713
  tools: { get: (name) => ctx.get("tools")?.get(name) },
@@ -2273,6 +2740,15 @@ function apply(ctx, rawConfig = {}) {
2273
2740
  }, (message) => {
2274
2741
  ctx.logger.warn(message);
2275
2742
  }, runHome);
2743
+ if (resultRef !== void 0) {
2744
+ if (!isPresent(await probeMtime(ioRegistry.provider(), resultRef))) {
2745
+ await registry.settle(handle.id, {
2746
+ state: "failed",
2747
+ failure: "the maintain report file is missing at settle time (write/io race) — the scan result was not persisted."
2748
+ });
2749
+ return;
2750
+ }
2751
+ }
2276
2752
  const state = outcome.ok ? "succeeded" : handle.signal.aborted ? "cancelled" : "failed";
2277
2753
  await registry.settle(handle.id, {
2278
2754
  state,
@@ -2697,8 +3173,8 @@ function apply(ctx, rawConfig = {}) {
2697
3173
  home: evolutionRoot(),
2698
3174
  ...mounted
2699
3175
  });
2700
- if (input === "doctor --json") return ok(JSON.stringify(report, null, 2));
2701
- return ok(renderDoctorText(report));
3176
+ if (input === "doctor --json") return ok(JSON.stringify(report.view, null, 2));
3177
+ return ok(renderDoctorText(report) + "\n" + renderDiagnosticsText(report.view));
2702
3178
  }
2703
3179
  if (input === "" || input === "help") return ok(`自进化:记忆、技能、会话回顾、技能整理——状态、待批准写入与维护。\n${renderHelpText()}`);
2704
3180
  return err(`/evolution ${input} 不是本构建能应答的子命令(已知子命令但缺参数也会落到这里)。\n${renderHint()}`);
@@ -2760,6 +3236,7 @@ function apply(ctx, rawConfig = {}) {
2760
3236
  ctx.inject(["settings"], () => {
2761
3237
  runMigrationOnce();
2762
3238
  });
3239
+ registerHostRoutes(ctx, () => makeDiagnosticsRoutes(() => assembleDoctorInputs(ctx, { runs: doctorRuns() })));
2763
3240
  (async () => {
2764
3241
  const loader = ctx.root.loader;
2765
3242
  try {
@@ -0,0 +1,27 @@
1
+ /**
2
+ * G6/W8: the command-face fallback for every Host data-plane surface.
3
+ *
4
+ * The browser panels depend on the platform's `connection` carrier (exact Fetch routes). When that
5
+ * carrier is absent (headless host, E-325) or a panel answer is missing, the operator is owed the
6
+ * equivalent command — the SAME use case, not a different implementation. This table is the ONE
7
+ * home of those pairs; the INSTALL runbook and the client `fallbacks.generated.ts` are generated
8
+ * from it (W7), and a spec pins its key set to HOST_ROUTES' four surfaces.
9
+ *
10
+ * Not exported from the package: this is a command-side contract, not a plugin API (W8 §2).
11
+ * @module @lmzhen/dsh-evolution-commands/command-fallback
12
+ */
13
+ /** One surface's command-face path(s): the family command, plus the platform CLI when one exists. */
14
+ export interface CommandFallbackHint {
15
+ readonly command: string;
16
+ readonly platformCli?: string;
17
+ }
18
+ /** The four Host surfaces (keyed exactly like the frozen `COMMAND_FALLBACK_SURFACES`). */
19
+ export type CommandFallbackSurface = 'approval' | 'skill-history' | 'skill-health' | 'diagnostics';
20
+ /**
21
+ * One entry per Host surface. The command column names the same service methods the routes call,
22
+ * so the answers (and refusal sentences) match the panel.
23
+ */
24
+ export declare const COMMAND_FALLBACKS: Readonly<Record<CommandFallbackSurface, CommandFallbackHint>>;
25
+ /** The ordered surface ids (the frozen W1 set). */
26
+ export declare const COMMAND_FALLBACK_SURFACES: readonly CommandFallbackSurface[];
27
+ //# sourceMappingURL=command-fallback.d.ts.map
@@ -0,0 +1,9 @@
1
+ import type { ConnectionFetchRouteLike } from '@lmzhen/dsh-evolution-core/http-routes';
2
+ import { type DoctorInputs } from './doctor-view.ts';
3
+ /**
4
+ * Build the diagnostics route over one input producer.
5
+ * @param getInputs - assembles the live view inputs (soft probes; may reject).
6
+ * @returns the exact Fetch route for `HOST_ROUTES.diagnostics.view`.
7
+ */
8
+ export declare function makeDiagnosticsRoutes(getInputs: () => Promise<DoctorInputs>): ConnectionFetchRouteLike[];
9
+ //# sourceMappingURL=diagnostics-route.d.ts.map
@@ -0,0 +1,98 @@
1
+ /**
2
+ * G6/W8: `buildDoctorView` — the ONE producer of DiagnosticsView.
3
+ *
4
+ * CLI `--json`, the diagnostics route (W6) and the client diagnostics card (W7) all call this
5
+ * function, so they cannot drift: 7 facts (declared/derived values, homes named) + 8 runtime rows
6
+ * (measured state, never rendered as ok when unreadable). The function is pure and NEVER throws: a
7
+ * missing/undecidable provider becomes an `unknown`/`warn`/`fail` row with a remedy, because a
8
+ * doctor that fails to answer is worse than one that names its blind spots.
9
+ * @module @lmzhen/dsh-evolution-commands/doctor-view
10
+ */
11
+ import type { Context } from '@deepseek-ai/cordis';
12
+ import { type ChannelState, type DiagnosticsView } from '@lmzhen/dsh-evolution-core';
13
+ /** The install form the running profile was classified as (W8 §2 runtime install-form-detected). */
14
+ export type InstallFormId = 'M1' | 'M2' | 'M3' | 'M4';
15
+ /** One preset's probe result (reviewer/maintainer). */
16
+ export interface PresetFacts {
17
+ readonly registered: boolean;
18
+ readonly broken?: string | undefined;
19
+ readonly id?: string | undefined;
20
+ }
21
+ /** The platform/connection pairing verdict. */
22
+ export type PlatformPairing = 'in-window' | 'host-newer' | 'out-of-window' | 'unknown';
23
+ /**
24
+ * Every input the view reads. Callers assemble it (assembleDoctorInputs does the soft probing);
25
+ * tests drive each branch directly.
26
+ */
27
+ export interface DoctorInputs {
28
+ readonly now: Date;
29
+ readonly platformLine: {
30
+ readonly version: string;
31
+ readonly floor: string;
32
+ };
33
+ readonly familyVersion: string;
34
+ readonly hostVersion?: string | undefined;
35
+ readonly form: {
36
+ readonly id: InstallFormId;
37
+ readonly evidence: readonly string[];
38
+ readonly confidence: 'declared' | 'inferred';
39
+ };
40
+ readonly probes: {
41
+ readonly agentPresets: boolean;
42
+ readonly connection: boolean;
43
+ };
44
+ readonly channels: {
45
+ readonly review: ChannelState;
46
+ readonly maintenance: ChannelState;
47
+ };
48
+ readonly presets: {
49
+ readonly reviewer?: PresetFacts | undefined;
50
+ readonly maintainer?: PresetFacts | undefined;
51
+ };
52
+ readonly host: {
53
+ readonly mounted: boolean;
54
+ readonly reason?: string | undefined;
55
+ readonly deploymentAllowsRemote?: boolean | undefined;
56
+ };
57
+ readonly runs: {
58
+ readonly running: number;
59
+ readonly orphaned: number;
60
+ };
61
+ readonly missingServices?: readonly string[] | undefined;
62
+ readonly sessionFoldClassifierPresent?: boolean | undefined;
63
+ readonly platformPairing?: PlatformPairing | undefined;
64
+ /** Human-facing total of registered host routes, when the carrier reports one (12 today). */
65
+ readonly hostRouteCount?: {
66
+ readonly mounted: number;
67
+ readonly total: number;
68
+ } | undefined;
69
+ }
70
+ /**
71
+ * Build the frozen DiagnosticsView. Pure; never throws.
72
+ * @param inputs - everything the 7+8 rows read (assembleDoctorInputs probes the live host).
73
+ */
74
+ export declare function buildDoctorView(inputs: DoctorInputs): DiagnosticsView;
75
+ /** A safe view for a host whose inputs could not be assembled: every row names the failure. */
76
+ export declare function unavailableDoctorView(now: Date, reason: string): DiagnosticsView;
77
+ /** The family version this build carries (nearest package.json above the running module). */
78
+ export declare function familyVersion(): string;
79
+ /**
80
+ * Assemble the view inputs through the live Context's soft probes.
81
+ *
82
+ * Every probe is optional: an absent service becomes a row value (unknown/warn), never a throw. The
83
+ * channel probe uses W5's own `createSubagentChannel` state machinery, so the doctor reports the
84
+ * SAME availability the review/maintenance runs would see.
85
+ * @param ctx - the host context.
86
+ * @param extras - call-site facts the context cannot answer (runs, host version, install form).
87
+ */
88
+ export declare function assembleDoctorInputs(ctx: Context, extras?: {
89
+ readonly runs?: {
90
+ readonly running: number;
91
+ readonly orphaned: number;
92
+ } | undefined;
93
+ readonly hostVersion?: string | undefined;
94
+ readonly form?: DoctorInputs['form'] | undefined;
95
+ readonly platformPairing?: PlatformPairing | undefined;
96
+ readonly now?: Date | undefined;
97
+ }): Promise<DoctorInputs>;
98
+ //# sourceMappingURL=doctor-view.d.ts.map
@@ -1,4 +1,5 @@
1
1
  import { type PlatformServiceProbe, type ScopedProbeReport } from '@lmzhen/dsh-evolution-core';
2
+ import type { DiagnosticsView } from '@lmzhen/dsh-evolution-core';
2
3
  import type { MountedProfile } from './profiles.ts';
3
4
  /** P2-20 (audit): the TS-side copy of the install-target tails. The script
4
5
  * side (install-layered.mjs, verify-profile-bundles.mjs) imports the list
@@ -87,12 +88,6 @@ export interface DoctorReport {
87
88
  foreignProfiles: string[];
88
89
  conflicts: string[];
89
90
  envIssues: string[];
90
- /** S0.2 (v37 P0-1): memory files carrying the platform's `{{` prompt-variable
91
- * syntax. Residual-risk detector — current builds neutralize the INJECTED text
92
- * (`MemoryStore.renderContext`), but a still-running older build fails every
93
- * pre-step of every session under this home, and an operator may want to clean
94
- * the file regardless. */
95
- memoryIssues: string[];
96
91
  /** S4/P-1+P-2 (platform gap, family mitigation): the session-query corpus
97
92
  * listing failed — reported verbatim, with the isolation recipe. Empty when the
98
93
  * service is absent or answering. */
@@ -146,6 +141,9 @@ export interface DoctorReport {
146
141
  /** G5: where the review row's presence came from: the live entry, or the aggregate inference. */
147
142
  serviceSource: 'platform' | 'bundles';
148
143
  actions: string[];
144
+ /** G6/W8 tail: the frozen DiagnosticsView (7 facts + 8 runtime rows) — the SAME object the
145
+ * diagnostics route and the client card render; `--json` emits it and the text renderer prints it. */
146
+ readonly view: DiagnosticsView;
149
147
  }
150
148
  /** Profile bundle rows for evolution-family packages across all profiles.
151
149
  *
@@ -162,4 +160,14 @@ export declare function diagnose(ctx: {
162
160
  mounted?: MountedProfile;
163
161
  }): Promise<DoctorReport>;
164
162
  export declare function renderDoctorText(report: DoctorReport): string;
163
+ /**
164
+ * G6/W8 tail: render the frozen DiagnosticsView as the doctor's diagnostics section.
165
+ *
166
+ * This is the ONE text face of the 7+8 rows: the same object `--json` and the diagnostics route
167
+ * answer, so the three readers cannot disagree. Legacy install/conflict sections keep their own
168
+ * renderer until the remaining W8-S7 rewrite lands.
169
+ * @param view - the assembled view.
170
+ * @returns the section text (ends with a newline).
171
+ */
172
+ export declare function renderDiagnosticsText(view: DiagnosticsView): string;
165
173
  //# sourceMappingURL=doctor.d.ts.map
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.26",
4
+ "version": "0.27.0",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -26,20 +26,23 @@
26
26
  ],
27
27
  "license": "MIT",
28
28
  "dependencies": {
29
- "@deepseek-ai/schemastery": "^3.18.4",
30
- "@lmzhen/dsh-evolution-approval": "^0.26.26",
31
- "@lmzhen/dsh-evolution-core": "^0.26.26",
32
- "@lmzhen/dsh-evolution-maintenance": "^0.26.26"
29
+ "@deepseek-ai/schemastery": "^3.18.5",
30
+ "@lmzhen/dsh-evolution-approval": "^0.27.0",
31
+ "@lmzhen/dsh-evolution-core": "^0.27.0",
32
+ "@lmzhen/dsh-evolution-maintenance": "^0.27.0"
33
33
  },
34
34
  "optionalDependencies": {
35
- "@lmzhen/dsh-evolution-agent-preset": "^0.26.26"
35
+ "@lmzhen/dsh-evolution-agent-preset": "^0.27.0"
36
36
  },
37
37
  "peerDependencies": {
38
- "@deepseek-ai/cordis": "^4.0.1",
39
- "@deepseek-ai/dsh-commands": "^0.2.0-rc.1",
40
- "@deepseek-ai/dsh-llm": "^0.2.0-rc.1"
38
+ "@deepseek-ai/cordis": "^4.0.5",
39
+ "@deepseek-ai/dsh-commands": "^0.2.1-rc.1",
40
+ "@deepseek-ai/dsh-llm": "^0.2.1-rc.1"
41
41
  },
42
42
  "devDependencies": {
43
- "@deepseek-ai/dsh-commands": "^0.2.0-rc.1"
43
+ "@deepseek-ai/dsh-commands": "^0.2.1-rc.1"
44
+ },
45
+ "engines": {
46
+ "dsh": ">=0.2.1-rc.1 <0.2.2-0"
44
47
  }
45
48
  }