@lmzhen/dsh-evolution-core 0.3.33 → 0.3.35

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 {
@@ -1779,9 +1807,13 @@ const SCOPE_ORDER = {
1779
1807
  strict: 3
1780
1808
  };
1781
1809
  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). */
1810
+ /** Minimum window size for the full-coverage scan (V6-05, 0.3.35). With the
1811
+ * proportional half-window step below, the overlap is `ceil(w/2)` — only a
1812
+ * window at or above this floor keeps the overlap above the longest pattern
1813
+ * span (~530 chars: `curl [^\n]{0,512} ...`), so a match straddling a window
1814
+ * boundary is fully inside at least one window (E-12, 0.3.16). The clamp
1815
+ * falls back to the default for a smaller caller value instead of risking a
1816
+ * blind zone. */
1785
1817
  const PATTERN_OVERLAP = 4096;
1786
1818
  /**
1787
1819
  * Scan text at `scope`. Patterns are cumulative: `strict` includes all scopes.
@@ -1791,7 +1823,7 @@ const PATTERN_OVERLAP = 4096;
1791
1823
  * characters (skill files may run to 100,000) is no longer a blind zone.
1792
1824
  */
1793
1825
  function scanThreats(text, scope = "strict", maxScanChars = 65536, options = NO_SCAN_OPTIONS) {
1794
- const windowSize = clampedNumber(maxScanChars, 65536, { min: 1 });
1826
+ const windowSize = clampedNumber(maxScanChars, 65536, { min: 4097 });
1795
1827
  const findings = [];
1796
1828
  if (ZERO_WIDTH_CHARS.test(text)) findings.push({
1797
1829
  label: "unicode_zero_width",
@@ -1807,7 +1839,7 @@ function scanThreats(text, scope = "strict", maxScanChars = 65536, options = NO_
1807
1839
  const windows = [];
1808
1840
  if (normalized.length <= windowSize) windows.push(normalized);
1809
1841
  else {
1810
- const step = Math.max(1, windowSize - PATTERN_OVERLAP);
1842
+ const step = Math.max(Math.floor(windowSize / 2), 1);
1811
1843
  for (let start = 0; start < normalized.length; start += step) windows.push(normalized.slice(start, start + windowSize));
1812
1844
  }
1813
1845
  const excluded = new Set(options.excludeLabels ?? []);
@@ -4738,4 +4770,4 @@ function evolutionHome(env = process.env) {
4738
4770
  return join(evolutionRoot(env), "evolution");
4739
4771
  }
4740
4772
  //#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 };
4773
+ 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 };
@@ -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.33",
4
+ "version": "0.3.35",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },