@lmzhen/dsh-evolution-core 0.1.0-rc.25 → 0.1.0-rc.27

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());
@@ -533,6 +570,7 @@ Frontmatter:
533
570
  - author: always the literal value "Hermes". NEVER fill it from the environment, git config, or any identity you can probe.
534
571
  - platforms: declare [macos], [linux], and/or [windows] only when the skill is genuinely OS-bound; omit for portable skills.
535
572
  - metadata.hermes.tags: a few Capitalized, Relevant, Tags.
573
+ - metadata.hermes.related_skills: [a, b] — name sibling skills this one builds on or is referenced by (optional; feeds the quality references factor).
536
574
 
537
575
  Body section order (omit only when empty):
538
576
  1. "# <Human Title>" then a 2-3 sentence intro: what it does, what it does NOT do, key dependency stance.
@@ -1685,6 +1723,11 @@ var SkillLibrary = class {
1685
1723
  const dir = skillDir(this.root, name);
1686
1724
  return await this.io.exists(markerPath(dir, "bundled"));
1687
1725
  }
1726
+ /** Whether the skill carries the pinned marker (the marker is the factual source; usage.pinned mirrors it). */
1727
+ async isPinned(name) {
1728
+ const dir = skillDir(this.root, name);
1729
+ return await this.io.exists(markerPath(dir, "pinned"));
1730
+ }
1688
1731
  /** Count non-empty support subdirectories (richness input for quality scoring). */
1689
1732
  async countSupportDirs(name) {
1690
1733
  const dir = skillDir(this.root, name);
@@ -1720,6 +1763,48 @@ var SkillLibrary = class {
1720
1763
  async listMutations() {
1721
1764
  return await loadMutations(this.root, this.io);
1722
1765
  }
1766
+ /**
1767
+ * Pin or unpin a skill (`.pinned` marker). Pinned skills are protected from
1768
+ * deletion, from background-review writes, and from the lifecycle — a
1769
+ * protective mutation, so the autonomous pipeline may never call it. The
1770
+ * marker write is the only state change; content is untouched.
1771
+ */
1772
+ async setPinned(name, pinned, origin = "foreground") {
1773
+ const normalized = name.trim();
1774
+ if (!SKILL_NAME_RE.test(normalized) || normalized.length > this.limits.maxNameLength) return {
1775
+ ok: false,
1776
+ message: `Invalid skill name "${normalized}". Use lowercase letters, digits, and hyphens (<= ${this.limits.maxNameLength}).`
1777
+ };
1778
+ if (origin === "background_review") return {
1779
+ ok: false,
1780
+ message: "Only the foreground (user or the main agent) may pin or unpin skills."
1781
+ };
1782
+ const dir = skillDir(this.root, normalized);
1783
+ const marker = markerPath(dir, "pinned");
1784
+ const existing = await this.io.exists(marker);
1785
+ if (pinned && existing) return {
1786
+ ok: true,
1787
+ message: `Skill "${normalized}" is already pinned.`,
1788
+ path: dir
1789
+ };
1790
+ if (!pinned && !existing) return {
1791
+ ok: true,
1792
+ message: `Skill "${normalized}" is not pinned; nothing to do.`,
1793
+ path: dir
1794
+ };
1795
+ if (!await this.io.exists(join(dir, "SKILL.md"))) return {
1796
+ ok: false,
1797
+ message: `Skill "${normalized}" not found.`
1798
+ };
1799
+ if (pinned) await this.io.writeText(marker, "");
1800
+ else await this.io.remove(marker);
1801
+ await this.audit(normalized, pinned ? "pin" : "unpin", null, null, pinned ? "pinned" : "unpinned");
1802
+ return {
1803
+ ok: true,
1804
+ message: pinned ? `Skill "${normalized}" pinned: protected from deletion, background review, and the lifecycle.` : `Skill "${normalized}" unpinned.`,
1805
+ path: dir
1806
+ };
1807
+ }
1723
1808
  async create(name, content, origin) {
1724
1809
  const normalized = name.trim();
1725
1810
  if (!SKILL_NAME_RE.test(normalized) || normalized.length > this.limits.maxNameLength) return {
@@ -2185,4 +2270,4 @@ var JsonState = class JsonState {
2185
2270
  }
2186
2271
  };
2187
2272
  //#endregion
2188
- 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 };
2273
+ 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
@@ -20,5 +20,5 @@ export interface PromptBundle {
20
20
  }
21
21
  export declare const PROMPT_BUNDLE: PromptBundle;
22
22
  export declare function verifyPromptBundle(bundle?: PromptBundle): boolean;
23
- export declare const DSH_AUTHORING_STANDARDS = "Follow the Hermes skill-authoring standards, translated to DSH tools.\n\nFrontmatter:\n- name: lowercase-hyphenated, <=64 chars, no spaces.\n- description: ONE sentence, <=60 characters, ends with a period. State the capability, not the implementation. No marketing words. Do NOT repeat the skill name. Count the characters before saving.\n- version: 0.1.0\n- author: always the literal value \"Hermes\". NEVER fill it from the environment, git config, or any identity you can probe.\n- platforms: declare [macos], [linux], and/or [windows] only when the skill is genuinely OS-bound; omit for portable skills.\n- metadata.hermes.tags: a few Capitalized, Relevant, Tags.\n\nBody section order (omit only when empty):\n1. \"# <Human Title>\" then a 2-3 sentence intro: what it does, what it does NOT do, key dependency stance.\n2. \"## When to Use\" \u2014 concrete trigger phrases.\n3. \"## Prerequisites\" \u2014 exact env vars, install steps, credentials.\n4. \"## How to Run\" \u2014 canonical invocation framed through DSH tools.\n5. \"## Quick Reference\" \u2014 flat command/endpoint list.\n6. \"## Procedure\" \u2014 numbered steps with copy-paste-exact commands.\n7. \"## Pitfalls\" \u2014 known limits and rate limits.\n8. \"## Verification\" \u2014 one check proving the skill worked.\n\nDSH-tool framing:\n- Reference DSH tools by name in backticks: `bash`, `str_replace_editor`, `write`, `skill`, `skill_manage`, `memory`.\n- Do not name wrapped shell utilities when a DSH tool already covers them.\n- Larger scripts belong under `scripts/` (written with `skill_manage write_file`) and are referenced from SKILL.md by relative path.\n\nQuality bar:\n- Prefer verbatim flags, paths, and APIs from the source. Never invent them.\n- Keep it tight: ~100 lines simple, ~200 complex.\n- No router/index/hub skills that only point at other skills.\n- References go in `references/`, templates in `templates/`.";
23
+ export declare const DSH_AUTHORING_STANDARDS = "Follow the Hermes skill-authoring standards, translated to DSH tools.\n\nFrontmatter:\n- name: lowercase-hyphenated, <=64 chars, no spaces.\n- description: ONE sentence, <=60 characters, ends with a period. State the capability, not the implementation. No marketing words. Do NOT repeat the skill name. Count the characters before saving.\n- version: 0.1.0\n- author: always the literal value \"Hermes\". NEVER fill it from the environment, git config, or any identity you can probe.\n- platforms: declare [macos], [linux], and/or [windows] only when the skill is genuinely OS-bound; omit for portable skills.\n- metadata.hermes.tags: a few Capitalized, Relevant, Tags.\n- metadata.hermes.related_skills: [a, b] \u2014 name sibling skills this one builds on or is referenced by (optional; feeds the quality references factor).\n\nBody section order (omit only when empty):\n1. \"# <Human Title>\" then a 2-3 sentence intro: what it does, what it does NOT do, key dependency stance.\n2. \"## When to Use\" \u2014 concrete trigger phrases.\n3. \"## Prerequisites\" \u2014 exact env vars, install steps, credentials.\n4. \"## How to Run\" \u2014 canonical invocation framed through DSH tools.\n5. \"## Quick Reference\" \u2014 flat command/endpoint list.\n6. \"## Procedure\" \u2014 numbered steps with copy-paste-exact commands.\n7. \"## Pitfalls\" \u2014 known limits and rate limits.\n8. \"## Verification\" \u2014 one check proving the skill worked.\n\nDSH-tool framing:\n- Reference DSH tools by name in backticks: `bash`, `str_replace_editor`, `write`, `skill`, `skill_manage`, `memory`.\n- Do not name wrapped shell utilities when a DSH tool already covers them.\n- Larger scripts belong under `scripts/` (written with `skill_manage write_file`) and are referenced from SKILL.md by relative path.\n\nQuality bar:\n- Prefer verbatim flags, paths, and APIs from the source. Never invent them.\n- Keep it tight: ~100 lines simple, ~200 complex.\n- No router/index/hub skills that only point at other skills.\n- References go in `references/`, templates in `templates/`.";
24
24
  //# sourceMappingURL=prompts.d.ts.map
@@ -64,12 +64,21 @@ export declare class SkillLibrary {
64
64
  isManaged(name: string): Promise<boolean>;
65
65
  /** Whether the skill carries the bundled marker (curator prune-builtins eligibility). */
66
66
  isBundled(name: string): Promise<boolean>;
67
+ /** Whether the skill carries the pinned marker (the marker is the factual source; usage.pinned mirrors it). */
68
+ isPinned(name: string): Promise<boolean>;
67
69
  /** Count non-empty support subdirectories (richness input for quality scoring). */
68
70
  countSupportDirs(name: string): Promise<number>;
69
71
  /** Best-effort audit trail entry; never blocks the mutation. */
70
72
  private audit;
71
73
  /** Recent mutation audit records (read-only inspection surface). */
72
74
  listMutations(): Promise<MutationRecord[]>;
75
+ /**
76
+ * Pin or unpin a skill (`.pinned` marker). Pinned skills are protected from
77
+ * deletion, from background-review writes, and from the lifecycle — a
78
+ * protective mutation, so the autonomous pipeline may never call it. The
79
+ * marker write is the only state change; content is untouched.
80
+ */
81
+ setPinned(name: string, pinned: boolean, origin?: WriteOrigin): Promise<SkillActionResult>;
73
82
  create(name: string, content: string, origin: 'foreground' | 'background_review'): Promise<SkillActionResult>;
74
83
  update(name: string, content: string, origin?: WriteOrigin): Promise<SkillActionResult>;
75
84
  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.25",
4
+ "version": "0.1.0-rc.27",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },