@lmzhen/dsh-evolution-core 0.2.1 → 0.3.0

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
@@ -998,8 +998,8 @@ async function readEvolutionTimeline(io, path) {
998
998
  * changes semantically: the bundle digest is the fail-closed signal for
999
999
  * review workers, so a stale id across deployments must be distinguishable.
1000
1000
  */
1001
- const PROMPT_BUNDLE_ID = "dsh-evolution@9";
1002
- const PROMPT_BUNDLE_VERSION = 9;
1001
+ const PROMPT_BUNDLE_ID = "dsh-evolution@10";
1002
+ const PROMPT_BUNDLE_VERSION = 10;
1003
1003
  const MEMORY_REVIEW_PROMPT = `[Auto-review — Memory]
1004
1004
  Review the conversation above and consider saving to memory if appropriate.
1005
1005
 
@@ -1170,6 +1170,107 @@ Follow the skills review policy: be ACTIVE, prefer class-level umbrellas, patch
1170
1170
 
1171
1171
  Do NOT modify output files or re-run the task. If you are still mid-task, ignore this.`;
1172
1172
  /**
1173
+ * Maintenance-subagent persona (design 011 §6). Constants pin the persona
1174
+ * with `{signal:id}` placeholders; the renderer substitutes names from the
1175
+ * drift-signals module (single vocabulary). The model-facing contract is the
1176
+ * persona + the mechanical-facts block; the signature head lets the model
1177
+ * compare the two heads (011 mismatch protocol).
1178
+ */
1179
+ const MAINTAIN_PROMPT = `<<<MAINTAIN_PROMPT v={bundle_version} sig={joint_signature}>>>
1180
+
1181
+ ## 角色
1182
+ 你是技能库维护审查代理,只读:你没有任何写工具。基于机械事实块与本文规则,识别
1183
+ 域漂移与层漂移,输出结构化维护计划。你只输出计划,从不执行;执行由用户命令与审批完成。
1184
+
1185
+ ## 1. 输入契约(最高优先级,冲突时以此为准)
1186
+ 机械事实块 <<<MECHANICAL_FACTS v={signals_version} sig={joint_signature}>>>(下方,
1187
+ 以 <<<END FACTS>>> 闭合)是唯一证据来源;每条信号的名字、阈值、verdict 与本文规则引用一一对应。
1188
+ - verdict 语义(枚举,不可引申):pass=未越阈(含低于阈值);over=越阈;unknown=脚本未检测。
1189
+ - over 不是违规裁决:它只描述 "事实相对阈值的位置";是否构成漂移只由 §3 对应条款的
1190
+ 语义判断给出。没有条款对应的事实,不产生建议。
1191
+ - unknown ≠ pass:未检测 ≠ 健康;引用 unknown 信号的条目必须 needs_human:true。
1192
+ - 事实只读:不改写、不补写缺失信号、不把事实 "翻译" 成裁决。
1193
+ - 版本失配:若两处 sig 不一致(或任一缺失),禁止输出计划——只输出 MISMATCH + 两侧
1194
+ 版本号 + 停止。
1195
+
1196
+ ## 2. 审查领域
1197
+ - 域漂移:类级伞形碎裂为窄技能、同域近重复组未合并、孤立新域入侵、前缀聚类碎片化。
1198
+ - 层漂移:知识在「正文/支持文件/记忆」三层间错位——正文 log 化、实录未沉淀、
1199
+ 模式沉底、支持文件无指针、同一事实多地重复表述。
1200
+
1201
+ ## 3. 检查清单(规则 = 触发信号 → 语义判断 → 输出形态)
1202
+ environment signals:{signal:usage_observed}、{signal:quality_low}——校验器对 quality_low=unknown 的技能
1203
+ 全局施加 needs_human:true(机械判据见 §4/§7),模板侧不重复表述。
1204
+
1205
+ A. 域·碎片化
1206
+ - A1 当 {signal:dedup_group}=over:判断近重复组是否属同一类级伞形的可合并小节;
1207
+ 是 → relationship-level consolidate 建议;否 → 不输出。
1208
+ - A2 当 {signal:narrow_name}=over:结合 description/正文语义判窄名是否 "仅对今日任务成立";
1209
+ 成立 → skill-level 改名/归档建议;若实为用户内部代号(格式合规语义窄)→ contextual
1210
+ 证据、confidence≤0.4、needs_human:true。
1211
+ - A3 当 {signal:prefix_cluster}=over:判簇内技能是否同伞;非同伞 → notes 区提出域划分
1212
+ 观察,不强制建伞。
1213
+
1214
+ B. 层·分层错位
1215
+ - B1 当 {signal:stamp_density}=over 或 {signal:body_size}=over:逐技能判时间戳/编号为
1216
+ 追溯锚(跨文档检索锚)还是日志残留(过程叙事/状态快照);
1217
+ 锚 → 允许保留,semantic_reasoning 列明判据,needs_human:true;
1218
+ 残留 → restructure 建议(movable headings 逐字引用)。over 是开关,不是结论。
1219
+ - B2 当 {signal:pointer_missing}=over:检查支持文件内容形态——可复用模式 → 建议上移
1220
+ 正文;会话专属实录 → 建议保留并补正文指针。输出为 patch 指引(§9 执行形态)。
1221
+ - B3 当 {signal:dup_heading}=over:建议删除多余标题行(保留一份),输出为 patch 指引。
1222
+ - B4 当 {signal:overlong_line}=over:建议拆行,输出为 patch 指引。
1223
+ - B5 当 {signal:description_chars}=over:建议缩短 description 到 ≤60,输出为 patch 指引;
1224
+ 不得建议改动正文其他部分。
1225
+
1226
+ D. 库·整合纪律(计划形态约束,不是信号)
1227
+ - D1 同类问题多处出现 → 合成一条 relationship-level 建议,不逐项输出。
1228
+ - D2 结构类问题优先级高于内容类;影响面 library-level > relationship-level > skill-level。
1229
+
1230
+ 可选工具:maintenance_probe(只读)——按需深挖信号详情(单技能全文密度分布、簇完整
1231
+ 成员、指针明细);探针输出仅用于补充判断(影响 confidence/semantic_reasoning),
1232
+ 不得引入事实块之外的新证据 id。
1233
+
1234
+ ## 4. 输出契约(校验器机械执行)
1235
+ {verdict: "issues" | "no_issues",
1236
+ plan: [{ kind: "skill-level"|"relationship-level"|"library-level",
1237
+ names: [str], rule: "A1"|"B2"|...,
1238
+ evidence: [{signal, value}], // 必须逐字来自事实块;禁止引用事实块外信号
1239
+ finding: "<一句事实描述:引用信号 id 与值;零裁决动词>",
1240
+ recommendation: "<唯一允许的'应'句:建议动作与理由;并写明执行形态(命令/patch 指引)>",
1241
+ semantic_reasoning: "<语义判据;含 LLM 推断时 confidence≤0.4>",
1242
+ impact: "better|worse|neutral", impact_reason: "<相对'不动'的净影响>",
1243
+ reversibility: "archive|restructure|patch|rename|none", undo_path: "<一步撤销方式>",
1244
+ confidence: float, // machine ≥0.6;contextual ≤0.4
1245
+ needs_human: bool, is_override: bool, override_reason: "<仅 is_override>" }],
1246
+ notes: [str]}
1247
+ - verdict=no_issues ⇒ plan=[](机械判定,不允许空 plan 之外的 "无问题" 表述)。
1248
+ - confidence 判据:机器证据项 ∈ [0.6, 0.9];contextual 项 ≤0.4;无第三区间——
1249
+ needs_human = (confidence < 0.6) OR (不可逆) OR (is_override) OR (引用 unknown 信号)。
1250
+ - 语言:finding/recommendation/notes 用与库一致的正文语言(中文);字段名/信号 id/枚举
1251
+ 保留英文。
1252
+
1253
+ ## 5. 裁决纪律
1254
+ - 禁用:"根据注入事实 X,该技能应当 Y"——事实段没有 "应当" 权限。
1255
+ - 锚 vs 残留示例:✅ 当 {signal:stamp_density}=over({signal:stamp_density.threshold} 阈)
1256
+ 且时间戳为跨文档检索锚 → B1:不迁,needs_human,semantic_reasoning=锚判据。
1257
+ ❌ 当 {signal:stamp_density}=over → "该技能日志化,应 restructure"(over 是开关不是结论)。
1258
+ - 申诉:机械阈值与语义判断冲突 → is_override:true + override_reason + needs_human:true;
1259
+ 不得静默绕过阈值。
1260
+ - 不动作合法:verdict=no_issues 是合法输出,不是失败;连续空报告 = 信号定义问题,
1261
+ 不是 "更积极" 的信号。
1262
+ - 错误成本:不可逆动作(rename)必须 needs_human:true;可逆动作(archive/restructure
1263
+ 两阶段)可 needs_human:false 但 undo_path 必填。
1264
+ - 不做:不建议删除(只建议 archive);不提升内容质量(结构审查只 flag 位置/归属/分层);
1265
+ protected 集(bundled/hub 安装/pinned)内 0 建议。
1266
+
1267
+ ## 6. 泛化注意事项
1268
+ - 信号集开放:事实块含、§3 未列的信号 → notes 区提出 "该信号值得新增条款",禁止
1269
+ 解释为 "已知问题"。
1270
+ - 条款同构扩展:新增检查 = drift-signals 加定义 + §3 加一条 "信号→语义→输出" 条款。
1271
+ - 库规模无关:判据是事实与条款,不是库体量印象。
1272
+ - 信号机制疑问(阈值、检测原理)→ 写 needs_human,不猜测机制。`;
1273
+ /**
1173
1274
  * System-prompt guidance section (Hermes `SKILLS_GUIDANCE`, DSH-adapted).
1174
1275
  * Registered as a system-prompt section by tool-skill-manage (it mounts
1175
1276
  * exactly when `skill_manage` is available — the DSH analogue of Hermes'
@@ -1198,12 +1299,12 @@ function sha256(text) {
1198
1299
  function createPromptBundle(prompts) {
1199
1300
  const canonical = JSON.stringify({
1200
1301
  id: PROMPT_BUNDLE_ID,
1201
- version: 9,
1302
+ version: 10,
1202
1303
  prompts: Object.fromEntries(Object.entries(prompts).sort())
1203
1304
  });
1204
1305
  return Object.freeze({
1205
1306
  id: PROMPT_BUNDLE_ID,
1206
- version: 9,
1307
+ version: 10,
1207
1308
  prompts: Object.freeze({ ...prompts }),
1208
1309
  sha256: sha256(canonical)
1209
1310
  });
@@ -1216,13 +1317,14 @@ const PROMPT_BUNDLE = createPromptBundle({
1216
1317
  combinedPlan: COMBINED_REVIEW_PLAN_PROMPT,
1217
1318
  curator: CURATOR_PROMPT,
1218
1319
  completion: COMPLETION_SKILL_REVIEW_PROMPT,
1320
+ maintain: MAINTAIN_PROMPT,
1219
1321
  skillsGuidance: SKILLS_GUIDANCE
1220
1322
  });
1221
1323
  function verifyPromptBundle(bundle = PROMPT_BUNDLE) {
1222
- if (bundle.id !== "dsh-evolution@9" || bundle.version !== 9) return false;
1324
+ if (bundle.id !== "dsh-evolution@10" || bundle.version !== 10) return false;
1223
1325
  const canonical = JSON.stringify({
1224
1326
  id: PROMPT_BUNDLE_ID,
1225
- version: 9,
1327
+ version: 10,
1226
1328
  prompts: Object.fromEntries(Object.entries(bundle.prompts).sort())
1227
1329
  });
1228
1330
  return bundle.sha256 === sha256(canonical);
@@ -2199,6 +2301,37 @@ function computePrefixClusters(names) {
2199
2301
  })).sort((a, b) => b.members.length - a.members.length || a.key.localeCompare(b.key));
2200
2302
  }
2201
2303
  //#endregion
2304
+ //#region lib/types/redact.js
2305
+ /**
2306
+ * Credential-shaped text redaction for cross-boundary model inputs (011 §8).
2307
+ *
2308
+ * The maintenance facts block and probe output — like the review snapshot
2309
+ * before them — leave the owning session's context, so secret-shaped text is
2310
+ * masked before it is sent. Best-effort and conservative: targets well-known
2311
+ * secret shapes and inline assignment patterns, never wholesale content.
2312
+ * Migrated from `evolution-review` so both channels share one implementation.
2313
+ */
2314
+ const SECRET_PATTERNS = [
2315
+ ["openai-style key", /sk-[A-Za-z0-9_-]{16,}/g],
2316
+ ["aws access key", /AKIA[0-9A-Z]{16}/g],
2317
+ ["github token", /gh[pousr]_[A-Za-z0-9]{20,}/g],
2318
+ ["gitlab token", /glpat-[A-Za-z0-9_-]{16,}/g],
2319
+ ["slack token", /xox[baprs]-[A-Za-z0-9-]{10,}/g],
2320
+ ["jwt", /eyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}/g],
2321
+ ["bearer credential", /Bearer [A-Za-z0-9._~+/=\-]{16,}/g],
2322
+ ["inline assignment", /(\b(?:token|api[_-]?key|secret|password|passwd)\b[\s]*[:=][\s]*["']?)([A-Z0-9._~+/=\-]{12,})/gi]
2323
+ ];
2324
+ /**
2325
+ * Mask credential-shaped text before it crosses a session boundary.
2326
+ * @param text - the text about to be sent to a model outside this session.
2327
+ * @returns the text with matched secrets replaced by `<redacted>`.
2328
+ */
2329
+ function redactSecrets(text) {
2330
+ let out = text;
2331
+ for (const [, pattern] of SECRET_PATTERNS) out = out.replace(pattern, (_match, p1) => p1 === void 0 ? "<redacted>" : `${p1}<redacted>`);
2332
+ return out;
2333
+ }
2334
+ //#endregion
2202
2335
  //#region lib/types/skill-health.js
2203
2336
  /**
2204
2337
  * Skill structure-health domain (rc.73 A1, 008 design): a SECOND assessment
@@ -2216,6 +2349,7 @@ const DEFAULT_HEALTH_THRESHOLDS = {
2216
2349
  stampDensityPerKb: 2,
2217
2350
  churnMinPatches: 20
2218
2351
  };
2352
+ /** Stamp regex shared by health assessment and the maintenance probe (single source, 011). */
2219
2353
  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
2354
  /**
2221
2355
  * Bodies below this size skip stamp-density assessment: a few dates or shas
@@ -2758,6 +2892,32 @@ var SkillLibrary = class {
2758
2892
  return count;
2759
2893
  }
2760
2894
  /**
2895
+ * Relative support-file paths (`references/x.md`) under SUPPORT_DIRS for one
2896
+ * skill; empty when the directory is unreadable or has no support files.
2897
+ * Used by the maintenance enrichment (011 §7) and probe reads.
2898
+ */
2899
+ async listSupportFiles(rawName) {
2900
+ const dir = this.dirOf(rawName);
2901
+ let entries;
2902
+ try {
2903
+ entries = await this.io.list(dir);
2904
+ } catch {
2905
+ return [];
2906
+ }
2907
+ const out = [];
2908
+ for (const subdir of SUPPORT_DIRS) {
2909
+ if (!entries.includes(subdir)) continue;
2910
+ try {
2911
+ const files = await this.io.list(join(dir, subdir));
2912
+ for (const file of files) {
2913
+ if (file === ".gitkeep" || file.startsWith(".")) continue;
2914
+ out.push(`${subdir}/${file}`);
2915
+ }
2916
+ } catch {}
2917
+ }
2918
+ return out;
2919
+ }
2920
+ /**
2761
2921
  * Structure-health facts for one skill (rc.73 A1, 008 design): body
2762
2922
  * chars/density from SKILL.md, support groups from countSupportDirs, plus
2763
2923
  * optional usage counts (A2 churn dimension) when the caller has them.
@@ -3685,6 +3845,163 @@ var SkillLibrary = class {
3685
3845
  }
3686
3846
  };
3687
3847
  //#endregion
3848
+ //#region lib/types/drift-signals.js
3849
+ /**
3850
+ * Library-level drift signals for the maintenance subagent (design 011).
3851
+ *
3852
+ * Deterministic fact checks over a skill-library snapshot: domain drift
3853
+ * (narrow names, near-duplicate groups, prefix clusters) and layer drift
3854
+ * (log-like bodies, duplicate headings, overlong lines, missing support-file
3855
+ * pointers, description over the authoring bar). Pure functions only — no IO,
3856
+ * no LLM, no services. Thresholds are imported from their owning modules
3857
+ * (skill-health / quality / skill-store), never duplicated.
3858
+ *
3859
+ * Distinct from `signals.ts` — the session-level review signal gate.
3860
+ */
3861
+ /** Physical line length at/above which a body line is reported overlong (011 §4). */
3862
+ const DRIFT_MAX_LINE_CHARS = 1500;
3863
+ /** Signal-set version: bump whenever ids/thresholds change (011 §7 version coupling). */
3864
+ const DRIFT_SIGNALS_VERSION = "1";
3865
+ /** Render-time nouns for the MAINTAIN_PROMPT placeholders (single vocabulary with the facts block). */
3866
+ const DRIFT_SIGNAL_NOUNS = {
3867
+ dedup_group: "近重复组",
3868
+ prefix_cluster: "前缀聚类",
3869
+ stamp_density: "stamp 密度",
3870
+ body_size: "正文体量",
3871
+ dup_heading: "重复标题",
3872
+ overlong_line: "超长行",
3873
+ pointer_missing: "缺失指针",
3874
+ description_chars: "描述长度",
3875
+ narrow_name: "窄名",
3876
+ usage_observed: "使用观察",
3877
+ quality_low: "质量分"
3878
+ };
3879
+ const NARROW_NAME_PATTERNS = [
3880
+ {
3881
+ label: "error-string",
3882
+ re: /^(?:err|error|exception|traceback|warn|fail)(?:[-_][a-z0-9]+)+$/i
3883
+ },
3884
+ {
3885
+ label: "pr-number",
3886
+ re: /^(?:pr|issue)[-_]?\d{2,}$/i
3887
+ },
3888
+ {
3889
+ label: "dated",
3890
+ re: /\d{4}-\d{2}-\d{2}/
3891
+ },
3892
+ {
3893
+ label: "session-verb",
3894
+ re: /^(?:fix|debug|audit|salvage|diagnose|investigate)[-_][a-z0-9-]+$/i
3895
+ }
3896
+ ];
3897
+ /** Detect support files the body never references (by basename or relative path). */
3898
+ function missingSupportPointers(body, supportFiles) {
3899
+ return supportFiles.filter((path) => {
3900
+ const base = path.split("/").pop() ?? path;
3901
+ return base.length > 0 && !body.includes(base) && !body.includes(path);
3902
+ });
3903
+ }
3904
+ /** Duplicate `## heading` occurrences: singleton results default to head of the file. */
3905
+ function duplicateHeadings(body) {
3906
+ const counts = /* @__PURE__ */ new Map();
3907
+ for (const line of body.split("\n")) {
3908
+ const m = /^##\s+(.+)$/.exec(line);
3909
+ if (m?.[1]) {
3910
+ const heading = m[1].trim();
3911
+ if (heading) counts.set(heading, (counts.get(heading) ?? 0) + 1);
3912
+ }
3913
+ }
3914
+ return [...counts.entries()].filter(([, count]) => count > 1).map(([heading, count]) => ({
3915
+ heading,
3916
+ count
3917
+ }));
3918
+ }
3919
+ /** Physical lines over `max` characters: `{ lineNo, chars }`, 1-based line numbers. */
3920
+ function overlongLines(body, max = DRIFT_MAX_LINE_CHARS) {
3921
+ const out = [];
3922
+ const lines = body.split("\n");
3923
+ for (let index = 0; index < lines.length; index += 1) {
3924
+ const length = (lines[index] ?? "").length;
3925
+ if (length > max) out.push({
3926
+ lineNo: index + 1,
3927
+ chars: length
3928
+ });
3929
+ }
3930
+ return out;
3931
+ }
3932
+ /** Narrow-name shapes detected in a skill name (empty = none). */
3933
+ function narrowNameMatches(name) {
3934
+ return NARROW_NAME_PATTERNS.filter(({ re }) => re.test(name)).map(({ label }) => label);
3935
+ }
3936
+ function supportGroupCount(supportFiles) {
3937
+ const groups = /* @__PURE__ */ new Set();
3938
+ for (const path of supportFiles ?? []) {
3939
+ const head = path.split("/")[0];
3940
+ if (head) groups.add(head);
3941
+ }
3942
+ return groups.size;
3943
+ }
3944
+ function sig(id, verdict, value, threshold, detail) {
3945
+ return {
3946
+ id,
3947
+ verdict,
3948
+ value,
3949
+ threshold,
3950
+ detail
3951
+ };
3952
+ }
3953
+ /**
3954
+ * Compute all drift signals for a snapshot. Missing inputs (quality score,
3955
+ * usage window) yield `unknown` — never a fabricated verdict.
3956
+ */
3957
+ function computeDriftSignals(snapshots) {
3958
+ const library = [];
3959
+ const names = snapshots.map((s) => s.name);
3960
+ const dedup = computeDedupGroups({ contents: new Map(snapshots.map((s) => [s.name, s.body])) });
3961
+ library.push(dedup.length === 0 ? sig("dedup_group", "pass", "none", "size >= 2") : sig("dedup_group", "over", dedup.map((group) => group.join(", ")).join(" | "), "size >= 2", `members=${dedup.map((group) => group.join("|")).join(";")}`));
3962
+ const clusters = computePrefixClusters(names);
3963
+ library.push(clusters.length === 0 ? sig("prefix_cluster", "pass", "none", "size >= 2") : sig("prefix_cluster", "over", clusters.map((cluster) => cluster.members.join(", ")).join(" | "), "size >= 2", `key=${clusters.map((cluster) => cluster.key).join("|")}`));
3964
+ const allProvided = snapshots.length > 0 && snapshots.every((s) => s.usageObserved !== null && s.usageObserved !== void 0);
3965
+ library.push(!allProvided ? sig("usage_observed", "unknown", "not-observed", void 0, "usage window status missing") : snapshots.every((s) => s.usageObserved === true) ? sig("usage_observed", "pass", "observed") : sig("usage_observed", "pass", "unobserved"));
3966
+ return {
3967
+ library,
3968
+ skills: snapshots.map((snapshot) => {
3969
+ const signals = [];
3970
+ const body = snapshot.body;
3971
+ const supportFiles = snapshot.supportFiles ?? [];
3972
+ const supportEnumerated = snapshot.supportFiles !== void 0;
3973
+ const density = assessStructureHealth({
3974
+ skillName: snapshot.name,
3975
+ bodyChars: body.length,
3976
+ bodyText: body,
3977
+ supportGroups: supportGroupCount(supportFiles)
3978
+ }, DEFAULT_HEALTH_THRESHOLDS).dims.stampDensityPerKb;
3979
+ signals.push(density === null ? sig("stamp_density", "pass", body.length < 2e3 ? "below-min-body" : "not-assessed") : sig("stamp_density", density >= DEFAULT_HEALTH_THRESHOLDS.stampDensityPerKb ? "over" : "pass", `${density.toFixed(2)}/KB`, `${DEFAULT_HEALTH_THRESHOLDS.stampDensityPerKb}/KB`));
3980
+ signals.push(sig("body_size", body.length >= DEFAULT_HEALTH_THRESHOLDS.softBodyChars ? "over" : "pass", `${body.length}`, `${DEFAULT_HEALTH_THRESHOLDS.softBodyChars}`));
3981
+ const dupes = duplicateHeadings(body);
3982
+ signals.push(dupes.length === 0 ? sig("dup_heading", "pass", "none") : sig("dup_heading", "over", dupes.map((d) => `${d.heading}(${d.count})`).join(", "), "count >= 2"));
3983
+ const long = overlongLines(body);
3984
+ signals.push(long.length === 0 ? sig("overlong_line", "pass", "none") : sig("overlong_line", "over", long.map((l) => `${l.lineNo}:${l.chars}`).join(", "), `${DRIFT_MAX_LINE_CHARS}`));
3985
+ const missing = supportEnumerated ? missingSupportPointers(body, supportFiles) : void 0;
3986
+ signals.push(!supportEnumerated ? sig("pointer_missing", "unknown", "not-enumerated", void 0, "support files not enumerated") : (missing ?? []).length === 0 ? sig("pointer_missing", "pass", "none") : sig("pointer_missing", "over", missing?.join(", ") ?? ""));
3987
+ const narrow = narrowNameMatches(snapshot.name);
3988
+ signals.push(narrow.length === 0 ? sig("narrow_name", "pass", "none") : sig("narrow_name", "over", narrow.join(", "), void 0, `name=${snapshot.name}`));
3989
+ const description = snapshot.description;
3990
+ signals.push(description === void 0 ? sig("description_chars", "unknown", "missing", "60") : sig("description_chars", description.length > 60 ? "over" : "pass", `${description.length}`, `60`));
3991
+ const quality = snapshot.quality;
3992
+ signals.push(quality === null || quality === void 0 ? sig("quality_low", "unknown", "not-assessed") : sig("quality_low", quality < .3 ? "over" : "pass", quality.toFixed(2), `${LOW_QUALITY_THRESHOLD}`));
3993
+ return {
3994
+ name: snapshot.name,
3995
+ signals
3996
+ };
3997
+ })
3998
+ };
3999
+ }
4000
+ /** Convenience: fetch one signal from an assessment or library list. */
4001
+ function findDriftSignal(signals, id) {
4002
+ return signals.find((signal) => signal.id === id);
4003
+ }
4004
+ //#endregion
3688
4005
  //#region lib/types/state-store.js
3689
4006
  /**
3690
4007
  * Evolution home path helper: `$DSH_HOME/evolution` for plugin-owned sidecar
@@ -3694,4 +4011,4 @@ function evolutionHome(env = process.env) {
3694
4011
  return join(env.DSH_HOME ?? join(homedir(), ".dsh"), "evolution");
3695
4012
  }
3696
4013
  //#endregion
3697
- 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 };
4014
+ export { AUTHORING_DESCRIPTION_BAR, COMBINED_REVIEW_PLAN_PROMPT, COMBINED_REVIEW_PROMPT, COMPLETION_SKILL_REVIEW_PROMPT, CURATOR_DRY_RUN_BANNER, CURATOR_PROMPT, DEFAULT_ARCHIVE_AFTER_DAYS, DEFAULT_CONSOLIDATION_FAILURES, DEFAULT_CURATOR_INTERVAL_HOURS, DEFAULT_HEALTH_THRESHOLDS, DEFAULT_MAX_OPS_PER_PLAN, DEFAULT_MEMORY_CHAR_LIMIT, DEFAULT_MIN_IDLE_HOURS, DEFAULT_MUTATION_CAP, DEFAULT_REVIEW_MEMORY_INTERVAL, DEFAULT_REVIEW_SKILL_INTERVAL, DEFAULT_SKILL_CONTENT_CHARS, DEFAULT_SKILL_LIMITS, DEFAULT_SKILL_REVIEW_COMPLETION_MIN_TOOL_CALLS, DEFAULT_SKILL_REVIEW_TRIGGER, DEFAULT_STALE_AFTER_DAYS, DEFAULT_SUBSTANTIVE_MIN_AGENT_CHARS, DEFAULT_SUBSTANTIVE_MIN_TOOL_CALLS, DEFAULT_SUBSTANTIVE_MIN_USER_CHARS, DEFAULT_USER_CHAR_LIMIT, DRIFT_MAX_LINE_CHARS, DRIFT_SIGNALS_VERSION, DRIFT_SIGNAL_NOUNS, DSH_AUTHORING_STANDARDS, ENTRY_DELIMITER, EVENT_ARCHIVE_PREFIX, EVENT_LOG_RETAIN_ARCHIVES, EVENT_LOG_ROTATE_AT, EVENT_LOG_VERSION, EvolutionGateSet, HEALTH_STAMP_RE, LOW_QUALITY_THRESHOLD, MAINTAIN_PROMPT, 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, computeDriftSignals, computeLifecycleTransitions, computePrefixClusters, computeQualityScores, computeScopeView, contentHash, createGateSet, duplicateHeadings, emptyRecord, evaluateThreat, eventsFile, evolutionHome, evolutionIoAdapter, findDriftSignal, foldCuratorFields, foldTurn, getRecord, latestActivityAt, lifecycleCandidate, listEventArchives, loadMutations, loadSuppressedNames, loadUsage, markAgentCreated, memoryRoot, missingSupportPointers, mutateUsage, mutationsFile, narrowNameMatches, nodeEvolutionIo, normalizeUsageRecord, observeEvent, overlongLines, parseCuratorNominations, parseEvolutionEvents, parseFrontmatter, readEvolutionEvents, readEvolutionTimeline, recordMutation, redactSecrets, relatedSkillNames, renderCuratorReportMarkdown, resolveOrigins, retainEventArchives, reviewPrompt, saveSuppressedNames, saveUsage, scanContentThreats, scanMemoryThreats, scanThreats, skillsRoot, suppressedFile, transactIo, updateSuppressedNames, usageFile, usageObserved, validateFrontmatter, verifyPromptBundle };
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Library-level drift signals for the maintenance subagent (design 011).
3
+ *
4
+ * Deterministic fact checks over a skill-library snapshot: domain drift
5
+ * (narrow names, near-duplicate groups, prefix clusters) and layer drift
6
+ * (log-like bodies, duplicate headings, overlong lines, missing support-file
7
+ * pointers, description over the authoring bar). Pure functions only — no IO,
8
+ * no LLM, no services. Thresholds are imported from their owning modules
9
+ * (skill-health / quality / skill-store), never duplicated.
10
+ *
11
+ * Distinct from `signals.ts` — the session-level review signal gate.
12
+ */
13
+ /** One skill's library state; the assembler (not this module) reads IO. */
14
+ export interface DriftSkillSnapshot {
15
+ name: string;
16
+ /** SKILL.md content (frontmatter included). */
17
+ body: string;
18
+ /** Frontmatter description, when the assembler parsed it. */
19
+ description?: string | undefined;
20
+ /** Support-file relative paths (e.g. `references/x.md`), when known. */
21
+ supportFiles?: readonly string[] | undefined;
22
+ /** Quality score 0..1, when the assembler computed it; null/undefined = unknown. */
23
+ quality?: number | null | undefined;
24
+ /** Usage observation window status; null/undefined = unknown. */
25
+ usageObserved?: boolean | null | undefined;
26
+ }
27
+ /** verdict=over means "relatively positioned above the threshold", never a violation. */
28
+ export type DriftVerdict = 'pass' | 'over' | 'unknown';
29
+ export interface DriftSignal {
30
+ id: string;
31
+ verdict: DriftVerdict;
32
+ /** Human-readable measured value; message text is redactable. */
33
+ value: string;
34
+ /** Threshold reference, when the signal has one. */
35
+ threshold?: string | undefined;
36
+ /** Extra evidence (matched shapes, line numbers, group members). */
37
+ detail?: string | undefined;
38
+ }
39
+ export interface DriftSkillAssessment {
40
+ name: string;
41
+ signals: ReadonlyArray<DriftSignal>;
42
+ }
43
+ export interface DriftReport {
44
+ /** Library-wide signals (dedup_group, prefix_cluster, usage_observed). */
45
+ library: ReadonlyArray<DriftSignal>;
46
+ /** Per-skill signals. */
47
+ skills: ReadonlyArray<DriftSkillAssessment>;
48
+ }
49
+ /** Physical line length at/above which a body line is reported overlong (011 §4). */
50
+ export declare const DRIFT_MAX_LINE_CHARS = 1500;
51
+ /** Signal-set version: bump whenever ids/thresholds change (011 §7 version coupling). */
52
+ export declare const DRIFT_SIGNALS_VERSION = "1";
53
+ /** Render-time nouns for the MAINTAIN_PROMPT placeholders (single vocabulary with the facts block). */
54
+ export declare const DRIFT_SIGNAL_NOUNS: Readonly<Record<string, string>>;
55
+ /** Detect support files the body never references (by basename or relative path). */
56
+ export declare function missingSupportPointers(body: string, supportFiles: readonly string[]): string[];
57
+ /** Duplicate `## heading` occurrences: singleton results default to head of the file. */
58
+ export declare function duplicateHeadings(body: string): Array<{
59
+ heading: string;
60
+ count: number;
61
+ }>;
62
+ /** Physical lines over `max` characters: `{ lineNo, chars }`, 1-based line numbers. */
63
+ export declare function overlongLines(body: string, max?: number): Array<{
64
+ lineNo: number;
65
+ chars: number;
66
+ }>;
67
+ /** Narrow-name shapes detected in a skill name (empty = none). */
68
+ export declare function narrowNameMatches(name: string): string[];
69
+ /**
70
+ * Compute all drift signals for a snapshot. Missing inputs (quality score,
71
+ * usage window) yield `unknown` — never a fabricated verdict.
72
+ */
73
+ export declare function computeDriftSignals(snapshots: ReadonlyArray<DriftSkillSnapshot>): DriftReport;
74
+ /** Convenience: fetch one signal from an assessment or library list. */
75
+ export declare function findDriftSignal(signals: ReadonlyArray<DriftSignal>, id: string): DriftSignal | undefined;
76
+ //# sourceMappingURL=drift-signals.d.ts.map
@@ -41,16 +41,20 @@ export interface EvolutionEvent {
41
41
  seq: number;
42
42
  /** ISO timestamp at append time. */
43
43
  at: string;
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';
44
+ /** Tagged-union discriminator: feedback increments, learn actions, usage
45
+ * observation anchors (C semantics: `usage` events carry the library-wide
46
+ * count snapshot at the moment the observation window opened), and
47
+ * maintain scans (011: verdict + recommendation count + runId). */
48
+ type: 'feedback' | 'learn' | 'usage' | 'maintain';
48
49
  target?: string | undefined;
49
50
  kind?: 'skill' | 'session' | undefined;
50
51
  rating?: 'positive' | 'negative' | undefined;
51
52
  note?: string | undefined;
52
53
  source?: string | undefined;
53
54
  request?: string | undefined;
55
+ runId?: string | undefined;
56
+ verdict?: string | undefined;
57
+ recommendations?: number | undefined;
54
58
  /** Library-wide usage totals (usage events; counts are cumulative, not deltas). */
55
59
  counts?: {
56
60
  skills?: number;
@@ -17,8 +17,10 @@ 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 './redact.ts';
20
21
  export * from './skill-health.ts';
21
22
  export * from './signals.ts';
23
+ export * from './drift-signals.ts';
22
24
  export * from './skill-store.ts';
23
25
  export * from './state-store.ts';
24
26
  export * from './threats.ts';
@@ -3,14 +3,22 @@
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@9";
7
- export declare const PROMPT_BUNDLE_VERSION = 9;
6
+ export declare const PROMPT_BUNDLE_ID = "dsh-evolution@10";
7
+ export declare const PROMPT_BUNDLE_VERSION = 10;
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
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
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
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
+ /**
15
+ * Maintenance-subagent persona (design 011 §6). Constants pin the persona
16
+ * with `{signal:id}` placeholders; the renderer substitutes names from the
17
+ * drift-signals module (single vocabulary). The model-facing contract is the
18
+ * persona + the mechanical-facts block; the signature head lets the model
19
+ * compare the two heads (011 mismatch protocol).
20
+ */
21
+ export declare const MAINTAIN_PROMPT = "<<<MAINTAIN_PROMPT v={bundle_version} sig={joint_signature}>>>\n\n## \u89D2\u8272\n\u4F60\u662F\u6280\u80FD\u5E93\u7EF4\u62A4\u5BA1\u67E5\u4EE3\u7406\uFF0C\u53EA\u8BFB\uFF1A\u4F60\u6CA1\u6709\u4EFB\u4F55\u5199\u5DE5\u5177\u3002\u57FA\u4E8E\u673A\u68B0\u4E8B\u5B9E\u5757\u4E0E\u672C\u6587\u89C4\u5219\uFF0C\u8BC6\u522B\n\u57DF\u6F02\u79FB\u4E0E\u5C42\u6F02\u79FB\uFF0C\u8F93\u51FA\u7ED3\u6784\u5316\u7EF4\u62A4\u8BA1\u5212\u3002\u4F60\u53EA\u8F93\u51FA\u8BA1\u5212\uFF0C\u4ECE\u4E0D\u6267\u884C\uFF1B\u6267\u884C\u7531\u7528\u6237\u547D\u4EE4\u4E0E\u5BA1\u6279\u5B8C\u6210\u3002\n\n## 1. \u8F93\u5165\u5951\u7EA6\uFF08\u6700\u9AD8\u4F18\u5148\u7EA7\uFF0C\u51B2\u7A81\u65F6\u4EE5\u6B64\u4E3A\u51C6\uFF09\n\u673A\u68B0\u4E8B\u5B9E\u5757 <<<MECHANICAL_FACTS v={signals_version} sig={joint_signature}>>>\uFF08\u4E0B\u65B9\uFF0C\n\u4EE5 <<<END FACTS>>> \u95ED\u5408\uFF09\u662F\u552F\u4E00\u8BC1\u636E\u6765\u6E90\uFF1B\u6BCF\u6761\u4FE1\u53F7\u7684\u540D\u5B57\u3001\u9608\u503C\u3001verdict \u4E0E\u672C\u6587\u89C4\u5219\u5F15\u7528\u4E00\u4E00\u5BF9\u5E94\u3002\n- verdict \u8BED\u4E49\uFF08\u679A\u4E3E\uFF0C\u4E0D\u53EF\u5F15\u7533\uFF09\uFF1Apass=\u672A\u8D8A\u9608\uFF08\u542B\u4F4E\u4E8E\u9608\u503C\uFF09\uFF1Bover=\u8D8A\u9608\uFF1Bunknown=\u811A\u672C\u672A\u68C0\u6D4B\u3002\n- over \u4E0D\u662F\u8FDD\u89C4\u88C1\u51B3\uFF1A\u5B83\u53EA\u63CF\u8FF0 \"\u4E8B\u5B9E\u76F8\u5BF9\u9608\u503C\u7684\u4F4D\u7F6E\"\uFF1B\u662F\u5426\u6784\u6210\u6F02\u79FB\u53EA\u7531 \u00A73 \u5BF9\u5E94\u6761\u6B3E\u7684\n \u8BED\u4E49\u5224\u65AD\u7ED9\u51FA\u3002\u6CA1\u6709\u6761\u6B3E\u5BF9\u5E94\u7684\u4E8B\u5B9E\uFF0C\u4E0D\u4EA7\u751F\u5EFA\u8BAE\u3002\n- unknown \u2260 pass\uFF1A\u672A\u68C0\u6D4B \u2260 \u5065\u5EB7\uFF1B\u5F15\u7528 unknown \u4FE1\u53F7\u7684\u6761\u76EE\u5FC5\u987B needs_human:true\u3002\n- \u4E8B\u5B9E\u53EA\u8BFB\uFF1A\u4E0D\u6539\u5199\u3001\u4E0D\u8865\u5199\u7F3A\u5931\u4FE1\u53F7\u3001\u4E0D\u628A\u4E8B\u5B9E \"\u7FFB\u8BD1\" \u6210\u88C1\u51B3\u3002\n- \u7248\u672C\u5931\u914D\uFF1A\u82E5\u4E24\u5904 sig \u4E0D\u4E00\u81F4\uFF08\u6216\u4EFB\u4E00\u7F3A\u5931\uFF09\uFF0C\u7981\u6B62\u8F93\u51FA\u8BA1\u5212\u2014\u2014\u53EA\u8F93\u51FA MISMATCH + \u4E24\u4FA7\n \u7248\u672C\u53F7 + \u505C\u6B62\u3002\n\n## 2. \u5BA1\u67E5\u9886\u57DF\n- \u57DF\u6F02\u79FB\uFF1A\u7C7B\u7EA7\u4F1E\u5F62\u788E\u88C2\u4E3A\u7A84\u6280\u80FD\u3001\u540C\u57DF\u8FD1\u91CD\u590D\u7EC4\u672A\u5408\u5E76\u3001\u5B64\u7ACB\u65B0\u57DF\u5165\u4FB5\u3001\u524D\u7F00\u805A\u7C7B\u788E\u7247\u5316\u3002\n- \u5C42\u6F02\u79FB\uFF1A\u77E5\u8BC6\u5728\u300C\u6B63\u6587/\u652F\u6301\u6587\u4EF6/\u8BB0\u5FC6\u300D\u4E09\u5C42\u95F4\u9519\u4F4D\u2014\u2014\u6B63\u6587 log \u5316\u3001\u5B9E\u5F55\u672A\u6C89\u6DC0\u3001\n \u6A21\u5F0F\u6C89\u5E95\u3001\u652F\u6301\u6587\u4EF6\u65E0\u6307\u9488\u3001\u540C\u4E00\u4E8B\u5B9E\u591A\u5730\u91CD\u590D\u8868\u8FF0\u3002\n\n## 3. \u68C0\u67E5\u6E05\u5355\uFF08\u89C4\u5219 = \u89E6\u53D1\u4FE1\u53F7 \u2192 \u8BED\u4E49\u5224\u65AD \u2192 \u8F93\u51FA\u5F62\u6001\uFF09\nenvironment signals\uFF1A{signal:usage_observed}\u3001{signal:quality_low}\u2014\u2014\u6821\u9A8C\u5668\u5BF9 quality_low=unknown \u7684\u6280\u80FD\n\u5168\u5C40\u65BD\u52A0 needs_human:true\uFF08\u673A\u68B0\u5224\u636E\u89C1 \u00A74/\u00A77\uFF09\uFF0C\u6A21\u677F\u4FA7\u4E0D\u91CD\u590D\u8868\u8FF0\u3002\n\nA. \u57DF\u00B7\u788E\u7247\u5316\n- A1 \u5F53 {signal:dedup_group}=over\uFF1A\u5224\u65AD\u8FD1\u91CD\u590D\u7EC4\u662F\u5426\u5C5E\u540C\u4E00\u7C7B\u7EA7\u4F1E\u5F62\u7684\u53EF\u5408\u5E76\u5C0F\u8282\uFF1B\n \u662F \u2192 relationship-level consolidate \u5EFA\u8BAE\uFF1B\u5426 \u2192 \u4E0D\u8F93\u51FA\u3002\n- A2 \u5F53 {signal:narrow_name}=over\uFF1A\u7ED3\u5408 description/\u6B63\u6587\u8BED\u4E49\u5224\u7A84\u540D\u662F\u5426 \"\u4EC5\u5BF9\u4ECA\u65E5\u4EFB\u52A1\u6210\u7ACB\"\uFF1B\n \u6210\u7ACB \u2192 skill-level \u6539\u540D/\u5F52\u6863\u5EFA\u8BAE\uFF1B\u82E5\u5B9E\u4E3A\u7528\u6237\u5185\u90E8\u4EE3\u53F7\uFF08\u683C\u5F0F\u5408\u89C4\u8BED\u4E49\u7A84\uFF09\u2192 contextual\n \u8BC1\u636E\u3001confidence\u22640.4\u3001needs_human:true\u3002\n- A3 \u5F53 {signal:prefix_cluster}=over\uFF1A\u5224\u7C07\u5185\u6280\u80FD\u662F\u5426\u540C\u4F1E\uFF1B\u975E\u540C\u4F1E \u2192 notes \u533A\u63D0\u51FA\u57DF\u5212\u5206\n \u89C2\u5BDF\uFF0C\u4E0D\u5F3A\u5236\u5EFA\u4F1E\u3002\n\nB. \u5C42\u00B7\u5206\u5C42\u9519\u4F4D\n- B1 \u5F53 {signal:stamp_density}=over \u6216 {signal:body_size}=over\uFF1A\u9010\u6280\u80FD\u5224\u65F6\u95F4\u6233/\u7F16\u53F7\u4E3A\n \u8FFD\u6EAF\u951A\uFF08\u8DE8\u6587\u6863\u68C0\u7D22\u951A\uFF09\u8FD8\u662F\u65E5\u5FD7\u6B8B\u7559\uFF08\u8FC7\u7A0B\u53D9\u4E8B/\u72B6\u6001\u5FEB\u7167\uFF09\uFF1B\n \u951A \u2192 \u5141\u8BB8\u4FDD\u7559\uFF0Csemantic_reasoning \u5217\u660E\u5224\u636E\uFF0Cneeds_human:true\uFF1B\n \u6B8B\u7559 \u2192 restructure \u5EFA\u8BAE\uFF08movable headings \u9010\u5B57\u5F15\u7528\uFF09\u3002over \u662F\u5F00\u5173\uFF0C\u4E0D\u662F\u7ED3\u8BBA\u3002\n- B2 \u5F53 {signal:pointer_missing}=over\uFF1A\u68C0\u67E5\u652F\u6301\u6587\u4EF6\u5185\u5BB9\u5F62\u6001\u2014\u2014\u53EF\u590D\u7528\u6A21\u5F0F \u2192 \u5EFA\u8BAE\u4E0A\u79FB\n \u6B63\u6587\uFF1B\u4F1A\u8BDD\u4E13\u5C5E\u5B9E\u5F55 \u2192 \u5EFA\u8BAE\u4FDD\u7559\u5E76\u8865\u6B63\u6587\u6307\u9488\u3002\u8F93\u51FA\u4E3A patch \u6307\u5F15\uFF08\u00A79 \u6267\u884C\u5F62\u6001\uFF09\u3002\n- B3 \u5F53 {signal:dup_heading}=over\uFF1A\u5EFA\u8BAE\u5220\u9664\u591A\u4F59\u6807\u9898\u884C\uFF08\u4FDD\u7559\u4E00\u4EFD\uFF09\uFF0C\u8F93\u51FA\u4E3A patch \u6307\u5F15\u3002\n- B4 \u5F53 {signal:overlong_line}=over\uFF1A\u5EFA\u8BAE\u62C6\u884C\uFF0C\u8F93\u51FA\u4E3A patch \u6307\u5F15\u3002\n- B5 \u5F53 {signal:description_chars}=over\uFF1A\u5EFA\u8BAE\u7F29\u77ED description \u5230 \u226460\uFF0C\u8F93\u51FA\u4E3A patch \u6307\u5F15\uFF1B\n \u4E0D\u5F97\u5EFA\u8BAE\u6539\u52A8\u6B63\u6587\u5176\u4ED6\u90E8\u5206\u3002\n\nD. \u5E93\u00B7\u6574\u5408\u7EAA\u5F8B\uFF08\u8BA1\u5212\u5F62\u6001\u7EA6\u675F\uFF0C\u4E0D\u662F\u4FE1\u53F7\uFF09\n- D1 \u540C\u7C7B\u95EE\u9898\u591A\u5904\u51FA\u73B0 \u2192 \u5408\u6210\u4E00\u6761 relationship-level \u5EFA\u8BAE\uFF0C\u4E0D\u9010\u9879\u8F93\u51FA\u3002\n- D2 \u7ED3\u6784\u7C7B\u95EE\u9898\u4F18\u5148\u7EA7\u9AD8\u4E8E\u5185\u5BB9\u7C7B\uFF1B\u5F71\u54CD\u9762 library-level > relationship-level > skill-level\u3002\n\n\u53EF\u9009\u5DE5\u5177\uFF1Amaintenance_probe\uFF08\u53EA\u8BFB\uFF09\u2014\u2014\u6309\u9700\u6DF1\u6316\u4FE1\u53F7\u8BE6\u60C5\uFF08\u5355\u6280\u80FD\u5168\u6587\u5BC6\u5EA6\u5206\u5E03\u3001\u7C07\u5B8C\u6574\n\u6210\u5458\u3001\u6307\u9488\u660E\u7EC6\uFF09\uFF1B\u63A2\u9488\u8F93\u51FA\u4EC5\u7528\u4E8E\u8865\u5145\u5224\u65AD\uFF08\u5F71\u54CD confidence/semantic_reasoning\uFF09\uFF0C\n\u4E0D\u5F97\u5F15\u5165\u4E8B\u5B9E\u5757\u4E4B\u5916\u7684\u65B0\u8BC1\u636E id\u3002\n\n## 4. \u8F93\u51FA\u5951\u7EA6\uFF08\u6821\u9A8C\u5668\u673A\u68B0\u6267\u884C\uFF09\n{verdict: \"issues\" | \"no_issues\",\n plan: [{ kind: \"skill-level\"|\"relationship-level\"|\"library-level\",\n names: [str], rule: \"A1\"|\"B2\"|...,\n evidence: [{signal, value}], // \u5FC5\u987B\u9010\u5B57\u6765\u81EA\u4E8B\u5B9E\u5757\uFF1B\u7981\u6B62\u5F15\u7528\u4E8B\u5B9E\u5757\u5916\u4FE1\u53F7\n finding: \"<\u4E00\u53E5\u4E8B\u5B9E\u63CF\u8FF0\uFF1A\u5F15\u7528\u4FE1\u53F7 id \u4E0E\u503C\uFF1B\u96F6\u88C1\u51B3\u52A8\u8BCD>\",\n recommendation: \"<\u552F\u4E00\u5141\u8BB8\u7684'\u5E94'\u53E5\uFF1A\u5EFA\u8BAE\u52A8\u4F5C\u4E0E\u7406\u7531\uFF1B\u5E76\u5199\u660E\u6267\u884C\u5F62\u6001\uFF08\u547D\u4EE4/patch \u6307\u5F15\uFF09>\",\n semantic_reasoning: \"<\u8BED\u4E49\u5224\u636E\uFF1B\u542B LLM \u63A8\u65AD\u65F6 confidence\u22640.4>\",\n impact: \"better|worse|neutral\", impact_reason: \"<\u76F8\u5BF9'\u4E0D\u52A8'\u7684\u51C0\u5F71\u54CD>\",\n reversibility: \"archive|restructure|patch|rename|none\", undo_path: \"<\u4E00\u6B65\u64A4\u9500\u65B9\u5F0F>\",\n confidence: float, // machine \u22650.6\uFF1Bcontextual \u22640.4\n needs_human: bool, is_override: bool, override_reason: \"<\u4EC5 is_override>\" }],\n notes: [str]}\n- verdict=no_issues \u21D2 plan=[]\uFF08\u673A\u68B0\u5224\u5B9A\uFF0C\u4E0D\u5141\u8BB8\u7A7A plan \u4E4B\u5916\u7684 \"\u65E0\u95EE\u9898\" \u8868\u8FF0\uFF09\u3002\n- confidence \u5224\u636E\uFF1A\u673A\u5668\u8BC1\u636E\u9879 \u2208 [0.6, 0.9]\uFF1Bcontextual \u9879 \u22640.4\uFF1B\u65E0\u7B2C\u4E09\u533A\u95F4\u2014\u2014\n needs_human = (confidence < 0.6) OR (\u4E0D\u53EF\u9006) OR (is_override) OR (\u5F15\u7528 unknown \u4FE1\u53F7)\u3002\n- \u8BED\u8A00\uFF1Afinding/recommendation/notes \u7528\u4E0E\u5E93\u4E00\u81F4\u7684\u6B63\u6587\u8BED\u8A00\uFF08\u4E2D\u6587\uFF09\uFF1B\u5B57\u6BB5\u540D/\u4FE1\u53F7 id/\u679A\u4E3E\n \u4FDD\u7559\u82F1\u6587\u3002\n\n## 5. \u88C1\u51B3\u7EAA\u5F8B\n- \u7981\u7528\uFF1A\"\u6839\u636E\u6CE8\u5165\u4E8B\u5B9E X\uFF0C\u8BE5\u6280\u80FD\u5E94\u5F53 Y\"\u2014\u2014\u4E8B\u5B9E\u6BB5\u6CA1\u6709 \"\u5E94\u5F53\" \u6743\u9650\u3002\n- \u951A vs \u6B8B\u7559\u793A\u4F8B\uFF1A\u2705 \u5F53 {signal:stamp_density}=over\uFF08{signal:stamp_density.threshold} \u9608\uFF09\n \u4E14\u65F6\u95F4\u6233\u4E3A\u8DE8\u6587\u6863\u68C0\u7D22\u951A \u2192 B1\uFF1A\u4E0D\u8FC1\uFF0Cneeds_human\uFF0Csemantic_reasoning=\u951A\u5224\u636E\u3002\n \u274C \u5F53 {signal:stamp_density}=over \u2192 \"\u8BE5\u6280\u80FD\u65E5\u5FD7\u5316\uFF0C\u5E94 restructure\"\uFF08over \u662F\u5F00\u5173\u4E0D\u662F\u7ED3\u8BBA\uFF09\u3002\n- \u7533\u8BC9\uFF1A\u673A\u68B0\u9608\u503C\u4E0E\u8BED\u4E49\u5224\u65AD\u51B2\u7A81 \u2192 is_override:true + override_reason + needs_human:true\uFF1B\n \u4E0D\u5F97\u9759\u9ED8\u7ED5\u8FC7\u9608\u503C\u3002\n- \u4E0D\u52A8\u4F5C\u5408\u6CD5\uFF1Averdict=no_issues \u662F\u5408\u6CD5\u8F93\u51FA\uFF0C\u4E0D\u662F\u5931\u8D25\uFF1B\u8FDE\u7EED\u7A7A\u62A5\u544A = \u4FE1\u53F7\u5B9A\u4E49\u95EE\u9898\uFF0C\n \u4E0D\u662F \"\u66F4\u79EF\u6781\" \u7684\u4FE1\u53F7\u3002\n- \u9519\u8BEF\u6210\u672C\uFF1A\u4E0D\u53EF\u9006\u52A8\u4F5C\uFF08rename\uFF09\u5FC5\u987B needs_human:true\uFF1B\u53EF\u9006\u52A8\u4F5C\uFF08archive/restructure\n \u4E24\u9636\u6BB5\uFF09\u53EF needs_human:false \u4F46 undo_path \u5FC5\u586B\u3002\n- \u4E0D\u505A\uFF1A\u4E0D\u5EFA\u8BAE\u5220\u9664\uFF08\u53EA\u5EFA\u8BAE archive\uFF09\uFF1B\u4E0D\u63D0\u5347\u5185\u5BB9\u8D28\u91CF\uFF08\u7ED3\u6784\u5BA1\u67E5\u53EA flag \u4F4D\u7F6E/\u5F52\u5C5E/\u5206\u5C42\uFF09\uFF1B\n protected \u96C6\uFF08bundled/hub \u5B89\u88C5/pinned\uFF09\u5185 0 \u5EFA\u8BAE\u3002\n\n## 6. \u6CDB\u5316\u6CE8\u610F\u4E8B\u9879\n- \u4FE1\u53F7\u96C6\u5F00\u653E\uFF1A\u4E8B\u5B9E\u5757\u542B\u3001\u00A73 \u672A\u5217\u7684\u4FE1\u53F7 \u2192 notes \u533A\u63D0\u51FA \"\u8BE5\u4FE1\u53F7\u503C\u5F97\u65B0\u589E\u6761\u6B3E\"\uFF0C\u7981\u6B62\n \u89E3\u91CA\u4E3A \"\u5DF2\u77E5\u95EE\u9898\"\u3002\n- \u6761\u6B3E\u540C\u6784\u6269\u5C55\uFF1A\u65B0\u589E\u68C0\u67E5 = drift-signals \u52A0\u5B9A\u4E49 + \u00A73 \u52A0\u4E00\u6761 \"\u4FE1\u53F7\u2192\u8BED\u4E49\u2192\u8F93\u51FA\" \u6761\u6B3E\u3002\n- \u5E93\u89C4\u6A21\u65E0\u5173\uFF1A\u5224\u636E\u662F\u4E8B\u5B9E\u4E0E\u6761\u6B3E\uFF0C\u4E0D\u662F\u5E93\u4F53\u91CF\u5370\u8C61\u3002\n- \u4FE1\u53F7\u673A\u5236\u7591\u95EE\uFF08\u9608\u503C\u3001\u68C0\u6D4B\u539F\u7406\uFF09\u2192 \u5199 needs_human\uFF0C\u4E0D\u731C\u6D4B\u673A\u5236\u3002";
14
22
  /**
15
23
  * System-prompt guidance section (Hermes `SKILLS_GUIDANCE`, DSH-adapted).
16
24
  * Registered as a system-prompt section by tool-skill-manage (it mounts
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Credential-shaped text redaction for cross-boundary model inputs (011 §8).
3
+ *
4
+ * The maintenance facts block and probe output — like the review snapshot
5
+ * before them — leave the owning session's context, so secret-shaped text is
6
+ * masked before it is sent. Best-effort and conservative: targets well-known
7
+ * secret shapes and inline assignment patterns, never wholesale content.
8
+ * Migrated from `evolution-review` so both channels share one implementation.
9
+ */
10
+ /**
11
+ * Mask credential-shaped text before it crosses a session boundary.
12
+ * @param text - the text about to be sent to a model outside this session.
13
+ * @returns the text with matched secrets replaced by `<redacted>`.
14
+ */
15
+ export declare function redactSecrets(text: string): string;
16
+ //# sourceMappingURL=redact.d.ts.map
@@ -22,6 +22,8 @@ export interface SkillHealthThresholds {
22
22
  churnMinPatches: number;
23
23
  }
24
24
  export declare const DEFAULT_HEALTH_THRESHOLDS: SkillHealthThresholds;
25
+ /** Stamp regex shared by health assessment and the maintenance probe (single source, 011). */
26
+ export declare const HEALTH_STAMP_RE: RegExp;
25
27
  export type SkillHealthVerdict = 'healthy' | 'warn' | 'needs-restructure';
26
28
  /** Facts a caller already has; assessors never do IO. */
27
29
  export interface SkillHealthSnapshot {
@@ -168,6 +168,12 @@ export declare class SkillLibrary {
168
168
  isPinned(rawName: string): Promise<boolean>;
169
169
  /** Count non-empty support subdirectories (richness input for quality scoring). */
170
170
  countSupportDirs(rawName: string): Promise<number>;
171
+ /**
172
+ * Relative support-file paths (`references/x.md`) under SUPPORT_DIRS for one
173
+ * skill; empty when the directory is unreadable or has no support files.
174
+ * Used by the maintenance enrichment (011 §7) and probe reads.
175
+ */
176
+ listSupportFiles(rawName: string): Promise<string[]>;
171
177
  /**
172
178
  * Structure-health facts for one skill (rc.73 A1, 008 design): body
173
179
  * chars/density from SKILL.md, support groups from countSupportDirs, plus
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.2.1",
4
+ "version": "0.3.0",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },