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

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
@@ -308,6 +308,50 @@ function parseCuratorNominations(text) {
308
308
  consolidations: consolidations.filter((item) => valid(item.from) && valid(item.into))
309
309
  };
310
310
  }
311
+ /**
312
+ * The lifecycle-candidate gate, shared by the transition engine and the scope
313
+ * view so the two can never disagree: records failing ANY of these gates are
314
+ * outside the managed scope.
315
+ */
316
+ function lifecycleCandidate(name, record, config, bundled) {
317
+ if (record.pinned) return false;
318
+ if (config.excludeSkillNames?.has(name)) return false;
319
+ if (config.suppressedNames?.has(name)) return false;
320
+ if (config.referencedSkillNames?.has(name)) return false;
321
+ if (!(record.created_by === "agent" || config.manageUnmanaged === true) && !(config.pruneBuiltins === true && bundled)) return false;
322
+ if (PROTECTED_BUILTIN_SKILLS.has(name)) return false;
323
+ if (record.state === "archived") return false;
324
+ return true;
325
+ }
326
+ /**
327
+ * Read-only scope classification, derived from the SAME gate the transition
328
+ * engine uses (`lifecycleCandidate`), so the view always predicts what a
329
+ * curator pass may touch.
330
+ */
331
+ function computeScopeView(usage, config) {
332
+ const managed = [];
333
+ const watched = [];
334
+ const exempted = [];
335
+ const protectedNames = [];
336
+ for (const [name, record] of usage) {
337
+ if (config.excludeSkillNames?.has(name) || config.referencedSkillNames?.has(name)) {
338
+ exempted.push(name);
339
+ continue;
340
+ }
341
+ const bundled = config.bundledNames?.has(name) === true;
342
+ if (record.pinned || bundled || config.suppressedNames?.has(name) === true) protectedNames.push(name);
343
+ if (lifecycleCandidate(name, record, config, bundled)) {
344
+ managed.push(name);
345
+ if (record.state === "stale" || record.quality_warn === true) watched.push(name);
346
+ }
347
+ }
348
+ return {
349
+ managed: managed.sort(),
350
+ watched: watched.sort(),
351
+ exempted: exempted.sort(),
352
+ protected: protectedNames.sort()
353
+ };
354
+ }
311
355
  function daysSince(iso, created, now) {
312
356
  return (now - new Date(iso ?? created).getTime()) / 864e5;
313
357
  }
@@ -319,14 +363,7 @@ function computeLifecycleTransitions(usage, config, now = /* @__PURE__ */ new Da
319
363
  markStale: []
320
364
  };
321
365
  for (const [name, record] of usage) {
322
- if (record.pinned) continue;
323
- if (config.excludeSkillNames?.has(name)) continue;
324
- if (config.suppressedNames?.has(name)) continue;
325
- if (config.referencedSkillNames?.has(name)) continue;
326
- const bundled = config.bundledNames?.has(name) === true;
327
- if (!(record.created_by === "agent" || config.manageUnmanaged === true) && !(config.pruneBuiltins === true && bundled)) continue;
328
- if (PROTECTED_BUILTIN_SKILLS.has(name)) continue;
329
- if (record.state === "archived") continue;
366
+ if (!lifecycleCandidate(name, record, config, config.bundledNames?.has(name) === true)) continue;
330
367
  const age = daysSince(null, record.created_at, now.getTime());
331
368
  if (record.use_count === 0 && age < config.staleAfterDays) continue;
332
369
  const idle = daysSince(latestActivityAt(record), record.created_at, now.getTime());
@@ -1659,8 +1696,15 @@ var SkillLibrary = class {
1659
1696
  return summaries;
1660
1697
  }
1661
1698
  async read(name) {
1699
+ if (this.badName(name) !== null) return null;
1662
1700
  return this.io.readText(join(skillDir(this.root, name), "SKILL.md"));
1663
1701
  }
1702
+ /** Name-format guard shared by every path-building mutator/reader. */
1703
+ badName(name) {
1704
+ const normalized = name.trim();
1705
+ 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}).`;
1706
+ return null;
1707
+ }
1664
1708
  async writeProtection(name, origin = "foreground") {
1665
1709
  const dir = skillDir(this.root, name);
1666
1710
  for (const marker of ["bundled", "hub-installed"]) if (await this.io.exists(markerPath(dir, marker))) return marker;
@@ -1683,11 +1727,19 @@ var SkillLibrary = class {
1683
1727
  }
1684
1728
  /** Whether the skill carries the bundled marker (curator prune-builtins eligibility). */
1685
1729
  async isBundled(name) {
1730
+ if (this.badName(name) !== null) return false;
1686
1731
  const dir = skillDir(this.root, name);
1687
1732
  return await this.io.exists(markerPath(dir, "bundled"));
1688
1733
  }
1734
+ /** Whether the skill carries the pinned marker (the marker is the factual source; usage.pinned mirrors it). */
1735
+ async isPinned(name) {
1736
+ if (this.badName(name) !== null) return false;
1737
+ const dir = skillDir(this.root, name);
1738
+ return await this.io.exists(markerPath(dir, "pinned"));
1739
+ }
1689
1740
  /** Count non-empty support subdirectories (richness input for quality scoring). */
1690
1741
  async countSupportDirs(name) {
1742
+ if (this.badName(name) !== null) return 0;
1691
1743
  const dir = skillDir(this.root, name);
1692
1744
  let entries;
1693
1745
  try {
@@ -1721,6 +1773,48 @@ var SkillLibrary = class {
1721
1773
  async listMutations() {
1722
1774
  return await loadMutations(this.root, this.io);
1723
1775
  }
1776
+ /**
1777
+ * Pin or unpin a skill (`.pinned` marker). Pinned skills are protected from
1778
+ * deletion, from background-review writes, and from the lifecycle — a
1779
+ * protective mutation, so the autonomous pipeline may never call it. The
1780
+ * marker write is the only state change; content is untouched.
1781
+ */
1782
+ async setPinned(name, pinned, origin = "foreground") {
1783
+ const normalized = name.trim();
1784
+ if (!SKILL_NAME_RE.test(normalized) || normalized.length > this.limits.maxNameLength) return {
1785
+ ok: false,
1786
+ message: `Invalid skill name "${normalized}". Use lowercase letters, digits, and hyphens (<= ${this.limits.maxNameLength}).`
1787
+ };
1788
+ if (origin === "background_review") return {
1789
+ ok: false,
1790
+ message: "Only the foreground (user or the main agent) may pin or unpin skills."
1791
+ };
1792
+ const dir = skillDir(this.root, normalized);
1793
+ const marker = markerPath(dir, "pinned");
1794
+ const existing = await this.io.exists(marker);
1795
+ if (pinned && existing) return {
1796
+ ok: true,
1797
+ message: `Skill "${normalized}" is already pinned.`,
1798
+ path: dir
1799
+ };
1800
+ if (!pinned && !existing) return {
1801
+ ok: true,
1802
+ message: `Skill "${normalized}" is not pinned; nothing to do.`,
1803
+ path: dir
1804
+ };
1805
+ if (!await this.io.exists(join(dir, "SKILL.md"))) return {
1806
+ ok: false,
1807
+ message: `Skill "${normalized}" not found.`
1808
+ };
1809
+ if (pinned) await this.io.writeText(marker, "");
1810
+ else await this.io.remove(marker);
1811
+ await this.audit(normalized, pinned ? "pin" : "unpin", null, null, pinned ? "pinned" : "unpinned");
1812
+ return {
1813
+ ok: true,
1814
+ message: pinned ? `Skill "${normalized}" pinned: protected from deletion, background review, and the lifecycle.` : `Skill "${normalized}" unpinned.`,
1815
+ path: dir
1816
+ };
1817
+ }
1724
1818
  async create(name, content, origin) {
1725
1819
  const normalized = name.trim();
1726
1820
  if (!SKILL_NAME_RE.test(normalized) || normalized.length > this.limits.maxNameLength) return {
@@ -1752,6 +1846,11 @@ var SkillLibrary = class {
1752
1846
  };
1753
1847
  }
1754
1848
  async update(name, content, origin = "foreground") {
1849
+ const badName = this.badName(name);
1850
+ if (badName) return {
1851
+ ok: false,
1852
+ message: badName
1853
+ };
1755
1854
  const dir = skillDir(this.root, name);
1756
1855
  const md = await this.io.readText(join(dir, "SKILL.md"));
1757
1856
  if (!md) return {
@@ -1782,6 +1881,11 @@ var SkillLibrary = class {
1782
1881
  };
1783
1882
  }
1784
1883
  async patch(name, oldString, newString, filePath = "", replaceAll = false, origin = "foreground") {
1884
+ const badName = this.badName(name);
1885
+ if (badName) return {
1886
+ ok: false,
1887
+ message: badName
1888
+ };
1785
1889
  const dir = skillDir(this.root, name);
1786
1890
  const skillMd = join(dir, "SKILL.md");
1787
1891
  if (!await this.io.exists(skillMd)) return {
@@ -1843,6 +1947,11 @@ var SkillLibrary = class {
1843
1947
  };
1844
1948
  }
1845
1949
  async archive(name, options = {}) {
1950
+ const badName = this.badName(name);
1951
+ if (badName) return {
1952
+ ok: false,
1953
+ message: badName
1954
+ };
1846
1955
  const dir = skillDir(this.root, name);
1847
1956
  const md = await this.io.readText(join(dir, "SKILL.md"));
1848
1957
  if (!md) return {
@@ -2001,6 +2110,11 @@ var SkillLibrary = class {
2001
2110
  };
2002
2111
  }
2003
2112
  async writeSupportFile(name, filePath, content, origin = "foreground") {
2113
+ const badName = this.badName(name);
2114
+ if (badName) return {
2115
+ ok: false,
2116
+ message: badName
2117
+ };
2004
2118
  const dir = skillDir(this.root, name);
2005
2119
  if (!await this.io.exists(join(dir, "SKILL.md"))) return {
2006
2120
  ok: false,
@@ -2036,6 +2150,11 @@ var SkillLibrary = class {
2036
2150
  };
2037
2151
  }
2038
2152
  async removeSupportFile(name, filePath, origin = "foreground") {
2153
+ const badName = this.badName(name);
2154
+ if (badName) return {
2155
+ ok: false,
2156
+ message: badName
2157
+ };
2039
2158
  const dir = skillDir(this.root, name);
2040
2159
  if (!await this.io.exists(join(dir, "SKILL.md"))) return {
2041
2160
  ok: false,
@@ -2186,4 +2305,4 @@ var JsonState = class JsonState {
2186
2305
  }
2187
2306
  };
2188
2307
  //#endregion
2189
- export { COMBINED_REVIEW_PROMPT, COMPLETION_SKILL_REVIEW_PROMPT, CURATOR_DRY_RUN_BANNER, CURATOR_PROMPT, DEFAULT_ARCHIVE_AFTER_DAYS, DEFAULT_CURATOR_INTERVAL_HOURS, 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, DSH_AUTHORING_STANDARDS, ENTRY_DELIMITER, JsonState, LOW_QUALITY_THRESHOLD, MAX_DESCRIPTION_LENGTH, MAX_SKILL_CONTENT_CHARS, MAX_SKILL_FILE_BYTES, MAX_SKILL_NAME_LENGTH, MEMORY_REVIEW_PROMPT, MUTATIONS_FILE_VERSION, MemoryStore, PROMPT_BUNDLE, PROMPT_BUNDLE_ID, PROMPT_BUNDLE_VERSION, PROTECTED_BUILTIN_SKILLS, QUALITY_WEIGHTS, SKILL_NAME_RE, SKILL_REVIEW_PROMPT, SUPPORT_DIRS, SUPPRESSED_FILE_VERSION, SkillLibrary, advanceReview, buildCuratorRunReport, buildLearnPrompt, bumpPatch, bumpUse, bumpView, computeDedupGroups, computeLifecycleTransitions, computeQualityScores, contentHash, emptyRecord, evaluateThreat, evolutionHome, evolutionIoAdapter, foldTurn, getRecord, latestActivityAt, loadMutations, loadSuppressedNames, loadUsage, markAgentCreated, memoryRoot, mutationsFile, nodeEvolutionIo, observeEvent, parseCuratorNominations, parseFrontmatter, recordMutation, reviewPrompt, saveSuppressedNames, saveUsage, scanContentThreats, scanMemoryThreats, scanThreats, skillsRoot, suppressedFile, usageFile, validateFrontmatter, verifyPromptBundle };
2308
+ export { COMBINED_REVIEW_PROMPT, COMPLETION_SKILL_REVIEW_PROMPT, CURATOR_DRY_RUN_BANNER, CURATOR_PROMPT, DEFAULT_ARCHIVE_AFTER_DAYS, DEFAULT_CURATOR_INTERVAL_HOURS, 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, DSH_AUTHORING_STANDARDS, ENTRY_DELIMITER, JsonState, LOW_QUALITY_THRESHOLD, MAX_DESCRIPTION_LENGTH, MAX_SKILL_CONTENT_CHARS, MAX_SKILL_FILE_BYTES, MAX_SKILL_NAME_LENGTH, MEMORY_REVIEW_PROMPT, MUTATIONS_FILE_VERSION, MemoryStore, PROMPT_BUNDLE, PROMPT_BUNDLE_ID, PROMPT_BUNDLE_VERSION, PROTECTED_BUILTIN_SKILLS, QUALITY_WEIGHTS, SKILL_NAME_RE, SKILL_REVIEW_PROMPT, SUPPORT_DIRS, SUPPRESSED_FILE_VERSION, SkillLibrary, advanceReview, buildCuratorRunReport, buildLearnPrompt, bumpPatch, bumpUse, bumpView, computeDedupGroups, computeLifecycleTransitions, computeQualityScores, computeScopeView, contentHash, emptyRecord, evaluateThreat, evolutionHome, evolutionIoAdapter, foldTurn, getRecord, latestActivityAt, lifecycleCandidate, loadMutations, loadSuppressedNames, loadUsage, markAgentCreated, memoryRoot, mutationsFile, nodeEvolutionIo, observeEvent, parseCuratorNominations, parseFrontmatter, recordMutation, reviewPrompt, saveSuppressedNames, saveUsage, scanContentThreats, scanMemoryThreats, scanThreats, skillsRoot, suppressedFile, usageFile, validateFrontmatter, verifyPromptBundle };
@@ -2,7 +2,7 @@
2
2
  * Deterministic skill curator: active → stale → archived transitions.
3
3
  * Pure function; file moves are performed by SkillLibrary.
4
4
  */
5
- import type { UsageMap } from './usage.ts';
5
+ import type { UsageMap, UsageRecord } from './usage.ts';
6
6
  export { PROTECTED_BUILTIN_SKILLS } from './constants.ts';
7
7
  export interface CuratorConfig {
8
8
  staleAfterDays: number;
@@ -87,5 +87,27 @@ export interface CuratorNominations {
87
87
  * is re-validated against the tree before any file move happens downstream.
88
88
  */
89
89
  export declare function parseCuratorNominations(text: string): CuratorNominations;
90
+ /**
91
+ * The lifecycle-candidate gate, shared by the transition engine and the scope
92
+ * view so the two can never disagree: records failing ANY of these gates are
93
+ * outside the managed scope.
94
+ */
95
+ export declare function lifecycleCandidate(name: string, record: UsageRecord, config: CuratorConfig, bundled: boolean): boolean;
96
+ export interface ScopeView {
97
+ /** Skills inside the lifecycle scope right now (candidate gate + active state). */
98
+ managed: string[];
99
+ /** Managed skills already flagged stale or quality-warned — the ones to watch. */
100
+ watched: string[];
101
+ /** Explicitly exempted by excludeSkillNames / referencedSkillNames. */
102
+ exempted: string[];
103
+ /** Carrying a protection marker (pinned / bundled / hub-installed). */
104
+ protected: string[];
105
+ }
106
+ /**
107
+ * Read-only scope classification, derived from the SAME gate the transition
108
+ * engine uses (`lifecycleCandidate`), so the view always predicts what a
109
+ * curator pass may touch.
110
+ */
111
+ export declare function computeScopeView(usage: UsageMap, config: CuratorConfig): ScopeView;
90
112
  export declare function computeLifecycleTransitions(usage: UsageMap, config: CuratorConfig, now?: Date): CuratorResult;
91
113
  //# sourceMappingURL=curator.d.ts.map
@@ -57,6 +57,8 @@ export declare class SkillLibrary {
57
57
  constructor(root?: string, io?: EvolutionIoLike, limits?: SkillLimits);
58
58
  list(): Promise<SkillSummary[]>;
59
59
  read(name: string): Promise<string | null>;
60
+ /** Name-format guard shared by every path-building mutator/reader. */
61
+ private badName;
60
62
  writeProtection(name: string, origin?: WriteOrigin): Promise<string | null>;
61
63
  deleteProtection(name: string, options?: {
62
64
  allowBundled?: boolean;
@@ -64,12 +66,21 @@ export declare class SkillLibrary {
64
66
  isManaged(name: string): Promise<boolean>;
65
67
  /** Whether the skill carries the bundled marker (curator prune-builtins eligibility). */
66
68
  isBundled(name: string): Promise<boolean>;
69
+ /** Whether the skill carries the pinned marker (the marker is the factual source; usage.pinned mirrors it). */
70
+ isPinned(name: string): Promise<boolean>;
67
71
  /** Count non-empty support subdirectories (richness input for quality scoring). */
68
72
  countSupportDirs(name: string): Promise<number>;
69
73
  /** Best-effort audit trail entry; never blocks the mutation. */
70
74
  private audit;
71
75
  /** Recent mutation audit records (read-only inspection surface). */
72
76
  listMutations(): Promise<MutationRecord[]>;
77
+ /**
78
+ * Pin or unpin a skill (`.pinned` marker). Pinned skills are protected from
79
+ * deletion, from background-review writes, and from the lifecycle — a
80
+ * protective mutation, so the autonomous pipeline may never call it. The
81
+ * marker write is the only state change; content is untouched.
82
+ */
83
+ setPinned(name: string, pinned: boolean, origin?: WriteOrigin): Promise<SkillActionResult>;
73
84
  create(name: string, content: string, origin: 'foreground' | 'background_review'): Promise<SkillActionResult>;
74
85
  update(name: string, content: string, origin?: WriteOrigin): Promise<SkillActionResult>;
75
86
  patch(name: string, oldString: string, newString: string, filePath?: string, replaceAll?: boolean, origin?: WriteOrigin): Promise<SkillActionResult>;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@lmzhen/dsh-evolution-core",
3
3
  "description": "Shared stores, prompts, signals and lifecycle logic for the dsh-evolution plugin family (community build)",
4
- "version": "0.1.0-rc.26",
4
+ "version": "0.1.0-rc.28",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },