@lmzhen/dsh-evolution-commands 0.3.16 → 0.3.18
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 +76 -15
- package/lib/types/index.d.ts +14 -1
- package/package.json +3 -3
package/lib/index.js
CHANGED
|
@@ -2,13 +2,13 @@ import { createRequire } from "node:module";
|
|
|
2
2
|
import { createUserMessage } from "@deepseek-ai/dsh-llm";
|
|
3
3
|
import { SkillLibrary, appendEvolutionEvent, buildLearnPrompt, composePresetComposition, eventsFile } from "@lmzhen/dsh-evolution-core";
|
|
4
4
|
import { buildEnrichment, buildMaintainFacts, runMaintain, snapshotFromLibrary } from "@lmzhen/dsh-evolution-maintenance";
|
|
5
|
-
import { copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { copyFileSync, existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
6
6
|
import { homedir } from "node:os";
|
|
7
7
|
import { dirname, join } from "node:path";
|
|
8
8
|
import { fileURLToPath } from "node:url";
|
|
9
9
|
//#region lib/types/index.js
|
|
10
10
|
/**
|
|
11
|
-
* Human commands for the evolution family: /evolution learn|pending|curator|restore|consolidate.
|
|
11
|
+
* Human commands for the evolution family: /evolution learn|pending|curator|restore|consolidate|skills refresh.
|
|
12
12
|
* @module @lmzhen/dsh-evolution-commands
|
|
13
13
|
*/
|
|
14
14
|
/** Enrichment maps shared by the full scan and the `--facts` preview (v12). */
|
|
@@ -19,11 +19,12 @@ function apply(ctx, rawConfig = {}) {
|
|
|
19
19
|
let lastMaintainRunId = "";
|
|
20
20
|
let maintainInFlightSince = 0;
|
|
21
21
|
ctx.inject(["commands"], (commandCtx) => {
|
|
22
|
-
commandCtx.commands
|
|
22
|
+
const commands = commandCtx.commands;
|
|
23
|
+
commandCtx.effect(() => commands.register({
|
|
23
24
|
name: "evolution",
|
|
24
25
|
description: "Self-evolution status and approval controls",
|
|
25
26
|
recordInput: false,
|
|
26
|
-
input: { hint: "pending | approve <id> | reject <id> | curator run|pause|resume|status|report|scope | restore | consolidate <target> <sources...> | skill restore <name> | skills health | learn [request] | maintain [--timeout ms] | preset install | restructure <name> \"<heading>\" <to_file> | replay" },
|
|
27
|
+
input: { hint: "pending | approve <id> | reject <id> | curator run|pause|resume|status|report|scope | mutations | restore | consolidate <target> <sources...> | skill restore <name> | skills health | skills refresh | learn [request] | maintain [--timeout ms | --facts] | preset install | restructure <name> \"<heading>\" <to_file> | replay" },
|
|
27
28
|
async handler(invocation) {
|
|
28
29
|
const input = invocation.rawInput?.trim() ?? "";
|
|
29
30
|
const ok = (text) => ({
|
|
@@ -36,8 +37,9 @@ function apply(ctx, rawConfig = {}) {
|
|
|
36
37
|
});
|
|
37
38
|
const approval = ctx.get("evolutionApproval");
|
|
38
39
|
if (input === "pending") {
|
|
39
|
-
const pending = approval ? await approval.list("pending") : [];
|
|
40
|
-
|
|
40
|
+
const pending = approval ? [...await approval.list("pending"), ...await approval.list("executing")] : [];
|
|
41
|
+
if (pending.length === 0) return ok("No pending evolution writes.");
|
|
42
|
+
return ok(pending.map((p) => `${p.id} ${p.kind} ${p.status === "executing" ? "EXECUTING " : ""}${p.summary}`).join("\n") + (pending.some((p) => p.status === "executing") ? "\n(EXECUTING: a previous approve may have crashed after running; verify the write manually, then reject — approve will not re-run it)" : ""));
|
|
41
43
|
}
|
|
42
44
|
if (input.startsWith("approve ")) {
|
|
43
45
|
const id = input.slice(8).trim();
|
|
@@ -155,6 +157,10 @@ function apply(ctx, rawConfig = {}) {
|
|
|
155
157
|
if (rows.length === 0) return ok(banner ? `Structure health: all skills healthy. ${banner}` : "Structure health: all skills healthy.");
|
|
156
158
|
return ok([banner ? `Structure health (${rows.length} degraded): ${banner}` : `Structure health (${rows.length} degraded):`, ...rows.map(line)].join("\n"));
|
|
157
159
|
}
|
|
160
|
+
if (input === "skills refresh") {
|
|
161
|
+
ctx.emit("evolution/skills-refresh");
|
|
162
|
+
return ok("Skill catalog refresh requested: caches dropped, catalog re-read on next lookup.");
|
|
163
|
+
}
|
|
158
164
|
if (input === "learn" || input.startsWith("learn ")) {
|
|
159
165
|
const request = input === "learn" ? "" : input.slice(6).trim();
|
|
160
166
|
invocation.agent.inject(createUserMessage({
|
|
@@ -212,7 +218,8 @@ function apply(ctx, rawConfig = {}) {
|
|
|
212
218
|
const outcome = await runMaintain({
|
|
213
219
|
library,
|
|
214
220
|
subagents,
|
|
215
|
-
parent: invocation.agent
|
|
221
|
+
parent: invocation.agent,
|
|
222
|
+
evolutionPolicy: { get: () => ctx.get("evolutionPolicy")?.get() }
|
|
216
223
|
}, {
|
|
217
224
|
timeoutMs: runTimeoutMs,
|
|
218
225
|
descriptions: () => enrichment.descriptions,
|
|
@@ -229,7 +236,7 @@ function apply(ctx, rawConfig = {}) {
|
|
|
229
236
|
source: "manual",
|
|
230
237
|
runId: outcome.runId,
|
|
231
238
|
verdict: outcome.verdict,
|
|
232
|
-
recommendations: outcome.text
|
|
239
|
+
recommendations: countMaintainRecommendations(outcome.text)
|
|
233
240
|
}).catch((error) => {
|
|
234
241
|
ctx.logger.warn(`evolution-commands: failed to record maintain event: ${String(error)}`);
|
|
235
242
|
});
|
|
@@ -253,8 +260,13 @@ function apply(ctx, rawConfig = {}) {
|
|
|
253
260
|
if (!registry) return err("Agent preset registry not mounted — cannot compose the Evolution preset against the runtime standard.");
|
|
254
261
|
const composition = composePresetComposition(await registry.read("standard"), readFileSync(deltaPath, "utf8"));
|
|
255
262
|
mkdirSync(target, { recursive: true });
|
|
256
|
-
|
|
257
|
-
|
|
263
|
+
atomicWriteFiles(target, [{
|
|
264
|
+
name: "agent.cordis.yml",
|
|
265
|
+
content: composition
|
|
266
|
+
}, {
|
|
267
|
+
name: "preset.yml",
|
|
268
|
+
content: readFileSync(presetPath)
|
|
269
|
+
}]);
|
|
258
270
|
return ok(`Evolution agent preset installed to ${target} (runtime standard + delta). Restart the session switcher to select it.`);
|
|
259
271
|
} catch (error) {
|
|
260
272
|
return err(`Preset install failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -284,9 +296,9 @@ function apply(ctx, rawConfig = {}) {
|
|
|
284
296
|
if (!replay) return err("Replay service not mounted.");
|
|
285
297
|
return ok(replay.compare().report);
|
|
286
298
|
}
|
|
287
|
-
return ok("Evolution: memory, skills, review, curator. Use /evolution pending | approve <id> | reject <id> | curator run | curator status | curator pause | curator resume | curator report | curator scope | restore | consolidate <target> <source...> | skill restore <name> | skills health | learn [request] | maintain | preset install | restructure <name> \"<heading>\" <to_file> | replay.");
|
|
299
|
+
return ok("Evolution: memory, skills, review, curator. Use /evolution pending | approve <id> | reject <id> | curator run | curator status | curator pause | curator resume | curator report | curator scope | mutations | restore | consolidate <target> <source...> | skill restore <name> | skills health | skills refresh | learn [request] | maintain [--timeout ms | --facts] | preset install | restructure <name> \"<heading>\" <to_file> | replay.");
|
|
288
300
|
}
|
|
289
|
-
});
|
|
301
|
+
}));
|
|
290
302
|
});
|
|
291
303
|
}
|
|
292
304
|
/**
|
|
@@ -299,13 +311,62 @@ function apply(ctx, rawConfig = {}) {
|
|
|
299
311
|
*/
|
|
300
312
|
function resolveAgentPresetDir(importMetaUrl) {
|
|
301
313
|
const dir = dirname(fileURLToPath(importMetaUrl));
|
|
302
|
-
const
|
|
314
|
+
const siblingCandidate = join(dir, "..", "dsh-evolution-agent-preset");
|
|
315
|
+
const candidates = [siblingCandidate, join(dir, "..", "..", "evolution-agent")];
|
|
303
316
|
for (const candidate of candidates) if (existsSync(join(candidate, "agent.cordis.yml")) && existsSync(join(candidate, "preset.yml"))) return candidate;
|
|
304
317
|
try {
|
|
305
318
|
return dirname(createRequire(importMetaUrl).resolve("@lmzhen/dsh-evolution-agent-preset/package.json"));
|
|
306
319
|
} catch {
|
|
307
|
-
return
|
|
320
|
+
return siblingCandidate;
|
|
308
321
|
}
|
|
309
322
|
}
|
|
323
|
+
/**
|
|
324
|
+
* S6.3 (E-40): atomically replace a set of files within one directory.
|
|
325
|
+
* Each file is staged to a sibling `<name>.tmp`, then renamed into place
|
|
326
|
+
* (atomic on the same filesystem); a pre-existing file keeps a single
|
|
327
|
+
* `<name>.bak`. A failure at ANY staging step removes the staged temps and
|
|
328
|
+
* leaves the previous files untouched — a half-written target is impossible.
|
|
329
|
+
*/
|
|
330
|
+
function atomicWriteFiles(targetDir, writes) {
|
|
331
|
+
const stage = (name) => join(targetDir, `${name}.tmp`);
|
|
332
|
+
try {
|
|
333
|
+
for (const { name, content } of writes) writeFileSync(stage(name), content);
|
|
334
|
+
for (const { name } of writes) {
|
|
335
|
+
const finalPath = join(targetDir, name);
|
|
336
|
+
const bakPath = join(targetDir, `${name}.bak`);
|
|
337
|
+
if (existsSync(finalPath) && !existsSync(bakPath)) copyFileSync(finalPath, bakPath);
|
|
338
|
+
}
|
|
339
|
+
for (const { name } of writes) {
|
|
340
|
+
const finalPath = join(targetDir, name);
|
|
341
|
+
try {
|
|
342
|
+
renameSync(stage(name), finalPath);
|
|
343
|
+
} catch {
|
|
344
|
+
rmSync(finalPath, { force: true });
|
|
345
|
+
renameSync(stage(name), finalPath);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
} catch (error) {
|
|
349
|
+
for (const { name } of writes) rmSync(stage(name), {
|
|
350
|
+
force: true,
|
|
351
|
+
recursive: true
|
|
352
|
+
});
|
|
353
|
+
throw error;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* Count the recommendation bullets in a rendered maintain plan.
|
|
358
|
+
* `formatPlan` renders each recommendation as a line starting with `- [<kind>]`;
|
|
359
|
+
* this matches that bullet (anchored at line start) instead of any string
|
|
360
|
+
* prefix, so a `- note` line can never inflate the count.
|
|
361
|
+
*
|
|
362
|
+
* @param text - the rendered maintain result text, or `undefined`.
|
|
363
|
+
* @returns the number of recommendation bullets in `text`.
|
|
364
|
+
* @remarks the count is still text-derived — MaintainOutcome does not expose the
|
|
365
|
+
* structured plan array, so a structured count would require a
|
|
366
|
+
* evolution-maintenance outcome change (out of this package's scope).
|
|
367
|
+
*/
|
|
368
|
+
function countMaintainRecommendations(text) {
|
|
369
|
+
return text?.match(/^- \[/gm)?.length ?? 0;
|
|
370
|
+
}
|
|
310
371
|
//#endregion
|
|
311
|
-
export { apply, name };
|
|
372
|
+
export { apply, countMaintainRecommendations, name };
|
package/lib/types/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Human commands for the evolution family: /evolution learn|pending|curator|restore|consolidate.
|
|
2
|
+
* Human commands for the evolution family: /evolution learn|pending|curator|restore|consolidate|skills refresh.
|
|
3
3
|
* @module @deepseek-ai/dsh-evolution-commands
|
|
4
4
|
*/
|
|
5
5
|
import type { Context } from '@deepseek-ai/cordis';
|
|
@@ -22,4 +22,17 @@ export interface Config {
|
|
|
22
22
|
maintainTimeoutMs?: number | undefined;
|
|
23
23
|
}
|
|
24
24
|
export declare function apply(ctx: Context, rawConfig?: Config): void;
|
|
25
|
+
/**
|
|
26
|
+
* Count the recommendation bullets in a rendered maintain plan.
|
|
27
|
+
* `formatPlan` renders each recommendation as a line starting with `- [<kind>]`;
|
|
28
|
+
* this matches that bullet (anchored at line start) instead of any string
|
|
29
|
+
* prefix, so a `- note` line can never inflate the count.
|
|
30
|
+
*
|
|
31
|
+
* @param text - the rendered maintain result text, or `undefined`.
|
|
32
|
+
* @returns the number of recommendation bullets in `text`.
|
|
33
|
+
* @remarks the count is still text-derived — MaintainOutcome does not expose the
|
|
34
|
+
* structured plan array, so a structured count would require a
|
|
35
|
+
* evolution-maintenance outcome change (out of this package's scope).
|
|
36
|
+
*/
|
|
37
|
+
export declare function countMaintainRecommendations(text: string | undefined): number;
|
|
25
38
|
//# 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 (community build)",
|
|
4
|
-
"version": "0.3.
|
|
4
|
+
"version": "0.3.18",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -33,8 +33,8 @@
|
|
|
33
33
|
"license": "MIT",
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"@deepseek-ai/schemastery": "^3.18.1",
|
|
36
|
-
"@lmzhen/dsh-evolution-core": "^0.3.
|
|
37
|
-
"@lmzhen/dsh-evolution-maintenance": "^0.3.
|
|
36
|
+
"@lmzhen/dsh-evolution-core": "^0.3.18",
|
|
37
|
+
"@lmzhen/dsh-evolution-maintenance": "^0.3.18"
|
|
38
38
|
},
|
|
39
39
|
"peerDependencies": {
|
|
40
40
|
"@deepseek-ai/cordis": "^4.0.1",
|