@lmzhen/dsh-evolution-commands 0.3.80 → 0.3.82
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 +159 -13
- package/lib/types/doctor.d.ts +36 -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 { MAX_TIMER_DELAY_MS, appendEvolutionEvent, assertSkillsRootAliasRetired, buildLearnPrompt, clampedNumber, composePresetComposition, eventsFile, evolutionRoot, newSkillLibrary, resolveRootConfig } from "@lmzhen/dsh-evolution-core";
|
|
5
|
+
import { MAX_TIMER_DELAY_MS, appendEvolutionEvent, assertSkillsRootAliasRetired, buildLearnPrompt, clampedNumber, composePresetComposition, eventsFile, evolutionRoot, isMissingPath, newSkillLibrary, 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";
|
|
@@ -323,8 +323,9 @@ async function diagnose(ctx, options = {}) {
|
|
|
323
323
|
if (host && preset) conflicts.push("evolution-host and evolution-preset are installed together — the infra rows double-mount. Keep ONE.");
|
|
324
324
|
if (full && presetDirInstalled) conflicts.push(`evolution-all and the layered Evolution preset (${layeredArtifactLabel}) are both present — the model rows double-mount. Keep ONE (use layered without all, or drop the preset).`);
|
|
325
325
|
if (preset && presetDirInstalled) conflicts.push(`evolution-preset and the layered Evolution preset (${layeredArtifactLabel}) are both present — the model rows double-mount (the preset bundle carries the same model rows as all). Keep ONE (drop the preset bundle, or remove the layered preset).`);
|
|
326
|
+
const reviewMounted = full || host || preset;
|
|
326
327
|
const services = {
|
|
327
|
-
review:
|
|
328
|
+
review: reviewMounted,
|
|
328
329
|
curator: has("evolutionCurator"),
|
|
329
330
|
approval: has("evolutionApproval"),
|
|
330
331
|
skillUsage: has("skillUsage"),
|
|
@@ -333,10 +334,13 @@ async function diagnose(ctx, options = {}) {
|
|
|
333
334
|
let pendingCount = null;
|
|
334
335
|
let executingCount = null;
|
|
335
336
|
const approvalService = ctx.get("evolutionApproval");
|
|
336
|
-
|
|
337
|
-
|
|
337
|
+
const approvalList = approvalService?.list?.bind(approvalService);
|
|
338
|
+
if (approvalList !== void 0) try {
|
|
339
|
+
const pendingProbe = await probeBounded(() => approvalList("pending"), "approval pending listing");
|
|
340
|
+
const rows = pendingProbe.ok ? pendingProbe.value : void 0;
|
|
338
341
|
pendingCount = Array.isArray(rows) ? rows.length : null;
|
|
339
|
-
const
|
|
342
|
+
const executingProbe = await probeBounded(() => approvalList("executing"), "approval executing listing");
|
|
343
|
+
const executing = executingProbe.ok ? executingProbe.value : void 0;
|
|
340
344
|
executingCount = Array.isArray(executing) ? executing.length : null;
|
|
341
345
|
} catch {
|
|
342
346
|
pendingCount = null;
|
|
@@ -346,6 +350,10 @@ async function diagnose(ctx, options = {}) {
|
|
|
346
350
|
const deploymentForm = installForm === "layered" ? "variant" : installForm === "full" || installForm === "preset" ? "attach" : installForm === "host" ? "host-only" : installForm === "preset-only" ? "preset-only" : "none";
|
|
347
351
|
const mountConflicts = conflicts.filter((row) => !row.includes("DEGRADED"));
|
|
348
352
|
const undecidable = mountConflicts.length !== conflicts.length;
|
|
353
|
+
const scopedProbe = {
|
|
354
|
+
rows: undecidable ? null : reviewMounted ? ["evolution-review", "skill-usage"] : [],
|
|
355
|
+
...scopedProbeReport()
|
|
356
|
+
};
|
|
349
357
|
const actions = [];
|
|
350
358
|
if (mountConflicts.length > 0) actions.push("Resolve the conflict first: keep exactly one of evolution-all / evolution-host / evolution-preset / layered.");
|
|
351
359
|
else if (undecidable) actions.push("The install form is UNDECIDABLE (see the DEGRADED row above): an unreadable profiles directory or profile manifest hides whatever is installed, so this report must not add or remove a bundle. Fix the reported read failure, then re-run /evolution doctor.");
|
|
@@ -357,7 +365,12 @@ async function diagnose(ctx, options = {}) {
|
|
|
357
365
|
if (services.review && !services.curator) actions.push("Curator service is not mounted — automatic curation is off; verify the host/all bundle row set is complete.");
|
|
358
366
|
if (pendingCount === null && services.approval) actions.push("Approval service is mounted but pending listing failed — check the evolution state service.");
|
|
359
367
|
const memoryIssues = memoryInterpolationIssues(home);
|
|
368
|
+
const budgetIssues = memoryBudgetIssues(ctx);
|
|
369
|
+
const queryIssues = await sessionQueryIssues(ctx);
|
|
370
|
+
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");
|
|
371
|
+
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)");
|
|
360
372
|
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.");
|
|
373
|
+
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.");
|
|
361
374
|
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.`);
|
|
362
375
|
for (const row of presetFreshness) {
|
|
363
376
|
if (row.status !== "differs") continue;
|
|
@@ -370,18 +383,126 @@ async function diagnose(ctx, options = {}) {
|
|
|
370
383
|
conflicts,
|
|
371
384
|
envIssues: env,
|
|
372
385
|
memoryIssues,
|
|
386
|
+
budgetIssues,
|
|
387
|
+
queryIssues,
|
|
373
388
|
services,
|
|
374
389
|
pendingCount,
|
|
375
390
|
executingCount,
|
|
376
391
|
presetFreshness,
|
|
392
|
+
scopedProbe,
|
|
377
393
|
actions
|
|
378
394
|
};
|
|
379
395
|
}
|
|
396
|
+
/**
|
|
397
|
+
* S2-12③ (FLOW5-4): the memory budget is configured in TWO places —
|
|
398
|
+
* `memory-files`' `memoryCharLimit`/`userCharLimit` (what the STORE enforces)
|
|
399
|
+
* and `evolution-policy`'s `memoryChars`/`userChars` (what the review PLANS
|
|
400
|
+
* against; `memory-files` follows the policy only for an UNSET limit). A
|
|
401
|
+
* disagreement therefore means an explicit contradiction, or a `memory-files`
|
|
402
|
+
* row mounted before the policy existed — either way the reviewer budgets ops
|
|
403
|
+
* the store will refuse. Read-only, like every other doctor row.
|
|
404
|
+
*
|
|
405
|
+
* @param ctx - the runtime service view (`get(name)`).
|
|
406
|
+
* @returns one message per disagreeing surface; empty when not comparable.
|
|
407
|
+
*/
|
|
408
|
+
/** S4 review (source-first pass) R-1: how long a diagnostic probe may wait.
|
|
409
|
+
* Doctor must ANSWER even when the thing it probes is blocked — the failure it
|
|
410
|
+
* reports (a concurrent writer stalling the corpus) is exactly the one that
|
|
411
|
+
* would otherwise hang the command forever. */
|
|
412
|
+
const PROBE_TIMEOUT_MS = 5e3;
|
|
413
|
+
/**
|
|
414
|
+
* Bound a diagnostic probe. A never-settling service is a FINDING, not a hang:
|
|
415
|
+
* the probe reports it like any other failure.
|
|
416
|
+
*
|
|
417
|
+
* Takes a THUNK, not a promise (review R-2): a mounted-but-broken service can
|
|
418
|
+
* throw synchronously, and `probeBounded(Promise.resolve(service.list()))` would
|
|
419
|
+
* let that throw escape the probe — crashing the command instead of reporting.
|
|
420
|
+
*
|
|
421
|
+
* @param work - the probe's work, invoked inside the guard.
|
|
422
|
+
* @param label - the surface being probed (named in the finding).
|
|
423
|
+
* @returns the value, or a message describing the failure/timeout.
|
|
424
|
+
*/
|
|
425
|
+
async function probeBounded(work, label) {
|
|
426
|
+
let started;
|
|
427
|
+
try {
|
|
428
|
+
started = work();
|
|
429
|
+
} catch (error) {
|
|
430
|
+
return {
|
|
431
|
+
ok: false,
|
|
432
|
+
message: `${label} failed: ${error instanceof Error ? error.message : String(error)}`
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
let timer;
|
|
436
|
+
const timeout = new Promise((resolve) => {
|
|
437
|
+
timer = setTimeout(() => {
|
|
438
|
+
resolve({
|
|
439
|
+
ok: false,
|
|
440
|
+
message: `${label} did not answer within ${PROBE_TIMEOUT_MS}ms — the corpus is blocked (a concurrent writer can stall the observation) or the service is wedged; isolate the most recently written session and re-run`
|
|
441
|
+
});
|
|
442
|
+
}, PROBE_TIMEOUT_MS);
|
|
443
|
+
});
|
|
444
|
+
const settled = started.then((value) => ({
|
|
445
|
+
ok: true,
|
|
446
|
+
value
|
|
447
|
+
}), (error) => ({
|
|
448
|
+
ok: false,
|
|
449
|
+
message: `${label} failed: ${error instanceof Error ? error.message : String(error)}`
|
|
450
|
+
}));
|
|
451
|
+
const outcome = await Promise.race([settled, timeout]);
|
|
452
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
453
|
+
return outcome;
|
|
454
|
+
}
|
|
455
|
+
/**
|
|
456
|
+
* S4/P-1+P-2 (platform report P-1/P-2, family mitigation) — the session-query
|
|
457
|
+
* index can fail AS A WHOLE: a concurrent writer leaves the persistence
|
|
458
|
+
* observation unstable ('did not stabilize after one retry') and ONE
|
|
459
|
+
* header-conflicting session aborts the same try block, which the tool surface
|
|
460
|
+
* folds into 'storage is unavailable'. Doctor cannot read the index's row count
|
|
461
|
+
* or the last stabilization result (the platform exposes no such API — recorded
|
|
462
|
+
* in the platform report), but it CAN exercise the corpus listing and report the
|
|
463
|
+
* verbatim failure with the actionable half: isolate the offending session.
|
|
464
|
+
*
|
|
465
|
+
* @param ctx - the runtime service view (`get(name)`).
|
|
466
|
+
* @returns one or two messages; empty when the service is absent or healthy.
|
|
467
|
+
*/
|
|
468
|
+
async function sessionQueryIssues(ctx) {
|
|
469
|
+
const service = ctx.get("sessionQuery");
|
|
470
|
+
const list = service?.listSessions?.bind(service);
|
|
471
|
+
if (list === void 0) return [];
|
|
472
|
+
const outcome = await probeBounded(() => list(), "session-query listing");
|
|
473
|
+
if (!outcome.ok) return [outcome.message, "isolate the offending session — move it OUT of the corpus with a manifest (never delete it) and re-run; the platform already retries the observation once, so retrying first is not the answer"];
|
|
474
|
+
return Array.isArray(outcome.value) ? [] : ["session-query answered a listing with a non-list value — the service is mounted but not honouring its read contract"];
|
|
475
|
+
}
|
|
476
|
+
function memoryBudgetIssues(ctx) {
|
|
477
|
+
const budget = ctx.get("evolutionMemoryBudget");
|
|
478
|
+
const policy = ctx.get("evolutionPolicy")?.get?.();
|
|
479
|
+
if (budget === void 0 || policy === void 0) return [];
|
|
480
|
+
const issues = [];
|
|
481
|
+
if (budget.memoryCharLimit !== void 0 && policy.memoryChars !== void 0 && budget.memoryCharLimit !== policy.memoryChars) issues.push(`store enforces memoryCharLimit=${budget.memoryCharLimit} (source: ${budget.memorySource ?? "unknown"}) while evolution-policy declares memoryChars=${policy.memoryChars} — the review plans against the policy value and the store refuses what exceeds its own`);
|
|
482
|
+
if (budget.userCharLimit !== void 0 && policy.userChars !== void 0 && budget.userCharLimit !== policy.userChars) issues.push(`store enforces userCharLimit=${budget.userCharLimit} (source: ${budget.userSource ?? "unknown"}) while evolution-policy declares userChars=${policy.userChars} — same divergence as the memory limit`);
|
|
483
|
+
return issues;
|
|
484
|
+
}
|
|
485
|
+
/** S0-4 (v43 G-1 / J-1): the scoped-row reconciliation line. The verdict is the
|
|
486
|
+
* runtime witness, the row set is what this home installs, and the two forms are
|
|
487
|
+
* spelled out because they read identically in the numbers (`never-hit`) while
|
|
488
|
+
* needing opposite responses: under the host-only form nothing can ever match,
|
|
489
|
+
* under the variant form the match arrives with the first Evolution-preset
|
|
490
|
+
* session.
|
|
491
|
+
* @param check - the report's scoped-row check.
|
|
492
|
+
* @returns one line, or the earlier diagnostic when either half is unknown. */
|
|
493
|
+
function scopedProbeLine(check) {
|
|
494
|
+
if (check.rows === null) return `scoped rows: (undecidable), probe=${check.verdict} — a DEGRADED bundle read hides which bundles are installed, so the scoped rows cannot be reconciled with the probe`;
|
|
495
|
+
if (check.rows.length === 0) return `scoped rows: (none mounted), probe=${check.verdict} — no installed bundle carries the session-scoped rows (evolution-review / skill-usage), so the gate is inactive here`;
|
|
496
|
+
const rows = check.rows.map((row) => `${row}=on`).join("/");
|
|
497
|
+
const detail = check.verdict === "hit" ? `${check.hits} scoped evaluation(s) carried the family tools` : check.verdict === "never-hit" ? `${check.misses} scoped evaluation(s) resolved false — review injection and skill-usage telemetry are inert for every session observed; HOST-ONLY installs reach this because tool-memory / tool-skill-manage are not dependencies of evolution-host, VARIANT installs only match once a session runs the Evolution preset` : "the gate has not evaluated a session in this process yet — no session event has reached it since startup";
|
|
498
|
+
return `scoped rows: ${rows}, probe=${check.verdict} (${detail})`;
|
|
499
|
+
}
|
|
380
500
|
function renderDoctorText(report) {
|
|
381
501
|
const lines = [
|
|
382
502
|
`Evolution doctor — install form: ${report.installForm} (deployment: ${report.deploymentForm})`,
|
|
383
503
|
`bundles (all profiles): ${report.bundles.length > 0 ? report.bundles.join(", ") : "(none)"}`,
|
|
384
504
|
`services: review=${report.services.review} (inferred from bundles, all profiles) curator=${report.services.curator} approval=${report.services.approval} skillUsage=${report.services.skillUsage} io=${report.services.io}`,
|
|
505
|
+
scopedProbeLine(report.scopedProbe),
|
|
385
506
|
`pending: ${report.pendingCount === null ? "unknown" : report.pendingCount}`,
|
|
386
507
|
`executing: ${report.executingCount === null ? "unknown" : report.executingCount}`
|
|
387
508
|
];
|
|
@@ -393,6 +514,8 @@ function renderDoctorText(report) {
|
|
|
393
514
|
if (report.conflicts.length > 0) lines.push("conflicts:", ...report.conflicts.map((line) => ` ! ${line}`));
|
|
394
515
|
if (report.envIssues.length > 0) lines.push("env:", ...report.envIssues.map((line) => ` ! ${line}`));
|
|
395
516
|
if (report.memoryIssues.length > 0) lines.push("memory:", ...report.memoryIssues.map((line) => ` ! ${line}`));
|
|
517
|
+
if (report.budgetIssues.length > 0) lines.push("memory budget:", ...report.budgetIssues.map((line) => ` ! ${line}`));
|
|
518
|
+
if (report.queryIssues.length > 0) lines.push("session search:", ...report.queryIssues.map((line) => ` ! ${line}`));
|
|
396
519
|
if (report.actions.length > 0) lines.push("next steps:", ...report.actions.map((line) => ` → ${line}`));
|
|
397
520
|
return lines.join("\n");
|
|
398
521
|
}
|
|
@@ -516,6 +639,15 @@ function apply(ctx, rawConfig = {}) {
|
|
|
516
639
|
kind: "error",
|
|
517
640
|
text
|
|
518
641
|
});
|
|
642
|
+
const invocationAgent = invocation.agent;
|
|
643
|
+
const agentMissing = (need) => invocationAgent === void 0 ? err(`E-305: this invocation carries no agent — \`${need}\` needs a session-backed call (run it from a session in the GUI or the CLI).`) : void 0;
|
|
644
|
+
const unreplayableWriteRefusal = (what) => {
|
|
645
|
+
if (approval === void 0) return void 0;
|
|
646
|
+
const session = invocationAgent?.session;
|
|
647
|
+
const sessionPolicy = effectiveSessionPolicy(ctx, session);
|
|
648
|
+
if (!(approval.isEnabled !== false && sessionPolicy !== "never" && approval.stageForeground !== false)) return void 0;
|
|
649
|
+
return err(`E-306: this deployment stages foreground writes, but \`/evolution ${what}\` is not replayable through the skill runner — there is nothing to stage. Run it from a session whose approval policy is 'never', or set \`stageForeground: false\` on the evolution-approval row, then repeat the command.`);
|
|
650
|
+
};
|
|
519
651
|
const approval = ctx.get("evolutionApproval");
|
|
520
652
|
const pendingMatch = /^pending(?: --detail)?$/.exec(input);
|
|
521
653
|
if (pendingMatch) {
|
|
@@ -628,6 +760,8 @@ function apply(ctx, rawConfig = {}) {
|
|
|
628
760
|
if (input === "restore" || input.startsWith("restore ")) {
|
|
629
761
|
const tail = input.slice(7).trim();
|
|
630
762
|
if (tail !== "") return err(`\`restore\` takes no arguments (got "${tail}"). Use \`restore\` for a whole-tree rollback, or \`skill restore <name>\` for one skill.`);
|
|
763
|
+
const restoreRefusal = unreplayableWriteRefusal("restore");
|
|
764
|
+
if (restoreRefusal) return restoreRefusal;
|
|
631
765
|
const curator = ctx.get("evolutionCurator");
|
|
632
766
|
const result = curator ? await curator.restoreSnapshot() : {
|
|
633
767
|
ok: false,
|
|
@@ -636,6 +770,8 @@ function apply(ctx, rawConfig = {}) {
|
|
|
636
770
|
return result.ok ? ok(result.message) : err(result.message);
|
|
637
771
|
}
|
|
638
772
|
if (input.startsWith("consolidate ")) {
|
|
773
|
+
const consolidateRefusal = unreplayableWriteRefusal("consolidate");
|
|
774
|
+
if (consolidateRefusal) return consolidateRefusal;
|
|
639
775
|
const planTail = /\s--plan\s+(\S+)\s*$/.exec(input) ?? null;
|
|
640
776
|
const planRunId = planTail?.[1] ?? void 0;
|
|
641
777
|
const [target, ...sources] = (planTail ? input.slice(0, planTail.index) : input).slice(12).trim().split(/\s+/).filter(Boolean);
|
|
@@ -651,6 +787,8 @@ function apply(ctx, rawConfig = {}) {
|
|
|
651
787
|
if (input.startsWith("skill restore ")) {
|
|
652
788
|
const name = input.slice(14).trim();
|
|
653
789
|
if (!name) return err("Usage: /evolution skill restore <name>");
|
|
790
|
+
const skillRestoreRefusal = unreplayableWriteRefusal("skill restore");
|
|
791
|
+
if (skillRestoreRefusal) return skillRestoreRefusal;
|
|
654
792
|
const curator = ctx.get("evolutionCurator");
|
|
655
793
|
const result = curator ? await curator.restore(name) : {
|
|
656
794
|
ok: false,
|
|
@@ -685,9 +823,15 @@ function apply(ctx, rawConfig = {}) {
|
|
|
685
823
|
summary: "learn request"
|
|
686
824
|
}
|
|
687
825
|
});
|
|
688
|
-
const
|
|
689
|
-
if (
|
|
690
|
-
|
|
826
|
+
const missingAgent = agentMissing("learn");
|
|
827
|
+
if (missingAgent) return missingAgent;
|
|
828
|
+
const agent = invocationAgent;
|
|
829
|
+
let woke = false;
|
|
830
|
+
if (typeof agent.followup === "function") {
|
|
831
|
+
agent.followup(message);
|
|
832
|
+
woke = true;
|
|
833
|
+
} else if (typeof agent.inject === "function") agent.inject(message);
|
|
834
|
+
else return err("E-305: the invocation agent exposes neither `followup` nor `inject` — this learn request has no delivery channel.");
|
|
691
835
|
const eventIo = ctx.get("evolutionIo")?.provider();
|
|
692
836
|
if (eventIo) appendEvolutionEvent(eventIo, eventsFile(evolutionRoot()), {
|
|
693
837
|
type: "learn",
|
|
@@ -848,9 +992,9 @@ function apply(ctx, rawConfig = {}) {
|
|
|
848
992
|
const ioRegistry = ctx.get("evolutionIo");
|
|
849
993
|
if (!ioRegistry) return err("Evolution IO registry not mounted — restructure unavailable.");
|
|
850
994
|
if (approval) {
|
|
851
|
-
const session =
|
|
995
|
+
const session = invocationAgent?.session;
|
|
852
996
|
const sessionPolicy = effectiveSessionPolicy(ctx, session);
|
|
853
|
-
if (approval.isEnabled !== false && sessionPolicy !== "never" && approval.stageForeground !== false && !approval.hasRunner("skill")) return err("Restructure cannot be staged: no skill replay runner is registered — mount the tool-skill-manage row (evolution-agent preset, or evolution-all) or disable evolution-approval.");
|
|
997
|
+
if (session !== void 0 && approval.isEnabled !== false && sessionPolicy !== "never" && approval.stageForeground !== false && !approval.hasRunner("skill")) return err("Restructure cannot be staged: no skill replay runner is registered — mount the tool-skill-manage row (evolution-agent preset, or evolution-all) or disable evolution-approval.");
|
|
854
998
|
const decision = await approval.request({
|
|
855
999
|
kind: "skill",
|
|
856
1000
|
summary: `/evolution restructure ${name}${planRunId ? ` (plan ${planRunId})` : ""}`,
|
|
@@ -867,8 +1011,10 @@ function apply(ctx, rawConfig = {}) {
|
|
|
867
1011
|
libraryOrigin: "foreground"
|
|
868
1012
|
},
|
|
869
1013
|
origin: "foreground",
|
|
870
|
-
...session
|
|
871
|
-
|
|
1014
|
+
...session !== void 0 ? {
|
|
1015
|
+
sessionId: session.id,
|
|
1016
|
+
session
|
|
1017
|
+
} : {},
|
|
872
1018
|
...sessionPolicy !== void 0 ? { sessionPolicy } : {}
|
|
873
1019
|
});
|
|
874
1020
|
if (decision.action === "staged") return ok(`${decision.message}${planRunId ? `\n[audit] plan=${planRunId}` : ""}`);
|
|
@@ -966,7 +1112,7 @@ const defaultFs = {
|
|
|
966
1112
|
try {
|
|
967
1113
|
return statSync(path).mtimeMs;
|
|
968
1114
|
} catch (error) {
|
|
969
|
-
if (error
|
|
1115
|
+
if (isMissingPath(error)) return null;
|
|
970
1116
|
throw error;
|
|
971
1117
|
}
|
|
972
1118
|
}
|
package/lib/types/doctor.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type ScopedProbeReport } from '@lmzhen/dsh-evolution-core';
|
|
1
2
|
/** G3-② (B2): one delivered preset variant compared against a fresh generation.
|
|
2
3
|
* "absent" is the user simply not using that base (never an action); "unknown"
|
|
3
4
|
* is a comparison that could not run, kept distinct from a verified "fresh". */
|
|
@@ -10,6 +11,29 @@ export interface PresetFreshnessRow {
|
|
|
10
11
|
/** Why the comparison could not run — set on unknown rows only. */
|
|
11
12
|
detail?: string;
|
|
12
13
|
}
|
|
14
|
+
/** S0-4 (v43 G-1 / J-1): the session-scoped rows against the runtime witness.
|
|
15
|
+
*
|
|
16
|
+
* The gate has two halves nobody reconciled: the bundle turns `sessionScoped` on
|
|
17
|
+
* for `evolution-review` and `skill-usage`, and the runtime witness knows
|
|
18
|
+
* whether ANY session ever saw the family's model tools. `never-hit` with the
|
|
19
|
+
* rows mounted is a finding — the host-only form mounts no model row at all
|
|
20
|
+
* (tool-memory / tool-skill-manage are devDependencies of evolution-host) and an
|
|
21
|
+
* overlay may disable either row, so review injection and skill-usage telemetry
|
|
22
|
+
* run for nobody. `idle` claims nothing: the gate simply has not been asked yet
|
|
23
|
+
* in this process. Read-only, like every other row of this report. */
|
|
24
|
+
export interface ScopedProbeCheck {
|
|
25
|
+
/** The scoped rows the installed bundles mount (`evolution-review`,
|
|
26
|
+
* `skill-usage`); empty when no bundle is installed, and null when a DEGRADED
|
|
27
|
+
* bundle read makes the set undecidable — an unreadable manifest is not
|
|
28
|
+
* evidence of absence (the INST-01 / S2.1 discipline). */
|
|
29
|
+
rows: string[] | null;
|
|
30
|
+
/** The runtime witness, verbatim (`scopedProbeReport()`). */
|
|
31
|
+
verdict: ScopedProbeReport['verdict'];
|
|
32
|
+
/** Scoped gate evaluations that resolved true. */
|
|
33
|
+
hits: number;
|
|
34
|
+
/** Scoped gate evaluations that resolved false. */
|
|
35
|
+
misses: number;
|
|
36
|
+
}
|
|
13
37
|
export interface DoctorReport {
|
|
14
38
|
/** OPT-23 (2026-09): `preset-only` — the delivered Evolution preset
|
|
15
39
|
* artifact exists but NO profile carries an evolution bundle (e.g. the
|
|
@@ -39,6 +63,14 @@ export interface DoctorReport {
|
|
|
39
63
|
* pre-step of every session under this home, and an operator may want to clean
|
|
40
64
|
* the file regardless. */
|
|
41
65
|
memoryIssues: string[];
|
|
66
|
+
/** S4/P-1+P-2 (platform gap, family mitigation): the session-query corpus
|
|
67
|
+
* listing failed — reported verbatim, with the isolation recipe. Empty when the
|
|
68
|
+
* service is absent or answering. */
|
|
69
|
+
queryIssues: string[];
|
|
70
|
+
/** S2-12③ (FLOW5-4): the memory budget's two configuration surfaces disagree —
|
|
71
|
+
* `memory-files`' store limit vs `evolution-policy`'s planning value. Empty
|
|
72
|
+
* when either surface is absent (nothing to compare) or they agree. */
|
|
73
|
+
budgetIssues: string[];
|
|
42
74
|
services: {
|
|
43
75
|
review: boolean;
|
|
44
76
|
curator: boolean;
|
|
@@ -56,6 +88,10 @@ export interface DoctorReport {
|
|
|
56
88
|
* upgrade leaves a file describing a platform that no longer exists. Read-only:
|
|
57
89
|
* a stale snapshot is reported, never repaired. */
|
|
58
90
|
presetFreshness: PresetFreshnessRow[];
|
|
91
|
+
/** S0-4 (v43 G-1 / J-1): the scoped rows × the probe witness, so a deployment
|
|
92
|
+
* whose cross-session consumers are inert for every session stops looking
|
|
93
|
+
* healthy. See {@link ScopedProbeCheck}. */
|
|
94
|
+
scopedProbe: ScopedProbeCheck;
|
|
59
95
|
actions: string[];
|
|
60
96
|
}
|
|
61
97
|
/** Profile bundle rows for evolution-family packages across all profiles.
|
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.3.
|
|
4
|
+
"version": "0.3.82",
|
|
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.3.
|
|
31
|
-
"@lmzhen/dsh-evolution-core": "^0.3.
|
|
32
|
-
"@lmzhen/dsh-evolution-maintenance": "^0.3.
|
|
30
|
+
"@lmzhen/dsh-evolution-approval": "^0.3.82",
|
|
31
|
+
"@lmzhen/dsh-evolution-core": "^0.3.82",
|
|
32
|
+
"@lmzhen/dsh-evolution-maintenance": "^0.3.82"
|
|
33
33
|
},
|
|
34
34
|
"optionalDependencies": {
|
|
35
|
-
"@lmzhen/dsh-evolution-agent-preset": "^0.3.
|
|
35
|
+
"@lmzhen/dsh-evolution-agent-preset": "^0.3.82"
|
|
36
36
|
},
|
|
37
37
|
"peerDependencies": {
|
|
38
38
|
"@deepseek-ai/cordis": "^4.0.1",
|