@lmzhen/dsh-evolution-core 0.1.0 → 0.2.0-rc.2

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
@@ -374,6 +374,17 @@ function latestActivityAt(record) {
374
374
  return values.sort().reverse()[0] ?? null;
375
375
  }
376
376
  /**
377
+ * Whether the library has ANY observed read evidence (C observation window):
378
+ * reads were invisible to the usage sidecar before A2, so `view_count` zero
379
+ * means "never read" ONLY after the first observed read exists anywhere in
380
+ * the map. Before that, churn-based signals (write-ghost) are untrustworthy
381
+ * and callers must suppress them. Pure and derived — never persisted.
382
+ */
383
+ function usageObserved(usage) {
384
+ for (const record of usage.values()) if (record.view_count > 0) return true;
385
+ return false;
386
+ }
387
+ /**
377
388
  * Curator suppression sidecar: built-in skills the curator has archived stay
378
389
  * suppressed across re-seeds, so the lifecycle never fights a re-created
379
390
  * bundled skill. Best-effort load/save, mirroring the usage sidecar posture.
@@ -598,11 +609,18 @@ function parseCuratorNominations(text) {
598
609
  const consolidations = [];
599
610
  let section = null;
600
611
  let currentFrom = "";
612
+ let currentMode;
601
613
  for (const line of text.split("\n")) {
602
614
  const consolidated = /^\s*-\s*from:\s*([a-z0-9][a-z0-9-]*)\s*$/.exec(line);
603
615
  if (consolidated) {
604
616
  section = "consolidations";
605
617
  currentFrom = consolidated[1] ?? "";
618
+ currentMode = void 0;
619
+ continue;
620
+ }
621
+ const mode = /^\s*mode:\s*(append|reference)\s*$/.exec(line);
622
+ if (mode) {
623
+ if (currentFrom !== "") currentMode = mode[1] === "reference" ? "reference" : "append";
606
624
  continue;
607
625
  }
608
626
  const into = /^\s*into:\s*([a-z0-9][a-z0-9-]*)\s*$/.exec(line);
@@ -610,9 +628,11 @@ function parseCuratorNominations(text) {
610
628
  const intoName = into[1] ?? "";
611
629
  if (section === "consolidations" && currentFrom !== "" && currentFrom !== intoName) consolidations.push({
612
630
  from: currentFrom,
613
- into: intoName
631
+ into: intoName,
632
+ ...currentMode === void 0 ? {} : { mode: currentMode }
614
633
  });
615
634
  currentFrom = "";
635
+ currentMode = void 0;
616
636
  continue;
617
637
  }
618
638
  const pruned = /^\s*-\s*name:\s*([a-z0-9][a-z0-9-]*)\s*$/.exec(line);
@@ -748,6 +768,15 @@ function computeLifecycleTransitions(usage, config, now = /* @__PURE__ */ new Da
748
768
  * learn on target X" is answerable. The aggregate `feedback.json` is a
749
769
  * rebuildable boot cache, never the truth.
750
770
  *
771
+ * Usage events (C semantics, rc.73+): `type:'usage'` records are the
772
+ * OBSERVATION WINDOW ANCHOR — written once, when the library's first observed
773
+ * read (`view_count` 0 -> 1) happens. Before that anchor the usage sidecar
774
+ * has no read evidence (reads were invisible pre-A2), so churn-based health
775
+ * judgments are NOT trustworthy; the curator suppresses them (its
776
+ * `usageObserved()` gate) until the anchor exists. `counts` on the event is a
777
+ * cumulative library-wide snapshot (skills/views/use/patches) at that moment,
778
+ * and `window.opened` pins the window start for the timeline.
779
+ *
751
780
  * Rotation (rc.71, 007 design): when the active log reaches
752
781
  * `EVENT_LOG_ROTATE_AT` the older half is split into an archive
753
782
  * (`events-<lastArchivedSeq>.json`); the boot timeline merges active +
@@ -969,8 +998,8 @@ async function readEvolutionTimeline(io, path) {
969
998
  * changes semantically: the bundle digest is the fail-closed signal for
970
999
  * review workers, so a stale id across deployments must be distinguishable.
971
1000
  */
972
- const PROMPT_BUNDLE_ID = "dsh-evolution@7";
973
- const PROMPT_BUNDLE_VERSION = 7;
1001
+ const PROMPT_BUNDLE_ID = "dsh-evolution@9";
1002
+ const PROMPT_BUNDLE_VERSION = 9;
974
1003
  const MEMORY_REVIEW_PROMPT = `[Auto-review — Memory]
975
1004
  Review the conversation above and consider saving to memory if appropriate.
976
1005
 
@@ -1001,7 +1030,8 @@ Preference order — prefer the earliest action that fits, but do pick one when
1001
1030
  • templates/<name>.<ext> — starter files meant to be copied and modified (boilerplate configs, scaffolding, a known-good example the agent can reproduce with modifications).
1002
1031
  • scripts/<name>.<ext> — statically re-runnable actions the skill can invoke directly (verification scripts, fixture generators, deterministic probes, anything the agent should run rather than hand-type each time).
1003
1032
  Add support files via skill_manage action=write_file with file_path starting 'references/', 'templates/', or 'scripts/'. The umbrella's SKILL.md should gain a one-line pointer to any new support file so future agents know it exists.
1004
- 4. CREATE A NEW CLASS-LEVEL UMBRELLA SKILL when no existing skill covers the class. The name MUST be at the class level. The name MUST NOT be a specific PR number, error string, feature codename, library-alone name, or 'fix-X / debug-Y / audit-Z-today' session artifact. If the proposed name only makes sense for today's task, it's wrong — fall back to (1), (2), or (3).
1033
+ 4. RESTRUCTURE a loaded skill whose body grew log-like — rc/sha/date-dense sections, session-detail spirals, or a fat body with no support files. Use skill_manage action=restructure with restructure: [{"heading": "<the exact ## heading text>", "to_file": "references/<topic>.md"}] — the ENTIRE ## section (from that heading to the next heading) moves into the support file and its position becomes a pointer line. The skill's name and directory never change. Only propose headings that exist verbatim in the body; never invent one, and never restructure a healthy small skill.
1034
+ 5. CREATE A NEW CLASS-LEVEL UMBRELLA SKILL when no existing skill covers the class. The name MUST be at the class level. The name MUST NOT be a specific PR number, error string, feature codename, library-alone name, or 'fix-X / debug-Y / audit-Z-today' session artifact. If the proposed name only makes sense for today's task, it's wrong — fall back to (1), (2), or (3).
1005
1035
 
1006
1036
  User-preference embedding (important): when the user expressed a style/format/workflow preference, the update belongs in the SKILL.md body, not just in memory. Memory captures 'who the user is and what the current situation and state of your operations are'; skills capture 'how to do this class of task for this user'. When they complain about how you handled a task, the skill that governs that task needs to carry the lesson.
1007
1037
 
@@ -1046,7 +1076,8 @@ Preference order for skills — pick the earliest that fits:
1046
1076
  1. UPDATE A CURRENTLY-LOADED SKILL. Check what skills were loaded or read in the conversation. If one of them covers the learning, PATCH it first. It was in play; it's the right place.
1047
1077
  2. UPDATE AN EXISTING UMBRELLA. Patch it.
1048
1078
  3. ADD A SUPPORT FILE under an existing umbrella via skill_manage action=write_file. Three kinds: references/<topic>.md for session-specific detail OR condensed knowledge banks (quoted research, API docs excerpts, domain notes) written concise and task-focused; templates/<name>.<ext> for starter files meant to be copied and modified; scripts/<name>.<ext> for statically re-runnable actions (verification, fixture generators, probes). Add a one-line pointer in SKILL.md so future agents find them.
1049
- 4. CREATE A NEW CLASS-LEVEL UMBRELLA when nothing exists. Name at the class level — NOT a PR number, error string, codename, library-alone name, or 'fix-X / debug-Y' session artifact. If the name only fits today's task, fall back to (1), (2), or (3).
1079
+ 4. RESTRUCTURE a loaded skill whose body grew log-like (rc/sha/date-dense sections, session-detail spirals, fat body with no support files) via skill_manage action=restructure with restructure: [{"heading": "<the exact ## heading text>", "to_file": "references/<topic>.md"}] — the ENTIRE ## section moves into the support file and its position becomes a pointer line; the skill's name and directory never change. Only propose headings that exist verbatim in the body.
1080
+ 5. CREATE A NEW CLASS-LEVEL UMBRELLA when nothing exists. Name at the class level — NOT a PR number, error string, codename, library-alone name, or 'fix-X / debug-Y' session artifact. If the name only fits today's task, fall back to (1), (2), or (3).
1050
1081
 
1051
1082
  Two-tier deposition discipline (DSH addition): classify before writing — PATTERN (symptom → mechanism → fix → verification) goes in the SKILL.md body; LOG (commit SHAs, npm/profile states, this release's change list, this session's narrative) goes in a references/ file. Body density IS reuse rate; a pattern fits in 2-8 physical lines.
1052
1083
 
@@ -1112,6 +1143,7 @@ When done, write a human summary THEN the structured machine-readable block. The
1112
1143
  \`\`\`yaml
1113
1144
  consolidations:
1114
1145
  - from: <old-skill-name>
1146
+ mode: reference # optional — ONLY for a 'demote': source is narrow-but-valuable session detail, write it as references/<source>.md under the umbrella instead of appending to the body. Default is append. Place this line BEFORE into:. NEVER use reference when the source body links its own references/ templates/ scripts/ files.
1115
1147
  into: <umbrella-skill-name>
1116
1148
  reason: <one short sentence — why merged, not just 'similar'>
1117
1149
  prunings:
@@ -1166,12 +1198,12 @@ function sha256(text) {
1166
1198
  function createPromptBundle(prompts) {
1167
1199
  const canonical = JSON.stringify({
1168
1200
  id: PROMPT_BUNDLE_ID,
1169
- version: 7,
1201
+ version: 9,
1170
1202
  prompts: Object.fromEntries(Object.entries(prompts).sort())
1171
1203
  });
1172
1204
  return Object.freeze({
1173
1205
  id: PROMPT_BUNDLE_ID,
1174
- version: 7,
1206
+ version: 9,
1175
1207
  prompts: Object.freeze({ ...prompts }),
1176
1208
  sha256: sha256(canonical)
1177
1209
  });
@@ -1187,10 +1219,10 @@ const PROMPT_BUNDLE = createPromptBundle({
1187
1219
  skillsGuidance: SKILLS_GUIDANCE
1188
1220
  });
1189
1221
  function verifyPromptBundle(bundle = PROMPT_BUNDLE) {
1190
- if (bundle.id !== "dsh-evolution@7" || bundle.version !== 7) return false;
1222
+ if (bundle.id !== "dsh-evolution@9" || bundle.version !== 9) return false;
1191
1223
  const canonical = JSON.stringify({
1192
1224
  id: PROMPT_BUNDLE_ID,
1193
- version: 7,
1225
+ version: 9,
1194
1226
  prompts: Object.fromEntries(Object.entries(bundle.prompts).sort())
1195
1227
  });
1196
1228
  return bundle.sha256 === sha256(canonical);
@@ -2167,6 +2199,61 @@ function computePrefixClusters(names) {
2167
2199
  })).sort((a, b) => b.members.length - a.members.length || a.key.localeCompare(b.key));
2168
2200
  }
2169
2201
  //#endregion
2202
+ //#region lib/types/skill-health.js
2203
+ /**
2204
+ * Skill structure-health domain (rc.73 A1, 008 design): a SECOND assessment
2205
+ * dimension beside the six-factor usage quality — document hygiene, consumed
2206
+ * by the curator health view and the `/evolution skills health` command.
2207
+ *
2208
+ * PURE and DERIVED: nothing is persisted; every assessment is computed from
2209
+ * file facts at read time. The judgment split follows the original's
2210
+ * boundary/治理 layering — deterministic signals here, refinement proposals
2211
+ * stay in the review/curator judgment layer. Never become a 7th factor of
2212
+ * `computeQualityScores` (different dimension, different consumers).
2213
+ */
2214
+ const DEFAULT_HEALTH_THRESHOLDS = {
2215
+ softBodyChars: 4e4,
2216
+ stampDensityPerKb: 2,
2217
+ churnMinPatches: 20
2218
+ };
2219
+ const HEALTH_STAMP_RE = /\brc\.\d+\b|\b[0-9a-f]{7,40}\b|\b\d{4}-\d{2}-\d{2}(?:T[0-9:.]+Z)?\b/g;
2220
+ /**
2221
+ * Bodies below this size skip stamp-density assessment: a few dates or shas
2222
+ * in a short body are ordinary documentation, not log-like content. With the
2223
+ * 1KB density floor a 3-date sentence in a small skill measured 3.0/KB and
2224
+ * warned on a perfectly healthy body (audit 2026-08-31 X1).
2225
+ */
2226
+ const MIN_STAMP_BODY_CHARS = 2e3;
2227
+ function assessStructureHealth(snapshot, thresholds = DEFAULT_HEALTH_THRESHOLDS) {
2228
+ const reasons = [];
2229
+ const dims = {
2230
+ bodyChars: snapshot.bodyChars,
2231
+ stampDensityPerKb: null,
2232
+ supportGroups: snapshot.supportGroups,
2233
+ churnPatches: null,
2234
+ churnReads: null
2235
+ };
2236
+ const needs = snapshot.bodyChars >= thresholds.softBodyChars * 2;
2237
+ if (needs) reasons.push(`body ${snapshot.bodyChars} chars is >= 2x the soft limit (${thresholds.softBodyChars}) — consider splitting or offloading`);
2238
+ else if (snapshot.bodyChars >= thresholds.softBodyChars) reasons.push(`body ${snapshot.bodyChars} chars above the soft limit (${thresholds.softBodyChars})`);
2239
+ if (snapshot.bodyText && snapshot.bodyChars >= MIN_STAMP_BODY_CHARS) {
2240
+ const kb = Math.max(1, snapshot.bodyChars / 1024);
2241
+ dims.stampDensityPerKb = (snapshot.bodyText.match(HEALTH_STAMP_RE) ?? []).length / kb;
2242
+ if (dims.stampDensityPerKb >= thresholds.stampDensityPerKb) reasons.push(`stamp density ${dims.stampDensityPerKb.toFixed(1)}/KB (rc/sha/date lines — log-like content in the body)`);
2243
+ }
2244
+ if (snapshot.supportGroups === 0 && snapshot.bodyChars >= thresholds.softBodyChars / 2) reasons.push(`large body (${snapshot.bodyChars} chars) with NO support files — session detail may belong in references/`);
2245
+ if (snapshot.patchCount !== void 0 && snapshot.readCount !== void 0) {
2246
+ dims.churnPatches = snapshot.patchCount;
2247
+ dims.churnReads = snapshot.readCount;
2248
+ if (snapshot.patchCount >= thresholds.churnMinPatches && snapshot.readCount === 0) reasons.push(`patched ${snapshot.patchCount} times but never read (write-ghost — content may be dead)`);
2249
+ }
2250
+ return {
2251
+ verdict: needs ? "needs-restructure" : reasons.length > 0 ? "warn" : "healthy",
2252
+ dims,
2253
+ reasons
2254
+ };
2255
+ }
2256
+ //#endregion
2170
2257
  //#region lib/types/signals.js
2171
2258
  /**
2172
2259
  * Deterministic review signal gate.
@@ -2259,6 +2346,10 @@ const DEFAULT_SKILL_LIMITS = {
2259
2346
  maxSkillContentChars: MAX_SKILL_CONTENT_CHARS,
2260
2347
  maxSkillFileBytes: MAX_SKILL_FILE_BYTES
2261
2348
  };
2349
+ /** Upper bound of moves per restructure proposal (validator and core agree). */
2350
+ const MAX_RESTRUCTURE_MOVES = 5;
2351
+ /** Restructure targets are plain markdown files under references/ — no subdirectories, no other support kind. */
2352
+ const RESTRUCTURE_TARGET_RE = /^references\/[a-z0-9][a-z0-9._-]*\.md$/;
2262
2353
  /** Extra file name carried inside a snapshot's `extras/` directory. */
2263
2354
  const SNAPSHOT_EXTRA_NAME_RE = /^[a-z0-9][a-z0-9._-]*$/;
2264
2355
  function skillsRoot(env = process.env) {
@@ -2350,7 +2441,7 @@ function validateFrontmatter(content, expectedName, limits = DEFAULT_SKILL_LIMIT
2350
2441
  if (expectedName && parsed.frontmatter.name !== expectedName) return `Frontmatter name "${parsed.frontmatter.name}" does not match target skill "${expectedName}".`;
2351
2442
  if (!parsed.frontmatter.description) return "Frontmatter must include a description field.";
2352
2443
  if (parsed.frontmatter.description.length > limits.maxDescriptionLength) return `Description exceeds ${limits.maxDescriptionLength} characters.`;
2353
- if (content.length > limits.maxSkillContentChars) return `SKILL.md content exceeds ${limits.maxSkillContentChars} characters.`;
2444
+ if (content.length > limits.maxSkillContentChars) return `SKILL.md content exceeds ${limits.maxSkillContentChars} characters. Consider splitting into a smaller SKILL.md with supporting files.`;
2354
2445
  return null;
2355
2446
  }
2356
2447
  /** Hermes authoring quality bar for descriptions (the 60-char Rule). The
@@ -2480,6 +2571,62 @@ function fuzzyPatch(content, oldString, newString, replaceAll = false) {
2480
2571
  }
2481
2572
  return null;
2482
2573
  }
2574
+ /**
2575
+ * Support-directory references in a markdown body (009 kernel): `references/…`,
2576
+ * `templates/…`, `scripts/…`, `assets/…` relative links. Pure — who checks
2577
+ * them and what the verdict is belongs to the caller's context (a moved/
2578
+ * appended body whose references travel with an archived source is a dangling
2579
+ * link; a restructure pointer is a fresh link to a file written in the same
2580
+ * plan).
2581
+ */
2582
+ function supportRefs(content) {
2583
+ const refs = [];
2584
+ for (const match of content.matchAll(/\b(?:references|templates|scripts|assets)\/[A-Za-z0-9._-]+\.md/g)) refs.push(match[0]);
2585
+ return refs;
2586
+ }
2587
+ function planRestructureSections(body, moves) {
2588
+ const lines = body.split("\n");
2589
+ const spans = [];
2590
+ for (const move of moves) {
2591
+ const wanted = move.heading.trim();
2592
+ const starts = [];
2593
+ for (let i = 0; i < lines.length; i += 1) if ((/^#{2}\s+(.+?)\s*$/.exec(lines[i] ?? "")?.[1]?.trim() ?? "") === wanted) starts.push(i);
2594
+ const [start] = starts;
2595
+ if (start === void 0) return { error: `no "## ${wanted}" heading in the body` };
2596
+ if (starts.length > 1) return { error: `heading "## ${wanted}" appears ${starts.length} times (ambiguous anchor)` };
2597
+ let end = lines.length;
2598
+ for (let i = start + 1; i < lines.length; i += 1) if (/^#{2}\s/.test(lines[i] ?? "")) {
2599
+ end = i;
2600
+ break;
2601
+ }
2602
+ if (end === start + 1) return { error: `heading "## ${wanted}" has an empty section` };
2603
+ if (spans.some((span) => start === span.start)) return { error: `heading "## ${wanted}" is moved twice` };
2604
+ spans.push({
2605
+ start,
2606
+ end,
2607
+ rel: move.toFile,
2608
+ heading: wanted,
2609
+ text: lines.slice(start, end).join("\n")
2610
+ });
2611
+ }
2612
+ const byStart = new Map(spans.map((span) => [span.start, span]));
2613
+ const rebuilt = [];
2614
+ for (let i = 0; i < lines.length; i += 1) {
2615
+ const span = byStart.get(i);
2616
+ if (span) {
2617
+ rebuilt.push(`> 详见 references/${span.rel.split("/").at(-1)}`);
2618
+ i = span.end - 1;
2619
+ } else rebuilt.push(lines[i] ?? "");
2620
+ }
2621
+ return {
2622
+ body: rebuilt.join("\n"),
2623
+ sections: spans.map(({ rel, heading, text }) => ({
2624
+ rel,
2625
+ heading,
2626
+ text
2627
+ }))
2628
+ };
2629
+ }
2483
2630
  var SkillLibrary = class {
2484
2631
  root;
2485
2632
  limits;
@@ -2603,6 +2750,25 @@ var SkillLibrary = class {
2603
2750
  }
2604
2751
  return count;
2605
2752
  }
2753
+ /**
2754
+ * Structure-health facts for one skill (rc.73 A1, 008 design): body
2755
+ * chars/density from SKILL.md, support groups from countSupportDirs, plus
2756
+ * optional usage counts (A2 churn dimension) when the caller has them.
2757
+ * Derived, never persisted; null when the skill is unreadable.
2758
+ */
2759
+ async assessHealth(rawName, thresholds = DEFAULT_HEALTH_THRESHOLDS, counts) {
2760
+ const name = rawName.trim();
2761
+ const content = await this.read(name);
2762
+ if (content === null) return null;
2763
+ return assessStructureHealth({
2764
+ skillName: name,
2765
+ bodyChars: content.length,
2766
+ bodyText: content,
2767
+ supportGroups: await this.countSupportDirs(name),
2768
+ patchCount: counts?.patchCount,
2769
+ readCount: counts?.readCount
2770
+ }, thresholds);
2771
+ }
2606
2772
  /** Best-effort audit trail entry; never blocks the mutation. */
2607
2773
  async audit(skillName, action, before, after, summary) {
2608
2774
  try {
@@ -2790,7 +2956,7 @@ var SkillLibrary = class {
2790
2956
  };
2791
2957
  if (patched.length > this.limits.maxSkillContentChars && target === skillMd) return {
2792
2958
  ok: false,
2793
- message: `Patched content exceeds ${this.limits.maxSkillContentChars} characters.`
2959
+ message: `Patched content exceeds ${this.limits.maxSkillContentChars} characters. Consider splitting into a smaller SKILL.md with supporting files.`
2794
2960
  };
2795
2961
  const threat = scanContentThreats(patched);
2796
2962
  if (threat) return {
@@ -2871,10 +3037,24 @@ var SkillLibrary = class {
2871
3037
  * Merge the bodies of `sources` into `target` and archive the sources with
2872
3038
  * an absorbed-into marker. Hermes-style consolidation: overlapping skills
2873
3039
  * collapse into one, and the originals stay recoverable under `.archive/`.
3040
+ *
3041
+ * `mode:'append'` (default) appends each source body to the target. The
3042
+ * target write goes through the tree-change kernel (009) — byte-level
3043
+ * rollback, audit and the mutation event are kernel-owned. Package-integrity
3044
+ * (009-I): append-mode consolidation REFUSES a source whose directory has
3045
+ * support files or whose body carries support-directory links — an append
3046
+ * would leave those references pointing at an archived package (dangling);
3047
+ * the refusal message directs to the reference mode / whole-package archive.
3048
+ *
3049
+ * `mode:'reference'` writes each source's body (frontmatter stripped) into
3050
+ * `target/references/<source>.md` and archives the source — the demote path
3051
+ * (009-II). A source body with support-directory links is refused there too
3052
+ * (the references file would carry links whose files were archived).
2874
3053
  */
2875
- async consolidate(target, sources, origin = "foreground") {
3054
+ async consolidate(target, sources, origin = "foreground", options = {}) {
2876
3055
  const targetName = target.trim();
2877
3056
  const normalizedSources = [...new Set(sources.map((name) => name.trim()))].filter((name) => name !== targetName);
3057
+ const mode = options.mode ?? "append";
2878
3058
  if (normalizedSources.length === 0) return {
2879
3059
  ok: false,
2880
3060
  message: "Consolidation requires at least one distinct source skill."
@@ -2894,36 +3074,86 @@ var SkillLibrary = class {
2894
3074
  ok: false,
2895
3075
  message: `Skill "${targetName}" is protected (${targetProtection}).`
2896
3076
  };
2897
- const parts = [];
2898
- for (const source of normalizedSources) {
2899
- const protection = await this.deleteProtection(source);
2900
- if (protection) return {
2901
- ok: false,
2902
- message: `Skill "${source}" is protected (${protection}).`
2903
- };
2904
- const sourceMd = await this.io.readText(join(this.dirOf(source), "SKILL.md"));
2905
- if (!sourceMd) return {
3077
+ const writes = [];
3078
+ if (mode === "append") {
3079
+ const parts = [];
3080
+ for (const source of normalizedSources) {
3081
+ const protection = await this.deleteProtection(source);
3082
+ if (protection) return {
3083
+ ok: false,
3084
+ message: `Skill "${source}" is protected (${protection}).`
3085
+ };
3086
+ const sourceMd = await this.io.readText(join(this.dirOf(source), "SKILL.md"));
3087
+ if (!sourceMd) return {
3088
+ ok: false,
3089
+ message: `Skill "${source}" not found.`
3090
+ };
3091
+ const parsed = parseFrontmatter(sourceMd);
3092
+ if (!parsed) return {
3093
+ ok: false,
3094
+ message: `Skill "${source}" has no valid frontmatter; refusing to merge.`
3095
+ };
3096
+ if (await this.countSupportDirs(source) > 0) return {
3097
+ ok: false,
3098
+ message: `Consolidation rejected: source "${source}" carries support files — use mode:'reference' or archive the whole package instead.`
3099
+ };
3100
+ const refs = supportRefs(parsed.body);
3101
+ if (refs.length > 0) return {
3102
+ ok: false,
3103
+ message: `Consolidation rejected: source "${source}" body references support files (${refs.join(", ")}) that would be left behind — use mode:'reference' or archive the whole package instead.`
3104
+ };
3105
+ parts.push(`\n<!-- consolidated from ${source} at ${(/* @__PURE__ */ new Date()).toISOString()} -->\n${parsed.body.trim()}`);
3106
+ }
3107
+ const merged = targetMd.trimEnd() + parts.join("\n") + "\n";
3108
+ const validation = validateFrontmatter(merged, targetName, this.limits);
3109
+ if (validation) return {
2906
3110
  ok: false,
2907
- message: `Skill "${source}" not found.`
3111
+ message: `Consolidation rejected: ${validation}`
2908
3112
  };
2909
- const parsed = parseFrontmatter(sourceMd);
2910
- if (!parsed) return {
3113
+ writes.push({
3114
+ target: join(targetDir, "SKILL.md"),
3115
+ content: merged
3116
+ });
3117
+ } else {
3118
+ for (const source of normalizedSources) {
3119
+ const protection = await this.deleteProtection(source);
3120
+ if (protection) return {
3121
+ ok: false,
3122
+ message: `Skill "${source}" is protected (${protection}).`
3123
+ };
3124
+ const sourceMd = await this.io.readText(join(this.dirOf(source), "SKILL.md"));
3125
+ if (!sourceMd) return {
3126
+ ok: false,
3127
+ message: `Skill "${source}" not found.`
3128
+ };
3129
+ const parsed = parseFrontmatter(sourceMd);
3130
+ if (!parsed) return {
3131
+ ok: false,
3132
+ message: `Skill "${source}" has no valid frontmatter; refusing to demote.`
3133
+ };
3134
+ const refs = supportRefs(parsed.body);
3135
+ if (refs.length > 0) return {
3136
+ ok: false,
3137
+ message: `Consolidation rejected: source "${source}" body references support files (${refs.join(", ")}) that would be left behind — archive the whole package instead.`
3138
+ };
3139
+ const target = join(targetDir, "references", `${source}.md`);
3140
+ writes.push({
3141
+ target,
3142
+ content: `<!-- demoted from ${source} at ${(/* @__PURE__ */ new Date()).toISOString()} -->\n${parsed.body.trim()}\n`
3143
+ });
3144
+ }
3145
+ const pointerLines = normalizedSources.map((source) => `\n> 详见 references/${source}.md`).join("");
3146
+ const extended = targetMd.trimEnd() + pointerLines + "\n";
3147
+ const validation = validateFrontmatter(extended, targetName, this.limits);
3148
+ if (validation) return {
2911
3149
  ok: false,
2912
- message: `Skill "${source}" has no valid frontmatter; refusing to merge.`
3150
+ message: `Consolidation rejected: ${validation}`
2913
3151
  };
2914
- parts.push(`\n<!-- consolidated from ${source} at ${(/* @__PURE__ */ new Date()).toISOString()} -->\n${parsed.body.trim()}`);
3152
+ writes.push({
3153
+ target: join(targetDir, "SKILL.md"),
3154
+ content: extended
3155
+ });
2915
3156
  }
2916
- const merged = targetMd.trimEnd() + parts.join("\n") + "\n";
2917
- const validation = validateFrontmatter(merged, targetName, this.limits);
2918
- if (validation) return {
2919
- ok: false,
2920
- message: `Consolidation rejected: ${validation}`
2921
- };
2922
- const threat = scanContentThreats(merged);
2923
- if (threat) return {
2924
- ok: false,
2925
- message: threat
2926
- };
2927
3157
  const archived = [];
2928
3158
  try {
2929
3159
  for (const source of normalizedSources) {
@@ -2931,20 +3161,23 @@ var SkillLibrary = class {
2931
3161
  if (!result.ok) throw new Error(result.message);
2932
3162
  archived.push(source);
2933
3163
  }
2934
- await this.io.writeText(join(targetDir, "SKILL.md"), merged);
3164
+ const result = await this.applyTreeChange({
3165
+ name: targetName,
3166
+ origin,
3167
+ protection: "write",
3168
+ writes,
3169
+ auditAction: "consolidate",
3170
+ auditSummary: `consolidated ${normalizedSources.join(", ")} (${mode}) into ${targetName}`,
3171
+ eventAction: "consolidate"
3172
+ });
3173
+ if (!result.ok) throw new Error(result.message);
2935
3174
  } catch (error) {
2936
- await this.io.writeText(join(targetDir, "SKILL.md"), targetMd).catch(() => {});
2937
3175
  for (const source of archived.reverse()) await this.restoreFromArchive(source).catch(() => {});
2938
3176
  return {
2939
3177
  ok: false,
2940
3178
  message: `Consolidation failed and was rolled back: ${error instanceof Error ? error.message : String(error)}`
2941
3179
  };
2942
3180
  }
2943
- this.notifyMutation({
2944
- action: "consolidate",
2945
- name: targetName,
2946
- filePath: targetDir
2947
- });
2948
3181
  return {
2949
3182
  ok: true,
2950
3183
  message: `Consolidated ${normalizedSources.join(", ")} into "${targetName}".`,
@@ -2952,6 +3185,197 @@ var SkillLibrary = class {
2952
3185
  };
2953
3186
  }
2954
3187
  /**
3188
+ * Content-distribution repair (008 batch B, 009-R kernel): move body
3189
+ * sections — anchored by their exact `## heading` lines — into references/
3190
+ * support files and replace each span with a pointer line. The skill
3191
+ * name/dir never change (routing stays; only content location shifts, so a
3192
+ * fat body sheds its log-like detail). Deterministic, never automatic:
3193
+ * candidates come from an approved review plan. The write batch goes
3194
+ * through the tree-change kernel — one commit point, byte-level rollback.
3195
+ * Package integrity (009-R): a moved section whose text carries
3196
+ * support-directory links is refused (those links' files stay behind in the
3197
+ * same package; the moved text belongs in references/ beside them).
3198
+ */
3199
+ async restructure(rawName, moves, origin = "foreground") {
3200
+ const name = rawName.trim();
3201
+ const badName = this.badName(name);
3202
+ if (badName) return {
3203
+ ok: false,
3204
+ message: badName
3205
+ };
3206
+ if (moves.length === 0) return {
3207
+ ok: false,
3208
+ message: "Restructure requires at least one section move."
3209
+ };
3210
+ if (moves.length > 5) return {
3211
+ ok: false,
3212
+ message: `Restructure exceeds 5 moves.`
3213
+ };
3214
+ for (const move of moves) {
3215
+ if (typeof move.heading !== "string" || !move.heading.trim()) return {
3216
+ ok: false,
3217
+ message: "Every restructure move needs a non-empty heading."
3218
+ };
3219
+ if (!RESTRUCTURE_TARGET_RE.test(move.toFile)) return {
3220
+ ok: false,
3221
+ message: `toFile must be references/<topic>.md (got "${move.toFile}").`
3222
+ };
3223
+ }
3224
+ const dir = this.dirOf(name);
3225
+ const md = await this.io.readText(join(dir, "SKILL.md"));
3226
+ if (!md) return {
3227
+ ok: false,
3228
+ message: `Skill "${name}" not found.`
3229
+ };
3230
+ const normalized = md.replace(/\r\n/g, "\n");
3231
+ const plan = planRestructureSections(normalized, moves);
3232
+ if ("error" in plan) return {
3233
+ ok: false,
3234
+ message: `Restructure rejected: ${plan.error}`
3235
+ };
3236
+ const frontmatterEnd = normalized.indexOf("\n---", 3);
3237
+ if (frontmatterEnd < 0) return {
3238
+ ok: false,
3239
+ message: "SKILL.md has no valid frontmatter; refusing to restructure."
3240
+ };
3241
+ const newMd = normalized.slice(0, frontmatterEnd + 4) + plan.body;
3242
+ const newMdCheck = validateFrontmatter(newMd, name, this.limits);
3243
+ if (newMdCheck) return {
3244
+ ok: false,
3245
+ message: `Restructure rejected: ${newMdCheck}`
3246
+ };
3247
+ for (const section of plan.sections) {
3248
+ const refs = supportRefs(section.text);
3249
+ if (refs.length > 0) return {
3250
+ ok: false,
3251
+ message: `Restructure rejected: section "## ${section.heading}" references support files (${refs.join(", ")}) that stay behind — split the section or move it with its files.`
3252
+ };
3253
+ }
3254
+ const byRel = /* @__PURE__ */ new Map();
3255
+ for (const section of plan.sections) {
3256
+ const entry = byRel.get(section.rel) ?? {
3257
+ rel: section.rel,
3258
+ texts: []
3259
+ };
3260
+ entry.texts.push(section.text);
3261
+ byRel.set(section.rel, entry);
3262
+ }
3263
+ const writes = [];
3264
+ for (const entry of byRel.values()) {
3265
+ const target = join(dir, ...entry.rel.split("/"));
3266
+ const base = (await this.io.readText(target).catch(() => null))?.trimEnd() ?? "";
3267
+ writes.push({
3268
+ target,
3269
+ content: base === "" ? entry.texts.join("\n\n") : `${base}\n\n${entry.texts.join("\n\n")}`
3270
+ });
3271
+ }
3272
+ writes.push({
3273
+ target: join(dir, "SKILL.md"),
3274
+ content: newMd
3275
+ });
3276
+ const result = await this.applyTreeChange({
3277
+ name,
3278
+ origin,
3279
+ protection: "write",
3280
+ writes,
3281
+ auditAction: "restructure",
3282
+ auditSummary: `moved ${plan.sections.length} section(s): ${[...byRel.keys()].join(", ")}`,
3283
+ eventAction: "restructure"
3284
+ });
3285
+ if (!result.ok) return result;
3286
+ return {
3287
+ ok: true,
3288
+ message: `Restructured "${name}": moved ${plan.sections.length} section(s) to references/.`,
3289
+ path: dir
3290
+ };
3291
+ }
3292
+ /**
3293
+ * Unified tree-change commit point (009 kernel): owns validation order,
3294
+ * pre-read rollback bytes, two-phase write with byte-level rollback, audit
3295
+ * and the mutation event. Mutators compose `TreeChangePlan`s — consolidate,
3296
+ * restructure (and future reference-mode consolidations) never implement
3297
+ * two-phase commit themselves.
3298
+ */
3299
+ async applyTreeChange(plan) {
3300
+ const name = plan.name.trim();
3301
+ const badName = this.badName(name);
3302
+ if (badName) return {
3303
+ ok: false,
3304
+ message: badName
3305
+ };
3306
+ const dir = this.dirOf(name);
3307
+ const md = await this.io.readText(join(dir, "SKILL.md"));
3308
+ if (!md) return {
3309
+ ok: false,
3310
+ message: `Skill "${name}" not found.`
3311
+ };
3312
+ const protection = plan.protection === "write" ? await this.writeProtection(name, plan.origin) : plan.protection === "delete" ? await this.deleteProtection(name) : null;
3313
+ if (protection) return {
3314
+ ok: false,
3315
+ message: `Skill "${name}" is protected (${protection}).`
3316
+ };
3317
+ for (const precondition of plan.preconditions ?? []) {
3318
+ const issue = await precondition({ dir });
3319
+ if (issue) return {
3320
+ ok: false,
3321
+ message: issue
3322
+ };
3323
+ }
3324
+ const landing = [];
3325
+ for (const write of plan.writes) {
3326
+ const previous = await this.io.readText(write.target).catch(() => null);
3327
+ if (Buffer.byteLength(write.content, "utf8") > this.limits.maxSkillFileBytes) return {
3328
+ ok: false,
3329
+ message: `Write exceeds ${this.limits.maxSkillFileBytes} bytes: ${write.target}`
3330
+ };
3331
+ const threat = scanContentThreats(write.content);
3332
+ if (threat) return {
3333
+ ok: false,
3334
+ message: threat
3335
+ };
3336
+ landing.push({
3337
+ target: write.target,
3338
+ content: write.content,
3339
+ previous
3340
+ });
3341
+ }
3342
+ const semantic = plan.validate?.({
3343
+ dir,
3344
+ currentMd: md
3345
+ }) ?? null;
3346
+ if (semantic) return {
3347
+ ok: false,
3348
+ message: semantic
3349
+ };
3350
+ const written = [];
3351
+ try {
3352
+ for (const entry of landing) {
3353
+ await this.io.writeText(entry.target, entry.content);
3354
+ written.push({
3355
+ target: entry.target,
3356
+ previous: entry.previous
3357
+ });
3358
+ }
3359
+ } catch (error) {
3360
+ for (const entry of written.reverse()) await (entry.previous === null ? this.io.remove(entry.target) : this.io.writeText(entry.target, entry.previous)).catch(() => {});
3361
+ return {
3362
+ ok: false,
3363
+ message: `Tree change failed and was rolled back: ${error instanceof Error ? error.message : String(error)}`
3364
+ };
3365
+ }
3366
+ await this.audit(name, plan.auditAction, md, landing.find((entry) => entry.target.endsWith("SKILL.md"))?.content ?? md, plan.auditSummary);
3367
+ this.notifyMutation({
3368
+ action: plan.eventAction,
3369
+ name,
3370
+ filePath: dir
3371
+ });
3372
+ return {
3373
+ ok: true,
3374
+ message: `${plan.eventAction} "${name}" succeeded.`,
3375
+ path: dir
3376
+ };
3377
+ }
3378
+ /**
2955
3379
  * Restore one skill from `.archive/` back to the active root. Hermes-style
2956
3380
  * recoverability: archival never deletes, and this is the control-plane
2957
3381
  * path back. The `.archive-reason` marker is dropped on restore.
@@ -3262,4 +3686,4 @@ function evolutionHome(env = process.env) {
3262
3686
  return join(env.DSH_HOME ?? join(homedir(), ".dsh"), "evolution");
3263
3687
  }
3264
3688
  //#endregion
3265
- 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_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, EVENT_ARCHIVE_PREFIX, EVENT_LOG_RETAIN_ARCHIVES, EVENT_LOG_ROTATE_AT, EVENT_LOG_VERSION, EvolutionGateSet, 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, SKILLS_GUIDANCE, SKILL_NAME_RE, SKILL_REVIEW_PLAN_PROMPT, SKILL_REVIEW_PROMPT, SNAPSHOT_EXTRA_NAME_RE, SUPPORT_DIRS, SUPPRESSED_FILE_VERSION, SkillLibrary, advanceReview, appendEvolutionEvent, applyCuratorFields, applyCuratorLifecycleFields, applyCuratorMetaFields, authoringFeedback, buildCuratorRunReport, buildLearnPrompt, bumpPatch, bumpUse, bumpView, computeDedupGroups, computeLifecycleTransitions, computePrefixClusters, computeQualityScores, computeScopeView, contentHash, createGateSet, emptyRecord, evaluateThreat, eventsFile, evolutionHome, evolutionIoAdapter, foldCuratorFields, foldTurn, getRecord, latestActivityAt, lifecycleCandidate, listEventArchives, loadMutations, loadSuppressedNames, loadUsage, markAgentCreated, memoryRoot, mutateUsage, mutationsFile, nodeEvolutionIo, normalizeUsageRecord, observeEvent, parseCuratorNominations, parseEvolutionEvents, parseFrontmatter, readEvolutionEvents, readEvolutionTimeline, recordMutation, relatedSkillNames, renderCuratorReportMarkdown, resolveOrigins, retainEventArchives, reviewPrompt, saveSuppressedNames, saveUsage, scanContentThreats, scanMemoryThreats, scanThreats, skillsRoot, suppressedFile, transactIo, updateSuppressedNames, usageFile, validateFrontmatter, verifyPromptBundle };
3689
+ 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, DSH_AUTHORING_STANDARDS, ENTRY_DELIMITER, EVENT_ARCHIVE_PREFIX, EVENT_LOG_RETAIN_ARCHIVES, EVENT_LOG_ROTATE_AT, EVENT_LOG_VERSION, EvolutionGateSet, LOW_QUALITY_THRESHOLD, MAX_DESCRIPTION_LENGTH, MAX_RESTRUCTURE_MOVES, 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, RESTRUCTURE_TARGET_RE, SKILLS_GUIDANCE, SKILL_NAME_RE, SKILL_REVIEW_PLAN_PROMPT, SKILL_REVIEW_PROMPT, SNAPSHOT_EXTRA_NAME_RE, SUPPORT_DIRS, SUPPRESSED_FILE_VERSION, SkillLibrary, advanceReview, appendEvolutionEvent, applyCuratorFields, applyCuratorLifecycleFields, applyCuratorMetaFields, assessStructureHealth, authoringFeedback, buildCuratorRunReport, buildLearnPrompt, bumpPatch, bumpUse, bumpView, computeDedupGroups, computeLifecycleTransitions, computePrefixClusters, computeQualityScores, computeScopeView, contentHash, createGateSet, emptyRecord, evaluateThreat, eventsFile, evolutionHome, evolutionIoAdapter, foldCuratorFields, foldTurn, getRecord, latestActivityAt, lifecycleCandidate, listEventArchives, loadMutations, loadSuppressedNames, loadUsage, markAgentCreated, memoryRoot, mutateUsage, mutationsFile, nodeEvolutionIo, normalizeUsageRecord, observeEvent, parseCuratorNominations, parseEvolutionEvents, parseFrontmatter, readEvolutionEvents, readEvolutionTimeline, recordMutation, relatedSkillNames, renderCuratorReportMarkdown, resolveOrigins, retainEventArchives, reviewPrompt, saveSuppressedNames, saveUsage, scanContentThreats, scanMemoryThreats, scanThreats, skillsRoot, suppressedFile, transactIo, updateSuppressedNames, usageFile, usageObserved, validateFrontmatter, verifyPromptBundle };
@@ -84,10 +84,14 @@ export declare function buildCuratorRunReport(input: CuratorReportInput): Curato
84
84
  * stale candidates / LLM nominations).
85
85
  */
86
86
  export declare function renderCuratorReportMarkdown(report: CuratorRunReport): string;
87
- /** One LLM-nominated consolidation: `from` merges into the umbrella `into`. */
87
+ /** One LLM-nominated consolidation: `from` merges into the umbrella `into`.
88
+ * `mode:'reference'` demotes the source (its body becomes
89
+ * `references/<source>.md` under the umbrella) instead of appending to the
90
+ * target body (009-II); absent means append. */
88
91
  export interface CuratorConsolidation {
89
92
  from: string;
90
93
  into: string;
94
+ mode?: 'append' | 'reference' | undefined;
91
95
  }
92
96
  /** Structured result of the optional curator LLM nomination pass. */
93
97
  export interface CuratorNominations {
@@ -6,6 +6,15 @@
6
6
  * learn on target X" is answerable. The aggregate `feedback.json` is a
7
7
  * rebuildable boot cache, never the truth.
8
8
  *
9
+ * Usage events (C semantics, rc.73+): `type:'usage'` records are the
10
+ * OBSERVATION WINDOW ANCHOR — written once, when the library's first observed
11
+ * read (`view_count` 0 -> 1) happens. Before that anchor the usage sidecar
12
+ * has no read evidence (reads were invisible pre-A2), so churn-based health
13
+ * judgments are NOT trustworthy; the curator suppresses them (its
14
+ * `usageObserved()` gate) until the anchor exists. `counts` on the event is a
15
+ * cumulative library-wide snapshot (skills/views/use/patches) at that moment,
16
+ * and `window.opened` pins the window start for the timeline.
17
+ *
9
18
  * Rotation (rc.71, 007 design): when the active log reaches
10
19
  * `EVENT_LOG_ROTATE_AT` the older half is split into an archive
11
20
  * (`events-<lastArchivedSeq>.json`); the boot timeline merges active +
@@ -32,14 +41,27 @@ export interface EvolutionEvent {
32
41
  seq: number;
33
42
  /** ISO timestamp at append time. */
34
43
  at: string;
35
- /** Tagged-union discriminator: feedback increments vs learn actions. */
36
- type: 'feedback' | 'learn';
44
+ /** Tagged-union discriminator: feedback increments, learn actions, and
45
+ * usage observation anchors (C semantics: `usage` events carry the library-
46
+ * wide count snapshot at the moment the observation window opened). */
47
+ type: 'feedback' | 'learn' | 'usage';
37
48
  target?: string | undefined;
38
49
  kind?: 'skill' | 'session' | undefined;
39
50
  rating?: 'positive' | 'negative' | undefined;
40
51
  note?: string | undefined;
41
52
  source?: string | undefined;
42
53
  request?: string | undefined;
54
+ /** Library-wide usage totals (usage events; counts are cumulative, not deltas). */
55
+ counts?: {
56
+ skills?: number;
57
+ views?: number;
58
+ use?: number;
59
+ patches?: number;
60
+ } | undefined;
61
+ /** Anchor fields for one event (usage: the observation window). */
62
+ window?: {
63
+ opened?: string;
64
+ } | undefined;
43
65
  }
44
66
  export declare function eventsFile(home: string): string;
45
67
  /**
@@ -17,6 +17,7 @@ export * from './memory-store.ts';
17
17
  export * from './mutations.ts';
18
18
  export * from './prompts.ts';
19
19
  export * from './quality.ts';
20
+ export * from './skill-health.ts';
20
21
  export * from './signals.ts';
21
22
  export * from './skill-store.ts';
22
23
  export * from './state-store.ts';
@@ -3,12 +3,12 @@
3
3
  * changes semantically: the bundle digest is the fail-closed signal for
4
4
  * review workers, so a stale id across deployments must be distinguishable.
5
5
  */
6
- export declare const PROMPT_BUNDLE_ID = "dsh-evolution@7";
7
- export declare const PROMPT_BUNDLE_VERSION = 7;
6
+ export declare const PROMPT_BUNDLE_ID = "dsh-evolution@9";
7
+ export declare const PROMPT_BUNDLE_VERSION = 9;
8
8
  export declare const MEMORY_REVIEW_PROMPT = "[Auto-review \u2014 Memory]\nReview the conversation above and consider saving to memory if appropriate.\n\nFocus on:\n1. Has the user revealed things about themselves \u2014 persona, desires, preferences, or personal details worth remembering?\n2. Has the user expressed expectations about how you should behave, their work style, or ways they want you to operate?\n\nIf something stands out, save it using the memory tool.\nIf nothing is worth saving, just say \"Nothing to save.\" and stop.";
9
- export declare const SKILL_REVIEW_PROMPT = "[Auto-review \u2014 Skills]\nReview the conversation above and update the skill library. Be ACTIVE \u2014 most sessions produce at least one skill update, even if small. A pass that does nothing is a missed learning opportunity, not a neutral outcome.\n\nTarget shape of the library: CLASS-LEVEL skills, each with a rich SKILL.md and a references/ directory for session-specific detail. Not a long flat list of narrow one-session-one-skill entries. This shapes HOW you update, not WHETHER you update.\n\nSignals to look for (any one of these warrants action):\n \u2022 User corrected your style, tone, format, legibility, or verbosity. Frustration signals like 'stop doing X', 'this is too verbose', 'don't format like this', 'why are you explaining', 'just give me the answer', 'you always do Y and I hate it', or an explicit 'remember this' are FIRST-CLASS skill signals, not just memory signals. Update the relevant skill(s) to embed the preference so the next session starts already knowing.\n \u2022 User corrected your workflow, approach, or sequence of steps. Encode the correction as a pitfall or explicit step in the skill that governs that class of task.\n \u2022 Non-trivial technique, fix, workaround, debugging path, or tool-usage pattern emerged that a future session would benefit from. Capture it.\n \u2022 A skill that got loaded or consulted this session turned out to be wrong, missing a step, or outdated. Patch it NOW.\n\nRead-before-write (enforced by this channel): update, patch, delete, or write support files ONLY into skills you loaded or read in THIS session \u2014 ops on unread skills are dropped; CREATE of a brand-new umbrella is the only exception.\n\nPreference order \u2014 prefer the earliest action that fits, but do pick one when a signal above fired:\n 1. UPDATE A CURRENTLY-LOADED SKILL. Look back through the conversation for skills the user loaded or you read. If any of them covers the territory of the new learning, PATCH that one first. It is the skill that was in play, so it's the right one to extend.\n 2. UPDATE AN EXISTING UMBRELLA. If no loaded skill fits but an existing class-level skill does, patch it. Add a subsection, a pitfall, or broaden a trigger.\n 3. ADD A SUPPORT FILE under an existing umbrella. Skills can be packaged with three kinds of support files \u2014 use the right directory per kind:\n \u2022 references/<topic>.md \u2014 session-specific detail (error transcripts, reproduction recipes, provider quirks) AND condensed knowledge banks: quoted research, API docs, external authoritative excerpts, or domain notes you found while working on the problem. Write it concise and for the value of the task, not as a full mirror of upstream docs.\n \u2022 templates/<name>.<ext> \u2014 starter files meant to be copied and modified (boilerplate configs, scaffolding, a known-good example the agent can reproduce with modifications).\n \u2022 scripts/<name>.<ext> \u2014 statically re-runnable actions the skill can invoke directly (verification scripts, fixture generators, deterministic probes, anything the agent should run rather than hand-type each time).\n Add support files via skill_manage action=write_file with file_path starting 'references/', 'templates/', or 'scripts/'. The umbrella's SKILL.md should gain a one-line pointer to any new support file so future agents know it exists.\n 4. CREATE A NEW CLASS-LEVEL UMBRELLA SKILL when no existing skill covers the class. The name MUST be at the class level. The name MUST NOT be a specific PR number, error string, feature codename, library-alone name, or 'fix-X / debug-Y / audit-Z-today' session artifact. If the proposed name only makes sense for today's task, it's wrong \u2014 fall back to (1), (2), or (3).\n\nUser-preference embedding (important): when the user expressed a style/format/workflow preference, the update belongs in the SKILL.md body, not just in memory. Memory captures 'who the user is and what the current situation and state of your operations are'; skills capture 'how to do this class of task for this user'. When they complain about how you handled a task, the skill that governs that task needs to carry the lesson.\n\nIf you notice two existing skills that overlap, note it in your reply \u2014 the background curator handles consolidation at scale.\n\nTwo-tier deposition discipline (DSH addition, same spirit as the umbrella rule): before writing, classify the knowledge:\n \u2022 PATTERN (reusable \u2014 symptom \u2192 mechanism \u2192 fix \u2192 verification, still valuable next session) belongs in the SKILL.md body.\n \u2022 LOG (one-off \u2014 commit SHAs, npm/profile states, what this release changed, this session's process narrative) belongs in a references/ file, never the body. Body density IS reuse rate. Keep new entries tight: a pattern fits in 2-8 physical lines; prefer changing the current-state pointer over appending history.\n\nProtected skills (DO NOT edit these):\n \u2022 Bundled skills (shipped with the platform).\n \u2022 Hub-installed skills (installed from a hub).\nPinned skills are read-only to THIS background review pass \u2014 the pinned write guard refuses background changes, so only the foreground may update or archive them. Foreground and delegated-subagent writes to pinned skills remain allowed.\nIf the only skills that need updating are protected, say 'Nothing to save.' and stop.\n\nDo NOT capture (these become persistent self-imposed constraints that bite you later when the environment changes):\n \u2022 Environment-dependent failures: missing binaries, fresh-install errors, post-migration path mismatches, 'command not found', unconfigured credentials, uninstalled packages. The user can fix these \u2014 they are not durable rules.\n \u2022 Negative claims about tools or features ('browser tools do not work', 'X tool is broken', 'cannot use Y'). These harden into refusals the agent cites against itself for months after the actual problem was fixed.\n \u2022 Session-specific transient errors that resolved before the conversation ended. If retrying worked, the lesson is the retry pattern, not the original failure.\n \u2022 One-off task narratives. A user asking 'summarize today's market' or 'analyze this PR' is not a class of work that warrants a skill.\n\nIf a tool failed because of setup state, capture the FIX (install command, config step, env var to set) under an existing setup or troubleshooting skill \u2014 never 'this tool does not work' as a standalone constraint.\n\n'Nothing to save.' is a real option but should NOT be the default. If the session ran smoothly with no corrections and produced no new technique, just say 'Nothing to save.' and stop. Otherwise, act.";
10
- export declare const COMBINED_REVIEW_PROMPT = "[Auto-review]\nReview the conversation above and update two things:\n\n**Memory**: who the user is. Did the user reveal persona, desires, preferences, personal details, or expectations about how you should behave? Save facts about the user and durable preferences with the memory tool.\n\n**Skills**: how to do this class of task. Be ACTIVE \u2014 most sessions produce at least one skill update. A pass that does nothing is a missed learning opportunity, not a neutral outcome.\n\nTarget shape of the skill library: CLASS-LEVEL skills with a rich SKILL.md and a references/ directory for session-specific detail. Not a long flat list of narrow one-session-one-skill entries.\n\nSignals that warrant a skill update (any one is enough):\n \u2022 User corrected your style, tone, format, legibility, verbosity, or approach. Frustration is a FIRST-CLASS skill signal, not just a memory signal. 'stop doing X', 'don't format like this', 'I hate when you Y' \u2014 embed the lesson in the skill that governs that task so the next session starts fixed.\n \u2022 Non-trivial technique, fix, workaround, or debugging path emerged.\n \u2022 A skill that was loaded or consulted turned out wrong, missing, or outdated \u2014 patch it now.\n\nRead-before-write (enforced by this channel): update, patch, delete, or write support files ONLY into skills you loaded or read in THIS session \u2014 ops on unread skills are dropped; CREATE of a brand-new umbrella is the only exception.\n\nPreference order for skills \u2014 pick the earliest that fits:\n 1. UPDATE A CURRENTLY-LOADED SKILL. Check what skills were loaded or read in the conversation. If one of them covers the learning, PATCH it first. It was in play; it's the right place.\n 2. UPDATE AN EXISTING UMBRELLA. Patch it.\n 3. ADD A SUPPORT FILE under an existing umbrella via skill_manage action=write_file. Three kinds: references/<topic>.md for session-specific detail OR condensed knowledge banks (quoted research, API docs excerpts, domain notes) written concise and task-focused; templates/<name>.<ext> for starter files meant to be copied and modified; scripts/<name>.<ext> for statically re-runnable actions (verification, fixture generators, probes). Add a one-line pointer in SKILL.md so future agents find them.\n 4. CREATE A NEW CLASS-LEVEL UMBRELLA when nothing exists. Name at the class level \u2014 NOT a PR number, error string, codename, library-alone name, or 'fix-X / debug-Y' session artifact. If the name only fits today's task, fall back to (1), (2), or (3).\n\nTwo-tier deposition discipline (DSH addition): classify before writing \u2014 PATTERN (symptom \u2192 mechanism \u2192 fix \u2192 verification) goes in the SKILL.md body; LOG (commit SHAs, npm/profile states, this release's change list, this session's narrative) goes in a references/ file. Body density IS reuse rate; a pattern fits in 2-8 physical lines.\n\nUser-preference embedding: when the user complains about how you handled a task, update the skill that governs that task \u2014 memory alone isn't enough. Memory says 'who the user is and what the current situation and state of your operations are'; skills say 'how to do this class of task for this user'. Both should carry user-preference lessons when relevant.\n\nIf you notice overlapping existing skills, mention it \u2014 the background curator handles consolidation.\n\nProtected skills (DO NOT edit these):\n \u2022 Bundled skills (shipped with the platform).\n \u2022 Hub-installed skills (installed from a hub).\nPinned skills are read-only to THIS background review pass \u2014 the pinned write guard refuses background changes, so only the foreground may update or archive them. Foreground and delegated-subagent writes to pinned skills remain allowed.\nIf the only skills that need updating are protected, say 'Nothing to save.' and stop.\n\nDo NOT capture as skills (these become persistent self-imposed constraints that bite you later when the environment changes):\n \u2022 Environment-dependent failures: missing binaries, fresh-install errors, post-migration path mismatches, 'command not found', unconfigured credentials, uninstalled packages. The user can fix these \u2014 they are not durable rules.\n \u2022 Negative claims about tools or features ('browser tools do not work', 'X tool is broken', 'cannot use Y'). These harden into refusals the agent cites against itself for months after the actual problem was fixed.\n \u2022 Session-specific transient errors that resolved before the conversation ended. If retrying worked, the lesson is the retry pattern, not the original failure.\n \u2022 One-off task narratives. A user asking 'summarize today's market' or 'analyze this PR' is not a class of work that warrants a skill.\n\nIf a tool failed because of setup state, capture the FIX (install command, config step, env var to set) under an existing setup or troubleshooting skill \u2014 never 'this tool does not work' as a standalone constraint.\n\nAct on whichever of the two dimensions has real signal. If genuinely nothing stands out on either, say 'Nothing to save.' and stop \u2014 but don't reach for that conclusion as a default.";
11
- export declare const CURATOR_PROMPT = "You are the skill curator. Maintain a healthy, class-level skill library, not a flat pile of narrow one-session skills.\n\nThis is an UMBRELLA-BUILDING consolidation pass, not a passive audit and not a duplicate-finder.\n\nThe goal is a LIBRARY OF CLASS-LEVEL INSTRUCTIONS. A skill collection of many narrow skills where each captures one session's specific bug is a FAILURE of the library. An agent searching skills matches on descriptions, not exact names; one broad umbrella with labeled subsections beats five narrow siblings for discoverability.\n\nRight target shape: class-level skills with rich SKILL.md + references/, templates/, scripts/ support files for session-specific detail.\n\nHard rules:\n1. NEVER hard-delete a skill. Archive (moving to .archive/) is the maximum destructive action; archives are recoverable, deletion is not.\n2. Do not touch bundled, hub-installed, pinned, or scheduled-task-referenced (referenced) skills. Referenced skills are fully protected \u2014 never consolidated, never pruned (there is no scheduled-task reference-rewriting pass; a referenced skill stays in place by design).\n3. Do not archive recently-created or never-used skills without strong evidence. \"use=0\" is NOT evidence either way \u2014 it only means the trigger has not come up yet. Never archive a never-used skill unless it is at least 30 days old AND its content is genuinely obsolete or fully absorbed elsewhere.\n4. Do NOT reject consolidation on the grounds that \"each skill has a distinct trigger\". The right bar is: would a human maintainer write this as N separate skills, or one skill with N labeled subsections? When the answer is the latter, merge.\n5. Judge overlap on CONTENT, not on usage counters.\n6. Before archiving a merged skill, ensure its unique content was preserved in the umbrella.\n\nHow to work:\n1. Scan the candidate list. Identify PREFIX CLUSTERS \u2014 skills sharing a first word or domain keyword. Expected cluster count scales with the library: a large collection may show 10-25 prefix clusters, a small one often has none \u2014 a clean \"nothing to consolidate\" summary is the correct small-library outcome, not a shortage of ambition.\n2. For each cluster with 2+ members, ask \"what is the UMBRELLA CLASS these skills serve?\" and consolidate:\n a. MERGE INTO AN EXISTING UMBRELLA (patch a labeled section for each sibling's unique insight, then archive the siblings).\n b. CREATE A NEW UMBRELLA SKILL.md covering the shared workflow with short labeled subsections, then archive the absorbed siblings.\n c. DEMOTE session-specific detail to references/, templates/, or scripts/ under the umbrella. Use the right directory per kind:\n \u2022 references/<topic>.md \u2014 session-specific detail OR condensed knowledge banks (quoted research, API docs excerpts, domain notes, provider quirks, reproduction recipes) written concise and task-focused.\n \u2022 templates/<name>.<ext> \u2014 starter files meant to be copied and modified.\n \u2022 scripts/<name>.<ext> \u2014 statically re-runnable actions (verification scripts, fixture generators, probes).\n3. Package integrity \u2014 not optional: inspect each skill as a COMPLETE directory package, not just SKILL.md. A skill root may include references/, templates/, scripts/, and assets/. If the source skill has support files OR its SKILL.md contains relative links to them, DO NOT flatten only SKILL.md into <umbrella>/references/<old>.md. Choose one safe path instead: keep it as a standalone skill, OR fully merge by re-homing every needed support file into the umbrella's canonical directories AND rewriting the destination instructions to the new paths, OR archive the entire original skill package unchanged. Never leave demoted instructions pointing at files left behind under the old skill directory.\n4. Flag skills whose NAME is too narrow (contains a PR number, a feature codename, a specific error string, an 'audit'/'diagnosis'/'salvage' session artifact) \u2014 they almost always belong as a subsection or support file under a class-level umbrella.\n5. Iterate. After one consolidation round, scan the remaining set and look for the NEXT umbrella opportunity. Don't stop after 3 merges.\n\nYou are a NOMINATOR, not an executor: this channel has NO tools. Your single deliverable is the structured YAML block below. Never narrate actions you did not take (\"merged\", \"patched\", \"archived\") \u2014 you are proposing, and the deterministic engine executes only names from the candidate pool it gave you. (A future execution view would expose skill_manage; today it does not.)\n\n'keep' is a legitimate decision ONLY when the skill is already a class-level umbrella and none of the proposed merges would improve discoverability. 'This is narrow but distinct from its siblings' is NOT a reason to keep \u2014 it's a reason to move it under an umbrella as a subsection or support file.\n\nExpected output: real umbrella-ification. Process every obvious cluster. If you end the pass with obvious clusters still untouched, you stopped too early \u2014 go back and look at the clusters you left alone.\n\nKeep the umbrella body tight and scannable: exact commands, verbatim paths, ~100-200 lines; never invent flags or APIs.\n\nWhen done, write a human summary THEN the structured machine-readable block. The block is the contract: every skill you would move to .archive/ MUST appear in exactly one of the two lists. Return ONLY the YAML block after the summary \u2014 no post-block prose. Format EXACTLY:\n\n## Structured summary (required)\n```yaml\nconsolidations:\n - from: <old-skill-name>\n into: <umbrella-skill-name>\n reason: <one short sentence \u2014 why merged, not just 'similar'>\nprunings:\n - name: <skill-name>\n reason: <one short sentence \u2014 why archived with no merge target>\n```\n\nEvery skill you would move to .archive/ MUST appear in exactly one of the two lists. If you consolidated X into umbrella Y (patched Y, wrote a references file to Y, or created Y with X's content absorbed), X goes under consolidations with into: Y. If you archived X with no absorption \u2014 truly stale, irrelevant, or obsolete \u2014 X goes under prunings. Leave a list empty (consolidations: []) if none. Do not omit the block. The block comes AFTER your human-readable summary of clusters processed, patches made, and decisions left alone.";
9
+ export declare const SKILL_REVIEW_PROMPT = "[Auto-review \u2014 Skills]\nReview the conversation above and update the skill library. Be ACTIVE \u2014 most sessions produce at least one skill update, even if small. A pass that does nothing is a missed learning opportunity, not a neutral outcome.\n\nTarget shape of the library: CLASS-LEVEL skills, each with a rich SKILL.md and a references/ directory for session-specific detail. Not a long flat list of narrow one-session-one-skill entries. This shapes HOW you update, not WHETHER you update.\n\nSignals to look for (any one of these warrants action):\n \u2022 User corrected your style, tone, format, legibility, or verbosity. Frustration signals like 'stop doing X', 'this is too verbose', 'don't format like this', 'why are you explaining', 'just give me the answer', 'you always do Y and I hate it', or an explicit 'remember this' are FIRST-CLASS skill signals, not just memory signals. Update the relevant skill(s) to embed the preference so the next session starts already knowing.\n \u2022 User corrected your workflow, approach, or sequence of steps. Encode the correction as a pitfall or explicit step in the skill that governs that class of task.\n \u2022 Non-trivial technique, fix, workaround, debugging path, or tool-usage pattern emerged that a future session would benefit from. Capture it.\n \u2022 A skill that got loaded or consulted this session turned out to be wrong, missing a step, or outdated. Patch it NOW.\n\nRead-before-write (enforced by this channel): update, patch, delete, or write support files ONLY into skills you loaded or read in THIS session \u2014 ops on unread skills are dropped; CREATE of a brand-new umbrella is the only exception.\n\nPreference order \u2014 prefer the earliest action that fits, but do pick one when a signal above fired:\n 1. UPDATE A CURRENTLY-LOADED SKILL. Look back through the conversation for skills the user loaded or you read. If any of them covers the territory of the new learning, PATCH that one first. It is the skill that was in play, so it's the right one to extend.\n 2. UPDATE AN EXISTING UMBRELLA. If no loaded skill fits but an existing class-level skill does, patch it. Add a subsection, a pitfall, or broaden a trigger.\n 3. ADD A SUPPORT FILE under an existing umbrella. Skills can be packaged with three kinds of support files \u2014 use the right directory per kind:\n \u2022 references/<topic>.md \u2014 session-specific detail (error transcripts, reproduction recipes, provider quirks) AND condensed knowledge banks: quoted research, API docs, external authoritative excerpts, or domain notes you found while working on the problem. Write it concise and for the value of the task, not as a full mirror of upstream docs.\n \u2022 templates/<name>.<ext> \u2014 starter files meant to be copied and modified (boilerplate configs, scaffolding, a known-good example the agent can reproduce with modifications).\n \u2022 scripts/<name>.<ext> \u2014 statically re-runnable actions the skill can invoke directly (verification scripts, fixture generators, deterministic probes, anything the agent should run rather than hand-type each time).\n Add support files via skill_manage action=write_file with file_path starting 'references/', 'templates/', or 'scripts/'. The umbrella's SKILL.md should gain a one-line pointer to any new support file so future agents know it exists.\n 4. RESTRUCTURE a loaded skill whose body grew log-like \u2014 rc/sha/date-dense sections, session-detail spirals, or a fat body with no support files. Use skill_manage action=restructure with restructure: [{\"heading\": \"<the exact ## heading text>\", \"to_file\": \"references/<topic>.md\"}] \u2014 the ENTIRE ## section (from that heading to the next heading) moves into the support file and its position becomes a pointer line. The skill's name and directory never change. Only propose headings that exist verbatim in the body; never invent one, and never restructure a healthy small skill.\n 5. CREATE A NEW CLASS-LEVEL UMBRELLA SKILL when no existing skill covers the class. The name MUST be at the class level. The name MUST NOT be a specific PR number, error string, feature codename, library-alone name, or 'fix-X / debug-Y / audit-Z-today' session artifact. If the proposed name only makes sense for today's task, it's wrong \u2014 fall back to (1), (2), or (3).\n\nUser-preference embedding (important): when the user expressed a style/format/workflow preference, the update belongs in the SKILL.md body, not just in memory. Memory captures 'who the user is and what the current situation and state of your operations are'; skills capture 'how to do this class of task for this user'. When they complain about how you handled a task, the skill that governs that task needs to carry the lesson.\n\nIf you notice two existing skills that overlap, note it in your reply \u2014 the background curator handles consolidation at scale.\n\nTwo-tier deposition discipline (DSH addition, same spirit as the umbrella rule): before writing, classify the knowledge:\n \u2022 PATTERN (reusable \u2014 symptom \u2192 mechanism \u2192 fix \u2192 verification, still valuable next session) belongs in the SKILL.md body.\n \u2022 LOG (one-off \u2014 commit SHAs, npm/profile states, what this release changed, this session's process narrative) belongs in a references/ file, never the body. Body density IS reuse rate. Keep new entries tight: a pattern fits in 2-8 physical lines; prefer changing the current-state pointer over appending history.\n\nProtected skills (DO NOT edit these):\n \u2022 Bundled skills (shipped with the platform).\n \u2022 Hub-installed skills (installed from a hub).\nPinned skills are read-only to THIS background review pass \u2014 the pinned write guard refuses background changes, so only the foreground may update or archive them. Foreground and delegated-subagent writes to pinned skills remain allowed.\nIf the only skills that need updating are protected, say 'Nothing to save.' and stop.\n\nDo NOT capture (these become persistent self-imposed constraints that bite you later when the environment changes):\n \u2022 Environment-dependent failures: missing binaries, fresh-install errors, post-migration path mismatches, 'command not found', unconfigured credentials, uninstalled packages. The user can fix these \u2014 they are not durable rules.\n \u2022 Negative claims about tools or features ('browser tools do not work', 'X tool is broken', 'cannot use Y'). These harden into refusals the agent cites against itself for months after the actual problem was fixed.\n \u2022 Session-specific transient errors that resolved before the conversation ended. If retrying worked, the lesson is the retry pattern, not the original failure.\n \u2022 One-off task narratives. A user asking 'summarize today's market' or 'analyze this PR' is not a class of work that warrants a skill.\n\nIf a tool failed because of setup state, capture the FIX (install command, config step, env var to set) under an existing setup or troubleshooting skill \u2014 never 'this tool does not work' as a standalone constraint.\n\n'Nothing to save.' is a real option but should NOT be the default. If the session ran smoothly with no corrections and produced no new technique, just say 'Nothing to save.' and stop. Otherwise, act.";
10
+ export declare const COMBINED_REVIEW_PROMPT = "[Auto-review]\nReview the conversation above and update two things:\n\n**Memory**: who the user is. Did the user reveal persona, desires, preferences, personal details, or expectations about how you should behave? Save facts about the user and durable preferences with the memory tool.\n\n**Skills**: how to do this class of task. Be ACTIVE \u2014 most sessions produce at least one skill update. A pass that does nothing is a missed learning opportunity, not a neutral outcome.\n\nTarget shape of the skill library: CLASS-LEVEL skills with a rich SKILL.md and a references/ directory for session-specific detail. Not a long flat list of narrow one-session-one-skill entries.\n\nSignals that warrant a skill update (any one is enough):\n \u2022 User corrected your style, tone, format, legibility, verbosity, or approach. Frustration is a FIRST-CLASS skill signal, not just a memory signal. 'stop doing X', 'don't format like this', 'I hate when you Y' \u2014 embed the lesson in the skill that governs that task so the next session starts fixed.\n \u2022 Non-trivial technique, fix, workaround, or debugging path emerged.\n \u2022 A skill that was loaded or consulted turned out wrong, missing, or outdated \u2014 patch it now.\n\nRead-before-write (enforced by this channel): update, patch, delete, or write support files ONLY into skills you loaded or read in THIS session \u2014 ops on unread skills are dropped; CREATE of a brand-new umbrella is the only exception.\n\nPreference order for skills \u2014 pick the earliest that fits:\n 1. UPDATE A CURRENTLY-LOADED SKILL. Check what skills were loaded or read in the conversation. If one of them covers the learning, PATCH it first. It was in play; it's the right place.\n 2. UPDATE AN EXISTING UMBRELLA. Patch it.\n 3. ADD A SUPPORT FILE under an existing umbrella via skill_manage action=write_file. Three kinds: references/<topic>.md for session-specific detail OR condensed knowledge banks (quoted research, API docs excerpts, domain notes) written concise and task-focused; templates/<name>.<ext> for starter files meant to be copied and modified; scripts/<name>.<ext> for statically re-runnable actions (verification, fixture generators, probes). Add a one-line pointer in SKILL.md so future agents find them.\n 4. RESTRUCTURE a loaded skill whose body grew log-like (rc/sha/date-dense sections, session-detail spirals, fat body with no support files) via skill_manage action=restructure with restructure: [{\"heading\": \"<the exact ## heading text>\", \"to_file\": \"references/<topic>.md\"}] \u2014 the ENTIRE ## section moves into the support file and its position becomes a pointer line; the skill's name and directory never change. Only propose headings that exist verbatim in the body.\n 5. CREATE A NEW CLASS-LEVEL UMBRELLA when nothing exists. Name at the class level \u2014 NOT a PR number, error string, codename, library-alone name, or 'fix-X / debug-Y' session artifact. If the name only fits today's task, fall back to (1), (2), or (3).\n\nTwo-tier deposition discipline (DSH addition): classify before writing \u2014 PATTERN (symptom \u2192 mechanism \u2192 fix \u2192 verification) goes in the SKILL.md body; LOG (commit SHAs, npm/profile states, this release's change list, this session's narrative) goes in a references/ file. Body density IS reuse rate; a pattern fits in 2-8 physical lines.\n\nUser-preference embedding: when the user complains about how you handled a task, update the skill that governs that task \u2014 memory alone isn't enough. Memory says 'who the user is and what the current situation and state of your operations are'; skills say 'how to do this class of task for this user'. Both should carry user-preference lessons when relevant.\n\nIf you notice overlapping existing skills, mention it \u2014 the background curator handles consolidation.\n\nProtected skills (DO NOT edit these):\n \u2022 Bundled skills (shipped with the platform).\n \u2022 Hub-installed skills (installed from a hub).\nPinned skills are read-only to THIS background review pass \u2014 the pinned write guard refuses background changes, so only the foreground may update or archive them. Foreground and delegated-subagent writes to pinned skills remain allowed.\nIf the only skills that need updating are protected, say 'Nothing to save.' and stop.\n\nDo NOT capture as skills (these become persistent self-imposed constraints that bite you later when the environment changes):\n \u2022 Environment-dependent failures: missing binaries, fresh-install errors, post-migration path mismatches, 'command not found', unconfigured credentials, uninstalled packages. The user can fix these \u2014 they are not durable rules.\n \u2022 Negative claims about tools or features ('browser tools do not work', 'X tool is broken', 'cannot use Y'). These harden into refusals the agent cites against itself for months after the actual problem was fixed.\n \u2022 Session-specific transient errors that resolved before the conversation ended. If retrying worked, the lesson is the retry pattern, not the original failure.\n \u2022 One-off task narratives. A user asking 'summarize today's market' or 'analyze this PR' is not a class of work that warrants a skill.\n\nIf a tool failed because of setup state, capture the FIX (install command, config step, env var to set) under an existing setup or troubleshooting skill \u2014 never 'this tool does not work' as a standalone constraint.\n\nAct on whichever of the two dimensions has real signal. If genuinely nothing stands out on either, say 'Nothing to save.' and stop \u2014 but don't reach for that conclusion as a default.";
11
+ export declare const CURATOR_PROMPT = "You are the skill curator. Maintain a healthy, class-level skill library, not a flat pile of narrow one-session skills.\n\nThis is an UMBRELLA-BUILDING consolidation pass, not a passive audit and not a duplicate-finder.\n\nThe goal is a LIBRARY OF CLASS-LEVEL INSTRUCTIONS. A skill collection of many narrow skills where each captures one session's specific bug is a FAILURE of the library. An agent searching skills matches on descriptions, not exact names; one broad umbrella with labeled subsections beats five narrow siblings for discoverability.\n\nRight target shape: class-level skills with rich SKILL.md + references/, templates/, scripts/ support files for session-specific detail.\n\nHard rules:\n1. NEVER hard-delete a skill. Archive (moving to .archive/) is the maximum destructive action; archives are recoverable, deletion is not.\n2. Do not touch bundled, hub-installed, pinned, or scheduled-task-referenced (referenced) skills. Referenced skills are fully protected \u2014 never consolidated, never pruned (there is no scheduled-task reference-rewriting pass; a referenced skill stays in place by design).\n3. Do not archive recently-created or never-used skills without strong evidence. \"use=0\" is NOT evidence either way \u2014 it only means the trigger has not come up yet. Never archive a never-used skill unless it is at least 30 days old AND its content is genuinely obsolete or fully absorbed elsewhere.\n4. Do NOT reject consolidation on the grounds that \"each skill has a distinct trigger\". The right bar is: would a human maintainer write this as N separate skills, or one skill with N labeled subsections? When the answer is the latter, merge.\n5. Judge overlap on CONTENT, not on usage counters.\n6. Before archiving a merged skill, ensure its unique content was preserved in the umbrella.\n\nHow to work:\n1. Scan the candidate list. Identify PREFIX CLUSTERS \u2014 skills sharing a first word or domain keyword. Expected cluster count scales with the library: a large collection may show 10-25 prefix clusters, a small one often has none \u2014 a clean \"nothing to consolidate\" summary is the correct small-library outcome, not a shortage of ambition.\n2. For each cluster with 2+ members, ask \"what is the UMBRELLA CLASS these skills serve?\" and consolidate:\n a. MERGE INTO AN EXISTING UMBRELLA (patch a labeled section for each sibling's unique insight, then archive the siblings).\n b. CREATE A NEW UMBRELLA SKILL.md covering the shared workflow with short labeled subsections, then archive the absorbed siblings.\n c. DEMOTE session-specific detail to references/, templates/, or scripts/ under the umbrella. Use the right directory per kind:\n \u2022 references/<topic>.md \u2014 session-specific detail OR condensed knowledge banks (quoted research, API docs excerpts, domain notes, provider quirks, reproduction recipes) written concise and task-focused.\n \u2022 templates/<name>.<ext> \u2014 starter files meant to be copied and modified.\n \u2022 scripts/<name>.<ext> \u2014 statically re-runnable actions (verification scripts, fixture generators, probes).\n3. Package integrity \u2014 not optional: inspect each skill as a COMPLETE directory package, not just SKILL.md. A skill root may include references/, templates/, scripts/, and assets/. If the source skill has support files OR its SKILL.md contains relative links to them, DO NOT flatten only SKILL.md into <umbrella>/references/<old>.md. Choose one safe path instead: keep it as a standalone skill, OR fully merge by re-homing every needed support file into the umbrella's canonical directories AND rewriting the destination instructions to the new paths, OR archive the entire original skill package unchanged. Never leave demoted instructions pointing at files left behind under the old skill directory.\n4. Flag skills whose NAME is too narrow (contains a PR number, a feature codename, a specific error string, an 'audit'/'diagnosis'/'salvage' session artifact) \u2014 they almost always belong as a subsection or support file under a class-level umbrella.\n5. Iterate. After one consolidation round, scan the remaining set and look for the NEXT umbrella opportunity. Don't stop after 3 merges.\n\nYou are a NOMINATOR, not an executor: this channel has NO tools. Your single deliverable is the structured YAML block below. Never narrate actions you did not take (\"merged\", \"patched\", \"archived\") \u2014 you are proposing, and the deterministic engine executes only names from the candidate pool it gave you. (A future execution view would expose skill_manage; today it does not.)\n\n'keep' is a legitimate decision ONLY when the skill is already a class-level umbrella and none of the proposed merges would improve discoverability. 'This is narrow but distinct from its siblings' is NOT a reason to keep \u2014 it's a reason to move it under an umbrella as a subsection or support file.\n\nExpected output: real umbrella-ification. Process every obvious cluster. If you end the pass with obvious clusters still untouched, you stopped too early \u2014 go back and look at the clusters you left alone.\n\nKeep the umbrella body tight and scannable: exact commands, verbatim paths, ~100-200 lines; never invent flags or APIs.\n\nWhen done, write a human summary THEN the structured machine-readable block. The block is the contract: every skill you would move to .archive/ MUST appear in exactly one of the two lists. Return ONLY the YAML block after the summary \u2014 no post-block prose. Format EXACTLY:\n\n## Structured summary (required)\n```yaml\nconsolidations:\n - from: <old-skill-name>\n mode: reference # optional \u2014 ONLY for a 'demote': source is narrow-but-valuable session detail, write it as references/<source>.md under the umbrella instead of appending to the body. Default is append. Place this line BEFORE into:. NEVER use reference when the source body links its own references/ templates/ scripts/ files.\n into: <umbrella-skill-name>\n reason: <one short sentence \u2014 why merged, not just 'similar'>\nprunings:\n - name: <skill-name>\n reason: <one short sentence \u2014 why archived with no merge target>\n```\n\nEvery skill you would move to .archive/ MUST appear in exactly one of the two lists. If you consolidated X into umbrella Y (patched Y, wrote a references file to Y, or created Y with X's content absorbed), X goes under consolidations with into: Y. If you archived X with no absorption \u2014 truly stale, irrelevant, or obsolete \u2014 X goes under prunings. Leave a list empty (consolidations: []) if none. Do not omit the block. The block comes AFTER your human-readable summary of clusters processed, patches made, and decisions left alone.";
12
12
  export declare const CURATOR_DRY_RUN_BANNER = "\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\nDRY-RUN \u2014 REPORT ONLY. DO NOT MUTATE THE SKILL LIBRARY.\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\nThis is a PREVIEW pass. Follow every instruction above EXCEPT:\n \u2022 Do NOT call skill_manage with action=create, update, patch, delete, write_file, or remove_file.\n \u2022 Do NOT move, copy, or rewrite any file under the skills tree.\n\nYour output IS the deliverable: produce the exact same human-readable summary and YAML block you would on a live run, describing the actions you WOULD take. A reviewer will decide whether to approve a live run.\n\nIf you accidentally take a mutating action, say so explicitly in the summary.";
13
13
  export declare const COMPLETION_SKILL_REVIEW_PROMPT = "[Auto-review \u2014 Skills \u00B7 task complete]\nYour current task now appears complete. Before wrapping up, review the approach and update the skill library via skill_manage.\n\nFollow the skills review policy: be ACTIVE, prefer class-level umbrellas, patch ONLY skills loaded or read this session, and capture non-trivial techniques and user corrections. Do NOT capture environment-dependent failures, negative claims about tools, or one-off task narratives.\n\nDo NOT modify output files or re-run the task. If you are still mid-task, ignore this.";
14
14
  /**
@@ -20,9 +20,9 @@ export declare const COMPLETION_SKILL_REVIEW_PROMPT = "[Auto-review \u2014 Skill
20
20
  */
21
21
  export declare const SKILLS_GUIDANCE = "Skills guidance:\n\u2022 After completing a complex task (5+ tool calls), fixing a tricky error, or discovering a non-trivial workflow, save the approach as a skill with skill_manage so you can reuse it next time.\n\u2022 When using a skill and finding it outdated, incomplete, or wrong, patch it immediately with skill_manage (action='patch') \u2014 don't wait to be asked. Skills that aren't maintained become liabilities.";
22
22
  /** Subagent-channel variant: same review policy, channel-limited deliverable (M-2). */
23
- export declare const SKILL_REVIEW_PLAN_PROMPT = "[Auto-review \u2014 Skills]\nReview the conversation above and update the skill library. Be ACTIVE \u2014 most sessions produce at least one skill update, even if small. A pass that does nothing is a missed learning opportunity, not a neutral outcome.\n\nTarget shape of the library: CLASS-LEVEL skills, each with a rich SKILL.md and a references/ directory for session-specific detail. Not a long flat list of narrow one-session-one-skill entries. This shapes HOW you update, not WHETHER you update.\n\nSignals to look for (any one of these warrants action):\n \u2022 User corrected your style, tone, format, legibility, or verbosity. Frustration signals like 'stop doing X', 'this is too verbose', 'don't format like this', 'why are you explaining', 'just give me the answer', 'you always do Y and I hate it', or an explicit 'remember this' are FIRST-CLASS skill signals, not just memory signals. Update the relevant skill(s) to embed the preference so the next session starts already knowing.\n \u2022 User corrected your workflow, approach, or sequence of steps. Encode the correction as a pitfall or explicit step in the skill that governs that class of task.\n \u2022 Non-trivial technique, fix, workaround, debugging path, or tool-usage pattern emerged that a future session would benefit from. Capture it.\n \u2022 A skill that got loaded or consulted this session turned out to be wrong, missing a step, or outdated. Patch it NOW.\n\nRead-before-write (enforced by this channel): update, patch, delete, or write support files ONLY into skills you loaded or read in THIS session \u2014 ops on unread skills are dropped; CREATE of a brand-new umbrella is the only exception.\n\nPreference order \u2014 prefer the earliest action that fits, but do pick one when a signal above fired:\n 1. UPDATE A CURRENTLY-LOADED SKILL. Look back through the conversation for skills the user loaded or you read. If any of them covers the territory of the new learning, PATCH that one first. It is the skill that was in play, so it's the right one to extend.\n 2. UPDATE AN EXISTING UMBRELLA. If no loaded skill fits but an existing class-level skill does, patch it. Add a subsection, a pitfall, or broaden a trigger.\n 3. ADD A SUPPORT FILE under an existing umbrella. Skills can be packaged with three kinds of support files \u2014 use the right directory per kind:\n \u2022 references/<topic>.md \u2014 session-specific detail (error transcripts, reproduction recipes, provider quirks) AND condensed knowledge banks: quoted research, API docs, external authoritative excerpts, or domain notes you found while working on the problem. Write it concise and for the value of the task, not as a full mirror of upstream docs.\n \u2022 templates/<name>.<ext> \u2014 starter files meant to be copied and modified (boilerplate configs, scaffolding, a known-good example the agent can reproduce with modifications).\n \u2022 scripts/<name>.<ext> \u2014 statically re-runnable actions the skill can invoke directly (verification scripts, fixture generators, deterministic probes, anything the agent should run rather than hand-type each time).\n Add support files via skill_manage action=write_file with file_path starting 'references/', 'templates/', or 'scripts/'. The umbrella's SKILL.md should gain a one-line pointer to any new support file so future agents know it exists.\n 4. CREATE A NEW CLASS-LEVEL UMBRELLA SKILL when no existing skill covers the class. The name MUST be at the class level. The name MUST NOT be a specific PR number, error string, feature codename, library-alone name, or 'fix-X / debug-Y / audit-Z-today' session artifact. If the proposed name only makes sense for today's task, it's wrong \u2014 fall back to (1), (2), or (3).\n\nUser-preference embedding (important): when the user expressed a style/format/workflow preference, the update belongs in the SKILL.md body, not just in memory. Memory captures 'who the user is and what the current situation and state of your operations are'; skills capture 'how to do this class of task for this user'. When they complain about how you handled a task, the skill that governs that task needs to carry the lesson.\n\nIf you notice two existing skills that overlap, note it in your reply \u2014 the background curator handles consolidation at scale.\n\nTwo-tier deposition discipline (DSH addition, same spirit as the umbrella rule): before writing, classify the knowledge:\n \u2022 PATTERN (reusable \u2014 symptom \u2192 mechanism \u2192 fix \u2192 verification, still valuable next session) belongs in the SKILL.md body.\n \u2022 LOG (one-off \u2014 commit SHAs, npm/profile states, what this release changed, this session's process narrative) belongs in a references/ file, never the body. Body density IS reuse rate. Keep new entries tight: a pattern fits in 2-8 physical lines; prefer changing the current-state pointer over appending history.\n\nProtected skills (DO NOT edit these):\n \u2022 Bundled skills (shipped with the platform).\n \u2022 Hub-installed skills (installed from a hub).\nPinned skills are read-only to THIS background review pass \u2014 the pinned write guard refuses background changes, so only the foreground may update or archive them. Foreground and delegated-subagent writes to pinned skills remain allowed.\nIf the only skills that need updating are protected, say 'Nothing to save.' and stop.\n\nDo NOT capture (these become persistent self-imposed constraints that bite you later when the environment changes):\n \u2022 Environment-dependent failures: missing binaries, fresh-install errors, post-migration path mismatches, 'command not found', unconfigured credentials, uninstalled packages. The user can fix these \u2014 they are not durable rules.\n \u2022 Negative claims about tools or features ('browser tools do not work', 'X tool is broken', 'cannot use Y'). These harden into refusals the agent cites against itself for months after the actual problem was fixed.\n \u2022 Session-specific transient errors that resolved before the conversation ended. If retrying worked, the lesson is the retry pattern, not the original failure.\n \u2022 One-off task narratives. A user asking 'summarize today's market' or 'analyze this PR' is not a class of work that warrants a skill.\n\nIf a tool failed because of setup state, capture the FIX (install command, config step, env var to set) under an existing setup or troubleshooting skill \u2014 never 'this tool does not work' as a standalone constraint.\n\n'Nothing to save.' is a real option but should NOT be the default. If the session ran smoothly with no corrections and produced no new technique, just say 'Nothing to save.' and stop. Otherwise, act.\n\nCHANNEL (subagent): this review channel mounts only the read-only `skill` tool \u2014 you have NO `skill_manage`, NO `memory`. Your deliverable is the structured JSON plan below (outputSchema). Describe the patches/creates you RECOMMEND in the plan; never narrate actions you took.";
23
+ export declare const SKILL_REVIEW_PLAN_PROMPT = "[Auto-review \u2014 Skills]\nReview the conversation above and update the skill library. Be ACTIVE \u2014 most sessions produce at least one skill update, even if small. A pass that does nothing is a missed learning opportunity, not a neutral outcome.\n\nTarget shape of the library: CLASS-LEVEL skills, each with a rich SKILL.md and a references/ directory for session-specific detail. Not a long flat list of narrow one-session-one-skill entries. This shapes HOW you update, not WHETHER you update.\n\nSignals to look for (any one of these warrants action):\n \u2022 User corrected your style, tone, format, legibility, or verbosity. Frustration signals like 'stop doing X', 'this is too verbose', 'don't format like this', 'why are you explaining', 'just give me the answer', 'you always do Y and I hate it', or an explicit 'remember this' are FIRST-CLASS skill signals, not just memory signals. Update the relevant skill(s) to embed the preference so the next session starts already knowing.\n \u2022 User corrected your workflow, approach, or sequence of steps. Encode the correction as a pitfall or explicit step in the skill that governs that class of task.\n \u2022 Non-trivial technique, fix, workaround, debugging path, or tool-usage pattern emerged that a future session would benefit from. Capture it.\n \u2022 A skill that got loaded or consulted this session turned out to be wrong, missing a step, or outdated. Patch it NOW.\n\nRead-before-write (enforced by this channel): update, patch, delete, or write support files ONLY into skills you loaded or read in THIS session \u2014 ops on unread skills are dropped; CREATE of a brand-new umbrella is the only exception.\n\nPreference order \u2014 prefer the earliest action that fits, but do pick one when a signal above fired:\n 1. UPDATE A CURRENTLY-LOADED SKILL. Look back through the conversation for skills the user loaded or you read. If any of them covers the territory of the new learning, PATCH that one first. It is the skill that was in play, so it's the right one to extend.\n 2. UPDATE AN EXISTING UMBRELLA. If no loaded skill fits but an existing class-level skill does, patch it. Add a subsection, a pitfall, or broaden a trigger.\n 3. ADD A SUPPORT FILE under an existing umbrella. Skills can be packaged with three kinds of support files \u2014 use the right directory per kind:\n \u2022 references/<topic>.md \u2014 session-specific detail (error transcripts, reproduction recipes, provider quirks) AND condensed knowledge banks: quoted research, API docs, external authoritative excerpts, or domain notes you found while working on the problem. Write it concise and for the value of the task, not as a full mirror of upstream docs.\n \u2022 templates/<name>.<ext> \u2014 starter files meant to be copied and modified (boilerplate configs, scaffolding, a known-good example the agent can reproduce with modifications).\n \u2022 scripts/<name>.<ext> \u2014 statically re-runnable actions the skill can invoke directly (verification scripts, fixture generators, deterministic probes, anything the agent should run rather than hand-type each time).\n Add support files via skill_manage action=write_file with file_path starting 'references/', 'templates/', or 'scripts/'. The umbrella's SKILL.md should gain a one-line pointer to any new support file so future agents know it exists.\n 4. RESTRUCTURE a loaded skill whose body grew log-like \u2014 rc/sha/date-dense sections, session-detail spirals, or a fat body with no support files. Use skill_manage action=restructure with restructure: [{\"heading\": \"<the exact ## heading text>\", \"to_file\": \"references/<topic>.md\"}] \u2014 the ENTIRE ## section (from that heading to the next heading) moves into the support file and its position becomes a pointer line. The skill's name and directory never change. Only propose headings that exist verbatim in the body; never invent one, and never restructure a healthy small skill.\n 5. CREATE A NEW CLASS-LEVEL UMBRELLA SKILL when no existing skill covers the class. The name MUST be at the class level. The name MUST NOT be a specific PR number, error string, feature codename, library-alone name, or 'fix-X / debug-Y / audit-Z-today' session artifact. If the proposed name only makes sense for today's task, it's wrong \u2014 fall back to (1), (2), or (3).\n\nUser-preference embedding (important): when the user expressed a style/format/workflow preference, the update belongs in the SKILL.md body, not just in memory. Memory captures 'who the user is and what the current situation and state of your operations are'; skills capture 'how to do this class of task for this user'. When they complain about how you handled a task, the skill that governs that task needs to carry the lesson.\n\nIf you notice two existing skills that overlap, note it in your reply \u2014 the background curator handles consolidation at scale.\n\nTwo-tier deposition discipline (DSH addition, same spirit as the umbrella rule): before writing, classify the knowledge:\n \u2022 PATTERN (reusable \u2014 symptom \u2192 mechanism \u2192 fix \u2192 verification, still valuable next session) belongs in the SKILL.md body.\n \u2022 LOG (one-off \u2014 commit SHAs, npm/profile states, what this release changed, this session's process narrative) belongs in a references/ file, never the body. Body density IS reuse rate. Keep new entries tight: a pattern fits in 2-8 physical lines; prefer changing the current-state pointer over appending history.\n\nProtected skills (DO NOT edit these):\n \u2022 Bundled skills (shipped with the platform).\n \u2022 Hub-installed skills (installed from a hub).\nPinned skills are read-only to THIS background review pass \u2014 the pinned write guard refuses background changes, so only the foreground may update or archive them. Foreground and delegated-subagent writes to pinned skills remain allowed.\nIf the only skills that need updating are protected, say 'Nothing to save.' and stop.\n\nDo NOT capture (these become persistent self-imposed constraints that bite you later when the environment changes):\n \u2022 Environment-dependent failures: missing binaries, fresh-install errors, post-migration path mismatches, 'command not found', unconfigured credentials, uninstalled packages. The user can fix these \u2014 they are not durable rules.\n \u2022 Negative claims about tools or features ('browser tools do not work', 'X tool is broken', 'cannot use Y'). These harden into refusals the agent cites against itself for months after the actual problem was fixed.\n \u2022 Session-specific transient errors that resolved before the conversation ended. If retrying worked, the lesson is the retry pattern, not the original failure.\n \u2022 One-off task narratives. A user asking 'summarize today's market' or 'analyze this PR' is not a class of work that warrants a skill.\n\nIf a tool failed because of setup state, capture the FIX (install command, config step, env var to set) under an existing setup or troubleshooting skill \u2014 never 'this tool does not work' as a standalone constraint.\n\n'Nothing to save.' is a real option but should NOT be the default. If the session ran smoothly with no corrections and produced no new technique, just say 'Nothing to save.' and stop. Otherwise, act.\n\nCHANNEL (subagent): this review channel mounts only the read-only `skill` tool \u2014 you have NO `skill_manage`, NO `memory`. Your deliverable is the structured JSON plan below (outputSchema). Describe the patches/creates you RECOMMEND in the plan; never narrate actions you took.";
24
24
  /** Subagent-channel variant of the combined review (M-2). */
25
- export declare const COMBINED_REVIEW_PLAN_PROMPT = "[Auto-review]\nReview the conversation above and update two things:\n\n**Memory**: who the user is. Did the user reveal persona, desires, preferences, personal details, or expectations about how you should behave? Save facts about the user and durable preferences with the memory tool.\n\n**Skills**: how to do this class of task. Be ACTIVE \u2014 most sessions produce at least one skill update. A pass that does nothing is a missed learning opportunity, not a neutral outcome.\n\nTarget shape of the skill library: CLASS-LEVEL skills with a rich SKILL.md and a references/ directory for session-specific detail. Not a long flat list of narrow one-session-one-skill entries.\n\nSignals that warrant a skill update (any one is enough):\n \u2022 User corrected your style, tone, format, legibility, verbosity, or approach. Frustration is a FIRST-CLASS skill signal, not just a memory signal. 'stop doing X', 'don't format like this', 'I hate when you Y' \u2014 embed the lesson in the skill that governs that task so the next session starts fixed.\n \u2022 Non-trivial technique, fix, workaround, or debugging path emerged.\n \u2022 A skill that was loaded or consulted turned out wrong, missing, or outdated \u2014 patch it now.\n\nRead-before-write (enforced by this channel): update, patch, delete, or write support files ONLY into skills you loaded or read in THIS session \u2014 ops on unread skills are dropped; CREATE of a brand-new umbrella is the only exception.\n\nPreference order for skills \u2014 pick the earliest that fits:\n 1. UPDATE A CURRENTLY-LOADED SKILL. Check what skills were loaded or read in the conversation. If one of them covers the learning, PATCH it first. It was in play; it's the right place.\n 2. UPDATE AN EXISTING UMBRELLA. Patch it.\n 3. ADD A SUPPORT FILE under an existing umbrella via skill_manage action=write_file. Three kinds: references/<topic>.md for session-specific detail OR condensed knowledge banks (quoted research, API docs excerpts, domain notes) written concise and task-focused; templates/<name>.<ext> for starter files meant to be copied and modified; scripts/<name>.<ext> for statically re-runnable actions (verification, fixture generators, probes). Add a one-line pointer in SKILL.md so future agents find them.\n 4. CREATE A NEW CLASS-LEVEL UMBRELLA when nothing exists. Name at the class level \u2014 NOT a PR number, error string, codename, library-alone name, or 'fix-X / debug-Y' session artifact. If the name only fits today's task, fall back to (1), (2), or (3).\n\nTwo-tier deposition discipline (DSH addition): classify before writing \u2014 PATTERN (symptom \u2192 mechanism \u2192 fix \u2192 verification) goes in the SKILL.md body; LOG (commit SHAs, npm/profile states, this release's change list, this session's narrative) goes in a references/ file. Body density IS reuse rate; a pattern fits in 2-8 physical lines.\n\nUser-preference embedding: when the user complains about how you handled a task, update the skill that governs that task \u2014 memory alone isn't enough. Memory says 'who the user is and what the current situation and state of your operations are'; skills say 'how to do this class of task for this user'. Both should carry user-preference lessons when relevant.\n\nIf you notice overlapping existing skills, mention it \u2014 the background curator handles consolidation.\n\nProtected skills (DO NOT edit these):\n \u2022 Bundled skills (shipped with the platform).\n \u2022 Hub-installed skills (installed from a hub).\nPinned skills are read-only to THIS background review pass \u2014 the pinned write guard refuses background changes, so only the foreground may update or archive them. Foreground and delegated-subagent writes to pinned skills remain allowed.\nIf the only skills that need updating are protected, say 'Nothing to save.' and stop.\n\nDo NOT capture as skills (these become persistent self-imposed constraints that bite you later when the environment changes):\n \u2022 Environment-dependent failures: missing binaries, fresh-install errors, post-migration path mismatches, 'command not found', unconfigured credentials, uninstalled packages. The user can fix these \u2014 they are not durable rules.\n \u2022 Negative claims about tools or features ('browser tools do not work', 'X tool is broken', 'cannot use Y'). These harden into refusals the agent cites against itself for months after the actual problem was fixed.\n \u2022 Session-specific transient errors that resolved before the conversation ended. If retrying worked, the lesson is the retry pattern, not the original failure.\n \u2022 One-off task narratives. A user asking 'summarize today's market' or 'analyze this PR' is not a class of work that warrants a skill.\n\nIf a tool failed because of setup state, capture the FIX (install command, config step, env var to set) under an existing setup or troubleshooting skill \u2014 never 'this tool does not work' as a standalone constraint.\n\nAct on whichever of the two dimensions has real signal. If genuinely nothing stands out on either, say 'Nothing to save.' and stop \u2014 but don't reach for that conclusion as a default.\n\nCHANNEL (subagent): this review channel mounts only the read-only `skill` tool \u2014 you have NO `skill_manage`, NO `memory`. Your deliverable is the structured JSON plan below (outputSchema). Describe the patches/creates you RECOMMEND in the plan; never narrate actions you took.";
25
+ export declare const COMBINED_REVIEW_PLAN_PROMPT = "[Auto-review]\nReview the conversation above and update two things:\n\n**Memory**: who the user is. Did the user reveal persona, desires, preferences, personal details, or expectations about how you should behave? Save facts about the user and durable preferences with the memory tool.\n\n**Skills**: how to do this class of task. Be ACTIVE \u2014 most sessions produce at least one skill update. A pass that does nothing is a missed learning opportunity, not a neutral outcome.\n\nTarget shape of the skill library: CLASS-LEVEL skills with a rich SKILL.md and a references/ directory for session-specific detail. Not a long flat list of narrow one-session-one-skill entries.\n\nSignals that warrant a skill update (any one is enough):\n \u2022 User corrected your style, tone, format, legibility, verbosity, or approach. Frustration is a FIRST-CLASS skill signal, not just a memory signal. 'stop doing X', 'don't format like this', 'I hate when you Y' \u2014 embed the lesson in the skill that governs that task so the next session starts fixed.\n \u2022 Non-trivial technique, fix, workaround, or debugging path emerged.\n \u2022 A skill that was loaded or consulted turned out wrong, missing, or outdated \u2014 patch it now.\n\nRead-before-write (enforced by this channel): update, patch, delete, or write support files ONLY into skills you loaded or read in THIS session \u2014 ops on unread skills are dropped; CREATE of a brand-new umbrella is the only exception.\n\nPreference order for skills \u2014 pick the earliest that fits:\n 1. UPDATE A CURRENTLY-LOADED SKILL. Check what skills were loaded or read in the conversation. If one of them covers the learning, PATCH it first. It was in play; it's the right place.\n 2. UPDATE AN EXISTING UMBRELLA. Patch it.\n 3. ADD A SUPPORT FILE under an existing umbrella via skill_manage action=write_file. Three kinds: references/<topic>.md for session-specific detail OR condensed knowledge banks (quoted research, API docs excerpts, domain notes) written concise and task-focused; templates/<name>.<ext> for starter files meant to be copied and modified; scripts/<name>.<ext> for statically re-runnable actions (verification, fixture generators, probes). Add a one-line pointer in SKILL.md so future agents find them.\n 4. RESTRUCTURE a loaded skill whose body grew log-like (rc/sha/date-dense sections, session-detail spirals, fat body with no support files) via skill_manage action=restructure with restructure: [{\"heading\": \"<the exact ## heading text>\", \"to_file\": \"references/<topic>.md\"}] \u2014 the ENTIRE ## section moves into the support file and its position becomes a pointer line; the skill's name and directory never change. Only propose headings that exist verbatim in the body.\n 5. CREATE A NEW CLASS-LEVEL UMBRELLA when nothing exists. Name at the class level \u2014 NOT a PR number, error string, codename, library-alone name, or 'fix-X / debug-Y' session artifact. If the name only fits today's task, fall back to (1), (2), or (3).\n\nTwo-tier deposition discipline (DSH addition): classify before writing \u2014 PATTERN (symptom \u2192 mechanism \u2192 fix \u2192 verification) goes in the SKILL.md body; LOG (commit SHAs, npm/profile states, this release's change list, this session's narrative) goes in a references/ file. Body density IS reuse rate; a pattern fits in 2-8 physical lines.\n\nUser-preference embedding: when the user complains about how you handled a task, update the skill that governs that task \u2014 memory alone isn't enough. Memory says 'who the user is and what the current situation and state of your operations are'; skills say 'how to do this class of task for this user'. Both should carry user-preference lessons when relevant.\n\nIf you notice overlapping existing skills, mention it \u2014 the background curator handles consolidation.\n\nProtected skills (DO NOT edit these):\n \u2022 Bundled skills (shipped with the platform).\n \u2022 Hub-installed skills (installed from a hub).\nPinned skills are read-only to THIS background review pass \u2014 the pinned write guard refuses background changes, so only the foreground may update or archive them. Foreground and delegated-subagent writes to pinned skills remain allowed.\nIf the only skills that need updating are protected, say 'Nothing to save.' and stop.\n\nDo NOT capture as skills (these become persistent self-imposed constraints that bite you later when the environment changes):\n \u2022 Environment-dependent failures: missing binaries, fresh-install errors, post-migration path mismatches, 'command not found', unconfigured credentials, uninstalled packages. The user can fix these \u2014 they are not durable rules.\n \u2022 Negative claims about tools or features ('browser tools do not work', 'X tool is broken', 'cannot use Y'). These harden into refusals the agent cites against itself for months after the actual problem was fixed.\n \u2022 Session-specific transient errors that resolved before the conversation ended. If retrying worked, the lesson is the retry pattern, not the original failure.\n \u2022 One-off task narratives. A user asking 'summarize today's market' or 'analyze this PR' is not a class of work that warrants a skill.\n\nIf a tool failed because of setup state, capture the FIX (install command, config step, env var to set) under an existing setup or troubleshooting skill \u2014 never 'this tool does not work' as a standalone constraint.\n\nAct on whichever of the two dimensions has real signal. If genuinely nothing stands out on either, say 'Nothing to save.' and stop \u2014 but don't reach for that conclusion as a default.\n\nCHANNEL (subagent): this review channel mounts only the read-only `skill` tool \u2014 you have NO `skill_manage`, NO `memory`. Your deliverable is the structured JSON plan below (outputSchema). Describe the patches/creates you RECOMMEND in the plan; never narrate actions you took.";
26
26
  export declare function reviewPrompt(kind: 'memory' | 'skill' | 'combined', channel?: 'agent' | 'plan'): string;
27
27
  export interface PromptBundle {
28
28
  id: string;
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Skill structure-health domain (rc.73 A1, 008 design): a SECOND assessment
3
+ * dimension beside the six-factor usage quality — document hygiene, consumed
4
+ * by the curator health view and the `/evolution skills health` command.
5
+ *
6
+ * PURE and DERIVED: nothing is persisted; every assessment is computed from
7
+ * file facts at read time. The judgment split follows the original's
8
+ * boundary/治理 layering — deterministic signals here, refinement proposals
9
+ * stay in the review/curator judgment layer. Never become a 7th factor of
10
+ * `computeQualityScores` (different dimension, different consumers).
11
+ */
12
+ export interface SkillHealthThresholds {
13
+ /** Soft body limit: body chars at/below stay 'healthy' by size; above ->
14
+ * 'warn'; >= 2x -> 'needs-restructure'. */
15
+ softBodyChars: number;
16
+ /** Stamp-density ceiling per KB of body text: rc.NN / commit shas / ISO
17
+ * dates per KB at/above this -> 'warn' (log-like content living in the
18
+ * body — the "invalid info" indicator). */
19
+ stampDensityPerKb: number;
20
+ /** Patch count at/above this with zero reads -> 'warn' (write-ghost: the
21
+ * skill is churned but nothing ever loads it). */
22
+ churnMinPatches: number;
23
+ }
24
+ export declare const DEFAULT_HEALTH_THRESHOLDS: SkillHealthThresholds;
25
+ export type SkillHealthVerdict = 'healthy' | 'warn' | 'needs-restructure';
26
+ /** Facts a caller already has; assessors never do IO. */
27
+ export interface SkillHealthSnapshot {
28
+ skillName: string;
29
+ bodyChars: number;
30
+ bodyText?: string | undefined;
31
+ /** Number of non-empty support groups (references/ templates/ scripts/). */
32
+ supportGroups: number;
33
+ /** Usage-side patch count, when the caller has it (A2 churn dimension). */
34
+ patchCount?: number | undefined;
35
+ /** Usage-side view count, when the caller has it (A2 churn dimension). */
36
+ readCount?: number | undefined;
37
+ }
38
+ export interface SkillHealthDim {
39
+ bodyChars: number;
40
+ stampDensityPerKb: number | null;
41
+ supportGroups: number;
42
+ /** Usage churn facts; null when the caller supplied no counts. */
43
+ churnPatches: number | null;
44
+ churnReads: number | null;
45
+ }
46
+ export interface SkillHealthAssessment {
47
+ verdict: SkillHealthVerdict;
48
+ dims: SkillHealthDim;
49
+ reasons: string[];
50
+ }
51
+ export declare function assessStructureHealth(snapshot: SkillHealthSnapshot, thresholds?: SkillHealthThresholds): SkillHealthAssessment;
52
+ //# sourceMappingURL=skill-health.d.ts.map
@@ -8,6 +8,7 @@
8
8
  */
9
9
  import { type EvolutionIoLike } from './io.ts';
10
10
  import { type MutationRecord } from './mutations.ts';
11
+ import { type SkillHealthAssessment, type SkillHealthThresholds } from './skill-health.ts';
11
12
  import type { EvolutionSkillMutatedEvent } from './events.ts';
12
13
  export interface SkillLimits {
13
14
  maxNameLength: number;
@@ -29,6 +30,22 @@ export interface SkillActionResult {
29
30
  message: string;
30
31
  path?: string;
31
32
  }
33
+ /**
34
+ * One section move of a restructure proposal (008 batch B): a body section
35
+ * anchored by its exact `## heading` line is moved to a references/ support
36
+ * file and replaced by a pointer line. The skill name/dir never change —
37
+ * restructure is a content-distribution repair, not a routing change.
38
+ */
39
+ export interface SkillRestructureMove {
40
+ /** The `##` heading title, matched as an exact line (leading `##` + spaces); no fuzzy matching. */
41
+ heading: string;
42
+ /** Destination support file: `references/<topic>.md` (references/ only — moved content is log/detail, never a template or script). */
43
+ toFile: string;
44
+ }
45
+ /** Upper bound of moves per restructure proposal (validator and core agree). */
46
+ export declare const MAX_RESTRUCTURE_MOVES = 5;
47
+ /** Restructure targets are plain markdown files under references/ — no subdirectories, no other support kind. */
48
+ export declare const RESTRUCTURE_TARGET_RE: RegExp;
32
49
  /** Extra file name carried inside a snapshot's `extras/` directory. */
33
50
  export declare const SNAPSHOT_EXTRA_NAME_RE: RegExp;
34
51
  /** An opaque side file stored under a snapshot's `extras/` (curator state etc.). */
@@ -151,6 +168,16 @@ export declare class SkillLibrary {
151
168
  isPinned(rawName: string): Promise<boolean>;
152
169
  /** Count non-empty support subdirectories (richness input for quality scoring). */
153
170
  countSupportDirs(rawName: string): Promise<number>;
171
+ /**
172
+ * Structure-health facts for one skill (rc.73 A1, 008 design): body
173
+ * chars/density from SKILL.md, support groups from countSupportDirs, plus
174
+ * optional usage counts (A2 churn dimension) when the caller has them.
175
+ * Derived, never persisted; null when the skill is unreadable.
176
+ */
177
+ assessHealth(rawName: string, thresholds?: SkillHealthThresholds, counts?: {
178
+ patchCount?: number;
179
+ readCount?: number;
180
+ }): Promise<SkillHealthAssessment | null>;
154
181
  /** Best-effort audit trail entry; never blocks the mutation. */
155
182
  private audit;
156
183
  /** Recent mutation audit records (read-only inspection surface). */
@@ -170,8 +197,44 @@ export declare class SkillLibrary {
170
197
  * Merge the bodies of `sources` into `target` and archive the sources with
171
198
  * an absorbed-into marker. Hermes-style consolidation: overlapping skills
172
199
  * collapse into one, and the originals stay recoverable under `.archive/`.
200
+ *
201
+ * `mode:'append'` (default) appends each source body to the target. The
202
+ * target write goes through the tree-change kernel (009) — byte-level
203
+ * rollback, audit and the mutation event are kernel-owned. Package-integrity
204
+ * (009-I): append-mode consolidation REFUSES a source whose directory has
205
+ * support files or whose body carries support-directory links — an append
206
+ * would leave those references pointing at an archived package (dangling);
207
+ * the refusal message directs to the reference mode / whole-package archive.
208
+ *
209
+ * `mode:'reference'` writes each source's body (frontmatter stripped) into
210
+ * `target/references/<source>.md` and archives the source — the demote path
211
+ * (009-II). A source body with support-directory links is refused there too
212
+ * (the references file would carry links whose files were archived).
213
+ */
214
+ consolidate(target: string, sources: string[], origin?: WriteOrigin, options?: {
215
+ mode?: 'append' | 'reference';
216
+ }): Promise<SkillActionResult>;
217
+ /**
218
+ * Content-distribution repair (008 batch B, 009-R kernel): move body
219
+ * sections — anchored by their exact `## heading` lines — into references/
220
+ * support files and replace each span with a pointer line. The skill
221
+ * name/dir never change (routing stays; only content location shifts, so a
222
+ * fat body sheds its log-like detail). Deterministic, never automatic:
223
+ * candidates come from an approved review plan. The write batch goes
224
+ * through the tree-change kernel — one commit point, byte-level rollback.
225
+ * Package integrity (009-R): a moved section whose text carries
226
+ * support-directory links is refused (those links' files stay behind in the
227
+ * same package; the moved text belongs in references/ beside them).
228
+ */
229
+ restructure(rawName: string, moves: SkillRestructureMove[], origin?: WriteOrigin): Promise<SkillActionResult>;
230
+ /**
231
+ * Unified tree-change commit point (009 kernel): owns validation order,
232
+ * pre-read rollback bytes, two-phase write with byte-level rollback, audit
233
+ * and the mutation event. Mutators compose `TreeChangePlan`s — consolidate,
234
+ * restructure (and future reference-mode consolidations) never implement
235
+ * two-phase commit themselves.
173
236
  */
174
- consolidate(target: string, sources: string[], origin?: WriteOrigin): Promise<SkillActionResult>;
237
+ private applyTreeChange;
175
238
  /**
176
239
  * Restore one skill from `.archive/` back to the active root. Hermes-style
177
240
  * recoverability: archival never deletes, and this is the control-plane
@@ -80,6 +80,14 @@ export declare function bumpUse(map: UsageMap, name: string, when?: Date): void;
80
80
  export declare function bumpPatch(map: UsageMap, name: string, when?: Date): void;
81
81
  export declare function markAgentCreated(map: UsageMap, name: string): void;
82
82
  export declare function latestActivityAt(record: UsageRecord): string | null;
83
+ /**
84
+ * Whether the library has ANY observed read evidence (C observation window):
85
+ * reads were invisible to the usage sidecar before A2, so `view_count` zero
86
+ * means "never read" ONLY after the first observed read exists anywhere in
87
+ * the map. Before that, churn-based signals (write-ghost) are untrustworthy
88
+ * and callers must suppress them. Pure and derived — never persisted.
89
+ */
90
+ export declare function usageObserved(usage: ReadonlyMap<string, UsageRecord>): boolean;
83
91
  /**
84
92
  * Curator suppression sidecar: built-in skills the curator has archived stay
85
93
  * suppressed across re-seeds, so the lifecycle never fights a re-created
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",
4
+ "version": "0.2.0-rc.2",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },