@lmzhen/dsh-evolution-commands 0.5.0 → 0.6.1
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 +259 -1
- package/lib/types/doctor.d.ts +4 -0
- package/lib/types/params.d.ts +95 -0
- package/package.json +5 -5
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));
|
package/lib/types/doctor.d.ts
CHANGED
|
@@ -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.
|
|
4
|
+
"version": "0.6.1",
|
|
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.
|
|
31
|
-
"@lmzhen/dsh-evolution-core": "^0.
|
|
32
|
-
"@lmzhen/dsh-evolution-maintenance": "^0.
|
|
30
|
+
"@lmzhen/dsh-evolution-approval": "^0.6.1",
|
|
31
|
+
"@lmzhen/dsh-evolution-core": "^0.6.1",
|
|
32
|
+
"@lmzhen/dsh-evolution-maintenance": "^0.6.1"
|
|
33
33
|
},
|
|
34
34
|
"optionalDependencies": {
|
|
35
|
-
"@lmzhen/dsh-evolution-agent-preset": "^0.
|
|
35
|
+
"@lmzhen/dsh-evolution-agent-preset": "^0.6.1"
|
|
36
36
|
},
|
|
37
37
|
"peerDependencies": {
|
|
38
38
|
"@deepseek-ai/cordis": "^4.0.1",
|