@lmzhen/dsh-tool-skill-manage 0.5.0 → 0.6.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/README.md CHANGED
@@ -20,6 +20,15 @@ Mutations (create/edit/update/patch/delete/write_file/remove_file/restructure) p
20
20
 
21
21
  `pin` and `unpin` are deliberately **outside** the approval seam. Pinning only lifts/restores the curator-lifecycle freeze (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`.
22
22
 
23
+ ## Configuration
24
+
25
+ - `maxSkillContentChars` / `maxSkillFileBytes` bound SKILL.md reads and support-file writes on this row.
26
+ - Deprecated name (G0/S0.3): `maxSkillContentChars` is the legacy spelling of the
27
+ policy row's `skillContentChars` (canonical id). The two are not auto-synchronised
28
+ yet — this row's value is what the write paths use (the G3 unification closes
29
+ that gap). Reading the legacy name still works; writing it is refused, and it is
30
+ removed in 0.7.0.
31
+
23
32
  ## Known limitations
24
33
 
25
34
  - No known durable consumer gaps at this time. Runtime contracts are covered by package and boundary tests.
package/lib/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { effectiveSessionPolicy } from "@lmzhen/dsh-evolution-approval";
2
2
  import z from "@deepseek-ai/schemastery";
3
3
  import { defineTool } from "@deepseek-ai/dsh-tools";
4
- import { DEFAULT_SKILL_LIMITS, DSH_AUTHORING_STANDARDS, SKILLS_GUIDANCE, SKILLS_GUIDANCE_SECTION_ORDER, SKILL_ACTION_REQUIRED_FIELDS, authoringFeedback, callingScope, clampedNumber, computeDedupGroups, contentHash, evolutionIoAdapter, isPresent, isUnknown, newSkillLibrary, parseFrontmatter, policyStageLimits, probePresent, probeUnknown, resolveExecOrigins } from "@lmzhen/dsh-evolution-core";
4
+ import { DEFAULT_ARCHIVE_RETENTION_POLICY, DEFAULT_CITATION_POLICY, DEFAULT_REFERENCE_REWRITE_POLICY, DEFAULT_SKILL_LIMITS, DEFAULT_SUPPORT_FILE_CHAR_POLICY, DSH_AUTHORING_STANDARDS, PARAM_NAMESPACES, SKILLS_GUIDANCE, SKILLS_GUIDANCE_SECTION_ORDER, SKILL_ACTION_REQUIRED_FIELDS, authoringFeedback, callingScope, clampedNumber, computeDedupGroups, contentHash, evolutionIoAdapter, installParamSection, isPresent, isUnknown, newSkillLibrary, parseFrontmatter, policyStageLimits, probePresent, probeUnknown, readNumberParam, resolveExecOrigins } from "@lmzhen/dsh-evolution-core";
5
5
  //#region lib/types/index.js
6
6
  /**
7
7
  * Model-facing skill_manage tool over ctx.evolutionIo + ctx.skillUsage.
@@ -41,6 +41,37 @@ const Config = z.object({
41
41
  threatExemptLabels: z.array(z.string()).default([]),
42
42
  strictCrossSource: z.boolean().default(false)
43
43
  });
44
+ /** Namespace the write caps and the four stage policies live in (core's PARAM_NAMESPACES). */
45
+ const SKILLS_SETTINGS_NAMESPACE = "evolution-skills";
46
+ /** Schema the platform validates the user layer against; defaults mirror the core
47
+ * constants and the row schema, so an empty document resolves to today's behaviour. */
48
+ const SKILLS_SETTINGS_SCHEMA = z.object({
49
+ skillContentChars: z.number().min(1).default(DEFAULT_SKILL_LIMITS.maxSkillContentChars),
50
+ maxSkillFileBytes: z.number().min(1).default(DEFAULT_SKILL_LIMITS.maxSkillFileBytes),
51
+ maxSkillNameLength: z.number().min(1).default(DEFAULT_SKILL_LIMITS.maxNameLength),
52
+ maxDescriptionLength: z.number().min(1).default(DEFAULT_SKILL_LIMITS.maxDescriptionLength),
53
+ descriptionStrict: z.boolean().default(false),
54
+ strictCrossSource: z.boolean().default(false),
55
+ citationPolicy: z.union([z.const("verify"), z.const("refuse")]).default(DEFAULT_CITATION_POLICY),
56
+ supportFileCharPolicy: z.union([z.const("report"), z.const("enforce")]).default(DEFAULT_SUPPORT_FILE_CHAR_POLICY)
57
+ });
58
+ /** The four caps a user may only tighten, in schema order (the sentry reads it). */
59
+ const SKILL_SETTINGS_CAPS = [
60
+ "skillContentChars",
61
+ "maxSkillFileBytes",
62
+ "maxSkillNameLength",
63
+ "maxDescriptionLength"
64
+ ];
65
+ /**
66
+ * Refuse a resolved section whose cap sits ABOVE the deployment's allocation.
67
+ * Windows have no single-field bound the schema could express, and a user who
68
+ * widened one would spend a budget the deployment set for the whole library.
69
+ * @param value - the resolved section the platform hands the owner.
70
+ * @param ceilings - the deployment values (the plugin row, after its clamps).
71
+ */
72
+ function validateSkillSettings(value, ceilings) {
73
+ for (const key of SKILL_SETTINGS_CAPS) if (value[key] > ceilings[key]) throw new Error(`${key} may only be tightened: ${value[key]} exceeds the deployment value ${ceilings[key]}`);
74
+ }
44
75
  /** v30 REV-02: read the protected-skill list off the (optional) policy
45
76
  * snapshot through an `unknown` boundary — the Context augmentation types the
46
77
  * getter non-optionally, but at runtime the row can be absent. */
@@ -73,27 +104,86 @@ function apply(ctx, rawConfig = {}) {
73
104
  if (value !== void 0 && result !== value) numericClamped.push(name);
74
105
  return result;
75
106
  };
107
+ const libraryLimits = {
108
+ ...policyStageLimits(policySnapshotOf(ctx.get("evolutionPolicy"))),
109
+ maxNameLength: limit("maxSkillNameLength", rawConfig.maxSkillNameLength, DEFAULT_SKILL_LIMITS.maxNameLength),
110
+ maxDescriptionLength: limit("maxDescriptionLength", rawConfig.maxDescriptionLength, DEFAULT_SKILL_LIMITS.maxDescriptionLength),
111
+ maxSkillContentChars: limit("maxSkillContentChars", readNumberParam(rawConfig, "skillContentChars"), DEFAULT_SKILL_LIMITS.maxSkillContentChars),
112
+ maxSkillFileBytes: limit("maxSkillFileBytes", rawConfig.maxSkillFileBytes, DEFAULT_SKILL_LIMITS.maxSkillFileBytes)
113
+ };
76
114
  const library = newSkillLibrary({
77
115
  config: rawConfig,
78
116
  io,
79
- limits: {
80
- ...policyStageLimits(policySnapshotOf(ctx.get("evolutionPolicy"))),
81
- maxNameLength: limit("maxSkillNameLength", rawConfig.maxSkillNameLength, DEFAULT_SKILL_LIMITS.maxNameLength),
82
- maxDescriptionLength: limit("maxDescriptionLength", rawConfig.maxDescriptionLength, DEFAULT_SKILL_LIMITS.maxDescriptionLength),
83
- maxSkillContentChars: limit("maxSkillContentChars", rawConfig.maxSkillContentChars, DEFAULT_SKILL_LIMITS.maxSkillContentChars),
84
- maxSkillFileBytes: limit("maxSkillFileBytes", rawConfig.maxSkillFileBytes, DEFAULT_SKILL_LIMITS.maxSkillFileBytes)
85
- },
117
+ limits: libraryLimits,
86
118
  ctx,
87
119
  threatExemptLabels: rawConfig.threatExemptLabels
88
120
  });
89
121
  if (numericClamped.length > 0) ctx.logger.warn(`tool-skill-manage: ${numericClamped.join(", ")} provided an invalid value; falling back to the default`);
122
+ const settingsBase = {
123
+ skillContentChars: libraryLimits.maxSkillContentChars,
124
+ maxSkillFileBytes: libraryLimits.maxSkillFileBytes,
125
+ maxSkillNameLength: libraryLimits.maxNameLength,
126
+ maxDescriptionLength: libraryLimits.maxDescriptionLength,
127
+ descriptionStrict: rawConfig.descriptionStrict ?? false,
128
+ strictCrossSource: rawConfig.strictCrossSource ?? false,
129
+ citationPolicy: libraryLimits.citationPolicy ?? DEFAULT_CITATION_POLICY,
130
+ supportFileCharPolicy: libraryLimits.supportFileCharPolicy ?? DEFAULT_SUPPORT_FILE_CHAR_POLICY
131
+ };
132
+ const section = {};
133
+ const settings = () => {
134
+ const stages = policyStageLimits(policySnapshotOf(ctx.get("evolutionPolicy")));
135
+ const overridden = (key) => section.overrides?.get(key);
136
+ const pick = (key) => overridden(key) ?? settingsBase[key];
137
+ const cap = (value, lower) => clampedNumber(value, lower, { min: 1 });
138
+ return {
139
+ skillContentChars: cap(pick("skillContentChars"), settingsBase.skillContentChars),
140
+ maxSkillFileBytes: cap(pick("maxSkillFileBytes"), settingsBase.maxSkillFileBytes),
141
+ maxSkillNameLength: cap(pick("maxSkillNameLength"), settingsBase.maxSkillNameLength),
142
+ maxDescriptionLength: cap(pick("maxDescriptionLength"), settingsBase.maxDescriptionLength),
143
+ descriptionStrict: pick("descriptionStrict"),
144
+ strictCrossSource: pick("strictCrossSource"),
145
+ citationPolicy: overridden("citationPolicy") ?? stages.citationPolicy ?? settingsBase.citationPolicy,
146
+ supportFileCharPolicy: overridden("supportFileCharPolicy") ?? stages.supportFileCharPolicy ?? settingsBase.supportFileCharPolicy
147
+ };
148
+ };
149
+ const applyLimits = () => {
150
+ const resolved = settings();
151
+ const stages = policyStageLimits(policySnapshotOf(ctx.get("evolutionPolicy")));
152
+ Object.assign(libraryLimits, {
153
+ maxNameLength: resolved.maxSkillNameLength,
154
+ maxDescriptionLength: resolved.maxDescriptionLength,
155
+ maxSkillContentChars: resolved.skillContentChars,
156
+ maxSkillFileBytes: resolved.maxSkillFileBytes,
157
+ citationPolicy: resolved.citationPolicy,
158
+ referenceRewrite: stages.referenceRewrite ?? DEFAULT_REFERENCE_REWRITE_POLICY,
159
+ archiveRetention: stages.archiveRetention ?? DEFAULT_ARCHIVE_RETENTION_POLICY,
160
+ supportFileCharPolicy: resolved.supportFileCharPolicy
161
+ });
162
+ };
163
+ section.overrides = installParamSection(ctx, PARAM_NAMESPACES["tool-skill-manage"] ?? "evolution-skills", SKILLS_SETTINGS_SCHEMA, settingsBase, {
164
+ warn: (message) => {
165
+ ctx.logger.warn("dsh-evolution-skills: " + message);
166
+ },
167
+ validate: (value) => {
168
+ validateSkillSettings(value, {
169
+ skillContentChars: settingsBase.skillContentChars,
170
+ maxSkillFileBytes: settingsBase.maxSkillFileBytes,
171
+ maxSkillNameLength: settingsBase.maxSkillNameLength,
172
+ maxDescriptionLength: settingsBase.maxDescriptionLength
173
+ });
174
+ },
175
+ onChange: () => {
176
+ applyLimits();
177
+ }
178
+ });
179
+ applyLimits();
90
180
  let crossSourceViewWarned = false;
91
181
  const crossSourceRefusal = async (rawName, phase = "stage") => {
92
182
  const replay = phase === "replay";
93
183
  if (typeof rawName !== "string" || rawName === "") return null;
94
184
  const probe = await catalogWinner(rawName);
95
185
  if (isUnknown(probe)) {
96
- if (rawConfig.strictCrossSource === true) return replay ? `Refused: the cross-source check could not be performed — ${probe.reason}. Reject this staged write, fix the catalog view, and re-stage "${rawName}".` : `Refused: the cross-source check could not be performed — ${probe.reason}. Fix the catalog view (or drop strictCrossSource) before writing "${rawName}".`;
186
+ if (settings().strictCrossSource) return replay ? `Refused: the cross-source check could not be performed — ${probe.reason}. Reject this staged write, fix the catalog view, and re-stage "${rawName}".` : `Refused: the cross-source check could not be performed — ${probe.reason}. Fix the catalog view (or drop strictCrossSource) before writing "${rawName}".`;
97
187
  if (!crossSourceViewWarned) {
98
188
  crossSourceViewWarned = true;
99
189
  ctx.logger.warn(`skill_manage: cross-source check skipped for this session — ${probe.reason}`);
@@ -103,7 +193,7 @@ function apply(ctx, rawConfig = {}) {
103
193
  if (isPresent(probe) && probe.value !== void 0 && probe.value.provider !== "dsh-evolution") {
104
194
  const winner = probe.value;
105
195
  const message = `skill "${rawName}" resolves to a higher-priority "${winner.source}" skill (provider "${winner.provider}"); this write lands on the family copy, which the catalog does NOT serve — edit the "${winner.source}" copy or rename.`;
106
- if (rawConfig.strictCrossSource === true) return `Refused: ${message}`;
196
+ if (settings().strictCrossSource) return `Refused: ${message}`;
107
197
  ctx.logger.warn(`skill_manage: ${message}`);
108
198
  }
109
199
  return null;
@@ -183,7 +273,7 @@ function apply(ctx, rawConfig = {}) {
183
273
  if (parsed) {
184
274
  const feedback = authoringFeedback(parsed.frontmatter);
185
275
  feedbackLines = feedback.lines;
186
- if (rawConfig.descriptionStrict === true && feedback.over60) return {
276
+ if (settings().descriptionStrict && feedback.over60) return {
187
277
  ok: false,
188
278
  message: `Authoring check: description ${feedback.descriptionChars}/60 characters exceeds the strict bar; tighten it to <=60 or set descriptionStrict=false.`,
189
279
  skills: []
@@ -417,4 +507,4 @@ function apply(ctx, rawConfig = {}) {
417
507
  });
418
508
  }
419
509
  //#endregion
420
- export { Config, apply, inject, name };
510
+ export { Config, SKILLS_SETTINGS_NAMESPACE, SKILLS_SETTINGS_SCHEMA, SKILL_SETTINGS_CAPS, apply, inject, name, validateSkillSettings };
@@ -19,6 +19,7 @@
19
19
  */
20
20
  import type { Context } from '@deepseek-ai/cordis';
21
21
  import z from '@deepseek-ai/schemastery';
22
+ import type { CitationPolicy, SupportFileCharPolicy } from '@lmzhen/dsh-evolution-core';
22
23
  export declare const name = "tool-skill-manage";
23
24
  export declare const inject: string[];
24
25
  export interface Config {
@@ -26,6 +27,11 @@ export interface Config {
26
27
  root?: string;
27
28
  maxSkillNameLength?: number;
28
29
  maxDescriptionLength?: number;
30
+ /** Read cap for SKILL.md writes. Same semantic as the policy row's
31
+ * `skillContentChars` (the canonical id, G0/S0.2) — but NO automatic
32
+ * shadowing today: this row's value is what the write paths use, which is
33
+ * what the G3 unification has to close. Deprecated alias (G0/S0.3): still
34
+ * readable, refused by writes; removed in 0.7.0. */
29
35
  maxSkillContentChars?: number;
30
36
  maxSkillFileBytes?: number;
31
37
  /** When true, create/update refuse a description over the 60-char authoring bar (default: advisory feedback only). */
@@ -42,5 +48,42 @@ export interface Config {
42
48
  strictCrossSource?: boolean;
43
49
  }
44
50
  export declare const Config: z<Config>;
51
+ /** Namespace the write caps and the four stage policies live in (core's PARAM_NAMESPACES). */
52
+ export declare const SKILLS_SETTINGS_NAMESPACE = "evolution-skills";
53
+ /** Write behaviour a user may change (G3/S3.4). Field names are the CANONICAL
54
+ * parameter ids from the registry. The four caps are TIGHTEN-ONLY: the settings
55
+ * `validate` hook refuses a value above what the deployment allocated, because
56
+ * raising them would let one user widen a shared library's write budget. */
57
+ export interface SkillSettings {
58
+ /** Character cap on a SKILL.md body. */
59
+ skillContentChars: number;
60
+ /** Byte cap on one support file. */
61
+ maxSkillFileBytes: number;
62
+ /** Character cap on a skill name. */
63
+ maxSkillNameLength: number;
64
+ /** Character cap on a skill description. */
65
+ maxDescriptionLength: number;
66
+ /** Refuse a description over the authoring bar instead of advising. */
67
+ descriptionStrict: boolean;
68
+ /** Refuse writes whose catalog entry resolves outside the family. */
69
+ strictCrossSource: boolean;
70
+ /** Refuse a move that would leave a dangling reference, or verify it. */
71
+ citationPolicy: CitationPolicy;
72
+ /** Warn about an oversize support file, or refuse the write. */
73
+ supportFileCharPolicy: SupportFileCharPolicy;
74
+ }
75
+ /** Schema the platform validates the user layer against; defaults mirror the core
76
+ * constants and the row schema, so an empty document resolves to today's behaviour. */
77
+ export declare const SKILLS_SETTINGS_SCHEMA: z<SkillSettings>;
78
+ /** The four caps a user may only tighten, in schema order (the sentry reads it). */
79
+ export declare const SKILL_SETTINGS_CAPS: readonly ["skillContentChars", "maxSkillFileBytes", "maxSkillNameLength", "maxDescriptionLength"];
80
+ /**
81
+ * Refuse a resolved section whose cap sits ABOVE the deployment's allocation.
82
+ * Windows have no single-field bound the schema could express, and a user who
83
+ * widened one would spend a budget the deployment set for the whole library.
84
+ * @param value - the resolved section the platform hands the owner.
85
+ * @param ceilings - the deployment values (the plugin row, after its clamps).
86
+ */
87
+ export declare function validateSkillSettings(value: SkillSettings, ceilings: Pick<SkillSettings, (typeof SKILL_SETTINGS_CAPS)[number]>): void;
45
88
  export declare function apply(ctx: Context, rawConfig?: Config): void;
46
89
  //# sourceMappingURL=index.d.ts.map
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.5.0",
4
+ "version": "0.6.0",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -27,24 +27,24 @@
27
27
  "license": "MIT",
28
28
  "dependencies": {
29
29
  "@deepseek-ai/schemastery": "^3.18.1",
30
- "@lmzhen/dsh-evolution-approval": "^0.5.0",
31
- "@lmzhen/dsh-evolution-core": "^0.5.0"
30
+ "@lmzhen/dsh-evolution-approval": "^0.6.0",
31
+ "@lmzhen/dsh-evolution-core": "^0.6.0"
32
32
  },
33
33
  "peerDependencies": {
34
34
  "@deepseek-ai/cordis": "^4.0.1",
35
35
  "@deepseek-ai/dsh-skill": "^0.1.5-rc.2",
36
36
  "@deepseek-ai/dsh-system-prompt": "^0.1.5-rc.2",
37
37
  "@deepseek-ai/dsh-tools": "^0.1.5-rc.2",
38
- "@lmzhen/dsh-evolution-io": "^0.5.0",
39
- "@lmzhen/dsh-skill-usage": "^0.5.0"
38
+ "@lmzhen/dsh-evolution-io": "^0.6.0",
39
+ "@lmzhen/dsh-skill-usage": "^0.6.0"
40
40
  },
41
41
  "devDependencies": {
42
42
  "@deepseek-ai/dsh-agent-loop-testkit": "^0.1.5-rc.2",
43
43
  "@deepseek-ai/dsh-skill": "^0.1.5-rc.2",
44
44
  "@deepseek-ai/dsh-system-prompt": "^0.1.5-rc.2",
45
45
  "@deepseek-ai/dsh-tools": "^0.1.5-rc.2",
46
- "@lmzhen/dsh-evolution-core": "^0.5.0",
47
- "@lmzhen/dsh-evolution-io": "^0.5.0",
48
- "@lmzhen/dsh-skill-usage": "^0.5.0"
46
+ "@lmzhen/dsh-evolution-core": "^0.6.0",
47
+ "@lmzhen/dsh-evolution-io": "^0.6.0",
48
+ "@lmzhen/dsh-skill-usage": "^0.6.0"
49
49
  }
50
50
  }