@lmzhen/dsh-evolution-commands 0.30.18 → 0.30.19

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
@@ -1477,6 +1477,8 @@ async function diagnose(ctx, options = {}) {
1477
1477
  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`);
1478
1478
  }
1479
1479
  const view = buildDoctorView(await assembleDoctorInputs(ctx));
1480
+ const merge = await mergeDoctorLine(ctx);
1481
+ if (merge.status !== "ok" && merge.remedy !== void 0) actions.push("合并行:" + merge.remedy);
1480
1482
  return {
1481
1483
  installForm,
1482
1484
  deploymentForm,
@@ -1500,7 +1502,35 @@ async function diagnose(ctx, options = {}) {
1500
1502
  dormantBundles,
1501
1503
  serviceSource: reviewRow === void 0 ? "bundles" : "platform",
1502
1504
  actions,
1503
- view
1505
+ view,
1506
+ merge
1507
+ };
1508
+ }
1509
+ /**
1510
+ * Probe the merge plugin's global-doctor line. The plugin exposes `doctorFact()` (which composes
1511
+ * its wire DoctorView); this module never reads merge files itself, so core stays free of merge
1512
+ * rows and the two producers cannot drift.
1513
+ * @param ctx - the host service view.
1514
+ * @returns the one-line merge fact; unknown on absence or failure (doctor must answer).
1515
+ */
1516
+ async function mergeDoctorLine(ctx) {
1517
+ const service = ctx.get("evolutionMerge");
1518
+ if (service?.doctorFact === void 0) return {
1519
+ status: "unknown",
1520
+ detail: "evolutionMerge 服务未挂载:本安装形态没有合并行",
1521
+ remedy: "安装含 evolution-merge 的 bundle 后重新运行 /evolution doctor"
1522
+ };
1523
+ const probed = await probeBounded(() => service.doctorFact(), "merge doctor");
1524
+ if (!probed.ok) return {
1525
+ status: "unknown",
1526
+ detail: "合并状态探测失败:" + probed.message,
1527
+ remedy: "运行 /evolution merge doctor 查看详情"
1528
+ };
1529
+ const value = probed.value;
1530
+ return {
1531
+ status: value.status === "ok" || value.status === "warn" || value.status === "fail" || value.status === "unknown" ? value.status : "unknown",
1532
+ detail: typeof value.detail === "string" && value.detail !== "" ? value.detail : "合并状态探测没有返回 detail",
1533
+ ...typeof value.remedy === "string" && value.remedy !== "" ? { remedy: value.remedy } : {}
1504
1534
  };
1505
1535
  }
1506
1536
  /**
@@ -1642,6 +1672,9 @@ function renderDoctorText(report) {
1642
1672
  if (report.dormantBundles.length > 0) extras.push(`installed but not selected: ${report.dormantBundles.join(", ")} — the Plugins page lists them; this runtime mounts neither`);
1643
1673
  extras.push(`bundles (all profiles): ${report.bundles.length > 0 ? report.bundles.join(", ") : "(none)"}`);
1644
1674
  extras.push(`services: review=${report.services.review} (${report.serviceSource === "platform" ? "the live row, this runtime" : "inferred from bundles, all profiles"}) curator=${report.services.curator} approval=${report.services.approval} skillUsage=${report.services.skillUsage} io=${report.services.io}`);
1675
+ const mergeStatus = report.merge.status === "ok" ? "正常" : report.merge.status === "warn" ? "注意" : report.merge.status === "fail" ? "异常" : "未知";
1676
+ extras.push("merge: [" + mergeStatus + "] " + report.merge.detail + (report.merge.remedy === void 0 ? "" : ";下一步:" + report.merge.remedy));
1677
+ if (report.merge.status !== "ok" && report.merge.remedy !== void 0) extras.push(" → " + report.merge.remedy);
1645
1678
  if (report.capabilityAbsence.length > 0) extras.push(`capability absence (${report.capabilityAbsence.length}): ${report.capabilityAbsence.map((entry) => `${entry.service} — ${entry.feature}`).join("; ")}`);
1646
1679
  extras.push(scopedProbeLine(report.scopedProbe));
1647
1680
  extras.push(legacyLine(report.legacy));
@@ -2178,6 +2211,108 @@ function parseSubcommand(input) {
2178
2211
  };
2179
2212
  }
2180
2213
  //#endregion
2214
+ //#region lib/types/command-catalog.js
2215
+ function buildSurface(defs, prefix) {
2216
+ const defaults = (def) => Object.fromEntries(def.flags.flatMap((flag) => {
2217
+ if (flag.default !== void 0) return [[flag.name, flag.default]];
2218
+ return flag.kind === "boolean" ? [[flag.name, "false"]] : [];
2219
+ }));
2220
+ return {
2221
+ wireVersion: 1,
2222
+ prefix,
2223
+ commands: defs.filter((def) => def.surface !== false).map((def) => ({
2224
+ id: def.id,
2225
+ words: def.words,
2226
+ flags: def.flags,
2227
+ positional: def.positional,
2228
+ readOnly: def.readOnly,
2229
+ needsRun: def.needsRun,
2230
+ defaults: defaults(def),
2231
+ rowActions: (def.rowActions ?? []).map((action) => ({
2232
+ id: action.id,
2233
+ command: action.command,
2234
+ confirm: action.confirm === true
2235
+ })),
2236
+ fallback: def.fallback
2237
+ })),
2238
+ fallback: defs.filter((def) => def.surface !== false && (def.rowActions === void 0 || def.rowActions.length === 0)).map((def) => ({
2239
+ id: def.id,
2240
+ command: def.fallback
2241
+ }))
2242
+ };
2243
+ }
2244
+ function parseCommand(defs, argv) {
2245
+ const [verb, ...rest] = argv;
2246
+ if (verb === void 0 || verb === "help" || verb === "--help" || verb === "-h") return {
2247
+ ok: true,
2248
+ id: "help",
2249
+ values: {},
2250
+ help: true
2251
+ };
2252
+ const candidates = defs.filter((item) => item.words.includes(verb));
2253
+ const def = candidates.find((item) => rest.filter((token) => token.startsWith("--")).every((token) => item.flags.some((flag) => flag.name === token.slice(2)))) ?? candidates[0];
2254
+ if (def === void 0) return {
2255
+ ok: false,
2256
+ code: "usage",
2257
+ message: "unknown command: " + verb
2258
+ };
2259
+ const values = {};
2260
+ const positional = [];
2261
+ for (let index = 0; index < rest.length; index += 1) {
2262
+ const token = rest[index];
2263
+ if (!token.startsWith("--")) {
2264
+ positional.push(token);
2265
+ continue;
2266
+ }
2267
+ const name = token.slice(2);
2268
+ const flag = def.flags.find((item) => item.name === name);
2269
+ if (flag === void 0) return {
2270
+ ok: false,
2271
+ code: "usage",
2272
+ message: "unknown flag: --" + name
2273
+ };
2274
+ if (flag.kind === "boolean") values[name] = true;
2275
+ else {
2276
+ const value = rest[index + 1];
2277
+ if (value === void 0 || value.startsWith("--")) return {
2278
+ ok: false,
2279
+ code: "usage",
2280
+ message: "missing value for --" + name
2281
+ };
2282
+ values[name] = value;
2283
+ index += 1;
2284
+ }
2285
+ }
2286
+ for (const flag of def.flags) if (flag.required === true && values[flag.name] === void 0) return {
2287
+ ok: false,
2288
+ code: "usage",
2289
+ message: "missing required flag: --" + flag.name
2290
+ };
2291
+ const required = def.positional.filter((item) => item.required).length;
2292
+ if (positional.length < required || positional.length > def.positional.length) return {
2293
+ ok: false,
2294
+ code: "usage",
2295
+ message: "usage: " + def.fallback
2296
+ };
2297
+ def.positional.forEach((item, index) => {
2298
+ const value = positional[index];
2299
+ if (value !== void 0) values[item.name] = value;
2300
+ });
2301
+ return {
2302
+ ok: true,
2303
+ id: def.id,
2304
+ values
2305
+ };
2306
+ }
2307
+ function aliasRegistration(version, def) {
2308
+ if (def.deprecatedAt === void 0) return "registered";
2309
+ const [major, minor] = version.split(".").map(Number);
2310
+ const [depMajor = 0, depMinor = 0] = def.deprecatedAt.split(".").map(Number);
2311
+ if ((major ?? 0) > depMajor || (major ?? 0) === depMajor && (minor ?? 0) >= depMinor + 1) return "removed";
2312
+ if ((major ?? 0) === depMajor && (minor ?? 0) === depMinor) return "deprecated";
2313
+ return "registered";
2314
+ }
2315
+ //#endregion
2181
2316
  //#region lib/types/index.js
2182
2317
  /**
2183
2318
  * Human commands for the evolution family: /evolution learn|pending|curator|restore|consolidate|skills refresh.
@@ -3342,4 +3477,4 @@ function safeStagedArgs(args) {
3342
3477
  }
3343
3478
  }
3344
3479
  //#endregion
3345
- export { Config, apply, atomicWriteFiles, name };
3480
+ export { Config, aliasRegistration, apply, atomicWriteFiles, buildSurface, name, parseCommand };
@@ -0,0 +1,87 @@
1
+ /** Family command catalog: one source for parser/help/surface/fallback. */
2
+ export interface CommandFlagDef {
3
+ readonly name: string;
4
+ readonly kind: 'boolean' | 'value';
5
+ readonly valueName?: string;
6
+ readonly repeatable?: boolean;
7
+ readonly values?: readonly string[];
8
+ readonly default?: string;
9
+ readonly required?: boolean;
10
+ }
11
+ export interface CommandPositionalDef {
12
+ readonly name: string;
13
+ readonly kind: 'skill' | 'target' | 'runId' | 'id' | 'json' | 'text';
14
+ readonly required: boolean;
15
+ readonly variadic?: boolean;
16
+ }
17
+ export interface RowActionDef {
18
+ readonly id: string;
19
+ readonly command: string;
20
+ readonly confirm?: boolean;
21
+ readonly enabledWhen?: string;
22
+ }
23
+ export interface CommandDef {
24
+ readonly id: string;
25
+ readonly words: readonly string[];
26
+ readonly summaryKey: string;
27
+ readonly helpKey: string;
28
+ readonly flags: readonly CommandFlagDef[];
29
+ readonly positional: readonly CommandPositionalDef[];
30
+ readonly readOnly: boolean;
31
+ readonly needsRun: boolean;
32
+ readonly aliases?: readonly string[];
33
+ readonly rowActions?: readonly RowActionDef[];
34
+ readonly fallback: string;
35
+ readonly deprecatedAt?: '0.31' | '0.32';
36
+ readonly surface?: boolean;
37
+ readonly usage?: boolean;
38
+ }
39
+ export interface CommandSurfaceCommand {
40
+ readonly id: string;
41
+ readonly words: readonly string[];
42
+ readonly flags: readonly {
43
+ readonly name: string;
44
+ readonly kind: 'boolean' | 'value';
45
+ readonly valueName?: string;
46
+ readonly repeatable?: boolean;
47
+ readonly values?: readonly string[];
48
+ }[];
49
+ readonly positional: readonly {
50
+ readonly name: string;
51
+ readonly kind: 'skill' | 'target' | 'runId' | 'id' | 'json' | 'text';
52
+ readonly required: boolean;
53
+ readonly variadic?: boolean;
54
+ }[];
55
+ readonly readOnly: boolean;
56
+ readonly needsRun: boolean;
57
+ readonly defaults: Readonly<Record<string, string>>;
58
+ readonly rowActions: readonly {
59
+ readonly id: string;
60
+ readonly command: string;
61
+ readonly confirm: boolean;
62
+ }[];
63
+ readonly fallback: string;
64
+ }
65
+ export interface CommandSurfaceView {
66
+ readonly wireVersion: 1;
67
+ readonly prefix: string;
68
+ readonly commands: readonly CommandSurfaceCommand[];
69
+ readonly fallback: readonly {
70
+ readonly id: string;
71
+ readonly command: string;
72
+ }[];
73
+ }
74
+ export type ParsedCommand = {
75
+ readonly ok: true;
76
+ readonly id: string;
77
+ readonly values: Readonly<Record<string, string | true>>;
78
+ readonly help?: true;
79
+ } | {
80
+ readonly ok: false;
81
+ readonly code: 'usage';
82
+ readonly message: string;
83
+ };
84
+ export declare function buildSurface(defs: readonly CommandDef[], prefix: string): CommandSurfaceView;
85
+ export declare function parseCommand(defs: readonly CommandDef[], argv: readonly string[]): ParsedCommand;
86
+ export declare function aliasRegistration(version: string, def: CommandDef): 'registered' | 'deprecated' | 'removed';
87
+ //# sourceMappingURL=command-catalog.d.ts.map
@@ -60,6 +60,12 @@ export interface LegacyMigrationRow {
60
60
  /** Keys still waiting for their row, as `rowId.key`. */
61
61
  readonly pending: readonly string[];
62
62
  }
63
+ /** The merge plugin's one-line contribution to the global doctor (cross-plugin read). */
64
+ export interface MergeDoctorLine {
65
+ readonly status: 'ok' | 'warn' | 'fail' | 'unknown';
66
+ readonly detail: string;
67
+ readonly remedy?: string;
68
+ }
63
69
  export interface DoctorReport {
64
70
  /** OPT-23 (2026-09): `preset-only` — the delivered Evolution preset
65
71
  * artifact exists but NO profile carries an evolution bundle (e.g. the
@@ -144,6 +150,8 @@ export interface DoctorReport {
144
150
  /** G6/W8 tail: the frozen DiagnosticsView (7 facts + 8 runtime rows) — the SAME object the
145
151
  * diagnostics route and the client card render; `--json` emits it and the text renderer prints it. */
146
152
  readonly view: DiagnosticsView;
153
+ /** The merge row: produced by the merge plugin's buildDoctorView consumer; absent service = unknown. */
154
+ readonly merge: MergeDoctorLine;
147
155
  }
148
156
  /** Profile bundle rows for evolution-family packages across all profiles.
149
157
  *
@@ -73,4 +73,5 @@ export declare const Config: z<Schemastery.ObjectS<NoInfer<{
73
73
  threatExemptLabels: z<string[], string[], "defined">;
74
74
  }>>, "plain">;
75
75
  export declare function apply(ctx: Context, rawConfig?: Config): void;
76
+ export * from './command-catalog.ts';
76
77
  //# sourceMappingURL=index.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.30.18",
4
+ "version": "0.30.19",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -27,12 +27,12 @@
27
27
  "license": "MIT",
28
28
  "dependencies": {
29
29
  "@deepseek-ai/schemastery": "^3.18.5",
30
- "@lmzhen/dsh-evolution-approval": "^0.30.18",
31
- "@lmzhen/dsh-evolution-core": "^0.30.18",
32
- "@lmzhen/dsh-evolution-maintenance": "^0.30.18"
30
+ "@lmzhen/dsh-evolution-approval": "^0.30.19",
31
+ "@lmzhen/dsh-evolution-core": "^0.30.19",
32
+ "@lmzhen/dsh-evolution-maintenance": "^0.30.19"
33
33
  },
34
34
  "optionalDependencies": {
35
- "@lmzhen/dsh-evolution-agent-preset": "^0.30.18"
35
+ "@lmzhen/dsh-evolution-agent-preset": "^0.30.19"
36
36
  },
37
37
  "peerDependencies": {
38
38
  "@deepseek-ai/cordis": "^4.0.5",