@lmzhen/dsh-evolution-commands 0.5.0 → 0.6.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
@@ -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 { DEFAULT_SKILL_LIMITS, MAX_TIMER_DELAY_MS, appendEvolutionEvent, assertSkillsRootAliasRetired, buildLearnPrompt, clampedNumber, composePresetComposition, eventsFile, evolutionRoot, isMissingPath, newSkillLibrary, policyStageLimits, resolveRootConfig, scopedProbeReport } from "@lmzhen/dsh-evolution-core";
5
+ import { DEFAULT_SKILL_LIMITS, MAX_TIMER_DELAY_MS, PARAM_EXPOSURE, PARAM_NAMESPACES, appendEvolutionEvent, assertSkillsRootAliasRetired, buildLearnPrompt, canonicalWriteId, clampedNumber, composePresetComposition, eventsFile, evolutionRoot, isDeprecatedParamId, isMissingPath, newSkillLibrary, policyStageLimits, resolveParamId, resolveRootConfig, scopedProbeReport } 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";
@@ -30,6 +30,52 @@ const EVOLUTION_BUNDLE_TAILS = new Set([
30
30
  "dsh-evolution-host",
31
31
  "dsh-evolution-preset"
32
32
  ]);
33
+ /**
34
+ * S4.3: the parameter-surface divergences a RUNNING deployment can show and the
35
+ * build-time guards cannot. Three classes, each with its own move:
36
+ * - a user override (the effective value is no longer the deployment's),
37
+ * - a deprecated alias still written in a user section (writes refuse it),
38
+ * - a declared user-writable parameter whose owner publishes no user layer in
39
+ * this composition (the registry promises a face this deployment never mounts).
40
+ * An unreadable settings surface is reported AS SUCH: silence would read as "no
41
+ * divergences", which is the one claim this section exists to check.
42
+ * @param ctx - the plugin context; the settings service is read optionally.
43
+ * @returns one readable line per divergence, empty when there is nothing to compare.
44
+ */
45
+ function paramDivergences(ctx) {
46
+ const provider = ctx.get("settings");
47
+ if (provider?.describe === void 0) return [];
48
+ let descriptors;
49
+ try {
50
+ descriptors = provider.describe({ redactSecrets: false });
51
+ } catch (error) {
52
+ return [`settings surface unreadable (${error instanceof Error ? error.message : String(error)}) — parameter divergences were NOT checked`];
53
+ }
54
+ const issues = [];
55
+ const registered = new Map(descriptors.map((descriptor) => [descriptor.ns, descriptor.user ?? {}]));
56
+ for (const [namespace, user] of registered) {
57
+ const keys = Object.keys(user);
58
+ if (keys.length > 0) {
59
+ const shown = keys.slice(0, 5).map((key) => `${key}=${JSON.stringify(user[key])}`);
60
+ const more = keys.length > shown.length ? ` and ${keys.length - shown.length} more` : "";
61
+ issues.push(`user override: ${namespace} sets ${keys.length} parameter(s) — ${shown.join(", ")}${more}`);
62
+ }
63
+ for (const key of keys) {
64
+ if (!isDeprecatedParamId(key)) continue;
65
+ issues.push(`deprecated name: ${namespace} still writes "${key}" — write "${resolveParamId(key)}" instead (writes refuse the alias; it is removed in 0.7.0)`);
66
+ }
67
+ }
68
+ const unreachable = PARAM_EXPOSURE.filter((entry) => entry.tier === "E3").filter((entry) => {
69
+ const namespace = PARAM_NAMESPACES[entry.owner];
70
+ return namespace === void 0 || !registered.has(namespace);
71
+ }).map((entry) => entry.id);
72
+ if (unreachable.length > 0) {
73
+ const shown = unreachable.slice(0, 6).join(", ");
74
+ const more = unreachable.length > 6 ? ` (+${unreachable.length - 6} more)` : "";
75
+ issues.push(`declared user-writable but unreachable here: ${shown}${more} — the owning package publishes no user layer in this composition`);
76
+ }
77
+ return issues;
78
+ }
33
79
  /** Profile bundle rows for evolution-family packages across all profiles.
34
80
  *
35
81
  * `onReadError` reports a profile whose manifest could not be READ or PARSED.
@@ -370,8 +416,10 @@ async function diagnose(ctx, options = {}) {
370
416
  if (pendingCount === null && services.approval) actions.push("Approval service is mounted but pending listing failed — check the evolution state service.");
371
417
  const memoryIssues = memoryInterpolationIssues(home);
372
418
  const budgetIssues = memoryBudgetIssues(ctx);
419
+ const paramIssues = paramDivergences(ctx);
373
420
  const queryIssues = await sessionQueryIssues(ctx);
374
421
  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");
422
+ 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.");
375
423
  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)");
376
424
  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.");
377
425
  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.");
@@ -388,6 +436,7 @@ async function diagnose(ctx, options = {}) {
388
436
  envIssues: env,
389
437
  memoryIssues,
390
438
  budgetIssues,
439
+ paramIssues,
391
440
  queryIssues,
392
441
  services,
393
442
  pendingCount,
@@ -519,11 +568,143 @@ function renderDoctorText(report) {
519
568
  if (report.envIssues.length > 0) lines.push("env:", ...report.envIssues.map((line) => ` ! ${line}`));
520
569
  if (report.memoryIssues.length > 0) lines.push("memory:", ...report.memoryIssues.map((line) => ` ! ${line}`));
521
570
  if (report.budgetIssues.length > 0) lines.push("memory budget:", ...report.budgetIssues.map((line) => ` ! ${line}`));
571
+ if (report.paramIssues.length > 0) lines.push("parameters:", ...report.paramIssues.map((line) => ` ! ${line}`));
522
572
  if (report.queryIssues.length > 0) lines.push("session search:", ...report.queryIssues.map((line) => ` ! ${line}`));
523
573
  if (report.actions.length > 0) lines.push("next steps:", ...report.actions.map((line) => ` → ${line}`));
524
574
  return lines.join("\n");
525
575
  }
526
576
  //#endregion
577
+ //#region lib/types/params.js
578
+ /**
579
+ * `/evolution params` view (G4/S4.1): the registry joined with the settings
580
+ * surface, so one text answers "which parameters exist, who may write them, and
581
+ * did I already override this one".
582
+ *
583
+ * The join key is the OWNER PACKAGE: core's `PARAM_NAMESPACES` maps a package to
584
+ * the one namespace it registers, and `settings.describe()` reports that
585
+ * namespace's raw user section (a key's PRESENCE is the override) plus the
586
+ * resolved value. Everything here is pure; the command handler supplies the data.
587
+ * @module @lmzhen/dsh-evolution-commands/params
588
+ */
589
+ /**
590
+ * Join the registry with the settings surface.
591
+ *
592
+ * `user` means the raw user section carries this key; `deployment` means the
593
+ * owner's namespace is registered but the key is unset (so the row/policy value
594
+ * applies); `unregistered` means the owner package publishes no namespace at all
595
+ * (or is not mounted), which is the honest answer for a deployment-only face.
596
+ * The distinction matters: 'no user section' must never read as 'not overridden'
597
+ * for a namespace that failed to register.
598
+ * @param sections - namespace to surface view, from `settings.describe()`.
599
+ * @param entries - registry entries (defaults to the family registry).
600
+ * @returns one row per registry entry, in registry order.
601
+ */
602
+ function paramSurfaceRows(sections, entries = PARAM_EXPOSURE) {
603
+ return entries.map((entry) => {
604
+ const base = {
605
+ id: entry.id,
606
+ group: entry.group,
607
+ tier: entry.tier,
608
+ applies: entry.applies,
609
+ owner: entry.owner
610
+ };
611
+ const namespace = PARAM_NAMESPACES[entry.owner];
612
+ const section = namespace === void 0 ? void 0 : sections.get(namespace);
613
+ if (section === void 0) return {
614
+ ...base,
615
+ source: "unregistered",
616
+ value: void 0
617
+ };
618
+ const user = section.user;
619
+ if (user !== void 0 && Object.hasOwn(user, entry.id)) return {
620
+ ...base,
621
+ source: "user",
622
+ value: user[entry.id]
623
+ };
624
+ return {
625
+ ...base,
626
+ source: "deployment",
627
+ value: section.value?.[entry.id]
628
+ };
629
+ });
630
+ }
631
+ /**
632
+ * The groups the registry actually carries, in registry order — the list an
633
+ * unknown `--group` value is answered with.
634
+ * @param entries - registry entries (defaults to the family registry).
635
+ * @returns distinct group names.
636
+ */
637
+ function paramGroups(entries = PARAM_EXPOSURE) {
638
+ return [...new Set(entries.map((entry) => entry.group))];
639
+ }
640
+ /** One cell: `—` when no face reports a value, JSON for arrays/objects. */
641
+ function renderValue(value) {
642
+ if (value === void 0) return "—";
643
+ if (typeof value === "string") return value;
644
+ return JSON.stringify(value);
645
+ }
646
+ /**
647
+ * Render the rows as fixed-width text (one parameter per line, never truncated).
648
+ * @param rows - rows from {@link paramSurfaceRows}.
649
+ * @param options - whether the settings service answered at all.
650
+ * @returns the command text.
651
+ */
652
+ function renderParamRows(rows, options) {
653
+ const header = "PARAMETER".padEnd(36) + "GROUP".padEnd(12) + "TIER".padEnd(5) + "APPLIES".padEnd(9) + "SOURCE".padEnd(14) + "VALUE";
654
+ const body = rows.map((row) => row.id.padEnd(36) + row.group.padEnd(12) + row.tier.padEnd(5) + row.applies.padEnd(9) + row.source.padEnd(14) + renderValue(row.value));
655
+ const tiers = [
656
+ "E0",
657
+ "E1",
658
+ "E2",
659
+ "E3",
660
+ "E4"
661
+ ].map((tier) => `${tier} ${rows.filter((row) => row.tier === tier).length}`).join(" / ");
662
+ const userRows = rows.filter((row) => row.source === "user").length;
663
+ const overrides = options.providerMounted ? `${userRows} overridden by the user.` : "overrides UNKNOWN (no settings provider).";
664
+ const note = options.providerMounted ? "SOURCE: user = you set it in settings; deployment = the row/policy value applies; unregistered = the owner publishes no user layer." : "settings provider not mounted — the user layer is unavailable, so every row shows its deployment face (this is NOT \"no overrides\").";
665
+ return [
666
+ header,
667
+ ...body,
668
+ "",
669
+ `${rows.length} parameter(s): ${tiers}; ${overrides}`,
670
+ note
671
+ ].join("\n");
672
+ }
673
+ /**
674
+ * Parse one command-line value into what the settings document stores: JSON
675
+ * when it parses (numbers, booleans, quoted strings, arrays, objects), the raw
676
+ * text otherwise, so a bare word like `enforce` stays a word.
677
+ * @param raw - the text typed after the parameter id.
678
+ * @returns the value to write.
679
+ */
680
+ function parseParamValue(raw) {
681
+ const text = raw.trim();
682
+ try {
683
+ return JSON.parse(text);
684
+ } catch {
685
+ return text;
686
+ }
687
+ }
688
+ /**
689
+ * Render the write result: the value move, then where it landed and when it
690
+ * takes effect (the two things a caller cannot infer from the command line).
691
+ * @param write - the accepted write.
692
+ * @returns the command text.
693
+ */
694
+ function renderPolicySet(write) {
695
+ const timing = write.applies === "live" ? "takes effect at the next use (live)" : "takes effect after a host restart (restart)";
696
+ const origin = write.wasOverridden ? "replacing your earlier override" : "now a user override (the deployment value stays underneath)";
697
+ return [`${write.id}: ${renderValue(write.before)} → ${renderValue(write.after)}`, `namespace ${write.namespace}; applies ${write.applies} — ${timing}; ${origin}`].join("\n");
698
+ }
699
+ /**
700
+ * Render the rows as JSON for scripts (same fields, no formatting).
701
+ * @param rows - rows from {@link paramSurfaceRows}.
702
+ * @returns a JSON document with a `params` array.
703
+ */
704
+ function renderParamJson(rows) {
705
+ return JSON.stringify({ params: rows }, null, 2);
706
+ }
707
+ //#endregion
527
708
  //#region lib/types/registry.js
528
709
  const COMMAND_ENTRIES = [
529
710
  {
@@ -582,6 +763,14 @@ const COMMAND_ENTRIES = [
582
763
  usage: "maintain [--timeout=<ms> | --facts]",
583
764
  summary: "run a maintenance scan (--facts: 0-token preview)"
584
765
  },
766
+ {
767
+ usage: "policy set <id> <value> [--expect <revision>]",
768
+ summary: "write one user-writable parameter through the settings service (E3 only; E1/E2 stay in cordis.yml)"
769
+ },
770
+ {
771
+ usage: "params [--group <name>] [--json]",
772
+ summary: "list every registered parameter: group, tier, timing, source and value (--json feeds scripts)"
773
+ },
585
774
  {
586
775
  usage: "preset install [--base <name>[,<name>...]]",
587
776
  summary: "generate one Evolution agent preset per named base into the user root (bases from the agent package's bases.json)"
@@ -1062,6 +1251,75 @@ function apply(ctx, rawConfig = {}) {
1062
1251
  if (!replay) return err("E-303: replay service not mounted. Next: mount the evolution-replay row (evolution-host/evolution-all) and run /evolution doctor.");
1063
1252
  return ok(replay.compare().report);
1064
1253
  }
1254
+ const paramsMatch = /^params(?: --group ([a-z-]+))?(?: --json)?$/.exec(input);
1255
+ if (paramsMatch) {
1256
+ const provider = ctx.get("settings");
1257
+ const sections = /* @__PURE__ */ new Map();
1258
+ if (provider?.describe !== void 0) try {
1259
+ for (const descriptor of provider.describe({ redactSecrets: false })) {
1260
+ const section = {};
1261
+ if (descriptor.user !== void 0) section.user = descriptor.user;
1262
+ const resolved = descriptor.value;
1263
+ if (typeof resolved === "object" && resolved !== null) section.value = resolved;
1264
+ sections.set(descriptor.ns, section);
1265
+ }
1266
+ } catch (error) {
1267
+ return err(`E-307: the settings service could not report its sections (${error instanceof Error ? error.message : String(error)}). /evolution params needs the user layer to tell an override from a deployment value; run /evolution doctor to see which services are mounted.`);
1268
+ }
1269
+ const rows = paramSurfaceRows(sections, PARAM_EXPOSURE);
1270
+ const group = paramsMatch[1];
1271
+ const selected = group === void 0 ? rows : rows.filter((row) => row.group === group);
1272
+ if (group !== void 0 && selected.length === 0) return err(`E-308: unknown parameter group "${group}" — known groups: ${paramGroups(PARAM_EXPOSURE).join(", ")}.`);
1273
+ if (input.endsWith("--json")) return ok(renderParamJson(selected));
1274
+ return ok(renderParamRows(selected, { providerMounted: provider !== void 0 }));
1275
+ }
1276
+ const policySetMatch = /^policy set ([A-Za-z][A-Za-z0-9.-]*) (.+?)(?: --expect (\d+))?$/.exec(input);
1277
+ if (policySetMatch) {
1278
+ const provider = ctx.get("settings");
1279
+ if (provider?.update === void 0 || provider.describe === void 0) return err("E-311: no settings service is mounted, so there is no user layer to write. Mount the settings row (packages/settings/settings-file) and retry; this command never edits cordis.yml — a deployment value belongs to the deployment. /evolution doctor lists the mounted services.");
1280
+ const rawId = policySetMatch[1] ?? "";
1281
+ const rawValue = policySetMatch[2] ?? "";
1282
+ let id;
1283
+ try {
1284
+ id = canonicalWriteId(rawId);
1285
+ } catch (error) {
1286
+ return err(`E-312: ${error instanceof Error ? error.message : String(error)} — /evolution params lists the canonical ids.`);
1287
+ }
1288
+ const entry = PARAM_EXPOSURE.find((candidate) => candidate.id === id);
1289
+ if (entry === void 0) return err(`E-313: unknown parameter "${id}" — /evolution params lists every registered id with its tier and user layer.`);
1290
+ if (entry.tier !== "E3") return err(`E-314: "${id}" is a deployment parameter (tier ${entry.tier}, owner ${entry.owner}) — it is written in cordis.yml on its plugin or policy row, not from a session. /evolution params shows who may write each row.`);
1291
+ const namespace = PARAM_NAMESPACES[entry.owner];
1292
+ if (namespace === void 0) return err(`E-315: "${id}" has no user layer — owner ${entry.owner} publishes no settings namespace, so the value stays with the deployment.`);
1293
+ let descriptor;
1294
+ try {
1295
+ descriptor = provider.describe({ redactSecrets: false }).find((candidate) => candidate.ns === namespace);
1296
+ } catch (error) {
1297
+ return err(`E-307: the settings service could not report its sections (${error instanceof Error ? error.message : String(error)}). /evolution policy set needs the current revision to write safely; run /evolution doctor to see which services are mounted.`);
1298
+ }
1299
+ if (descriptor === void 0) return err(`E-316: namespace "${namespace}" is not registered — owner ${entry.owner} is not mounted in this composition, so its parameters cannot be written from here. /evolution params marks those rows 'unregistered'.`);
1300
+ const current = descriptor.value;
1301
+ const resolved = typeof current === "object" && current !== null ? current : void 0;
1302
+ const expectedRaw = policySetMatch[3];
1303
+ const expected = expectedRaw === void 0 ? descriptor.revision : Number(expectedRaw);
1304
+ if (expectedRaw !== void 0 && descriptor.revision !== void 0 && Number(expectedRaw) !== descriptor.revision) return err(`E-309: revision conflict — you sent ${expectedRaw}, the document stands at ${descriptor.revision}. Re-read with /evolution params --json and retry.`);
1305
+ const value = parseParamValue(rawValue);
1306
+ try {
1307
+ await provider.update(namespace, { [id]: value }, expected);
1308
+ } catch (error) {
1309
+ const code = error?.code;
1310
+ const message = error instanceof Error ? error.message : String(error);
1311
+ if (code === "SETTINGS_CONFLICT") return err(`E-309: revision conflict — ${message}. Re-read with /evolution params --json and retry.`);
1312
+ return err(`E-310: the settings service refused the write: ${message} — the owning plugin's rule stands (a cross-field pair, or a cap that may only be tightened). /evolution params shows the current value.`);
1313
+ }
1314
+ return ok(renderPolicySet({
1315
+ id,
1316
+ namespace,
1317
+ applies: entry.applies,
1318
+ before: descriptor.user?.[id] ?? resolved?.[id],
1319
+ after: value,
1320
+ wasOverridden: descriptor.user !== void 0 && Object.hasOwn(descriptor.user, id)
1321
+ }));
1322
+ }
1065
1323
  if (input === "doctor" || input === "doctor --json") {
1066
1324
  const report = await diagnose(ctx, { home: evolutionRoot() });
1067
1325
  if (input === "doctor --json") return ok(JSON.stringify(report, null, 2));
@@ -76,6 +76,10 @@ export interface DoctorReport {
76
76
  * `memory-files`' store limit vs `evolution-policy`'s planning value. Empty
77
77
  * when either surface is absent (nothing to compare) or they agree. */
78
78
  budgetIssues: string[];
79
+ /** S4.3: parameter-surface divergences — user overrides, deprecated aliases
80
+ * still written, and E3 rows whose owner publishes no user layer here. Empty
81
+ * when the settings service is absent (nothing to compare). */
82
+ paramIssues: string[];
79
83
  services: {
80
84
  review: boolean;
81
85
  curator: boolean;
@@ -0,0 +1,95 @@
1
+ /**
2
+ * `/evolution params` view (G4/S4.1): the registry joined with the settings
3
+ * surface, so one text answers "which parameters exist, who may write them, and
4
+ * did I already override this one".
5
+ *
6
+ * The join key is the OWNER PACKAGE: core's `PARAM_NAMESPACES` maps a package to
7
+ * the one namespace it registers, and `settings.describe()` reports that
8
+ * namespace's raw user section (a key's PRESENCE is the override) plus the
9
+ * resolved value. Everything here is pure; the command handler supplies the data.
10
+ * @module @lmzhen/dsh-evolution-commands/params
11
+ */
12
+ import { type ParamExposure } from '@lmzhen/dsh-evolution-core';
13
+ /** One registered namespace, as `settings.describe()` reports it. */
14
+ export interface ParamSectionView {
15
+ /** Raw user section: a key's presence marks a user override. */
16
+ user?: Record<string, unknown> | undefined;
17
+ /** Resolved section (schema defaults < base < user). */
18
+ value?: Record<string, unknown> | undefined;
19
+ }
20
+ /** Which face a row's value comes from. */
21
+ export type ParamSource = 'user' | 'deployment' | 'unregistered';
22
+ /** One parameter as the command renders it. */
23
+ export interface ParamSurfaceRow {
24
+ id: string;
25
+ group: ParamExposure['group'];
26
+ tier: ParamExposure['tier'];
27
+ /** Settings-side timing: live, restart, or none (not user-writable). */
28
+ applies: ParamExposure['applies'];
29
+ owner: string;
30
+ source: ParamSource;
31
+ /** What that face holds, or undefined when no face reports a value. */
32
+ value: unknown;
33
+ }
34
+ /**
35
+ * Join the registry with the settings surface.
36
+ *
37
+ * `user` means the raw user section carries this key; `deployment` means the
38
+ * owner's namespace is registered but the key is unset (so the row/policy value
39
+ * applies); `unregistered` means the owner package publishes no namespace at all
40
+ * (or is not mounted), which is the honest answer for a deployment-only face.
41
+ * The distinction matters: 'no user section' must never read as 'not overridden'
42
+ * for a namespace that failed to register.
43
+ * @param sections - namespace to surface view, from `settings.describe()`.
44
+ * @param entries - registry entries (defaults to the family registry).
45
+ * @returns one row per registry entry, in registry order.
46
+ */
47
+ export declare function paramSurfaceRows(sections: ReadonlyMap<string, ParamSectionView>, entries?: readonly ParamExposure[]): ParamSurfaceRow[];
48
+ /**
49
+ * The groups the registry actually carries, in registry order — the list an
50
+ * unknown `--group` value is answered with.
51
+ * @param entries - registry entries (defaults to the family registry).
52
+ * @returns distinct group names.
53
+ */
54
+ export declare function paramGroups(entries?: readonly ParamExposure[]): string[];
55
+ /**
56
+ * Render the rows as fixed-width text (one parameter per line, never truncated).
57
+ * @param rows - rows from {@link paramSurfaceRows}.
58
+ * @param options - whether the settings service answered at all.
59
+ * @returns the command text.
60
+ */
61
+ export declare function renderParamRows(rows: readonly ParamSurfaceRow[], options: {
62
+ providerMounted: boolean;
63
+ }): string;
64
+ /**
65
+ * Parse one command-line value into what the settings document stores: JSON
66
+ * when it parses (numbers, booleans, quoted strings, arrays, objects), the raw
67
+ * text otherwise, so a bare word like `enforce` stays a word.
68
+ * @param raw - the text typed after the parameter id.
69
+ * @returns the value to write.
70
+ */
71
+ export declare function parseParamValue(raw: string): unknown;
72
+ /** One accepted write, as the command echoes it back. */
73
+ export interface ParamWriteEcho {
74
+ id: string;
75
+ namespace: string;
76
+ applies: ParamExposure['applies'];
77
+ before: unknown;
78
+ after: unknown;
79
+ /** Whether the user section already carried this key. */
80
+ wasOverridden: boolean;
81
+ }
82
+ /**
83
+ * Render the write result: the value move, then where it landed and when it
84
+ * takes effect (the two things a caller cannot infer from the command line).
85
+ * @param write - the accepted write.
86
+ * @returns the command text.
87
+ */
88
+ export declare function renderPolicySet(write: ParamWriteEcho): string;
89
+ /**
90
+ * Render the rows as JSON for scripts (same fields, no formatting).
91
+ * @param rows - rows from {@link paramSurfaceRows}.
92
+ * @returns a JSON document with a `params` array.
93
+ */
94
+ export declare function renderParamJson(rows: readonly ParamSurfaceRow[]): string;
95
+ //# sourceMappingURL=params.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.5.0",
4
+ "version": "0.6.0",
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.1",
30
- "@lmzhen/dsh-evolution-approval": "^0.5.0",
31
- "@lmzhen/dsh-evolution-core": "^0.5.0",
32
- "@lmzhen/dsh-evolution-maintenance": "^0.5.0"
30
+ "@lmzhen/dsh-evolution-approval": "^0.6.0",
31
+ "@lmzhen/dsh-evolution-core": "^0.6.0",
32
+ "@lmzhen/dsh-evolution-maintenance": "^0.6.0"
33
33
  },
34
34
  "optionalDependencies": {
35
- "@lmzhen/dsh-evolution-agent-preset": "^0.5.0"
35
+ "@lmzhen/dsh-evolution-agent-preset": "^0.6.0"
36
36
  },
37
37
  "peerDependencies": {
38
38
  "@deepseek-ai/cordis": "^4.0.1",