@lmzhen/dsh-evolution-core 0.3.34 → 0.3.36

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
@@ -1,5 +1,5 @@
1
1
  import { basename, dirname, join } from "node:path";
2
- import { cp, lstat, mkdir, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises";
2
+ import { cp, lstat, mkdir, open, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises";
3
3
  import { createHash, randomBytes } from "node:crypto";
4
4
  import { homedir } from "node:os";
5
5
  import { load } from "js-yaml";
@@ -149,11 +149,20 @@ function nodeEvolutionIo() {
149
149
  const lock = `${path}.lock`;
150
150
  let myClaim = "";
151
151
  for (let attempt = 0; attempt < 40; attempt += 1) {
152
+ let lockHandle = null;
152
153
  try {
153
154
  myClaim = `${process.pid}:${randomBytes(4).toString("hex")}`;
154
- await writeFile(lock, myClaim, { flag: "wx" });
155
+ lockHandle = await open(lock, "wx");
156
+ await lockHandle.writeFile(myClaim);
157
+ await lockHandle.close();
158
+ lockHandle = null;
155
159
  } catch (error) {
156
160
  const code = error?.code;
161
+ if (lockHandle) {
162
+ await lockHandle.close().catch(() => {});
163
+ await rm(lock, { force: true }).catch(() => {});
164
+ throw error;
165
+ }
157
166
  if (code !== "EEXIST" && code !== "EPERM") throw error;
158
167
  try {
159
168
  const st = await stat(lock);
@@ -169,13 +178,16 @@ function nodeEvolutionIo() {
169
178
  }
170
179
  continue;
171
180
  }
172
- if (Number.isInteger(holder) && holder > 0 && Date.now() - st.mtimeMs > 1e3 && !holderAlive) {
181
+ const staleDead = Number.isInteger(holder) && holder > 0 && Date.now() - st.mtimeMs > 1e3 && !holderAlive;
182
+ const staleEmpty = holderContent === "" && Date.now() - st.mtimeMs > 1e3;
183
+ if (staleDead || staleEmpty) {
173
184
  if (await readFile(lock, "utf8").catch(() => "") === holderContent) {
174
185
  const ticket = `${lock}.next`;
175
186
  try {
176
187
  const ticketBody = await readFile(ticket, "utf8").catch(() => "");
188
+ const ticketMtime = await stat(ticket).then((s) => s.mtimeMs, () => 0);
177
189
  const ticketHolder = Number(ticketBody.split(":")[0] ?? "");
178
- if ((!Number.isInteger(ticketHolder) || ticketHolder <= 0 || !isAlive(ticketHolder) || Date.now() - await stat(ticket).then((s) => s.mtimeMs, () => 0) > 1e3) && ticketBody !== "") await rm(ticket, { force: true }).catch(() => {});
190
+ if ((!Number.isInteger(ticketHolder) || ticketHolder <= 0 || !isAlive(ticketHolder) || Date.now() - ticketMtime > 1e3) && (ticketBody !== "" || Date.now() - ticketMtime > 1e3)) await rm(ticket, { force: true }).catch(() => {});
179
191
  } catch {}
180
192
  try {
181
193
  await writeFile(ticket, `${process.pid}:${randomBytes(4).toString("hex")}`, { flag: "wx" });
@@ -217,8 +229,24 @@ function nodeEvolutionIo() {
217
229
  return;
218
230
  }
219
231
  const prefix = `${base}.`;
232
+ const lockName = `${base}.lock`;
233
+ const ticketName = `${lockName}.next`;
220
234
  for (const name of entries) {
221
- if (!name.startsWith(prefix) || !name.endsWith(".tmp")) continue;
235
+ if (!name.startsWith(prefix) || name === lockName) continue;
236
+ if (!name.endsWith(".tmp")) {
237
+ if (name === ticketName) {
238
+ const ticketPath = join(dir, name);
239
+ try {
240
+ const body = await readFile(ticketPath, "utf8").catch(() => "");
241
+ const holder = Number(body.split(":")[0] ?? "");
242
+ const st = await stat(ticketPath);
243
+ const dead = !Number.isInteger(holder) || holder <= 0 || !isAlive(holder);
244
+ const old = Date.now() - st.mtimeMs > 1e3;
245
+ if (dead || old) await rm(ticketPath, { force: true });
246
+ } catch {}
247
+ }
248
+ continue;
249
+ }
222
250
  const tmpPath = join(dir, name);
223
251
  const holder = Number(name.slice(prefix.length, name.length - 4).split(".")[0] ?? "");
224
252
  try {
@@ -718,7 +746,8 @@ function buildCuratorRunReport(input) {
718
746
  failed: [...input.failed],
719
747
  ...input.consolidated === void 0 ? {} : { consolidated: [...input.consolidated] },
720
748
  ...input.snapshotPath === void 0 ? {} : { snapshotPath: input.snapshotPath },
721
- ...input.llmReviewEnabled === void 0 ? {} : { llmReviewEnabled: input.llmReviewEnabled }
749
+ ...input.llmReviewEnabled === void 0 ? {} : { llmReviewEnabled: input.llmReviewEnabled },
750
+ ...input.nominationsWarnings === void 0 ? {} : { nominationsWarnings: [...input.nominationsWarnings] }
722
751
  };
723
752
  }
724
753
  /**
@@ -737,7 +766,8 @@ function renderCuratorReportMarkdown(report) {
737
766
  `- **Archived**: ${report.archived.length}`,
738
767
  `- **Failed**: ${report.failed.length}`,
739
768
  ...report.snapshotPath === void 0 ? [] : [`- **Snapshot**: ${report.snapshotPath}`],
740
- ...report.llmReviewEnabled === void 0 ? [] : [`- **llmReview**: ${report.llmReviewEnabled}`]
769
+ ...report.llmReviewEnabled === void 0 ? [] : [`- **llmReview**: ${report.llmReviewEnabled}`],
770
+ ...report.nominationsWarnings === void 0 || report.nominationsWarnings.length === 0 ? [] : [`- **Nomination warnings**: ${report.nominationsWarnings.join("; ")}`]
741
771
  ];
742
772
  const section = (title, items) => items.length === 0 ? [] : [
743
773
  "",
@@ -763,10 +793,16 @@ const NOMINATION_NAME_RE = /^[a-z0-9][a-z0-9-]*$/;
763
793
  function parseCuratorNominations(text) {
764
794
  const prunings = [];
765
795
  const consolidations = [];
796
+ const warnings = [];
766
797
  let section = null;
767
798
  let currentFrom = "";
768
799
  let currentMode;
769
800
  for (const line of text.split("\n")) {
801
+ const header = /^\s*(consolidations|prunings)\s*:\s*$/.exec(line);
802
+ if (header) {
803
+ section = header[1] === "consolidations" ? "consolidations" : "prunings";
804
+ continue;
805
+ }
770
806
  const consolidated = /^\s*-\s*from:\s*([a-z0-9][a-z0-9-]*)\s*$/.exec(line);
771
807
  if (consolidated) {
772
808
  section = "consolidations";
@@ -777,6 +813,7 @@ function parseCuratorNominations(text) {
777
813
  const mode = /^\s*mode:\s*(append|reference)\s*$/.exec(line);
778
814
  if (mode) {
779
815
  if (currentFrom !== "") currentMode = mode[1] === "reference" ? "reference" : "append";
816
+ else warnings.push(`mode: ${mode[1]} ignored — no preceding "- from:" entry (the consolidation falls back to append)`);
780
817
  continue;
781
818
  }
782
819
  const into = /^\s*into:\s*([a-z0-9][a-z0-9-]*)\s*$/.exec(line);
@@ -793,6 +830,7 @@ function parseCuratorNominations(text) {
793
830
  }
794
831
  const pruned = /^\s*-\s*name:\s*([a-z0-9][a-z0-9-]*)\s*$/.exec(line);
795
832
  if (pruned) {
833
+ if (section === "consolidations") warnings.push("\"- name:\" inside the consolidations section flips the parse to prunings");
796
834
  section = "prunings";
797
835
  const name = pruned[1];
798
836
  if (name) prunings.push(name);
@@ -801,7 +839,8 @@ function parseCuratorNominations(text) {
801
839
  const valid = (name) => NOMINATION_NAME_RE.test(name);
802
840
  return {
803
841
  prunings: prunings.filter(valid),
804
- consolidations: consolidations.filter((item) => valid(item.from) && valid(item.into))
842
+ consolidations: consolidations.filter((item) => valid(item.from) && valid(item.into)),
843
+ warnings
805
844
  };
806
845
  }
807
846
  /**
@@ -836,7 +875,8 @@ function computeScopeView(usage, config, protectedNames, gates) {
836
875
  }
837
876
  const bundled = config.bundledNames?.has(name) === true;
838
877
  const suppressed = gateSet.suppressed.has(name);
839
- if (record.pinned || bundled || suppressed || protectedNames?.has(name) === true) protectedSet.add(name);
878
+ const isBuiltin = PROTECTED_BUILTIN_SKILLS.has(name);
879
+ if (record.pinned || bundled || suppressed || protectedNames?.has(name) === true || isBuiltin) protectedSet.add(name);
840
880
  if (lifecycleCandidate(name, record, config, bundled, gateSet)) {
841
881
  managed.push(name);
842
882
  if (record.state === "stale" || record.quality_warn === true) watched.push(name);
@@ -1779,9 +1819,13 @@ const SCOPE_ORDER = {
1779
1819
  strict: 3
1780
1820
  };
1781
1821
  const NO_SCAN_OPTIONS = {};
1782
- /** Window overlap for the full-coverage scan: far larger than the longest
1783
- * pattern span (~530 chars: `curl [^\n]{0,512} ...`), so a match straddling a
1784
- * window boundary is fully inside at least one window (E-12, 0.3.16). */
1822
+ /** Minimum window size for the full-coverage scan (V6-05, 0.3.35). With the
1823
+ * proportional half-window step below, the overlap is `ceil(w/2)` only a
1824
+ * window at or above this floor keeps the overlap above the longest pattern
1825
+ * span (~530 chars: `curl [^\n]{0,512} ...`), so a match straddling a window
1826
+ * boundary is fully inside at least one window (E-12, 0.3.16). The clamp
1827
+ * falls back to the default for a smaller caller value instead of risking a
1828
+ * blind zone. */
1785
1829
  const PATTERN_OVERLAP = 4096;
1786
1830
  /**
1787
1831
  * Scan text at `scope`. Patterns are cumulative: `strict` includes all scopes.
@@ -1791,7 +1835,7 @@ const PATTERN_OVERLAP = 4096;
1791
1835
  * characters (skill files may run to 100,000) is no longer a blind zone.
1792
1836
  */
1793
1837
  function scanThreats(text, scope = "strict", maxScanChars = 65536, options = NO_SCAN_OPTIONS) {
1794
- const windowSize = clampedNumber(maxScanChars, 65536, { min: 1 });
1838
+ const windowSize = clampedNumber(maxScanChars, 65536, { min: 4097 });
1795
1839
  const findings = [];
1796
1840
  if (ZERO_WIDTH_CHARS.test(text)) findings.push({
1797
1841
  label: "unicode_zero_width",
@@ -1807,7 +1851,7 @@ function scanThreats(text, scope = "strict", maxScanChars = 65536, options = NO_
1807
1851
  const windows = [];
1808
1852
  if (normalized.length <= windowSize) windows.push(normalized);
1809
1853
  else {
1810
- const step = Math.max(1, windowSize - PATTERN_OVERLAP);
1854
+ const step = Math.max(Math.floor(windowSize / 2), 1);
1811
1855
  for (let start = 0; start < normalized.length; start += step) windows.push(normalized.slice(start, start + windowSize));
1812
1856
  }
1813
1857
  const excluded = new Set(options.excludeLabels ?? []);
@@ -4470,6 +4514,15 @@ var SkillLibrary = class {
4470
4514
  };
4471
4515
  const target = join(dir, ...filePath.replace(/\\/g, "/").split("/").filter(Boolean));
4472
4516
  return await this.runSingleWrite(target, (current) => {
4517
+ if (current !== null && content.trimEnd() === current.trimEnd()) return {
4518
+ result: {
4519
+ ok: true,
4520
+ message: `Support file "${filePath}" unchanged: the supplied content already matches the current file; nothing written.`,
4521
+ noop: true,
4522
+ path: target
4523
+ },
4524
+ write: null
4525
+ };
4473
4526
  return {
4474
4527
  result: {
4475
4528
  ok: true,
@@ -4738,4 +4791,4 @@ function evolutionHome(env = process.env) {
4738
4791
  return join(evolutionRoot(env), "evolution");
4739
4792
  }
4740
4793
  //#endregion
4741
- export { AUTHORING_DESCRIPTION_BAR, COMBINED_REVIEW_PLAN_PROMPT, COMBINED_REVIEW_PROMPT, COMPLETION_SKILL_REVIEW_PROMPT, CURATOR_DRY_RUN_BANNER, CURATOR_PROMPT, DEFAULT_ARCHIVE_AFTER_DAYS, DEFAULT_CONSOLIDATION_FAILURES, DEFAULT_CURATOR_INTERVAL_HOURS, DEFAULT_HEALTH_THRESHOLDS, DEFAULT_MAX_OPS_PER_PLAN, DEFAULT_MEMORY_CHAR_LIMIT, DEFAULT_MIN_IDLE_HOURS, DEFAULT_MUTATION_CAP, DEFAULT_REVIEW_MEMORY_INTERVAL, DEFAULT_REVIEW_SKILL_INTERVAL, DEFAULT_SKILL_CONTENT_CHARS, DEFAULT_SKILL_LIMITS, DEFAULT_SKILL_REVIEW_COMPLETION_MIN_TOOL_CALLS, DEFAULT_SKILL_REVIEW_TRIGGER, DEFAULT_STALE_AFTER_DAYS, DEFAULT_SUBSTANTIVE_MIN_AGENT_CHARS, DEFAULT_SUBSTANTIVE_MIN_TOOL_CALLS, DEFAULT_SUBSTANTIVE_MIN_USER_CHARS, DEFAULT_USER_CHAR_LIMIT, DRIFT_MAX_LINE_CHARS, DRIFT_SIGNALS_VERSION, DRIFT_SIGNAL_NOUNS, DSH_AUTHORING_STANDARDS, ENTRY_DELIMITER, EVENT_ARCHIVE_PREFIX, EVENT_LOG_RETAIN_ARCHIVES, EVENT_LOG_ROTATE_AT, EVENT_LOG_VERSION, EVOLUTION_WRITE_TOOLS, EvolutionGateSet, FORBIDDEN_CONTROL_KEYS, HEALTH_STAMP_RE, LOW_QUALITY_THRESHOLD, MAINTAIN_PROMPT, MAX_DESCRIPTION_LENGTH, MAX_RESTRUCTURE_MOVES, MAX_SKILL_CONTENT_CHARS, MAX_SKILL_FILE_BYTES, MAX_SKILL_NAME_LENGTH, MEMORY_REVIEW_PROMPT, MIN_STAMP_BODY_CHARS, MUTATIONS_FILE_VERSION, MemoryStore, PROMPT_BUNDLE, PROMPT_BUNDLE_ID, PROMPT_BUNDLE_VERSION, PROTECTED_BUILTIN_SKILLS, QUALITY_WEIGHTS, RESTRUCTURE_TARGET_RE, SKILLS_GUIDANCE, SKILL_NAME_RE, SKILL_REVIEW_PLAN_PROMPT, SKILL_REVIEW_PROMPT, SNAPSHOT_EXTRA_NAME_RE, SUPPORT_DIRS, SUPPRESSED_FILE_VERSION, SkillLibrary, advanceReview, appendEvolutionEvent, applyCuratorFields, applyCuratorLifecycleFields, applyCuratorMetaFields, assessStructureHealth, authoringFeedback, buildCuratorRunReport, buildLearnPrompt, bumpPatch, bumpUse, bumpView, clampedNumber, composePresetComposition, computeDedupGroups, computeDriftSignals, computeLifecycleTransitions, computePrefixClusters, computeQualityScores, computeScopeView, contentHash, createGateSet, duplicateHeadings, emptyRecord, evaluateThreat, eventsFile, evolutionHome, evolutionIoAdapter, evolutionRoot, findDriftSignal, foldCuratorFields, foldTurn, frontmatterBlock, frontmatterYamlUnsafeValues, getRecord, latestActivityAt, lifecycleCandidate, listEventArchives, loadMutations, loadSuppressedNames, loadUsage, makeSerialQueue, markAgentCreated, markerEntryName, memoryRoot, missingSupportPointers, mutateUsage, mutationsFile, narrowNameMatches, nodeEvolutionIo, normalizeFrontmatter, normalizeUsageRecord, observeEvent, overlongLines, parseCuratorNominations, parseEvolutionEvents, parseFrontmatter, pendingSelfCleanup, readEvolutionEvents, readEvolutionTimeline, recordMutation, redactSecrets, relatedSkillNames, renameWithRetry, renderCuratorReportMarkdown, resolveOrigins, resolveSkillsRoot, retainEventArchives, reviewPrompt, saveSuppressedNames, saveUsage, scanContentThreats, scanMemoryThreats, scanThreats, skillsRoot, suppressedFile, transactIo, updateSuppressedNames, usageFile, usageObserved, validateFrontmatter, verifyPromptBundle, yamlPlainScalarNeedsQuotes };
4794
+ export { AUTHORING_DESCRIPTION_BAR, COMBINED_REVIEW_PLAN_PROMPT, COMBINED_REVIEW_PROMPT, COMPLETION_SKILL_REVIEW_PROMPT, CURATOR_DRY_RUN_BANNER, CURATOR_PROMPT, DEFAULT_ARCHIVE_AFTER_DAYS, DEFAULT_CONSOLIDATION_FAILURES, DEFAULT_CURATOR_INTERVAL_HOURS, DEFAULT_HEALTH_THRESHOLDS, DEFAULT_MAX_OPS_PER_PLAN, DEFAULT_MEMORY_CHAR_LIMIT, DEFAULT_MIN_IDLE_HOURS, DEFAULT_MUTATION_CAP, DEFAULT_REVIEW_MEMORY_INTERVAL, DEFAULT_REVIEW_SKILL_INTERVAL, DEFAULT_SKILL_CONTENT_CHARS, DEFAULT_SKILL_LIMITS, DEFAULT_SKILL_REVIEW_COMPLETION_MIN_TOOL_CALLS, DEFAULT_SKILL_REVIEW_TRIGGER, DEFAULT_STALE_AFTER_DAYS, DEFAULT_SUBSTANTIVE_MIN_AGENT_CHARS, DEFAULT_SUBSTANTIVE_MIN_TOOL_CALLS, DEFAULT_SUBSTANTIVE_MIN_USER_CHARS, DEFAULT_USER_CHAR_LIMIT, DRIFT_MAX_LINE_CHARS, DRIFT_SIGNALS_VERSION, DRIFT_SIGNAL_NOUNS, DSH_AUTHORING_STANDARDS, ENTRY_DELIMITER, EVENT_ARCHIVE_PREFIX, EVENT_LOG_RETAIN_ARCHIVES, EVENT_LOG_ROTATE_AT, EVENT_LOG_VERSION, EVOLUTION_WRITE_TOOLS, EvolutionGateSet, FORBIDDEN_CONTROL_KEYS, HEALTH_STAMP_RE, LOW_QUALITY_THRESHOLD, MAINTAIN_PROMPT, MAX_DESCRIPTION_LENGTH, MAX_RESTRUCTURE_MOVES, MAX_SKILL_CONTENT_CHARS, MAX_SKILL_FILE_BYTES, MAX_SKILL_NAME_LENGTH, MEMORY_REVIEW_PROMPT, MIN_STAMP_BODY_CHARS, MUTATIONS_FILE_VERSION, MemoryStore, PATTERN_OVERLAP, PROMPT_BUNDLE, PROMPT_BUNDLE_ID, PROMPT_BUNDLE_VERSION, PROTECTED_BUILTIN_SKILLS, QUALITY_WEIGHTS, RESTRUCTURE_TARGET_RE, SKILLS_GUIDANCE, SKILL_NAME_RE, SKILL_REVIEW_PLAN_PROMPT, SKILL_REVIEW_PROMPT, SNAPSHOT_EXTRA_NAME_RE, SUPPORT_DIRS, SUPPRESSED_FILE_VERSION, SkillLibrary, advanceReview, appendEvolutionEvent, applyCuratorFields, applyCuratorLifecycleFields, applyCuratorMetaFields, assessStructureHealth, authoringFeedback, buildCuratorRunReport, buildLearnPrompt, bumpPatch, bumpUse, bumpView, clampedNumber, composePresetComposition, computeDedupGroups, computeDriftSignals, computeLifecycleTransitions, computePrefixClusters, computeQualityScores, computeScopeView, contentHash, createGateSet, duplicateHeadings, emptyRecord, evaluateThreat, eventsFile, evolutionHome, evolutionIoAdapter, evolutionRoot, findDriftSignal, foldCuratorFields, foldTurn, frontmatterBlock, frontmatterYamlUnsafeValues, getRecord, latestActivityAt, lifecycleCandidate, listEventArchives, loadMutations, loadSuppressedNames, loadUsage, makeSerialQueue, markAgentCreated, markerEntryName, memoryRoot, missingSupportPointers, mutateUsage, mutationsFile, narrowNameMatches, nodeEvolutionIo, normalizeFrontmatter, normalizeUsageRecord, observeEvent, overlongLines, parseCuratorNominations, parseEvolutionEvents, parseFrontmatter, pendingSelfCleanup, readEvolutionEvents, readEvolutionTimeline, recordMutation, redactSecrets, relatedSkillNames, renameWithRetry, renderCuratorReportMarkdown, resolveOrigins, resolveSkillsRoot, retainEventArchives, reviewPrompt, saveSuppressedNames, saveUsage, scanContentThreats, scanMemoryThreats, scanThreats, skillsRoot, suppressedFile, transactIo, updateSuppressedNames, usageFile, usageObserved, validateFrontmatter, verifyPromptBundle, yamlPlainScalarNeedsQuotes };
@@ -63,6 +63,8 @@ export interface CuratorRunReport {
63
63
  snapshotPath?: string;
64
64
  /** Whether the LLM nomination pass was enabled for this run (decision visibility). */
65
65
  llmReviewEnabled?: boolean;
66
+ /** V6-35 (0.3.36): lenient-parse shape notes from the LLM nomination block. */
67
+ nominationsWarnings?: string[];
66
68
  }
67
69
  export interface CuratorReportInput {
68
70
  runId: string;
@@ -76,6 +78,7 @@ export interface CuratorReportInput {
76
78
  consolidated?: readonly CuratorConsolidation[];
77
79
  snapshotPath?: string;
78
80
  llmReviewEnabled?: boolean;
81
+ nominationsWarnings?: readonly string[];
79
82
  }
80
83
  export declare function buildCuratorRunReport(input: CuratorReportInput): CuratorRunReport;
81
84
  /**
@@ -97,6 +100,11 @@ export interface CuratorConsolidation {
97
100
  export interface CuratorNominations {
98
101
  prunings: string[];
99
102
  consolidations: CuratorConsolidation[];
103
+ /** V6-35 (0.3.36): lenient-parse shape notes (an entry the lenient logic
104
+ * silently dropped or re-routed). Parsing stays lenient — these are advisory
105
+ * and flow into the run report so the operator sees why a mode or a pruning
106
+ * went somewhere unexpected. */
107
+ warnings: string[];
100
108
  }
101
109
  /**
102
110
  * Parse the curator LLM's YAML nomination block (consolidations + prunings).
@@ -23,6 +23,14 @@ export interface ScanOptions {
23
23
  /** Pattern labels to skip during this scan. */
24
24
  excludeLabels?: readonly string[];
25
25
  }
26
+ /** Minimum window size for the full-coverage scan (V6-05, 0.3.35). With the
27
+ * proportional half-window step below, the overlap is `ceil(w/2)` — only a
28
+ * window at or above this floor keeps the overlap above the longest pattern
29
+ * span (~530 chars: `curl [^\n]{0,512} ...`), so a match straddling a window
30
+ * boundary is fully inside at least one window (E-12, 0.3.16). The clamp
31
+ * falls back to the default for a smaller caller value instead of risking a
32
+ * blind zone. */
33
+ export declare const PATTERN_OVERLAP = 4096;
26
34
  /**
27
35
  * Scan text at `scope`. Patterns are cumulative: `strict` includes all scopes.
28
36
  * `options.excludeLabels` removes matching patterns without changing `scope`.
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.3.34",
4
+ "version": "0.3.36",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },