@lmzhen/dsh-tool-skill-manage 0.3.16 → 0.3.18

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/README.md CHANGED
@@ -18,6 +18,16 @@ Adds the `skill_manage` tool schema to the request catalog; result tokens scale
18
18
 
19
19
  Tool schema is prefix-stable. Skill writes do not alter the current request prompt; catalog invalidation affects the next request.
20
20
 
21
+ ## Safety model
22
+
23
+ ### Approval seam
24
+
25
+ Mutations (create/edit/update/patch/delete/write_file/remove_file/restructure) pass through the evolution approval seam when `evolution-approval` is mounted; approved/staged writes are replayed by the registered runner with the library origin preserved.
26
+
27
+ ### pin/unpin: explicit exception (0.3.18, E-70)
28
+
29
+ `pin` and `unpin` are deliberately **outside** the approval seam. Pinning only lifts/restores the curator-lifecycle freeze — it changes a lifecycle flag, never content — and is fully reversible by the same tool. Routing it through `policy:'ask'` would let a staged-but-never-approved request hold the library in a pinned state invisibly. Tradeoff accepted: no approval on a lifecycle-flag flip; if product policy changes, pin/unpin should be wired into the same staging path as `patch`.
30
+
21
31
  ## Known Limitations and Deferred Work
22
32
 
23
33
 
package/lib/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import z from "@deepseek-ai/schemastery";
2
2
  import { defineTool } from "@deepseek-ai/dsh-tools";
3
- import { DEFAULT_SKILL_LIMITS, DSH_AUTHORING_STANDARDS, SKILLS_GUIDANCE, SkillLibrary, authoringFeedback, computeDedupGroups, evolutionIoAdapter, parseFrontmatter, resolveOrigins } from "@lmzhen/dsh-evolution-core";
3
+ import { DEFAULT_SKILL_LIMITS, DSH_AUTHORING_STANDARDS, SKILLS_GUIDANCE, SkillLibrary, authoringFeedback, computeDedupGroups, evolutionIoAdapter, parseFrontmatter, resolveOrigins, resolveSkillsRoot } from "@lmzhen/dsh-evolution-core";
4
4
  //#region lib/types/index.js
5
5
  /**
6
6
  * Model-facing skill_manage tool over ctx.evolutionIo + ctx.skillUsage.
@@ -9,6 +9,13 @@ import { DEFAULT_SKILL_LIMITS, DSH_AUTHORING_STANDARDS, SKILLS_GUIDANCE, SkillLi
9
9
  * approved/staged background writes are replayed by the registered runner.
10
10
  * The skill library itself never hard-deletes: archive is the maximum
11
11
  * destructive action and every curator mutation is snapshot-reversible.
12
+ *
13
+ * 0.3.18 (E-70) — pin/unpin are deliberately OUTSIDE the approval seam:
14
+ * pinning only lifts/restores the curator-lifecycle freeze and is fully
15
+ * reversible by the same tool; routing it through `policy:'ask'` would let a
16
+ * staged-then-stale request hold the library in a pinned state invisibly.
17
+ * The tradeoff (no approval on a lifecycle-flag flip) is accepted and
18
+ * documented in the README Safety model section.
12
19
  * @module @lmzhen/dsh-tool-skill-manage
13
20
  */
14
21
  const name = "tool-skill-manage";
@@ -17,12 +24,15 @@ const inject = [
17
24
  "skillUsage",
18
25
  "evolutionIo"
19
26
  ];
27
+ /** 0.3.18 (E-70): review output lists at most this many near-duplicate groups
28
+ * (a cap, not a per-group limit) — the one named bound for the slice below. */
29
+ const MAX_DEDUP_GROUPS_IN_REVIEW = 3;
20
30
  const Config = z.object({
21
31
  root: z.string().default(""),
22
- maxSkillNameLength: z.number().default(DEFAULT_SKILL_LIMITS.maxNameLength),
23
- maxDescriptionLength: z.number().default(DEFAULT_SKILL_LIMITS.maxDescriptionLength),
24
- maxSkillContentChars: z.number().default(DEFAULT_SKILL_LIMITS.maxSkillContentChars),
25
- maxSkillFileBytes: z.number().default(DEFAULT_SKILL_LIMITS.maxSkillFileBytes),
32
+ maxSkillNameLength: z.number().min(1).default(DEFAULT_SKILL_LIMITS.maxNameLength),
33
+ maxDescriptionLength: z.number().min(1).default(DEFAULT_SKILL_LIMITS.maxDescriptionLength),
34
+ maxSkillContentChars: z.number().min(1).default(DEFAULT_SKILL_LIMITS.maxSkillContentChars),
35
+ maxSkillFileBytes: z.number().min(1).default(DEFAULT_SKILL_LIMITS.maxSkillFileBytes),
26
36
  descriptionStrict: z.boolean().default(false)
27
37
  });
28
38
  /**
@@ -44,7 +54,7 @@ function apply(ctx, rawConfig = {}) {
44
54
  text: SKILLS_GUIDANCE
45
55
  }), "tool-skill-manage.skills-guidance");
46
56
  const io = evolutionIoAdapter(() => ctx.evolutionIo.provider());
47
- const library = new SkillLibrary(rawConfig.root || void 0, io, {
57
+ const library = new SkillLibrary(resolveSkillsRoot(rawConfig), io, {
48
58
  maxNameLength: rawConfig.maxSkillNameLength ?? DEFAULT_SKILL_LIMITS.maxNameLength,
49
59
  maxDescriptionLength: rawConfig.maxDescriptionLength ?? DEFAULT_SKILL_LIMITS.maxDescriptionLength,
50
60
  maxSkillContentChars: rawConfig.maxSkillContentChars ?? DEFAULT_SKILL_LIMITS.maxSkillContentChars,
@@ -106,12 +116,9 @@ function apply(ctx, rawConfig = {}) {
106
116
  if (result.ok) {
107
117
  if (result.normalizedFrontmatterFields && result.normalizedFrontmatterFields.length > 0) feedbackLines.push(`frontmatter auto-quoted (YAML compatibility): ${result.normalizedFrontmatterFields.join(", ")}`);
108
118
  const mutating = action !== "list" && action !== "review" && action !== "skip" && action !== "pin" && action !== "unpin";
109
- if (name && action === "create") {
110
- await ctx.skillUsage.ensureRecord(name);
111
- if (origin !== "foreground") await ctx.skillUsage.markAgentCreated(name);
112
- }
119
+ if (name && action === "create") await ctx.skillUsage.ensureRecordCreated(name, origin !== "foreground");
113
120
  if (name && action === "delete") await ctx.skillUsage.markArchived(name);
114
- else if (name && mutating && action !== "create") await ctx.skillUsage.record(name, "patch");
121
+ else if (name && mutating && action !== "create" && result.noop !== true) await ctx.skillUsage.record(name, "patch");
115
122
  }
116
123
  return {
117
124
  ok: result.ok,
@@ -128,7 +135,7 @@ function apply(ctx, rawConfig = {}) {
128
135
  return `- ${summary.name} | ${record?.state ?? "active"} | use:${record?.use_count ?? 0} view:${record?.view_count ?? 0} patch:${record?.patch_count ?? 0}${quality}${summary.protectedBy ? ` [${summary.protectedBy}]` : ""}`;
129
136
  });
130
137
  const groups = computeDedupGroups({ contents: new Map(await Promise.all(list.map(async (summary) => [summary.name, await library.read(summary.name) ?? ""]))) });
131
- const dedupLines = groups.slice(0, 3).map((group) => `- ${group.join(" ~ ")}`);
138
+ const dedupLines = groups.slice(0, MAX_DEDUP_GROUPS_IN_REVIEW).map((group) => `- ${group.join(" ~ ")}`);
132
139
  const warned = list.filter((summary) => report.get(summary.name)?.quality_warn === true).map((summary) => summary.name);
133
140
  const warningLine = warned.length === 0 ? "" : `\nWarning skills (${warned.length}): ${warned.join(", ")} — low quality; consider consolidating them before authoring new skills.`;
134
141
  return [
@@ -231,7 +238,7 @@ function apply(ctx, rawConfig = {}) {
231
238
  ok: true,
232
239
  message: decision.message,
233
240
  skills: [],
234
- pending_id: decision.pendingId ?? ""
241
+ ...decision.pendingId !== void 0 ? { pending_id: decision.pendingId } : {}
235
242
  };
236
243
  }
237
244
  return await executeCore(args, libraryOrigin);
@@ -5,6 +5,13 @@
5
5
  * approved/staged background writes are replayed by the registered runner.
6
6
  * The skill library itself never hard-deletes: archive is the maximum
7
7
  * destructive action and every curator mutation is snapshot-reversible.
8
+ *
9
+ * 0.3.18 (E-70) — pin/unpin are deliberately OUTSIDE the approval seam:
10
+ * pinning only lifts/restores the curator-lifecycle freeze and is fully
11
+ * reversible by the same tool; routing it through `policy:'ask'` would let a
12
+ * staged-then-stale request hold the library in a pinned state invisibly.
13
+ * The tradeoff (no approval on a lifecycle-flag flip) is accepted and
14
+ * documented in the README Safety model section.
8
15
  * @module @deepseek-ai/dsh-tool-skill-manage
9
16
  */
10
17
  import type { Context } from '@deepseek-ai/cordis';
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@lmzhen/dsh-tool-skill-manage",
3
3
  "description": "Model-facing skill_manage tool (community build)",
4
- "version": "0.3.16",
4
+ "version": "0.3.18",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -33,21 +33,21 @@
33
33
  "license": "MIT",
34
34
  "dependencies": {
35
35
  "@deepseek-ai/schemastery": "^3.18.1",
36
- "@lmzhen/dsh-evolution-core": "^0.3.16"
36
+ "@lmzhen/dsh-evolution-core": "^0.3.18"
37
37
  },
38
38
  "peerDependencies": {
39
39
  "@deepseek-ai/cordis": "^4.0.1",
40
40
  "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2",
41
41
  "@deepseek-ai/dsh-tools": "^0.1.1-rc.2",
42
- "@lmzhen/dsh-evolution-io": "^0.3.16",
43
- "@lmzhen/dsh-skill-usage": "^0.3.16"
42
+ "@lmzhen/dsh-evolution-io": "^0.3.18",
43
+ "@lmzhen/dsh-skill-usage": "^0.3.18"
44
44
  },
45
45
  "devDependencies": {
46
46
  "@deepseek-ai/dsh-agent-loop-testkit": "^0.1.1-rc.2",
47
47
  "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2",
48
48
  "@deepseek-ai/dsh-tools": "^0.1.1-rc.2",
49
- "@lmzhen/dsh-evolution-core": "^0.3.16",
50
- "@lmzhen/dsh-evolution-io": "^0.3.16",
51
- "@lmzhen/dsh-skill-usage": "^0.3.16"
49
+ "@lmzhen/dsh-evolution-core": "^0.3.18",
50
+ "@lmzhen/dsh-evolution-io": "^0.3.18",
51
+ "@lmzhen/dsh-skill-usage": "^0.3.18"
52
52
  }
53
53
  }