@lmzhen/dsh-evolution-core 0.3.60 → 0.3.62

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
@@ -117,7 +117,7 @@ async function renameWithRetry(tmp, target, fn = rename) {
117
117
  async function writeDurableTmp(target, content, openImpl = open) {
118
118
  const tmp = `${target}.${process.pid}.${randomBytes(6).toString("hex")}.tmp`;
119
119
  try {
120
- const handle = await openImpl(tmp, "wx");
120
+ const handle = await openImpl(tmp, "wx", process.platform === "win32" ? void 0 : await stat(target).then((value) => value.mode & 511).catch(() => void 0));
121
121
  try {
122
122
  await handle.writeFile(content, "utf8");
123
123
  await handle.sync();
@@ -312,8 +312,16 @@ function nodeEvolutionIo(lockAttempts = 40) {
312
312
  }).catch(async () => {
313
313
  const body = await readFile(lock, "utf8").catch(() => "");
314
314
  if (pendingSelfCleanup.size >= 64) {
315
- const oldest = pendingSelfCleanup.keys().next().value;
316
- if (oldest !== void 0) pendingSelfCleanup.delete(oldest);
315
+ let droppable;
316
+ for (const candidate of pendingSelfCleanup.keys()) {
317
+ if (candidate === lock) continue;
318
+ if (await readFile(candidate, "utf8").then(() => false, () => true)) {
319
+ droppable = candidate;
320
+ break;
321
+ }
322
+ }
323
+ const victim = droppable ?? pendingSelfCleanup.keys().next().value;
324
+ if (victim !== void 0) pendingSelfCleanup.delete(victim);
317
325
  }
318
326
  pendingSelfCleanup.set(lock, body);
319
327
  });
@@ -452,8 +460,9 @@ function nodeEvolutionIo(lockAttempts = 40) {
452
460
  async isSymlink(path) {
453
461
  try {
454
462
  return (await lstat(path)).isSymbolicLink();
455
- } catch {
456
- return null;
463
+ } catch (error) {
464
+ if (isMissing(error)) return null;
465
+ throw error;
457
466
  }
458
467
  },
459
468
  async mtime(path) {
@@ -614,7 +623,9 @@ function foldCuratorFields(disk, curated, stateOwned) {
614
623
  /** Whole-file usage write (V6-37, 0.3.37): this is the ONE path that bypasses
615
624
  * the malformed-defense and the transact lock — prefer `mutateUsage` for any
616
625
  * read-modify-write so a concurrent writer cannot lose its update and a
617
- * malformed sidecar stays recoverable. Kept for fixture/test seeding. */
626
+ * malformed sidecar stays recoverable. Kept for fixture/test seeding.
627
+ * @internal P3-16 (v14): no production caller (verified by grep); exported for
628
+ * the family's tests only. Do not use it to write the sidecar in new code. */
618
629
  async function saveUsage(root, map, io = nodeEvolutionIo()) {
619
630
  const obj = Object.fromEntries(map.entries());
620
631
  await io.writeText(usageFile(root), JSON.stringify(obj, null, 2));
@@ -778,6 +789,13 @@ const DEFAULT_USER_CHAR_LIMIT = 1375;
778
789
  /** Consolidation-failure backoff cap, shared by MemoryStore and memory-files' Config default. */
779
790
  const DEFAULT_CONSOLIDATION_FAILURES = 3;
780
791
  const DEFAULT_SKILL_CONTENT_CHARS = 1e5;
792
+ /** P3-19 (v14): defaults that were written twice (schema `.default()` AND the
793
+ * clamp fallback literal) now have one home per value. */
794
+ const DEFAULT_REVIEW_TIMEOUT_MS = 12e4;
795
+ const DEFAULT_REVIEW_CONTEXT_MESSAGES = 60;
796
+ const DEFAULT_REVIEW_MESSAGE_CHARS = 2e3;
797
+ const DEFAULT_CURATOR_BOOT_GRACE_SECONDS = 10;
798
+ const DEFAULT_CURATOR_REVIEW_MAX_TOKENS = 2048;
781
799
  /** 0.3.17 (S3.10, T-1): control-plane fields a model-facing write call may
782
800
  * never carry — single source for plan-validator, evolution-policy and the
783
801
  * threat scanner (they used to each hardcode the list). */
@@ -1339,10 +1357,14 @@ async function readEvolutionTimeline(io, path) {
1339
1357
  * reads inside patch YAML `!!js` expressions (session-query path/openAt) stay
1340
1358
  * in the profile config evaluation — they are NOT migrated (they are resolved
1341
1359
  * at cordis config time, not plugin time) but are documented in the README
1342
- * env table.
1360
+ * env table. `EVOLUTION_SCOPE` is read by the source installers only
1361
+ * (`packages/scripts/install-layered.mjs`, `packages/test-support/row-contract.ts`),
1362
+ * never by plugin runtime code.
1363
+ *
1364
+ * v14 P3-1: the former `EVOLUTION_ENV_KEYS` export was deleted — nothing read
1365
+ * it, so the "generated env reference" it claimed to source was never
1366
+ * generated (the README table is maintained by hand and now lists every key).
1343
1367
  */
1344
- /** Plugin-side DSH_EVOLUTION_* keys (config-layer keys are documented separately). */
1345
- const EVOLUTION_ENV_KEYS = ["DSH_EVOLUTION_ALLOW_ROW_COLLISIONS"];
1346
1368
  const ALLOW_ROW_COLLISIONS = "1";
1347
1369
  /** N-5 escape: `DSH_EVOLUTION_ALLOW_ROW_COLLISIONS=1` downgrades a delta-row
1348
1370
  * collision from fail-loud to warn+keep-both. Any other value (including an
@@ -1977,7 +1999,7 @@ const PATTERNS = [
1977
1999
  label: "read_secrets",
1978
2000
  category: "exfiltration",
1979
2001
  scope: "all",
1980
- regex: /\bcat\s+[^\n]{0,512}(?:\.env|credentials|\.netrc|\.pgpass|\.npmrc|\.pypirc)/i
2002
+ regex: /\bcat\s+[^\n]{0,512}(?:\.env(?!\w)|(?:\bcredentials\b)|\.netrc|\.pgpass|\.npmrc|\.pypirc)/i
1981
2003
  },
1982
2004
  {
1983
2005
  label: "ssh_backdoor",
@@ -2027,6 +2049,12 @@ const PATTERNS = [
2027
2049
  scope: "strict",
2028
2050
  regex: /(?:api[_-]?key|token|secret|password)\s*[=:]\s*["'][a-z0-9+/=_-]{20,}["']/i
2029
2051
  },
2052
+ {
2053
+ label: "jwt_like_secret",
2054
+ category: "hardcoded_secrets",
2055
+ scope: "strict",
2056
+ regex: /\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}/
2057
+ },
2030
2058
  {
2031
2059
  label: "private_key_block",
2032
2060
  category: "hardcoded_secrets",
@@ -2060,12 +2088,13 @@ const PATTERN_OVERLAP = 4096;
2060
2088
  function scanThreats(text, scope = "strict", maxScanChars = 65536, options = NO_SCAN_OPTIONS) {
2061
2089
  const windowSize = clampedNumber(maxScanChars, 65536, { min: 4097 });
2062
2090
  const findings = [];
2063
- if (ZERO_WIDTH_CHARS.test(text)) findings.push({
2091
+ const excluded = new Set(options.excludeLabels ?? []);
2092
+ if (ZERO_WIDTH_CHARS.test(text) && !excluded.has("unicode_zero_width")) findings.push({
2064
2093
  label: "unicode_zero_width",
2065
2094
  category: "unicode_obfuscation",
2066
2095
  scope
2067
2096
  });
2068
- if (BIDI_CHARS.test(text)) findings.push({
2097
+ if (BIDI_CHARS.test(text) && !excluded.has("unicode_bidi_override")) findings.push({
2069
2098
  label: "unicode_bidi_override",
2070
2099
  category: "unicode_obfuscation",
2071
2100
  scope
@@ -2077,7 +2106,6 @@ function scanThreats(text, scope = "strict", maxScanChars = 65536, options = NO_
2077
2106
  const step = Math.max(Math.floor(windowSize / 2), 1);
2078
2107
  for (let start = 0; start < normalized.length; start += step) windows.push(normalized.slice(start, start + windowSize));
2079
2108
  }
2080
- const excluded = new Set(options.excludeLabels ?? []);
2081
2109
  const seen = /* @__PURE__ */ new Set();
2082
2110
  for (const window of windows) for (const pattern of PATTERNS) {
2083
2111
  if (SCOPE_ORDER[pattern.scope] > SCOPE_ORDER[scope]) continue;
@@ -2121,9 +2149,11 @@ function scanContentThreats(text, maxScanChars = 65536, options = NO_SCAN_OPTION
2121
2149
  * V10-03 (P2-18): suffix the SkillLibrary/MemoryStore write gates append to a
2122
2150
  * block message — the hit label is already embedded by scanContentThreats /
2123
2151
  * scanMemoryThreats, this names the deployable self-heal path so the model
2124
- * (or operator) can allowlist a known-benign label. The evolution-threat tool
2125
- * channel deliberately does NOT append it: that channel has no
2126
- * threatExemptLabels option to advertise.
2152
+ * (or operator) can allowlist a known-benign label. P2-4 (v14): the
2153
+ * evolution-threat guard channel now carries `threatExemptLabels` too, so its
2154
+ * block message (which embeds the same exemption sentence) is accurate there
2155
+ * as well; this suffix stays store-side because the guard returns the scan
2156
+ * message verbatim.
2127
2157
  */
2128
2158
  const THREAT_EXEMPT_HINT = " If this is a legitimate false positive, the deployment can allow its label via the threatExemptLabels store option.";
2129
2159
  //#endregion
@@ -2300,11 +2330,14 @@ var MemoryStore = class {
2300
2330
  async backupFile(target) {
2301
2331
  const path = fileFor(this.root, target);
2302
2332
  const backup = `${path}.bak`;
2333
+ const staging = `${backup}.${process.pid}.${Date.now().toString(36)}.tmp`;
2303
2334
  try {
2304
- await this.io.remove(backup).catch(() => {});
2305
- await this.io.copy(path, backup);
2335
+ await this.io.remove(staging).catch(() => {});
2336
+ await this.io.copy(path, staging);
2337
+ await this.io.rename(staging, backup);
2306
2338
  return backup;
2307
2339
  } catch {
2340
+ await this.io.remove(staging).catch(() => {});
2308
2341
  return null;
2309
2342
  }
2310
2343
  }
@@ -2650,7 +2683,7 @@ var MemoryStore = class {
2650
2683
  const note = safe.length === entries.length ? "" : ` (${entries.length - safe.length} threat-matched entries filtered)`;
2651
2684
  const usage = limit > 0 ? ` [${Math.min(100, Math.floor(body.length * 100 / limit))}% — ${body.length}/${limit} chars]` : "";
2652
2685
  parts.push(`## ${label} (${safe.length} entries)${usage}${note}\n${body}`);
2653
- }
2686
+ } else if (entries.length > 0) parts.push(`## ${label} — ${entries.length} entries withheld by the security scan; none injected`);
2654
2687
  }
2655
2688
  return parts.join("\n\n");
2656
2689
  }
@@ -3982,14 +4015,29 @@ var SkillLibrary = class {
3982
4015
  dirOf(name) {
3983
4016
  return skillDir(this.root, name.trim());
3984
4017
  }
3985
- /** Name-format guard shared by every path-building mutator/reader. */
4018
+ /**
4019
+ * Name-format guard for every path-building entry point (P1-1/v14 closed the
4020
+ * one gap: `patch`). Write paths, protection probes and support-file
4021
+ * enumeration all call it before `dirOf`, so no directory path is ever built
4022
+ * from a name that could escape the skills root. `list()` and `snapshotAll()`
4023
+ * are the deliberate exceptions — their names come from `listNames()`, i.e.
4024
+ * from the tree itself, never from caller input.
4025
+ */
3986
4026
  badName(name) {
3987
4027
  const normalized = name.trim();
3988
4028
  if (!SKILL_NAME_RE.test(normalized) || normalized.length > this.limits.maxNameLength) return `Invalid skill name "${normalized}". Use lowercase letters, digits, and hyphens (<= ${this.limits.maxNameLength}).`;
3989
4029
  return null;
3990
4030
  }
4031
+ /**
4032
+ * Refusal reason for a write to `rawName`, or null when the write may
4033
+ * proceed: a protection marker on the directory, or an invalid name
4034
+ * (P1-1/v14 — the name guard lives HERE as well as at every entry point, so
4035
+ * no caller can build a path from an unvalidated name).
4036
+ */
3991
4037
  async writeProtection(rawName, origin = "foreground") {
3992
4038
  const name = rawName.trim();
4039
+ const badName = this.badName(name);
4040
+ if (badName) return badName;
3993
4041
  const dir = this.dirOf(name);
3994
4042
  for (const marker of ["bundled", "hub-installed"]) if (await this.io.exists(markerPath(dir, marker))) return marker;
3995
4043
  if (origin === "background_review" && await this.io.exists(markerPath(dir, "pinned"))) return "pinned";
@@ -3997,6 +4045,8 @@ var SkillLibrary = class {
3997
4045
  }
3998
4046
  async deleteProtection(rawName, options = {}) {
3999
4047
  const name = rawName.trim();
4048
+ const badName = this.badName(name);
4049
+ if (badName) return badName;
4000
4050
  const dir = this.dirOf(name);
4001
4051
  const markers = options.allowBundled ? ["hub-installed", "pinned"] : [
4002
4052
  "bundled",
@@ -4008,6 +4058,7 @@ var SkillLibrary = class {
4008
4058
  }
4009
4059
  async isManaged(rawName) {
4010
4060
  const name = rawName.trim();
4061
+ if (this.badName(name) !== null) return false;
4011
4062
  const dir = this.dirOf(name);
4012
4063
  return await this.io.exists(markerPath(dir, "hermes-managed"));
4013
4064
  }
@@ -4051,7 +4102,9 @@ var SkillLibrary = class {
4051
4102
  * Used by the maintenance enrichment (011 §7) and probe reads.
4052
4103
  */
4053
4104
  async listSupportFiles(rawName) {
4054
- const dir = this.dirOf(rawName);
4105
+ const name = rawName.trim();
4106
+ if (this.badName(name) !== null) return [];
4107
+ const dir = this.dirOf(name);
4055
4108
  let entries;
4056
4109
  try {
4057
4110
  entries = await this.io.list(dir);
@@ -4295,6 +4348,11 @@ var SkillLibrary = class {
4295
4348
  return await this.serial(() => this.patchCore(name, oldString, newString, filePath, replaceAll, origin));
4296
4349
  }
4297
4350
  async patchCore(name, oldString, newString, filePath, replaceAll, origin) {
4351
+ const badName = this.badName(name);
4352
+ if (badName) return {
4353
+ ok: false,
4354
+ message: badName
4355
+ };
4298
4356
  const dir = this.dirOf(name);
4299
4357
  const skillMd = join(dir, "SKILL.md");
4300
4358
  if (!await this.io.exists(skillMd)) return {
@@ -4333,6 +4391,13 @@ var SkillLibrary = class {
4333
4391
  },
4334
4392
  write: null
4335
4393
  };
4394
+ if (oldString === "" || trimPatternBoundaries(oldString) === "") return {
4395
+ result: {
4396
+ ok: false,
4397
+ message: `old_string is empty (or whitespace-only) — a patch of "${name}/${patchLabel}" needs an anchor; use update for a full rewrite.`
4398
+ },
4399
+ write: null
4400
+ };
4336
4401
  const patched = fuzzyPatch(md, oldString, newString, replaceAll);
4337
4402
  if (patched === null) return {
4338
4403
  result: {
@@ -4499,7 +4564,9 @@ var SkillLibrary = class {
4499
4564
  }
4500
4565
  }
4501
4566
  const reason = options.reason ?? (options.absorbedInto ? `Consolidated into ${options.absorbedInto}` : "Archived by self-evolution curator");
4502
- await this.io.writeText(join(dest, ".archive-reason"), `${(/* @__PURE__ */ new Date()).toISOString()}: ${reason}\n`);
4567
+ try {
4568
+ await this.io.writeText(join(dest, ".archive-reason"), `${(/* @__PURE__ */ new Date()).toISOString()}: ${reason}\n`);
4569
+ } catch {}
4503
4570
  await this.audit(name, "archive", md, null, reason);
4504
4571
  this.notifyMutation({
4505
4572
  action: "archive",
@@ -5256,4 +5323,4 @@ var SkillLibrary = class {
5256
5323
  }
5257
5324
  };
5258
5325
  //#endregion
5259
- 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_ENV_KEYS, EVOLUTION_WRITE_TOOLS, EvolutionGateSet, FORBIDDEN_CONTROL_KEYS, HEALTH_STAMP_RE, LOW_QUALITY_THRESHOLD, MAINTAIN_OUTPUT_INSTRUCTION, 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, THREAT_EXEMPT_HINT, advanceReview, allowRowCollisions, 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, writeDurableTmp, yamlPlainScalarNeedsQuotes };
5326
+ 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_BOOT_GRACE_SECONDS, DEFAULT_CURATOR_INTERVAL_HOURS, DEFAULT_CURATOR_REVIEW_MAX_TOKENS, DEFAULT_HEALTH_THRESHOLDS, DEFAULT_MAX_OPS_PER_PLAN, DEFAULT_MEMORY_CHAR_LIMIT, DEFAULT_MIN_IDLE_HOURS, DEFAULT_MUTATION_CAP, DEFAULT_REVIEW_CONTEXT_MESSAGES, DEFAULT_REVIEW_MEMORY_INTERVAL, DEFAULT_REVIEW_MESSAGE_CHARS, DEFAULT_REVIEW_SKILL_INTERVAL, DEFAULT_REVIEW_TIMEOUT_MS, 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_OUTPUT_INSTRUCTION, 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, THREAT_EXEMPT_HINT, advanceReview, allowRowCollisions, 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, writeDurableTmp, yamlPlainScalarNeedsQuotes };
@@ -54,6 +54,13 @@ export declare const DEFAULT_USER_CHAR_LIMIT = 1375;
54
54
  /** Consolidation-failure backoff cap, shared by MemoryStore and memory-files' Config default. */
55
55
  export declare const DEFAULT_CONSOLIDATION_FAILURES = 3;
56
56
  export declare const DEFAULT_SKILL_CONTENT_CHARS = 100000;
57
+ /** P3-19 (v14): defaults that were written twice (schema `.default()` AND the
58
+ * clamp fallback literal) now have one home per value. */
59
+ export declare const DEFAULT_REVIEW_TIMEOUT_MS = 120000;
60
+ export declare const DEFAULT_REVIEW_CONTEXT_MESSAGES = 60;
61
+ export declare const DEFAULT_REVIEW_MESSAGE_CHARS = 2000;
62
+ export declare const DEFAULT_CURATOR_BOOT_GRACE_SECONDS = 10;
63
+ export declare const DEFAULT_CURATOR_REVIEW_MAX_TOKENS = 2048;
57
64
  /** 0.3.17 (S3.10, T-1): control-plane fields a model-facing write call may
58
65
  * never carry — single source for plan-validator, evolution-policy and the
59
66
  * threat scanner (they used to each hardcode the list). */
@@ -7,10 +7,14 @@
7
7
  * reads inside patch YAML `!!js` expressions (session-query path/openAt) stay
8
8
  * in the profile config evaluation — they are NOT migrated (they are resolved
9
9
  * at cordis config time, not plugin time) but are documented in the README
10
- * env table.
10
+ * env table. `EVOLUTION_SCOPE` is read by the source installers only
11
+ * (`packages/scripts/install-layered.mjs`, `packages/test-support/row-contract.ts`),
12
+ * never by plugin runtime code.
13
+ *
14
+ * v14 P3-1: the former `EVOLUTION_ENV_KEYS` export was deleted — nothing read
15
+ * it, so the "generated env reference" it claimed to source was never
16
+ * generated (the README table is maintained by hand and now lists every key).
11
17
  */
12
- /** Plugin-side DSH_EVOLUTION_* keys (config-layer keys are documented separately). */
13
- export declare const EVOLUTION_ENV_KEYS: readonly ["DSH_EVOLUTION_ALLOW_ROW_COLLISIONS"];
14
18
  /** N-5 escape: `DSH_EVOLUTION_ALLOW_ROW_COLLISIONS=1` downgrades a delta-row
15
19
  * collision from fail-loud to warn+keep-both. Any other value (including an
16
20
  * EMPTY/whitespace string — a set-but-unset variable) keeps the fail-loud. */
@@ -47,7 +47,13 @@ export declare const HEALTH_STAMP_RE: RegExp;
47
47
  */
48
48
  export declare const MIN_STAMP_BODY_CHARS = 2000;
49
49
  export type SkillHealthVerdict = 'healthy' | 'warn' | 'needs-restructure';
50
- /** Facts a caller already has; assessors never do IO. */
50
+ /** Facts a caller already has; assessors never do IO.
51
+ *
52
+ * `bodyChars`/`bodyText` are the WHOLE `SKILL.md` text as read from disk
53
+ * (frontmatter included), matching both callers: `SkillLibrary.assessHealth`
54
+ * and `snapshotFromLibrary` feed `read()` verbatim. The field names predate
55
+ * that convention; the thresholds are calibrated against the whole file, so a
56
+ * caller must not strip frontmatter before measuring. */
51
57
  export interface SkillHealthSnapshot {
52
58
  skillName: string;
53
59
  bodyChars: number;
@@ -280,8 +280,21 @@ export declare class SkillLibrary {
280
280
 
281
281
  */
282
282
  private dirOf;
283
- /** Name-format guard shared by every path-building mutator/reader. */
283
+ /**
284
+ * Name-format guard for every path-building entry point (P1-1/v14 closed the
285
+ * one gap: `patch`). Write paths, protection probes and support-file
286
+ * enumeration all call it before `dirOf`, so no directory path is ever built
287
+ * from a name that could escape the skills root. `list()` and `snapshotAll()`
288
+ * are the deliberate exceptions — their names come from `listNames()`, i.e.
289
+ * from the tree itself, never from caller input.
290
+ */
284
291
  private badName;
292
+ /**
293
+ * Refusal reason for a write to `rawName`, or null when the write may
294
+ * proceed: a protection marker on the directory, or an invalid name
295
+ * (P1-1/v14 — the name guard lives HERE as well as at every entry point, so
296
+ * no caller can build a path from an unvalidated name).
297
+ */
285
298
  writeProtection(rawName: string, origin?: WriteOrigin): Promise<string | null>;
286
299
  deleteProtection(rawName: string, options?: {
287
300
  allowBundled?: boolean | undefined;
@@ -52,9 +52,11 @@ export declare function scanContentThreats(text: string, maxScanChars?: number,
52
52
  * V10-03 (P2-18): suffix the SkillLibrary/MemoryStore write gates append to a
53
53
  * block message — the hit label is already embedded by scanContentThreats /
54
54
  * scanMemoryThreats, this names the deployable self-heal path so the model
55
- * (or operator) can allowlist a known-benign label. The evolution-threat tool
56
- * channel deliberately does NOT append it: that channel has no
57
- * threatExemptLabels option to advertise.
55
+ * (or operator) can allowlist a known-benign label. P2-4 (v14): the
56
+ * evolution-threat guard channel now carries `threatExemptLabels` too, so its
57
+ * block message (which embeds the same exemption sentence) is accurate there
58
+ * as well; this suffix stays store-side because the guard returns the scan
59
+ * message verbatim.
58
60
  */
59
61
  export declare const THREAT_EXEMPT_HINT = " If this is a legitimate false positive, the deployment can allow its label via the threatExemptLabels store option.";
60
62
  //# sourceMappingURL=threats.d.ts.map
@@ -76,7 +76,9 @@ export declare function foldCuratorFields(disk: UsageMap, curated: UsageMap, sta
76
76
  /** Whole-file usage write (V6-37, 0.3.37): this is the ONE path that bypasses
77
77
  * the malformed-defense and the transact lock — prefer `mutateUsage` for any
78
78
  * read-modify-write so a concurrent writer cannot lose its update and a
79
- * malformed sidecar stays recoverable. Kept for fixture/test seeding. */
79
+ * malformed sidecar stays recoverable. Kept for fixture/test seeding.
80
+ * @internal P3-16 (v14): no production caller (verified by grep); exported for
81
+ * the family's tests only. Do not use it to write the sidecar in new code. */
80
82
  export declare function saveUsage(root: string, map: UsageMap, io?: EvolutionIoLike): Promise<void>;
81
83
  export declare function getRecord(map: UsageMap, name: string): UsageRecord;
82
84
  export declare function bumpView(map: UsageMap, name: string, when?: Date): void;
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.60",
4
+ "version": "0.3.62",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },