@lmzhen/dsh-evolution-core 0.1.0-rc.28 → 0.1.0-rc.29

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
@@ -28,12 +28,17 @@ function evolutionIoAdapter(provider) {
28
28
  };
29
29
  }
30
30
  function nodeEvolutionIo() {
31
+ const isMissing = (error) => {
32
+ const code = error?.code;
33
+ return code === "ENOENT" || code === "ENOTDIR";
34
+ };
31
35
  return {
32
36
  async readText(path) {
33
37
  try {
34
38
  return await readFile(path, "utf8");
35
- } catch {
36
- return null;
39
+ } catch (error) {
40
+ if (isMissing(error)) return null;
41
+ throw error;
37
42
  }
38
43
  },
39
44
  async writeText(path, content) {
@@ -59,8 +64,9 @@ function nodeEvolutionIo() {
59
64
  try {
60
65
  await stat(path);
61
66
  return true;
62
- } catch {
63
- return false;
67
+ } catch (error) {
68
+ if (isMissing(error)) return false;
69
+ throw error;
64
70
  }
65
71
  },
66
72
  async rename(path, destination) {
@@ -77,8 +83,9 @@ function nodeEvolutionIo() {
77
83
  async size(path) {
78
84
  try {
79
85
  return (await stat(path)).size;
80
- } catch {
81
- return null;
86
+ } catch (error) {
87
+ if (isMissing(error)) return null;
88
+ throw error;
82
89
  }
83
90
  }
84
91
  };
@@ -326,20 +333,22 @@ function lifecycleCandidate(name, record, config, bundled) {
326
333
  /**
327
334
  * Read-only scope classification, derived from the SAME gate the transition
328
335
  * engine uses (`lifecycleCandidate`), so the view always predicts what a
329
- * curator pass may touch.
336
+ * curator pass may touch. `protectedNames` carries the marker info the usage
337
+ * records lack (bundled / hub-installed / pinned from `SkillLibrary.list()`).
330
338
  */
331
- function computeScopeView(usage, config) {
339
+ function computeScopeView(usage, config, protectedNames) {
332
340
  const managed = [];
333
341
  const watched = [];
334
342
  const exempted = [];
335
- const protectedNames = [];
343
+ const protectedSet = /* @__PURE__ */ new Set();
336
344
  for (const [name, record] of usage) {
337
345
  if (config.excludeSkillNames?.has(name) || config.referencedSkillNames?.has(name)) {
338
346
  exempted.push(name);
339
347
  continue;
340
348
  }
341
349
  const bundled = config.bundledNames?.has(name) === true;
342
- if (record.pinned || bundled || config.suppressedNames?.has(name) === true) protectedNames.push(name);
350
+ const suppressed = config.suppressedNames?.has(name) === true;
351
+ if (record.pinned || bundled || suppressed || protectedNames?.has(name) === true) protectedSet.add(name);
343
352
  if (lifecycleCandidate(name, record, config, bundled)) {
344
353
  managed.push(name);
345
354
  if (record.state === "stale" || record.quality_warn === true) watched.push(name);
@@ -349,7 +358,7 @@ function computeScopeView(usage, config) {
349
358
  managed: managed.sort(),
350
359
  watched: watched.sort(),
351
360
  exempted: exempted.sort(),
352
- protected: protectedNames.sort()
361
+ protected: [...protectedSet].sort()
353
362
  };
354
363
  }
355
364
  function daysSince(iso, created, now) {
@@ -554,9 +563,10 @@ const PROMPT_BUNDLE = createPromptBundle({
554
563
  completion: COMPLETION_SKILL_REVIEW_PROMPT
555
564
  });
556
565
  function verifyPromptBundle(bundle = PROMPT_BUNDLE) {
566
+ if (bundle.id !== "dsh-evolution@2" || bundle.version !== 2) return false;
557
567
  const canonical = JSON.stringify({
558
- id: bundle.id,
559
- version: bundle.version,
568
+ id: PROMPT_BUNDLE_ID,
569
+ version: 2,
560
570
  prompts: Object.fromEntries(Object.entries(bundle.prompts).sort())
561
571
  });
562
572
  return bundle.sha256 === sha256(canonical);
@@ -950,11 +960,15 @@ var MemoryStore = class {
950
960
  limit: this.limitFor(target)
951
961
  };
952
962
  }
953
- /** Percent-based storage hint appended to success message once the target is ≥80% full. */
963
+ /**
964
+ * StorageHint percentage must clamp at 100 like the render header: a drifted
965
+ * entry can push chars past the limit, and "Storage at 125%" contradicts the
966
+ * clamped usage indicator.
967
+ */
954
968
  storageHint(target, chars) {
955
969
  const limit = this.limitFor(target);
956
970
  if (limit <= 0) return "";
957
- const percent = Math.floor(chars * 100 / limit);
971
+ const percent = Math.min(100, Math.floor(chars * 100 / limit));
958
972
  return percent >= 80 ? ` ⚠️ Storage at ${percent}% (${chars}/${limit} chars).` : "";
959
973
  }
960
974
  /**
@@ -966,10 +980,10 @@ var MemoryStore = class {
966
980
  */
967
981
  async backupFile(target) {
968
982
  const path = fileFor(this.root, target);
969
- const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[-:T]/g, "").slice(0, 14);
983
+ const unique = `${(/* @__PURE__ */ new Date()).toISOString().replace(/[-:T]/g, "").slice(0, 14)}-${Math.random().toString(36).slice(2, 8)}`;
970
984
  try {
971
- await this.io.copy(path, `${path}.bak.${stamp}`);
972
- return `${path}.bak.${stamp}`;
985
+ await this.io.copy(path, `${path}.bak.${unique}`);
986
+ return `${path}.bak.${unique}`;
973
987
  } catch {
974
988
  return null;
975
989
  }
@@ -997,6 +1011,16 @@ var MemoryStore = class {
997
1011
  async add(target, facts) {
998
1012
  const refusal = await this.oversizedRefusal(target);
999
1013
  if (refusal) return refusal;
1014
+ if (await this.detectDrift(target)) {
1015
+ const backup = await this.backupFile(target);
1016
+ return {
1017
+ ok: false,
1018
+ message: `External drift detected in memory file.${backup ? ` A backup was saved to ${basename(backup)}.` : ""} Resolve the drift before retrying.`,
1019
+ entries: [],
1020
+ chars: 0,
1021
+ limit: this.limitFor(target)
1022
+ };
1023
+ }
1000
1024
  const content = facts.trim();
1001
1025
  if (!content) return {
1002
1026
  ok: false,
@@ -1026,7 +1050,8 @@ var MemoryStore = class {
1026
1050
  }
1027
1051
  const next = [...entries, this.addDatePrefix ? `## ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}\n${content}` : content];
1028
1052
  const total = next.join(ENTRY_DELIMITER).length;
1029
- if (total > this.limitFor(target)) return this.failure(target, `Adding this entry would exceed the ${this.limitFor(target)} char limit. Consolidate or remove stale entries, then retry.`, entries);
1053
+ const addLimit = this.limitFor(target);
1054
+ if (addLimit > 0 && total > addLimit) return this.failure(target, `Adding this entry would exceed the ${addLimit} char limit. Consolidate or remove stale entries, then retry.`, entries);
1030
1055
  await this.write(target, next);
1031
1056
  this.resetFailures();
1032
1057
  return {
@@ -1100,7 +1125,8 @@ var MemoryStore = class {
1100
1125
  if (action === "remove") next.splice(index, 1);
1101
1126
  else next[index] = content;
1102
1127
  const total = next.join(ENTRY_DELIMITER).length;
1103
- if (total > this.limitFor(target)) return this.failure(target, `Resulting memory would exceed the ${this.limitFor(target)} char limit.`, entries);
1128
+ const mutateLimit = this.limitFor(target);
1129
+ if (mutateLimit > 0 && total > mutateLimit) return this.failure(target, `Resulting memory would exceed the ${mutateLimit} char limit.`, entries);
1104
1130
  await this.write(target, next);
1105
1131
  this.resetFailures();
1106
1132
  return {
@@ -1198,7 +1224,8 @@ var MemoryStore = class {
1198
1224
  }
1199
1225
  }
1200
1226
  const total = working.join(ENTRY_DELIMITER).length;
1201
- if (total > this.limitFor(target)) return this.failure(target, `Batch result (${total} chars) exceeds the ${this.limitFor(target)} limit. Remove or shorten more entries in the same batch.`, entries);
1227
+ const batchLimit = this.limitFor(target);
1228
+ if (batchLimit > 0 && total > batchLimit) return this.failure(target, `Batch result (${total} chars) exceeds the ${batchLimit} limit. Remove or shorten more entries in the same batch.`, entries);
1202
1229
  await this.write(target, working);
1203
1230
  this.resetFailures();
1204
1231
  return {
@@ -1262,7 +1289,8 @@ var MemoryStore = class {
1262
1289
  const raw = await this.io.readText(fileFor(this.root, target));
1263
1290
  if (raw === null) return false;
1264
1291
  const entries = normalizeEntries(raw);
1265
- if (entries.some((entry) => entry.length > this.limitFor(target))) return true;
1292
+ const limit = this.limitFor(target);
1293
+ if (limit > 0 && entries.some((entry) => entry.length > limit)) return true;
1266
1294
  return render(entries) !== raw;
1267
1295
  }
1268
1296
  };
@@ -106,8 +106,9 @@ export interface ScopeView {
106
106
  /**
107
107
  * Read-only scope classification, derived from the SAME gate the transition
108
108
  * engine uses (`lifecycleCandidate`), so the view always predicts what a
109
- * curator pass may touch.
109
+ * curator pass may touch. `protectedNames` carries the marker info the usage
110
+ * records lack (bundled / hub-installed / pinned from `SkillLibrary.list()`).
110
111
  */
111
- export declare function computeScopeView(usage: UsageMap, config: CuratorConfig): ScopeView;
112
+ export declare function computeScopeView(usage: UsageMap, config: CuratorConfig, protectedNames?: ReadonlyMap<string, string>): ScopeView;
112
113
  export declare function computeLifecycleTransitions(usage: UsageMap, config: CuratorConfig, now?: Date): CuratorResult;
113
114
  //# sourceMappingURL=curator.d.ts.map
@@ -48,7 +48,11 @@ export declare class MemoryStore {
48
48
  write(target: MemoryTarget, entries: string[]): Promise<void>;
49
49
  resetFailures(): void;
50
50
  private failure;
51
- /** Percent-based storage hint appended to success message once the target is ≥80% full. */
51
+ /**
52
+ * StorageHint percentage must clamp at 100 like the render header: a drifted
53
+ * entry can push chars past the limit, and "Storage at 125%" contradicts the
54
+ * clamped usage indicator.
55
+ */
52
56
  private storageHint;
53
57
  /**
54
58
  * Best-effort raw-copy backup of the on-disk file to `<file>.bak.<stamp>`
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@lmzhen/dsh-evolution-core",
3
3
  "description": "Shared stores, prompts, signals and lifecycle logic for the dsh-evolution plugin family (community build)",
4
- "version": "0.1.0-rc.28",
4
+ "version": "0.1.0-rc.29",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },