@lmzhen/dsh-evolution-curator 0.3.82 → 0.4.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/README.md CHANGED
@@ -1,32 +1,19 @@
1
- # @deepseek-ai/dsh-evolution-curator
1
+ # @lmzhen/dsh-evolution-curator
2
2
 
3
3
  Deterministic skill lifecycle and recovery
4
4
 
5
- ## Model Experience
5
+ ## Model surface
6
6
 
7
- ### Indirect model surface
8
-
9
- #### What the model sees
10
-
11
- `@deepseek-ai/dsh-evolution-curator` registers no direct prompt or tool schema itself. Model-visible effects are owned by the packages that consume this service.
12
-
13
- #### Token effect
14
-
15
- Zero direct token effect from this package; consumers add any model-visible tokens.
16
-
17
- #### KV Cache effect
18
-
19
- Independent of request-prefix construction. This package does not alter the assembled prompt or tool list.
20
-
21
- ## Known Limitations and Deferred Work
7
+ - **Model-visible:** nothing of its own: it registers no prompt section and no tool schema; the packages that consume this service own the model-visible effects.
8
+ - **Prompt prefix / KV cache:** independent of request-prefix construction — it does not alter the assembled prompt or tool list; family-level rules: `packages/README.md` §"Model-visible prompt prefix and the KV cache".
9
+ - **Mount it?** yes — the `evolution-curator` row, carried by the `evolution-host`, `evolution-all` and one-click `evolution-preset` bundles; it provides the `evolutionCurator` service the `/evolution curator …` commands and doctor read.
22
10
 
11
+ ## Known limitations
23
12
 
24
13
  - LLM nomination pass is advisory and disabled by default; deterministic lifecycle remains authoritative.
25
14
  - Consolidation is a REAL merger path: the LLM nomination (when enabled / via `recommend()`) proposes
26
- merge groups, the control plane gates them and applies the mutation — merged source bodies are appended
27
- verbatim rather than rewritten into a synthesized skill. The one-click `/evolution consolidate
28
- <target> <source...>` remains the direct manual path (P2-8, v11: the earlier "no LLM pass proposes
29
- merge groups" wording contradicted the tested nomination chain).
15
+ merge groups, the control plane gates them and applies the mutation — merged source bodies are appended
16
+ verbatim rather than rewritten into a synthesized skill.
30
17
 
31
18
  ## Recovery and consolidation
32
19
 
@@ -35,9 +22,12 @@ Independent of request-prefix construction. This package does not alter the asse
35
22
  - `consolidate(target, sources)` (service) / `/evolution consolidate` merges source bodies into the target, archives the sources with an absorbed-into marker, and folds their usage records into `archived` state. Both operations snapshot the full state first (`pre-consolidate` / `pre-restore`).
36
23
  - `restoreSnapshot` (service) / `/evolution restore` rolls the state back to the latest snapshot: active tree, usage/suppression sidecars, `.archive/` and the curator state carried in the snapshot (`curator-state.json`), so the interval gate does not immediately re-fire after a rollback. Skills that were skipped at snapshot time (a live writer held their lock; recorded in the manifest's `skipped` list) are NOT restored — the restore result names them, and `.backups` may hold a copy. The restore itself is undoable — the pre-rollback safety snapshot preserves the current tree plus its state.
37
24
 
38
- ## Automatic scheduling
25
+ ## Configuration
39
26
 
40
- - `autoStart` (default true) arms an hourly interval check plus a deferred catch-up check `bootGraceSeconds` (default 10) after host boot. Both decide due-ness from the **persisted** `lastRunAt`, so a restart with an overdue schedule runs the first pass within the boot grace instead of waiting a full interval. `bootGraceSeconds: 0` disables the deferral (not recommended: the check may run against a half-mounted host). All scheduling gates — interval, idle, first-run deferral, and the reentrancy guard — remain inside `run()`.
27
+ - `autoStart` (default true) arms an HOURLY tick that only asks whether the due-ness interval (`intervalHours`, default 168 h) has elapsed — the tick is not the interval — plus a deferred catch-up check `bootGraceSeconds` (default 10) after host boot. Both decide due-ness from the **persisted** `lastRunAt`, so a restart with an overdue schedule runs the first pass within the boot grace instead of waiting a full interval. `bootGraceSeconds: 0` disables the deferral (not recommended: the check may run against a half-mounted host). All scheduling gates — interval, idle, first-run deferral, and the reentrancy guard — remain inside `run()`.
41
28
  - `autoStart: false` disables both automatic checks; `/evolution curator run` (manual, gate-skipping) still works.
42
29
 
43
30
  **Runtime invariant:** No companion is published. The platform auto-assembles nothing and the family mounts no `<pkg>/invariant` cordis row, so a companion here would never execute (v37 S2.1 / I-3).
31
+ ## Notes and history
32
+
33
+ - The one-click `/evolution consolidate <target> <source...>` remains the direct manual path (P2-8, v11: the earlier "no LLM pass proposes merge groups" wording contradicted the tested nomination chain).
package/lib/index.js CHANGED
@@ -355,6 +355,7 @@ var EvolutionCurator = class extends Service {
355
355
  async restoreSnapshot() {
356
356
  const release = await this.acquireMutex();
357
357
  try {
358
+ if (!this.holdsInstance) return this.instanceHeldRefusal("restoreSnapshot");
358
359
  return await this.restoreSnapshotCore();
359
360
  } finally {
360
361
  release();
@@ -468,6 +469,27 @@ var EvolutionCurator = class extends Service {
468
469
  };
469
470
  return prev.then(() => release);
470
471
  }
472
+ /**
473
+ * PLAN S4.5 (2026-09-16) (audit P2-16): the manual control-plane entries
474
+ * (restore/consolidate/restoreSnapshot) honor the same per-home instance
475
+ * claim `run()` checks before every pass. The claim is taken at MOUNT and
476
+ * released at DISPOSE (core/instance-scope.ts), so `holdsInstance` is
477
+ * stable for this instance's lifetime: a single-instance deployment always
478
+ * holds it and every manual entry behaves exactly as before. The refusal
479
+ * fires only on a YIELDING second row of one process, whose manual mutation
480
+ * would otherwise race the holder row's in-flight run over the same tree —
481
+ * this instance's control-plane mutex cannot see the holder's chain. The
482
+ * shape is the plain `SkillActionResult` failure the command face already
483
+ * renders through `err(result.message)` — no new error type — and the
484
+ * leading `instance-held` token names the same outcome run() reports via
485
+ * its `skipped` field.
486
+ */
487
+ instanceHeldRefusal(operation) {
488
+ return {
489
+ ok: false,
490
+ message: `instance-held: this curator row yielded the per-home claim to another curator row of this process — ${operation} is refused alongside run() so the holder row stays the only control-plane writer (run it on the holder row)`
491
+ };
492
+ }
471
493
  async runCore(options = {}) {
472
494
  const { ignoreGates = false, dryRun = false } = options;
473
495
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -498,13 +520,13 @@ var EvolutionCurator = class extends Service {
498
520
  };
499
521
  if (!ignoreGates && persisted === null && (stateService !== void 0 || !this.statelessFirstRunDeferred)) {
500
522
  if (stateService) try {
501
- await stateService.saveCuratorState({
523
+ await stateService.transactCuratorState((current) => current === null ? {
502
524
  schemaVersion: 1,
503
525
  lastRunAt: Date.now(),
504
526
  runCount: 0,
505
527
  lastSummary: "first-run-deferred",
506
528
  paused: false
507
- });
529
+ } : null);
508
530
  } catch (error) {
509
531
  this.ctx.logger.warn(`evolution-curator: failed to persist the first-run baseline: ${error instanceof Error ? error.message : String(error)}`);
510
532
  }
@@ -547,7 +569,7 @@ var EvolutionCurator = class extends Service {
547
569
  if (text) contents.set(name, text);
548
570
  }
549
571
  const protectedNames = await this.protectedNameMap();
550
- const dedupMembers = [...new Set(computeDedupGroups({ contents }).filter((group) => group.length >= 2).flat())].filter((name) => !protectedNames.has(name));
572
+ const dedupMembers = [...new Set(computeDedupGroups({ contents }).groups.filter((group) => group.length >= 2).flat())].filter((name) => !protectedNames.has(name));
551
573
  await this.scoreTree(usage, treeNames, contents);
552
574
  const result = computeLifecycleTransitions(usage, {
553
575
  staleAfterDays: lifecycle.staleAfterDays,
@@ -1111,6 +1133,7 @@ var EvolutionCurator = class extends Service {
1111
1133
  async consolidate(target, sources) {
1112
1134
  const release = await this.acquireMutex();
1113
1135
  try {
1136
+ if (!this.holdsInstance) return this.instanceHeldRefusal("consolidate");
1114
1137
  return await this.consolidateMutate(target, sources);
1115
1138
  } finally {
1116
1139
  release();
@@ -1155,6 +1178,7 @@ var EvolutionCurator = class extends Service {
1155
1178
  async restore(name) {
1156
1179
  const release = await this.acquireMutex();
1157
1180
  try {
1181
+ if (!this.holdsInstance) return this.instanceHeldRefusal("restore");
1158
1182
  return await this.restoreMutate(name);
1159
1183
  } finally {
1160
1184
  release();
@@ -243,6 +243,22 @@ export declare class EvolutionCurator extends Service {
243
243
  private mutexDepth;
244
244
  private mutexTail;
245
245
  private acquireMutex;
246
+ /**
247
+ * PLAN S4.5 (2026-09-16) (audit P2-16): the manual control-plane entries
248
+ * (restore/consolidate/restoreSnapshot) honor the same per-home instance
249
+ * claim `run()` checks before every pass. The claim is taken at MOUNT and
250
+ * released at DISPOSE (core/instance-scope.ts), so `holdsInstance` is
251
+ * stable for this instance's lifetime: a single-instance deployment always
252
+ * holds it and every manual entry behaves exactly as before. The refusal
253
+ * fires only on a YIELDING second row of one process, whose manual mutation
254
+ * would otherwise race the holder row's in-flight run over the same tree —
255
+ * this instance's control-plane mutex cannot see the holder's chain. The
256
+ * shape is the plain `SkillActionResult` failure the command face already
257
+ * renders through `err(result.message)` — no new error type — and the
258
+ * leading `instance-held` token names the same outcome run() reports via
259
+ * its `skipped` field.
260
+ */
261
+ private instanceHeldRefusal;
246
262
  private runCore;
247
263
  /**
248
264
  * Seed baseline records for tree skills the sidecar has not seen yet, so
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@lmzhen/dsh-evolution-curator",
3
3
  "description": "Deterministic skill lifecycle and recovery (community build)",
4
- "version": "0.3.82",
4
+ "version": "0.4.0",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -27,20 +27,20 @@
27
27
  "license": "MIT",
28
28
  "dependencies": {
29
29
  "@deepseek-ai/schemastery": "^3.18.1",
30
- "@lmzhen/dsh-evolution-core": "^0.3.82"
30
+ "@lmzhen/dsh-evolution-core": "^0.4.0"
31
31
  },
32
32
  "peerDependencies": {
33
33
  "@deepseek-ai/cordis": "^4.0.1",
34
34
  "@deepseek-ai/dsh-llm": "^0.1.5-rc.2",
35
35
  "@deepseek-ai/dsh-session": "^0.1.5-rc.2",
36
- "@lmzhen/dsh-evolution-io": "^0.3.82",
37
- "@lmzhen/dsh-evolution-state": "^0.3.82"
36
+ "@lmzhen/dsh-evolution-io": "^0.4.0",
37
+ "@lmzhen/dsh-evolution-state": "^0.4.0"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@deepseek-ai/dsh-llm": "^0.1.5-rc.2",
41
41
  "@deepseek-ai/dsh-session": "^0.1.5-rc.2",
42
- "@lmzhen/dsh-evolution-core": "^0.3.82",
43
- "@lmzhen/dsh-evolution-io": "^0.3.82",
44
- "@lmzhen/dsh-evolution-state": "^0.3.82"
42
+ "@lmzhen/dsh-evolution-core": "^0.4.0",
43
+ "@lmzhen/dsh-evolution-io": "^0.4.0",
44
+ "@lmzhen/dsh-evolution-state": "^0.4.0"
45
45
  }
46
46
  }