@lmzhen/dsh-evolution-core 0.3.77 → 0.3.78

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
@@ -2,6 +2,8 @@ import { basename, dirname, isAbsolute, join, resolve } from "node:path";
2
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
+ import { readFileSync } from "node:fs";
6
+ import { fileURLToPath } from "node:url";
5
7
  import { scopeOf } from "@deepseek-ai/dsh-scope";
6
8
  import { load } from "js-yaml";
7
9
  //#region lib/types/io.js
@@ -1027,7 +1029,7 @@ const SKILL_ACTION_REQUIRED_FIELDS = {
1027
1029
  };
1028
1030
  /** Skill frontmatter `name` validated for the file name (lowercase + hyphen).
1029
1031
  * 计划 B-4 (v18): tightened to the UPSTREAM `SKILL_NAME` shape
1030
- * (`/^[a-z0-9]+(?:-[a-z0-9]+)*$/`, packages/skill/skill/src/index.ts:20). The
1032
+ * (`/^[a-z0-9]+(?:-[a-z0-9]+)*$/`, packages/skill/skill/src/index.ts:21). The
1031
1033
  * old form admitted trailing/consecutive hyphens, which upstream
1032
1034
  * `validateCandidate` throws on — and that throw aborts the WHOLE `ctx.skills`
1033
1035
  * collection. The catalog provider still filters such legacy tree entries so
@@ -3042,21 +3044,43 @@ function composePresetComposition(standardComposition, deltaComposition) {
3042
3044
  if (collisions.length > 0) console.warn(`evolution preset composition: warning — delta rows collide with standard rows (${collisions.join(", ")}); keeping both (DSH_EVOLUTION_ALLOW_ROW_COLLISIONS=1)`);
3043
3045
  return applyRowOverrides(`${standardComposition.replace(/\s+$/, "")}\n\n${deltaComposition.trim()}\n`);
3044
3046
  }
3045
- /** The composer-owned overrides, as DATA: one place states what the generated
3046
- * preset must carry beyond the platform composition. V10-14's cap is the first
3047
- * entry; install-layered.mjs keeps a byte-identical copy of this table. */
3048
- const ROW_OVERRIDES = [{
3049
- row: "tool-skill",
3050
- key: "config",
3051
- lines: [
3052
- " # V10-14: Hermes 60-char catalog cap injected by the preset composer (P1-2);",
3053
- " # this preset-scope row is the session-visible instance and no profile",
3054
- " # patch can reach it. Remove only to run the platform default (500).",
3055
- " config:",
3056
- " catalogDescriptionMaxLength: 60"
3057
- ],
3058
- missing: "evolution preset composition: warning — no `- id: tool-skill` row in the composed preset; the 60-char catalog cap was NOT injected (platform renamed the row? reconcile with the delta)"
3059
- }];
3047
+ /**
3048
+ * One table, two generation paths: `row-overrides.json` at the PACKAGE ROOT
3049
+ * states what a generated preset must carry beyond the platform composition,
3050
+ * and the source installer (`scripts/install-layered.mjs`) reads the same file.
3051
+ * The path resolves from both `src/` and the built `lib/`, which is why the
3052
+ * file sits at the root and ships in `files`.
3053
+ *
3054
+ * Before 0.3.77 each side carried its own hand-kept copy of this table; they
3055
+ * happened to stay byte-identical, which is exactly the kind of agreement no
3056
+ * test can keep the entries are now data, and a divergence is impossible.
3057
+ */
3058
+ const ROW_OVERRIDES_URL = new URL("../row-overrides.json", import.meta.url);
3059
+ let cachedOverrides;
3060
+ /**
3061
+ * Read and validate the shared override table.
3062
+ * @returns the entries, in file order. A malformed table fails LOUD: a composer
3063
+ * that silently skipped an entry would ship a preset running the platform
3064
+ * default while nothing downstream reported it.
3065
+ */
3066
+ function loadRowOverrides() {
3067
+ if (cachedOverrides !== void 0) return cachedOverrides;
3068
+ const parsed = JSON.parse(readFileSync(fileURLToPath(ROW_OVERRIDES_URL), "utf8"));
3069
+ if (!Array.isArray(parsed)) throw new Error("evolution-core: row-overrides.json must be an array of override entries");
3070
+ const entries = [];
3071
+ for (const [index, entry] of parsed.entries()) {
3072
+ const candidate = entry;
3073
+ if (candidate === null || typeof candidate !== "object" || typeof candidate.row !== "string" || candidate.row === "" || typeof candidate.key !== "string" || candidate.key === "" || !Array.isArray(candidate.lines) || candidate.lines.some((line) => typeof line !== "string") || typeof candidate.missingReason !== "string") throw new Error(`evolution-core: row-overrides.json entry ${index} is malformed (row/key/lines/missingReason are required)`);
3074
+ entries.push({
3075
+ row: candidate.row,
3076
+ key: candidate.key,
3077
+ lines: candidate.lines,
3078
+ missingReason: candidate.missingReason
3079
+ });
3080
+ }
3081
+ cachedOverrides = entries;
3082
+ return entries;
3083
+ }
3060
3084
  /**
3061
3085
  * Ensure every {@link RowOverride} inside its target row.
3062
3086
  *
@@ -3066,7 +3090,7 @@ const ROW_OVERRIDES = [{
3066
3090
  * override REPLACES `config` wholesale — the difference is why the composer can
3067
3091
  * add a key without erasing the platform's own config defaults.
3068
3092
  */
3069
- function applyRowOverrides(composition, overrides = ROW_OVERRIDES) {
3093
+ function applyRowOverrides(composition, overrides = loadRowOverrides()) {
3070
3094
  let lines = composition.split("\n");
3071
3095
  for (const override of overrides) lines = applyOneOverride(lines, override);
3072
3096
  return lines.join("\n");
@@ -3090,7 +3114,7 @@ function applyOneOverride(lines, override) {
3090
3114
  lines.splice(end + 1, 0, ...override.lines);
3091
3115
  i = end + override.lines.length;
3092
3116
  }
3093
- if (!found) console.warn(override.missing);
3117
+ if (!found) console.warn("evolution preset composition: warning — " + override.missingReason);
3094
3118
  return lines;
3095
3119
  }
3096
3120
  function compositionRowIds(composition) {
@@ -3807,6 +3831,177 @@ function valueOr(probe, fallback) {
3807
3831
  function mapProbe(probe, transform) {
3808
3832
  return probe.kind === "present" ? probePresent(transform(probe.value)) : probe;
3809
3833
  }
3834
+ /** The reason an `unknown` probe carries (message, never a bare String(object)). */
3835
+ function probeReason(error) {
3836
+ return error instanceof Error ? error.message : String(error);
3837
+ }
3838
+ /**
3839
+ * ENOENT/ENOTDIR are the ONE read failure that means "it is not there"; every
3840
+ * other failure is an IO error and stays unknown (same split as the node
3841
+ * backend's own isMissing, V8-23⑨ for the size probe).
3842
+ */
3843
+ function isMissingPath(error) {
3844
+ const code = error?.code;
3845
+ return code === "ENOENT" || code === "ENOTDIR";
3846
+ }
3847
+ /**
3848
+ * Three-state directory listing. The node backend already answers `[]` for a
3849
+ * MISSING directory (its own rc.50 P2-4 contract) and THROWS for an unreadable
3850
+ * one, so the value this adds is the second half: a backend that throws ENOENT
3851
+ * reads as absent, an EACCES/EIO reads as unknown — where a bare
3852
+ * `catch { return [] }` served a broken store as an empty one (the N14 class).
3853
+ */
3854
+ async function probeList(io, dir) {
3855
+ try {
3856
+ return probePresent(await io.list(dir));
3857
+ } catch (error) {
3858
+ return isMissingPath(error) ? probeAbsent() : probeUnknown(probeReason(error));
3859
+ }
3860
+ }
3861
+ /**
3862
+ * Three-state mtime read. Absent covers both "the path is missing" and "this
3863
+ * backend has no mtime probe" — the seam cannot tell those apart, so consumers
3864
+ * that must know say so in their own log line. A stat that FAILS is unknown.
3865
+ */
3866
+ async function probeMtime(io, path) {
3867
+ try {
3868
+ const value = await io.mtime?.(path) ?? null;
3869
+ return value === null ? probeAbsent() : probePresent(value);
3870
+ } catch (error) {
3871
+ return probeUnknown(probeReason(error));
3872
+ }
3873
+ }
3874
+ //#endregion
3875
+ //#region lib/types/instance-scope.js
3876
+ /**
3877
+ * B3 / G4 (0.3.78): the family's single-instance contract, made explicit.
3878
+ *
3879
+ * Every persisted sidecar is written under TWO assumptions: the IO backend's
3880
+ * cross-process write lock serializes different processes, and exactly ONE
3881
+ * instance of each writer service exists per evolution home. The second half
3882
+ * was implicit — `makeSerialQueue()` and the module-scope stores are
3883
+ * per-instance, so two rows writing one home interleave inside one file (the
3884
+ * "two instances, one file" class). This module makes that half enforceable: a
3885
+ * writer CLAIMS its key while mounted and releases it on dispose, and a second
3886
+ * claimant is told who holds the key instead of silently racing it.
3887
+ *
3888
+ * The claim is keyed by the HOME, not by the process: the contended resource is
3889
+ * the sidecar directory, so two instances resolving different homes (an
3890
+ * isolated test fixture, a second DSH_HOME) do not contend, while two rows on
3891
+ * one profile do.
3892
+ * @module @lmzhen/dsh-evolution-core/src/instance-scope
3893
+ */
3894
+ /** home+key -> holder. Single-process by construction: a claim can only
3895
+ * serialize instances inside this process, which is the whole point (the
3896
+ * cross-process half is the write lock, see persisted-write-inventory.json). */
3897
+ const claims = /* @__PURE__ */ new Map();
3898
+ /** `<home> :: <key>` — the registry key, exported so diagnostics name the
3899
+ * same unit the claim does. */
3900
+ function instanceClaimKey(home, key) {
3901
+ return `${home} :: ${key}`;
3902
+ }
3903
+ /** Take the claim for `key` at `home`. Grants while it is free or already
3904
+ * held BY `owner` — remounting the same instance is not a second instance. */
3905
+ function claimInstance(home, key, owner) {
3906
+ const id = instanceClaimKey(home, key);
3907
+ const current = claims.get(id);
3908
+ if (current === void 0 || current === owner) {
3909
+ claims.set(id, owner);
3910
+ return {
3911
+ granted: true,
3912
+ key: id,
3913
+ holder: owner
3914
+ };
3915
+ }
3916
+ return {
3917
+ granted: false,
3918
+ key: id,
3919
+ holder: current
3920
+ };
3921
+ }
3922
+ /** Release only the claim `owner` took — never another instance's. */
3923
+ function releaseInstance(home, key, owner) {
3924
+ const id = instanceClaimKey(home, key);
3925
+ if (claims.get(id) === owner) claims.delete(id);
3926
+ }
3927
+ /** The current holder of `key` at `home`, or undefined. */
3928
+ function instanceHolder(home, key) {
3929
+ return claims.get(instanceClaimKey(home, key));
3930
+ }
3931
+ //#endregion
3932
+ //#region lib/types/write-inventory.js
3933
+ /**
3934
+ * B3 / G4 (0.3.78): the persisted-write inventory — WHICH file the family
3935
+ * writes, WHO writes it, and WHAT keeps two writers apart.
3936
+ *
3937
+ * The table lives in `persisted-write-inventory.json` at the package root so
3938
+ * the TypeScript side, the architecture gate (`verify-arch-guards` rule N20)
3939
+ * and the regression specs read ONE file (the row-overrides.json pattern).
3940
+ *
3941
+ * "What keeps two writers apart" has exactly three answers in this family:
3942
+ * - `transact` — the IO backend's cross-process write lock (transactIo);
3943
+ * - `write-lock` — the per-target `<path>.lock` protocol of the skill tree;
3944
+ * - `instance-claim` — the per-home single-instance claim (instance-scope.ts),
3945
+ * for a writer whose sweeps cannot be expressed as one locked file.
3946
+ * A site that answers with NONE of them is the "two instances, one file" class:
3947
+ * a per-instance serial queue looks like serialization and is not.
3948
+ * @module @lmzhen/dsh-evolution-core/src/write-inventory
3949
+ */
3950
+ const SITES_URL = new URL("../persisted-write-inventory.json", import.meta.url);
3951
+ const SERIALIZATIONS = [
3952
+ "transact",
3953
+ "write-lock",
3954
+ "instance-claim"
3955
+ ];
3956
+ /** Parse + validate the table. A malformed table throws at import: a table the
3957
+ * gate cannot read must never degrade into "no declared write sites". */
3958
+ function parseSites(raw) {
3959
+ if (!Array.isArray(raw)) throw new Error("evolution-core: persisted-write-inventory.json must be an array of sites");
3960
+ const sites = [];
3961
+ for (const [index, entry] of raw.entries()) {
3962
+ const site = entry;
3963
+ const problems = [];
3964
+ if (typeof site.id !== "string" || site.id === "") problems.push("id");
3965
+ if (typeof site.path !== "string" || site.path === "") problems.push("path");
3966
+ if (typeof site.writer !== "string" || site.writer === "") problems.push("writer");
3967
+ if (typeof site.marker !== "string" || site.marker === "") problems.push("marker");
3968
+ if (typeof site.serializedBy !== "string" || !SERIALIZATIONS.includes(site.serializedBy)) problems.push("serializedBy");
3969
+ if (site.serializedBy === "instance-claim" && typeof site.instance !== "string") problems.push("instance");
3970
+ if (!Array.isArray(site.state)) problems.push("state");
3971
+ if (problems.length > 0) throw new Error(`evolution-core: persisted-write-inventory.json entry ${index} is malformed (missing/invalid: ${problems.join(", ")})`);
3972
+ sites.push({
3973
+ id: site.id,
3974
+ path: site.path,
3975
+ writer: site.writer,
3976
+ serializedBy: site.serializedBy,
3977
+ marker: site.marker,
3978
+ ...typeof site.instance === "string" ? { instance: site.instance } : {},
3979
+ state: site.state,
3980
+ note: typeof site.note === "string" ? site.note : ""
3981
+ });
3982
+ }
3983
+ if (new Set(sites.map((site) => site.id)).size !== sites.length) throw new Error("evolution-core: persisted-write-inventory.json declares duplicate site ids");
3984
+ return sites;
3985
+ }
3986
+ /** Instance keys held by writer services — the single source shared by the
3987
+ * claim site and the inventory row that declares it (rule N20 checks the row's
3988
+ * `instance` names one of these). */
3989
+ const INSTANCE_KEYS = {
3990
+ /** The per-home curator: report writing + the retention sweep. */
3991
+ curator: "evolution-curator" };
3992
+ /** The declared persisted write sites, in file order. */
3993
+ const PERSISTED_WRITE_SITES = parseSites(JSON.parse(readFileSync(fileURLToPath(SITES_URL), "utf8")));
3994
+ /** Sites serialized by the per-home instance claim, with their instance keys. */
3995
+ function instanceClaimedWriteSites() {
3996
+ return PERSISTED_WRITE_SITES.filter((site) => site.serializedBy === "instance-claim");
3997
+ }
3998
+ /** One declared site by id. An undeclared id throws — a stale caller must fail
3999
+ * loud rather than read "nothing is declared". */
4000
+ function persistedWriteSite(id) {
4001
+ const site = PERSISTED_WRITE_SITES.find((candidate) => candidate.id === id);
4002
+ if (site === void 0) throw new Error(`evolution-core: no persisted write site "${id}" in persisted-write-inventory.json`);
4003
+ return site;
4004
+ }
3810
4005
  //#endregion
3811
4006
  //#region lib/types/scope.js
3812
4007
  function callingScope(ctx, held) {
@@ -5666,48 +5861,40 @@ var SkillLibrary = class {
5666
5861
  const name = rawName.trim();
5667
5862
  if (this.badName(name) !== null) return 0;
5668
5863
  const dir = this.dirOf(name);
5669
- let entries;
5670
- try {
5671
- entries = await this.io.list(dir);
5672
- } catch {
5673
- return 0;
5674
- }
5864
+ const listed = await probeList(this.io, dir);
5865
+ if (!isPresent(listed)) return 0;
5675
5866
  let count = 0;
5676
5867
  for (const subdir of SUPPORT_DIRS) {
5677
- if (!entries.includes(subdir)) continue;
5678
- try {
5679
- if ((await this.io.list(join(dir, subdir))).some((file) => file !== ".gitkeep")) count += 1;
5680
- } catch {}
5868
+ if (!listed.value.includes(subdir)) continue;
5869
+ const files = await probeList(this.io, join(dir, subdir));
5870
+ if (isPresent(files) && files.value.some((file) => file !== ".gitkeep")) count += 1;
5681
5871
  }
5682
5872
  return count;
5683
5873
  }
5684
5874
  /**
5685
- * Relative support-file paths (`references/x.md`) under SUPPORT_DIRS for one
5686
- * skill; empty when the directory is unreadable or has no support files.
5687
- * Used by the maintenance enrichment (011 §7) and probe reads.
5875
+ * Support-file paths (`references/x.md`) under SUPPORT_DIRS for one skill,
5876
+ * as a THREE-state read (N14): a missing skill directory is absent, an
5877
+ * unreadable one is unknown. A partial listing is unknown too — the union
5878
+ * promises the present branch is complete (011 §7 enrichment, probe reads).
5688
5879
  */
5689
5880
  async listSupportFiles(rawName) {
5690
5881
  const name = rawName.trim();
5691
- if (this.badName(name) !== null) return [];
5882
+ if (this.badName(name) !== null) return probeAbsent();
5692
5883
  const dir = this.dirOf(name);
5693
- let entries;
5694
- try {
5695
- entries = await this.io.list(dir);
5696
- } catch {
5697
- return [];
5698
- }
5884
+ const listed = await probeList(this.io, dir);
5885
+ if (!isPresent(listed)) return listed;
5699
5886
  const out = [];
5700
5887
  for (const subdir of SUPPORT_DIRS) {
5701
- if (!entries.includes(subdir)) continue;
5702
- try {
5703
- const files = await this.io.list(join(dir, subdir));
5704
- for (const file of files) {
5705
- if (file === ".gitkeep" || file.startsWith(".")) continue;
5706
- out.push(`${subdir}/${file}`);
5707
- }
5708
- } catch {}
5888
+ if (!listed.value.includes(subdir)) continue;
5889
+ const files = await probeList(this.io, join(dir, subdir));
5890
+ if (isUnknown(files)) return files;
5891
+ if (!isPresent(files)) continue;
5892
+ for (const file of files.value) {
5893
+ if (file === ".gitkeep" || file.startsWith(".")) continue;
5894
+ out.push(`${subdir}/${file}`);
5895
+ }
5709
5896
  }
5710
- return out;
5897
+ return probePresent(out);
5711
5898
  }
5712
5899
  /**
5713
5900
  * Structure-health facts for one skill (rc.73 A1, 008 design): body
@@ -7189,21 +7376,25 @@ var SkillLibrary = class {
7189
7376
  }
7190
7377
  /** Keep only the newest N snapshots (Hermes keep=5 parity); older ones are removed outright. */
7191
7378
  async retainSnapshots(keep) {
7192
- const snapshots = await this.listSnapshots();
7379
+ const listed = await this.listSnapshots();
7380
+ if (isUnknown(listed)) {
7381
+ console.warn(`skill-store: snapshot retention skipped — the snapshot directory could not be listed (${listed.reason})`);
7382
+ return;
7383
+ }
7384
+ const snapshots = isPresent(listed) ? listed.value : [];
7193
7385
  for (const snapshot of snapshots.slice(keep)) try {
7194
7386
  await this.io.remove(snapshot.path);
7195
7387
  } catch {}
7196
7388
  }
7389
+ /** Snapshots as a THREE-state read (N14): absent = no .backups directory,
7390
+ * unknown = it exists but could not be listed (a caller must not read a
7391
+ * failed listing as "no snapshots to restore"). */
7197
7392
  async listSnapshots() {
7198
7393
  const backupRoot = join(this.root, ".backups");
7199
- let entries;
7200
- try {
7201
- entries = await this.io.list(backupRoot);
7202
- } catch {
7203
- return [];
7204
- }
7394
+ const listed = await probeList(this.io, backupRoot);
7395
+ if (!isPresent(listed)) return listed;
7205
7396
  const out = [];
7206
- for (const name of entries.sort().reverse()) {
7397
+ for (const name of listed.value.sort().reverse()) {
7207
7398
  if (!name.startsWith("skills-")) continue;
7208
7399
  const manifest = await this.readSnapshotManifest(join(backupRoot, name));
7209
7400
  if (manifest === null) {
@@ -7221,7 +7412,7 @@ var SkillLibrary = class {
7221
7412
  });
7222
7413
  }
7223
7414
  out.sort((a, b) => (b.createdAt || "").localeCompare(a.createdAt || "") || b.path.localeCompare(a.path));
7224
- return out;
7415
+ return probePresent(out);
7225
7416
  }
7226
7417
  /**
7227
7418
  * Read the extras of a snapshot, restricted to the names declared in the
@@ -7249,7 +7440,12 @@ var SkillLibrary = class {
7249
7440
  * snapshot so the rollback itself is undoable with the same state.
7250
7441
  */
7251
7442
  async restoreLatestSnapshot(extras = []) {
7252
- const latest = (await this.listSnapshots())[0];
7443
+ const listed = await this.listSnapshots();
7444
+ if (isUnknown(listed)) return {
7445
+ ok: false,
7446
+ message: `Snapshot restore refused: the snapshot directory could not be read (${listed.reason}) — nothing was changed.`
7447
+ };
7448
+ const latest = isPresent(listed) ? listed.value[0] : void 0;
7253
7449
  if (!latest) return {
7254
7450
  ok: false,
7255
7451
  message: "No skill snapshot available."
@@ -7411,4 +7607,4 @@ function sessionAudited(ctx, sessionId, sessionScoped) {
7411
7607
  return sessionSeesFamilyTools(ctx, sessionId);
7412
7608
  }
7413
7609
  //#endregion
7414
- export { AUTHORING_DESCRIPTION_BAR, COMBINED_REVIEW_PLAN_PROMPT, COMBINED_REVIEW_PROMPT, COMPLETION_SKILL_REVIEW_PROMPT, CURATOR_DRY_RUN_BANNER, CURATOR_PROMPT, DEAD_LOCK_TAKEOVER_MS, DEFAULT_ARCHIVE_AFTER_DAYS, DEFAULT_CONSOLIDATION_FAILURES, DEFAULT_CURATOR_BOOT_GRACE_SECONDS, DEFAULT_CURATOR_INTERVAL_HOURS, DEFAULT_CURATOR_MODEL, DEFAULT_CURATOR_REVIEW_MAX_TOKENS, DEFAULT_HEALTH_THRESHOLDS, DEFAULT_MAX_OPS_PER_PLAN, DEFAULT_MEMORY_CHAR_LIMIT, DEFAULT_MEMORY_REVIEW_MODEL, 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_MODEL, 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, DISPATCH_EVENT_TYPES, DRIFT_MAX_LINE_CHARS, DRIFT_SIGNALS_VERSION, DRIFT_SIGNAL_NOUNS, DSH_AUTHORING_STANDARDS, EMPTY_LOCK_TAKEOVER_MS, ENTRY_DELIMITER, EVENT_ARCHIVE_PREFIX, EVENT_LOG_RETAIN_ARCHIVES, EVENT_LOG_ROTATE_AT, EVENT_LOG_VERSION, EVOLUTION_WRITE_TOOLS, EvolutionGateSet, FAMILY_SESSION_TOOL_NAMES, FORBIDDEN_CONTROL_KEYS, HEALTH_STAMP_RE, LOCK_BODY_RE, LOCK_SUFFIX, LOCK_TEAR_TAKEOVER_MS, LOW_QUALITY_THRESHOLD, LostWriteLock, MAINTAIN_OUTPUT_INSTRUCTION, MAINTAIN_PROMPT, MAX_DESCRIPTION_LENGTH, MAX_RESTRUCTURE_MOVES, MAX_SKILL_CONTENT_CHARS, MAX_SKILL_FILE_BYTES, MAX_SKILL_NAME_LENGTH, MAX_TIMER_DELAY_MS, MEMORY_GUIDANCE_SECTION_ORDER, MEMORY_REVIEW_PROMPT, MIN_STAMP_BODY_CHARS, MUTATIONS_FILE_VERSION, MemoryStore, NATIVE_CALL_EVENT, NATIVE_RESULT_EVENT, PATTERN_OVERLAP, PROMPT_BUNDLE, PROMPT_BUNDLE_ID, PROMPT_BUNDLE_VERSION, PROTECTED_BUILTIN_SKILLS, PTC_DISPATCH_EVENT, PTC_DISPATCH_START_EVENT, QUALITY_WEIGHTS, RESTRUCTURE_TARGET_RE, SKILLS_GUIDANCE, SKILLS_GUIDANCE_SECTION_ORDER, SKILL_ACTION_REQUIRED_FIELDS, SKILL_NAME_RE, SKILL_REVIEW_PLAN_PROMPT, SKILL_REVIEW_PROMPT, SNAPSHOT_EXTRA_NAME_RE, SUPPORT_DIRS, SUPPRESSED_FILE_VERSION, SkillLibrary, THREAT_EXEMPTION_HINT, ToolDispatchNormalizer, ToolDispatchPayloadError, advanceReview, allowRowCollisions, appendEvolutionEvent, applyCuratorLifecycleFields, applyCuratorMetaFields, assertDispatchPayload, assertSkillsRootAliasRetired, assessStructureHealth, authoringFeedback, buildCuratorRunReport, buildLearnPrompt, bumpPatch, bumpUse, bumpView, callingScope, clampedNumber, clearReviewChannel, collectReadSkillNames, composePresetComposition, computeDedupGroups, computeDriftSignals, computeLifecycleTransitions, computePrefixClusters, computeQualityScores, computeScopeView, contentHash, countDispatches, createGateSet, decideTakeover, duplicateHeadings, emptyRecord, evaluateThreat, eventsFile, evolutionEventPayloadIssue, evolutionHome, evolutionIoAdapter, evolutionRoot, findDriftSignal, foldCuratorFields, foldToolDispatches, foldTurn, frontmatterBlock, frontmatterCatalogInvalid, getRecord, isAbsent, isCommittedWarning, isGlobalRead, isPresent, isProcessAlive, isProgramToolName, isReviewChannelSession, isSkillReadToolName, isSkillToolName, isUnknown, latestActivityAt, lifecycleCandidate, listEventArchives, loadMutations, loadSuppressedNames, loadUsage, makeSerialQueue, mapProbe, markAgentCreated, markReviewChannel, markerEntryName, memoryRoot, missingSupportPointers, mutateUsage, mutationsFile, narrowNameMatches, neutralizePromptVariables, newSkillLibrary, nodeEvolutionIo, normalizeFrontmatter, normalizeUsageRecord, observeEvent, overlongLines, parseCuratorNominations, parseEvolutionEvents, parseFrontmatter, parseLockBody, pendingSelfCleanup, probeAbsent, probePresent, probeUnknown, readDispatchSignal, readEvolutionEvents, readEvolutionTimeline, recordMutation, redactSecrets, relatedSkillNames, renameWithRetry, renderCuratorReportMarkdown, resolveOrigins, resolveRootConfig, resolveSkillsRoot, retainEventArchives, reviewPrompt, saveSuppressedNames, saveUsage, scanContentThreats, scanMemoryThreats, scanThreats, sessionAudited, sessionSeesFamilyTools, skillReadNameOf, skillsRoot, suppressedFile, sweepReviewChannelSessions, transactIo, updateSuppressedNames, usageFile, usageObserved, validateFrontmatter, validateRestructureTarget, valueOr, verifyPromptBundle, writeDurableTmp, yamlPlainScalarNeedsQuotes };
7610
+ export { AUTHORING_DESCRIPTION_BAR, COMBINED_REVIEW_PLAN_PROMPT, COMBINED_REVIEW_PROMPT, COMPLETION_SKILL_REVIEW_PROMPT, CURATOR_DRY_RUN_BANNER, CURATOR_PROMPT, DEAD_LOCK_TAKEOVER_MS, DEFAULT_ARCHIVE_AFTER_DAYS, DEFAULT_CONSOLIDATION_FAILURES, DEFAULT_CURATOR_BOOT_GRACE_SECONDS, DEFAULT_CURATOR_INTERVAL_HOURS, DEFAULT_CURATOR_MODEL, DEFAULT_CURATOR_REVIEW_MAX_TOKENS, DEFAULT_HEALTH_THRESHOLDS, DEFAULT_MAX_OPS_PER_PLAN, DEFAULT_MEMORY_CHAR_LIMIT, DEFAULT_MEMORY_REVIEW_MODEL, 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_MODEL, 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, DISPATCH_EVENT_TYPES, DRIFT_MAX_LINE_CHARS, DRIFT_SIGNALS_VERSION, DRIFT_SIGNAL_NOUNS, DSH_AUTHORING_STANDARDS, EMPTY_LOCK_TAKEOVER_MS, ENTRY_DELIMITER, EVENT_ARCHIVE_PREFIX, EVENT_LOG_RETAIN_ARCHIVES, EVENT_LOG_ROTATE_AT, EVENT_LOG_VERSION, EVOLUTION_WRITE_TOOLS, EvolutionGateSet, FAMILY_SESSION_TOOL_NAMES, FORBIDDEN_CONTROL_KEYS, HEALTH_STAMP_RE, INSTANCE_KEYS, LOCK_BODY_RE, LOCK_SUFFIX, LOCK_TEAR_TAKEOVER_MS, LOW_QUALITY_THRESHOLD, LostWriteLock, MAINTAIN_OUTPUT_INSTRUCTION, MAINTAIN_PROMPT, MAX_DESCRIPTION_LENGTH, MAX_RESTRUCTURE_MOVES, MAX_SKILL_CONTENT_CHARS, MAX_SKILL_FILE_BYTES, MAX_SKILL_NAME_LENGTH, MAX_TIMER_DELAY_MS, MEMORY_GUIDANCE_SECTION_ORDER, MEMORY_REVIEW_PROMPT, MIN_STAMP_BODY_CHARS, MUTATIONS_FILE_VERSION, MemoryStore, NATIVE_CALL_EVENT, NATIVE_RESULT_EVENT, PATTERN_OVERLAP, PERSISTED_WRITE_SITES, PROMPT_BUNDLE, PROMPT_BUNDLE_ID, PROMPT_BUNDLE_VERSION, PROTECTED_BUILTIN_SKILLS, PTC_DISPATCH_EVENT, PTC_DISPATCH_START_EVENT, QUALITY_WEIGHTS, RESTRUCTURE_TARGET_RE, SKILLS_GUIDANCE, SKILLS_GUIDANCE_SECTION_ORDER, SKILL_ACTION_REQUIRED_FIELDS, SKILL_NAME_RE, SKILL_REVIEW_PLAN_PROMPT, SKILL_REVIEW_PROMPT, SNAPSHOT_EXTRA_NAME_RE, SUPPORT_DIRS, SUPPRESSED_FILE_VERSION, SkillLibrary, THREAT_EXEMPTION_HINT, ToolDispatchNormalizer, ToolDispatchPayloadError, advanceReview, allowRowCollisions, appendEvolutionEvent, applyCuratorLifecycleFields, applyCuratorMetaFields, assertDispatchPayload, assertSkillsRootAliasRetired, assessStructureHealth, authoringFeedback, buildCuratorRunReport, buildLearnPrompt, bumpPatch, bumpUse, bumpView, callingScope, claimInstance, clampedNumber, clearReviewChannel, collectReadSkillNames, composePresetComposition, computeDedupGroups, computeDriftSignals, computeLifecycleTransitions, computePrefixClusters, computeQualityScores, computeScopeView, contentHash, countDispatches, createGateSet, decideTakeover, duplicateHeadings, emptyRecord, evaluateThreat, eventsFile, evolutionEventPayloadIssue, evolutionHome, evolutionIoAdapter, evolutionRoot, findDriftSignal, foldCuratorFields, foldToolDispatches, foldTurn, frontmatterBlock, frontmatterCatalogInvalid, getRecord, instanceClaimKey, instanceClaimedWriteSites, instanceHolder, isAbsent, isCommittedWarning, isGlobalRead, isMissingPath, isPresent, isProcessAlive, isProgramToolName, isReviewChannelSession, isSkillReadToolName, isSkillToolName, isUnknown, latestActivityAt, lifecycleCandidate, listEventArchives, loadMutations, loadRowOverrides, loadSuppressedNames, loadUsage, makeSerialQueue, mapProbe, markAgentCreated, markReviewChannel, markerEntryName, memoryRoot, missingSupportPointers, mutateUsage, mutationsFile, narrowNameMatches, neutralizePromptVariables, newSkillLibrary, nodeEvolutionIo, normalizeFrontmatter, normalizeUsageRecord, observeEvent, overlongLines, parseCuratorNominations, parseEvolutionEvents, parseFrontmatter, parseLockBody, pendingSelfCleanup, persistedWriteSite, probeAbsent, probeList, probeMtime, probePresent, probeReason, probeUnknown, readDispatchSignal, readEvolutionEvents, readEvolutionTimeline, recordMutation, redactSecrets, relatedSkillNames, releaseInstance, renameWithRetry, renderCuratorReportMarkdown, resolveOrigins, resolveRootConfig, resolveSkillsRoot, retainEventArchives, reviewPrompt, saveSuppressedNames, saveUsage, scanContentThreats, scanMemoryThreats, scanThreats, sessionAudited, sessionSeesFamilyTools, skillReadNameOf, skillsRoot, suppressedFile, sweepReviewChannelSessions, transactIo, updateSuppressedNames, usageFile, usageObserved, validateFrontmatter, validateRestructureTarget, valueOr, verifyPromptBundle, writeDurableTmp, yamlPlainScalarNeedsQuotes };
@@ -37,7 +37,7 @@
37
37
  export declare const SKILL_ACTION_REQUIRED_FIELDS: Readonly<Record<string, readonly string[]>>;
38
38
  /** Skill frontmatter `name` validated for the file name (lowercase + hyphen).
39
39
  * 计划 B-4 (v18): tightened to the UPSTREAM `SKILL_NAME` shape
40
- * (`/^[a-z0-9]+(?:-[a-z0-9]+)*$/`, packages/skill/skill/src/index.ts:20). The
40
+ * (`/^[a-z0-9]+(?:-[a-z0-9]+)*$/`, packages/skill/skill/src/index.ts:21). The
41
41
  * old form admitted trailing/consecutive hyphens, which upstream
42
42
  * `validateCandidate` throws on — and that throw aborts the WHOLE `ctx.skills`
43
43
  * collection. The catalog provider still filters such legacy tree entries so
@@ -41,6 +41,8 @@ export * from './redact.ts';
41
41
  export * from './review-channel.ts';
42
42
  export * from './serial.ts';
43
43
  export * from './probe.ts';
44
+ export * from './instance-scope.ts';
45
+ export * from './write-inventory.ts';
44
46
  export * from './scope.ts';
45
47
  export * from './skill-health.ts';
46
48
  export * from './signals.ts';
@@ -0,0 +1,35 @@
1
+ /**
2
+ * B3 / G4 (0.3.78): the family's single-instance contract, made explicit.
3
+ *
4
+ * Every persisted sidecar is written under TWO assumptions: the IO backend's
5
+ * cross-process write lock serializes different processes, and exactly ONE
6
+ * instance of each writer service exists per evolution home. The second half
7
+ * was implicit — `makeSerialQueue()` and the module-scope stores are
8
+ * per-instance, so two rows writing one home interleave inside one file (the
9
+ * "two instances, one file" class). This module makes that half enforceable: a
10
+ * writer CLAIMS its key while mounted and releases it on dispose, and a second
11
+ * claimant is told who holds the key instead of silently racing it.
12
+ *
13
+ * The claim is keyed by the HOME, not by the process: the contended resource is
14
+ * the sidecar directory, so two instances resolving different homes (an
15
+ * isolated test fixture, a second DSH_HOME) do not contend, while two rows on
16
+ * one profile do.
17
+ * @module @lmzhen/dsh-evolution-core/src/instance-scope
18
+ */
19
+ /** Outcome of a claim. `holder` is the current owner either way. */
20
+ export interface InstanceClaimResult {
21
+ readonly granted: boolean;
22
+ readonly key: string;
23
+ readonly holder: string;
24
+ }
25
+ /** `<home> :: <key>` — the registry key, exported so diagnostics name the
26
+ * same unit the claim does. */
27
+ export declare function instanceClaimKey(home: string, key: string): string;
28
+ /** Take the claim for `key` at `home`. Grants while it is free or already
29
+ * held BY `owner` — remounting the same instance is not a second instance. */
30
+ export declare function claimInstance(home: string, key: string, owner: string): InstanceClaimResult;
31
+ /** Release only the claim `owner` took — never another instance's. */
32
+ export declare function releaseInstance(home: string, key: string, owner: string): void;
33
+ /** The current holder of `key` at `home`, or undefined. */
34
+ export declare function instanceHolder(home: string, key: string): string | undefined;
35
+ //# sourceMappingURL=instance-scope.d.ts.map
@@ -26,4 +26,42 @@
26
26
  * @returns the composed preset composition (standard rows first, then delta).
27
27
  */
28
28
  export declare function composePresetComposition(standardComposition: string, deltaComposition: string): string;
29
+ /**
30
+ * V10-14 (P1-2), 0.3.53: inject the Hermes 60-char catalog cap onto the
31
+ * standard-sourced `- id: tool-skill` row of a composed preset.
32
+ *
33
+ * The session-visible `tool-skill` instance mounts in the agent preset's own
34
+ * standing scope; a profile-root patch (evolution-host/cordis.patch.yml)
35
+ * cannot reach it, so without this injection the catalog's read side runs the
36
+ * platform default (500). Text-level rewrite in the same line-scan style as
37
+ * compositionRowIds (no YAML library):
38
+ * - idempotent: a tool-skill item that already carries a `config:` key is
39
+ * left byte-identical, so re-running the installer never doubles the key;
40
+ * - the injected block carries a marker comment so a diff of the generated
41
+ * preset can tell composer-owned text from platform text;
42
+ * - a composition WITHOUT a tool-skill row is returned unchanged with a
43
+ * one-time warning (a renamed platform row must not brick the install,
44
+ * but the missed cap must be observable).
45
+ * The source installer reads the SAME table (`row-overrides.json`) through its
46
+ * own `injectToolSkillCap`, so the two paths cannot diverge (0.3.77).
47
+ */
48
+ /** One entry of the shared override table (see {@link loadRowOverrides}). */
49
+ interface RowOverride {
50
+ /** Top-level row id this override targets (`- id: <row>`). */
51
+ row: string;
52
+ /** The child key whose presence makes the override inert (idempotence). */
53
+ key: string;
54
+ /** Rendered, already-indented YAML lines to ensure inside the row. */
55
+ lines: string[];
56
+ /** Warning tail shared by both consumers; each prefixes its own logger tag. */
57
+ missingReason: string;
58
+ }
59
+ /**
60
+ * Read and validate the shared override table.
61
+ * @returns the entries, in file order. A malformed table fails LOUD: a composer
62
+ * that silently skipped an entry would ship a preset running the platform
63
+ * default while nothing downstream reported it.
64
+ */
65
+ export declare function loadRowOverrides(): RowOverride[];
66
+ export {};
29
67
  //# sourceMappingURL=preset-composition.d.ts.map
@@ -9,6 +9,7 @@
9
9
  * unverifiable): constructors are prefixed so the type can never be confused
10
10
  * with a value, and only `present` yields a value.
11
11
  */
12
+ import type { EvolutionIoLike } from './io.ts';
12
13
  export type Probe<T> = {
13
14
  kind: 'present';
14
15
  value: T;
@@ -35,4 +36,26 @@ export declare function isUnknown<T>(probe: Probe<T>): probe is {
35
36
  /** Only a PRESENT probe yields a value; absent and unknown both fall back. */
36
37
  export declare function valueOr<T>(probe: Probe<T>, fallback: T): T;
37
38
  export declare function mapProbe<T, U>(probe: Probe<T>, transform: (value: T) => U): Probe<U>;
39
+ /** The reason an `unknown` probe carries (message, never a bare String(object)). */
40
+ export declare function probeReason(error: unknown): string;
41
+ /**
42
+ * ENOENT/ENOTDIR are the ONE read failure that means "it is not there"; every
43
+ * other failure is an IO error and stays unknown (same split as the node
44
+ * backend's own isMissing, V8-23⑨ for the size probe).
45
+ */
46
+ export declare function isMissingPath(error: unknown): boolean;
47
+ /**
48
+ * Three-state directory listing. The node backend already answers `[]` for a
49
+ * MISSING directory (its own rc.50 P2-4 contract) and THROWS for an unreadable
50
+ * one, so the value this adds is the second half: a backend that throws ENOENT
51
+ * reads as absent, an EACCES/EIO reads as unknown — where a bare
52
+ * `catch { return [] }` served a broken store as an empty one (the N14 class).
53
+ */
54
+ export declare function probeList(io: EvolutionIoLike, dir: string): Promise<Probe<string[]>>;
55
+ /**
56
+ * Three-state mtime read. Absent covers both "the path is missing" and "this
57
+ * backend has no mtime probe" — the seam cannot tell those apart, so consumers
58
+ * that must know say so in their own log line. A stat that FAILS is unknown.
59
+ */
60
+ export declare function probeMtime(io: EvolutionIoLike, path: string): Promise<Probe<number>>;
38
61
  //# sourceMappingURL=probe.d.ts.map
@@ -33,6 +33,7 @@
33
33
  * (OPT-07); the movers' probe→rename window is owned by the io.ts protocol.
34
34
  */
35
35
  import { transactIo, type EvolutionIoLike } from './io.ts';
36
+ import { type Probe } from './probe.ts';
36
37
  import { type MutationRecord } from './mutations.ts';
37
38
  import { type SkillHealthAssessment, type SkillHealthThresholds } from './skill-health.ts';
38
39
  import type { EvolutionSkillMutatedEvent } from './events.ts';
@@ -483,11 +484,12 @@ export declare class SkillLibrary {
483
484
  /** Count non-empty support subdirectories (richness input for quality scoring). */
484
485
  countSupportDirs(rawName: string): Promise<number>;
485
486
  /**
486
- * Relative support-file paths (`references/x.md`) under SUPPORT_DIRS for one
487
- * skill; empty when the directory is unreadable or has no support files.
488
- * Used by the maintenance enrichment (011 §7) and probe reads.
487
+ * Support-file paths (`references/x.md`) under SUPPORT_DIRS for one skill,
488
+ * as a THREE-state read (N14): a missing skill directory is absent, an
489
+ * unreadable one is unknown. A partial listing is unknown too — the union
490
+ * promises the present branch is complete (011 §7 enrichment, probe reads).
489
491
  */
490
- listSupportFiles(rawName: string): Promise<string[]>;
492
+ listSupportFiles(rawName: string): Promise<Probe<string[]>>;
491
493
  /**
492
494
  * Structure-health facts for one skill (rc.73 A1, 008 design): body
493
495
  * chars/density from SKILL.md, support groups from countSupportDirs, plus
@@ -664,11 +666,14 @@ export declare class SkillLibrary {
664
666
  readSnapshotManifest(path: string): Promise<SnapshotManifest | null>;
665
667
  /** Keep only the newest N snapshots (Hermes keep=5 parity); older ones are removed outright. */
666
668
  private retainSnapshots;
667
- listSnapshots(): Promise<Array<{
669
+ /** Snapshots as a THREE-state read (N14): absent = no .backups directory,
670
+ * unknown = it exists but could not be listed (a caller must not read a
671
+ * failed listing as "no snapshots to restore"). */
672
+ listSnapshots(): Promise<Probe<Array<{
668
673
  path: string;
669
674
  createdAt: string;
670
675
  reason: string;
671
- }>>;
676
+ }>>>;
672
677
  /**
673
678
  * Read the extras of a snapshot, restricted to the names declared in the
674
679
  * manifest — an `extras/` directory is never listed directly, so unknown
@@ -0,0 +1,55 @@
1
+ /**
2
+ * B3 / G4 (0.3.78): the persisted-write inventory — WHICH file the family
3
+ * writes, WHO writes it, and WHAT keeps two writers apart.
4
+ *
5
+ * The table lives in `persisted-write-inventory.json` at the package root so
6
+ * the TypeScript side, the architecture gate (`verify-arch-guards` rule N20)
7
+ * and the regression specs read ONE file (the row-overrides.json pattern).
8
+ *
9
+ * "What keeps two writers apart" has exactly three answers in this family:
10
+ * - `transact` — the IO backend's cross-process write lock (transactIo);
11
+ * - `write-lock` — the per-target `<path>.lock` protocol of the skill tree;
12
+ * - `instance-claim` — the per-home single-instance claim (instance-scope.ts),
13
+ * for a writer whose sweeps cannot be expressed as one locked file.
14
+ * A site that answers with NONE of them is the "two instances, one file" class:
15
+ * a per-instance serial queue looks like serialization and is not.
16
+ * @module @lmzhen/dsh-evolution-core/src/write-inventory
17
+ */
18
+ /** How concurrent writers of one site are kept apart. */
19
+ export type WriteSerialization = 'transact' | 'write-lock' | 'instance-claim';
20
+ /** One declared persisted write site. */
21
+ export interface PersistedWriteSite {
22
+ /** Stable id; referenced by the gate's failure text and by the specs. */
23
+ readonly id: string;
24
+ /** The path(s) written, with the roots the family resolves at runtime. */
25
+ readonly path: string;
26
+ /** Repo-relative module that owns the write. */
27
+ readonly writer: string;
28
+ /** The serialization the writer implements (see the module docblock). */
29
+ readonly serializedBy: WriteSerialization;
30
+ /** Literal that must appear in `writer` — the gate's proof of the claim. */
31
+ readonly marker: string;
32
+ /** The instance key the writer holds when `serializedBy` is instance-claim. */
33
+ readonly instance?: string;
34
+ /** Module-scope state keys (N12 registry keys) this writer keeps, if any. */
35
+ readonly state: readonly string[];
36
+ /** One line on what the file holds; the inventory reads as a whole. */
37
+ readonly note: string;
38
+ }
39
+ /** Instance keys held by writer services — the single source shared by the
40
+ * claim site and the inventory row that declares it (rule N20 checks the row's
41
+ * `instance` names one of these). */
42
+ export declare const INSTANCE_KEYS: {
43
+ /** The per-home curator: report writing + the retention sweep. */
44
+ readonly curator: "evolution-curator";
45
+ };
46
+ /** The declared persisted write sites, in file order. */
47
+ export declare const PERSISTED_WRITE_SITES: readonly PersistedWriteSite[];
48
+ /** Sites serialized by the per-home instance claim, with their instance keys. */
49
+ export declare function instanceClaimedWriteSites(): readonly (PersistedWriteSite & {
50
+ readonly instance: string;
51
+ })[];
52
+ /** One declared site by id. An undeclared id throws — a stale caller must fail
53
+ * loud rather than read "nothing is declared". */
54
+ export declare function persistedWriteSite(id: string): PersistedWriteSite;
55
+ //# sourceMappingURL=write-inventory.d.ts.map
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.77",
4
+ "version": "0.3.78",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -18,11 +18,13 @@
18
18
  "types": "./lib/types/index.d.ts",
19
19
  "default": "./lib/index.js"
20
20
  },
21
- "./package.json": "./package.json"
21
+ "./package.json": "./package.json",
22
+ "./row-overrides.json": "./row-overrides.json"
22
23
  },
23
24
  "files": [
24
25
  "lib/*.js",
25
- "lib/types/**/*.d.ts"
26
+ "lib/types/**/*.d.ts",
27
+ "row-overrides.json"
26
28
  ],
27
29
  "license": "MIT",
28
30
  "dependencies": {
@@ -0,0 +1,14 @@
1
+ [
2
+ {
3
+ "row": "tool-skill",
4
+ "key": "config",
5
+ "lines": [
6
+ " # V10-14: Hermes 60-char catalog cap — injected by the preset composer (P1-2);",
7
+ " # this preset-scope row is the session-visible instance and no profile",
8
+ " # patch can reach it. Remove only to run the platform default (500).",
9
+ " config:",
10
+ " catalogDescriptionMaxLength: 60"
11
+ ],
12
+ "missingReason": "no `- id: tool-skill` row in the composed preset; the 60-char catalog cap was NOT injected (platform renamed the row? reconcile with the delta)"
13
+ }
14
+ ]