@lmzhen/dsh-evolution-commands 0.3.51 → 0.3.53

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
@@ -23,7 +23,7 @@ function apply(ctx, rawConfig = {}) {
23
23
  name: "evolution",
24
24
  description: "Self-evolution status and approval controls",
25
25
  recordInput: false,
26
- input: { hint: "pending [--detail] | 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" },
26
+ input: { hint: "pending [--detail] | 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
27
  async handler(invocation) {
28
28
  const input = invocation.rawInput?.trim() ?? "";
29
29
  const ok = (text) => ({
@@ -74,7 +74,8 @@ function apply(ctx, rawConfig = {}) {
74
74
  if (!curator) return err("Curator service not mounted.");
75
75
  const paused = input === "curator pause";
76
76
  await curator.setPaused(paused);
77
- return ok(paused ? "Curator automatic curation paused. Manual /evolution curator run is unaffected; resume with /evolution curator resume." : "Curator automatic curation resumed. The next scheduled pass waits one interval (first-run defer semantics).");
77
+ const notPersisted = ctx.get("evolutionState") !== void 0 ? "" : "\nNOTE: no evolution state service is mounted — this setting is NOT persisted and will not survive this process.";
78
+ return ok((paused ? "Curator automatic curation paused. Manual /evolution curator run is unaffected; resume with /evolution curator resume." : "Curator automatic curation resumed. The next scheduled pass waits one interval (first-run defer semantics).") + notPersisted);
78
79
  }
79
80
  if (input === "curator status") {
80
81
  const curator = ctx.get("evolutionCurator");
@@ -210,14 +211,14 @@ function apply(ctx, rawConfig = {}) {
210
211
  }), enrichment.usageObservedValue, void 0);
211
212
  return ok(`Maintenance facts (0-token preview):\n${facts}`);
212
213
  }
213
- const maintainArgs = /^maintain(?: --timeout (\d+))?\s*$/.exec(input);
214
+ const maintainArgs = /^maintain(?:\s+--timeout[ =](\d+))?\s*$/.exec(input);
214
215
  if (maintainArgs) {
215
216
  const runTimeoutMs = maintainArgs[1] ? Number(maintainArgs[1]) : config.maintainTimeoutMs ?? 6e5;
216
217
  if (!Number.isSafeInteger(runTimeoutMs) || runTimeoutMs <= 0 || runTimeoutMs > 4294967295) return err("Invalid --timeout value: expected a positive integer number of milliseconds up to 4294967295 (e.g. /evolution maintain --timeout 600000).");
217
- if (maintainInFlightSince > 0) return ok(`Maintenance scan is already running (since ~${Math.max(1, Math.round((Date.now() - maintainInFlightSince) / 1e3))}s ago) — re-submitting now would cancel it. Wait for it to settle; the result appears when it finishes.`);
218
+ if (maintainInFlightSince > 0) return err(`Maintenance scan is already running (since ~${Math.max(1, Math.round((Date.now() - maintainInFlightSince) / 1e3))}s ago) — re-submitting now would cancel it. Wait for it to settle; the result appears when it finishes.`);
218
219
  const cooldownMs = clampedNumber(config.maintainCooldownMs, 3e4, { min: 0 });
219
220
  const sinceLast = Date.now() - lastMaintainAt;
220
- if (cooldownMs > 0 && sinceLast < cooldownMs) return ok(`Maintenance cooldown active (${Math.ceil((cooldownMs - sinceLast) / 1e3)}s) — latest scan ${lastMaintainRunId}; re-running now would spend another model call.`);
221
+ if (cooldownMs > 0 && sinceLast < cooldownMs) return err(`Maintenance cooldown active (${Math.ceil((cooldownMs - sinceLast) / 1e3)}s) — latest scan ${lastMaintainRunId}; re-running now would spend another model call.`);
221
222
  const ioRegistry = ctx.get("evolutionIo");
222
223
  const subagents = ctx.get("subagents");
223
224
  if (!ioRegistry) return err("Evolution IO registry not mounted — maintenance scan unavailable.");
@@ -231,6 +232,7 @@ function apply(ctx, rawConfig = {}) {
231
232
  subagents,
232
233
  parent: invocation.agent,
233
234
  evolutionPolicy: { get: () => ctx.get("evolutionPolicy")?.get() },
235
+ tools: { get: (name) => ctx.get("tools")?.get(name) },
234
236
  logger: { warn: (message) => {
235
237
  ctx.logger.warn(message);
236
238
  } }
@@ -250,7 +252,7 @@ function apply(ctx, rawConfig = {}) {
250
252
  source: "manual",
251
253
  runId: outcome.runId,
252
254
  verdict: outcome.verdict,
253
- recommendations: countMaintainRecommendations(outcome.text)
255
+ recommendations: outcome.recommendationCount
254
256
  }).catch((error) => {
255
257
  ctx.logger.warn(`evolution-commands: failed to record maintain event: ${String(error)}`);
256
258
  });
@@ -262,7 +264,7 @@ function apply(ctx, rawConfig = {}) {
262
264
  lastMaintainAt = Date.now();
263
265
  }
264
266
  }
265
- if (/^maintain\b/.test(input)) return err("Unknown maintain arguments: expected bare `maintain` or `maintain --timeout <ms>` (a positive integer). Got: " + input);
267
+ if (/^maintain\b/.test(input)) return err("Unknown maintain arguments: expected bare `maintain`, `maintain --timeout <ms>` or `maintain --timeout=<ms>` (a positive integer). Got: " + input);
266
268
  if (input === "preset install") {
267
269
  const target = join(evolutionRoot(), ".agent-presets", "evolution");
268
270
  try {
@@ -300,7 +302,7 @@ function apply(ctx, rawConfig = {}) {
300
302
  if (!ioRegistry) return err("Evolution IO registry not mounted — restructure unavailable.");
301
303
  const result = await new SkillLibrary(resolveSkillsRoot({ root: config.skillsRoot }), ioRegistry.provider(), void 0, (event) => {
302
304
  ctx.emit("evolution/skill-mutated", event);
303
- }).restructure(name, [{
305
+ }, void 0, config.threatExemptLabels ?? []).restructure(name, [{
304
306
  heading,
305
307
  toFile
306
308
  }], "foreground");
@@ -313,7 +315,7 @@ function apply(ctx, rawConfig = {}) {
313
315
  if (!replay) return err("Replay service not mounted.");
314
316
  return ok(replay.compare().report);
315
317
  }
316
- 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.");
318
+ 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.");
317
319
  }
318
320
  }));
319
321
  });
@@ -442,32 +444,6 @@ function atomicWriteFiles(targetDir, writes, fs = defaultFs) {
442
444
  throw error;
443
445
  }
444
446
  }
445
- /**
446
- * Count the recommendation bullets in a rendered maintain plan.
447
- * `formatPlan` renders each recommendation as a line starting with `- [<kind>]`;
448
- * this matches that bullet (anchored at line start) instead of any string
449
- * prefix, so a `- note` line can never inflate the count.
450
- *
451
- * @param text - the rendered maintain result text, or `undefined`.
452
- * @returns the number of recommendation bullets in `text`.
453
- * @remarks the count is still text-derived — MaintainOutcome does not expose the
454
- * structured plan array, so a structured count would require a
455
- * evolution-maintenance outcome change (out of this package's scope).
456
- */
457
- function countMaintainRecommendations(text) {
458
- if (!text) return 0;
459
- return beforeNotesHeader(text).match(/^- \[/gm)?.length ?? 0;
460
- }
461
- /** Locate the maintain-plan `Notes:` section header as a standalone line.
462
- * Returns everything before it (the plan section), or the whole text when the
463
- * header is absent. formatPlan emits the header via `lines.push('Notes:')`, so
464
- * it is always a line that is exactly `Notes:` (never an indented/embedded
465
- * continuation), letting us ignore a `Notes:` that appears inside a field. */
466
- function beforeNotesHeader(text) {
467
- const header = /(^|\n)Notes:(?=\n)/.exec(text);
468
- if (!header) return text;
469
- return text.slice(0, header.index);
470
- }
471
447
  /** F-328: render staged args for `pending --detail`, truncated to 500 chars.
472
448
  * args may be non-JSON (a tool produced garbage), so the render is fail-safe.
473
449
  * V4-19: a truncation is marked with `…(truncated N chars)` so the operator is
@@ -476,6 +452,7 @@ function beforeNotesHeader(text) {
476
452
  function safeStagedArgs(args) {
477
453
  try {
478
454
  const json = JSON.stringify(args);
455
+ if (typeof json !== "string") return "(unserializable)";
479
456
  if (json.length > 500) return `${json.slice(0, 500)}…(truncated ${json.length - 500} chars)`;
480
457
  return json;
481
458
  } catch {
@@ -483,4 +460,4 @@ function safeStagedArgs(args) {
483
460
  }
484
461
  }
485
462
  //#endregion
486
- export { apply, atomicWriteFiles, countMaintainRecommendations, name };
463
+ export { apply, atomicWriteFiles, name };
@@ -22,6 +22,15 @@ export interface Config {
22
22
  * 4-8 min — the 13:38 successful run used --timeout 600000; the old 120s
23
23
  * default deadlined bare runs mid-analysis (14:37 run aborted at 119.94s). */
24
24
  maintainTimeoutMs?: number | undefined;
25
+ /** Threat-scan exemption labels for WRITE-path skill mutations (P2-18).
26
+ * Threaded into the SkillLibrary this package constructs for `restructure`
27
+ * (its only write path); the core-side constructor option carries the same
28
+ * field name `threatExemptLabels` (P2-18 core batch — the field name is the
29
+ * linkage contract, keep in sync). Default empty: strict-scan behavior is
30
+ * unchanged; a deployment opts specific labels out explicitly. Read-only
31
+ * paths (maintain / maintain --facts) never threat-scan, so this never
32
+ * widens them. */
33
+ threatExemptLabels?: string[] | undefined;
25
34
  }
26
35
  export declare function apply(ctx: Context, rawConfig?: Config): void;
27
36
  /** F-211: the fs surface atomicWriteFiles uses — injectable so the commit-phase
@@ -65,18 +74,5 @@ export declare function atomicWriteFiles(targetDir: string, writes: Array<{
65
74
  name: string;
66
75
  content: string | Uint8Array;
67
76
  }>, fs?: FsOps): void;
68
- /**
69
- * Count the recommendation bullets in a rendered maintain plan.
70
- * `formatPlan` renders each recommendation as a line starting with `- [<kind>]`;
71
- * this matches that bullet (anchored at line start) instead of any string
72
- * prefix, so a `- note` line can never inflate the count.
73
- *
74
- * @param text - the rendered maintain result text, or `undefined`.
75
- * @returns the number of recommendation bullets in `text`.
76
- * @remarks the count is still text-derived — MaintainOutcome does not expose the
77
- * structured plan array, so a structured count would require a
78
- * evolution-maintenance outcome change (out of this package's scope).
79
- */
80
- export declare function countMaintainRecommendations(text: string | undefined): number;
81
77
  export {};
82
78
  //# sourceMappingURL=index.d.ts.map
package/package.json CHANGED
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "name": "@lmzhen/dsh-evolution-commands",
3
3
  "description": "Human commands for the evolution family (community build)",
4
- "version": "0.3.51",
4
+ "version": "0.3.53",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
8
8
  "repository": {
9
9
  "type": "git",
10
10
  "url": "git+https://github.com/lmzhen/dsh-evolution.git",
11
- "directory": "packages/dsh-evolution-commands"
11
+ "directory": "packages/evolution-commands"
12
12
  },
13
13
  "type": "module",
14
14
  "main": "lib/index.js",
@@ -30,12 +30,12 @@
30
30
  ],
31
31
  "license": "MIT",
32
32
  "dependencies": {
33
- "@lmzhen/dsh-evolution-approval": "^0.3.51",
34
- "@lmzhen/dsh-evolution-core": "^0.3.51",
35
- "@lmzhen/dsh-evolution-maintenance": "^0.3.51"
33
+ "@lmzhen/dsh-evolution-approval": "^0.3.53",
34
+ "@lmzhen/dsh-evolution-core": "^0.3.53",
35
+ "@lmzhen/dsh-evolution-maintenance": "^0.3.53"
36
36
  },
37
37
  "optionalDependencies": {
38
- "@lmzhen/dsh-evolution-agent-preset": "^0.3.51"
38
+ "@lmzhen/dsh-evolution-agent-preset": "^0.3.53"
39
39
  },
40
40
  "peerDependencies": {
41
41
  "@deepseek-ai/cordis": "^4.0.1",