@lmzhen/dsh-tool-skill-manage 0.9.0 → 0.11.1
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 +22 -0
- package/lib/index.js +44 -5
- package/lib/types/index.d.ts +4 -0
- package/package.json +8 -8
package/README.md
CHANGED
|
@@ -72,6 +72,28 @@ only the second one refuses a write:
|
|
|
72
72
|
The third row is a view, not a rule: a description the family accepts can still be cut in a catalog
|
|
73
73
|
viewer, which is why the authoring feedback names the bar rather than treating the cut as a limit.
|
|
74
74
|
|
|
75
|
+
### The create-time duplicate hint
|
|
76
|
+
|
|
77
|
+
A `create` compares its candidate against the library listing before it writes, over the name +
|
|
78
|
+
description projection (core `nearDuplicateSummaries`, at `SUMMARY_DUPLICATE_HINT_THRESHOLD`), and
|
|
79
|
+
names the closest existing skills in the same "Authoring check" block. Three properties are
|
|
80
|
+
deliberate: it adds no read of its own (`library.list()` already reads every SKILL.md to parse
|
|
81
|
+
its frontmatter — that listing is what publishes `description` — and the hint never calls
|
|
82
|
+
`library.read`, never holds a body and never compares content); it is a HINT (the write proceeds
|
|
83
|
+
— only the model's next decision changes); and a listing failure degrades to a named line instead of
|
|
84
|
+
silence, because "nothing similar was checked" and "nothing similar exists" must not look alike. The
|
|
85
|
+
library-wide BODY scan stays where it was, in the `review` action.
|
|
86
|
+
|
|
87
|
+
### The retention line
|
|
88
|
+
|
|
89
|
+
A whole-body replacement that keeps less than half of the previous SKILL.md says so in the same result
|
|
90
|
+
message (`Content kept 26% of the previous body (2104 of 8123 characters); the replaced version is
|
|
91
|
+
preserved in this skill's history.`). It is computed where the write path still holds both bodies — the
|
|
92
|
+
library's own in-lock read, core `contentRetentionFeedback` — because a caller re-reading the file
|
|
93
|
+
would race the writers the library serializes. Like the duplicate hint it is FEEDBACK, not a gate: no ratio
|
|
94
|
+
refuses a write, small skills stay quiet (a 40-character skill cut in half is not news), and patches,
|
|
95
|
+
support-file writes, creates and archives have no ratio to report.
|
|
96
|
+
|
|
75
97
|
## Known limitations
|
|
76
98
|
|
|
77
99
|
- 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_ARCHIVE_RETENTION_POLICY, DEFAULT_CITATION_POLICY, DEFAULT_REFERENCE_REWRITE_POLICY, DEFAULT_SKILL_LIMITS, DEFAULT_SUPPORT_FILE_CHAR_POLICY, DSH_AUTHORING_STANDARDS, SKILLS_GUIDANCE, SKILLS_GUIDANCE_SECTION_ORDER, SKILL_ACTION_REQUIRED_FIELDS, authoringFeedback, callingScope, clampedNumber, computeDedupGroups, contentHash, errorText, evolutionIoAdapter, installParamSection, isPresent, isUnknown, isUnreadWrite, newSkillLibrary, paramNamespace, parseFrontmatter, policyStageLimits, probePresent, probeUnknown, readNumberParam, resolveExecOrigins, sessionReadSkillNames } from "@lmzhen/dsh-evolution-core";
|
|
4
|
+
import { DEFAULT_ARCHIVE_RETENTION_POLICY, DEFAULT_CITATION_POLICY, DEFAULT_REFERENCE_REWRITE_POLICY, DEFAULT_SKILL_LIMITS, DEFAULT_SKILL_VERSION_KEEP, DEFAULT_SUPPORT_FILE_CHAR_POLICY, DSH_AUTHORING_STANDARDS, SKILLS_GUIDANCE, SKILLS_GUIDANCE_SECTION_ORDER, SKILL_ACTION_REQUIRED_FIELDS, authoringFeedback, callingScope, clampedNumber, computeDedupGroups, contentHash, errorText, evolutionIoAdapter, installParamSection, isPresent, isUnknown, isUnreadWrite, nearDuplicateSummaries, newSkillLibrary, paramNamespace, parseFrontmatter, policyStageLimits, probePresent, probeUnknown, readNumberParam, resolveExecOrigins, sessionReadSkillNames } from "@lmzhen/dsh-evolution-core";
|
|
5
5
|
//#region lib/types/write-gates.js
|
|
6
6
|
/**
|
|
7
7
|
* The write-admission sequence: ONE ordered table of the reasons a skill write is refused.
|
|
@@ -213,7 +213,8 @@ const Config = z.object({
|
|
|
213
213
|
maxSkillFileBytes: z.number().min(1).default(DEFAULT_SKILL_LIMITS.maxSkillFileBytes),
|
|
214
214
|
descriptionStrict: z.boolean().default(false),
|
|
215
215
|
threatExemptLabels: z.array(z.string()).default([]),
|
|
216
|
-
strictCrossSource: z.boolean().default(false)
|
|
216
|
+
strictCrossSource: z.boolean().default(false),
|
|
217
|
+
skillVersionKeep: z.number().min(1).default(DEFAULT_SKILL_LIMITS.versionKeep ?? DEFAULT_SKILL_VERSION_KEEP)
|
|
217
218
|
});
|
|
218
219
|
/** Schema the platform validates the user layer against; defaults mirror the core
|
|
219
220
|
* constants and the row schema, so an empty document resolves to today's behaviour. */
|
|
@@ -376,7 +377,8 @@ function apply(ctx, rawConfig = {}) {
|
|
|
376
377
|
maxNameLength: limit("maxSkillNameLength", rawConfig.maxSkillNameLength, DEFAULT_SKILL_LIMITS.maxNameLength),
|
|
377
378
|
maxDescriptionLength: limit("maxDescriptionLength", rawConfig.maxDescriptionLength, DEFAULT_SKILL_LIMITS.maxDescriptionLength),
|
|
378
379
|
maxSkillContentChars: limit("maxSkillContentChars", readNumberParam(rawConfig, "skillContentChars"), DEFAULT_SKILL_LIMITS.maxSkillContentChars),
|
|
379
|
-
maxSkillFileBytes: limit("maxSkillFileBytes", rawConfig.maxSkillFileBytes, DEFAULT_SKILL_LIMITS.maxSkillFileBytes)
|
|
380
|
+
maxSkillFileBytes: limit("maxSkillFileBytes", rawConfig.maxSkillFileBytes, DEFAULT_SKILL_LIMITS.maxSkillFileBytes),
|
|
381
|
+
versionKeep: limit("skillVersionKeep", rawConfig.skillVersionKeep, DEFAULT_SKILL_LIMITS.versionKeep ?? DEFAULT_SKILL_VERSION_KEEP)
|
|
380
382
|
};
|
|
381
383
|
const library = newSkillLibrary({
|
|
382
384
|
config: rawConfig,
|
|
@@ -523,10 +525,12 @@ function apply(ctx, rawConfig = {}) {
|
|
|
523
525
|
};
|
|
524
526
|
let feedbackLines = [];
|
|
525
527
|
const stagedAnchor = typeof args.staged_from_sha256 === "string" && args.staged_from_sha256 !== "" ? args.staged_from_sha256 === "absent" ? { absent: true } : { sha256: args.staged_from_sha256 } : void 0;
|
|
528
|
+
let candidateDescription = "";
|
|
526
529
|
if ((action === "create" || action === "edit" || action === "update") && args.content) {
|
|
527
530
|
const parsed = parseFrontmatter(args.content);
|
|
528
531
|
if (parsed) {
|
|
529
532
|
const feedback = authoringFeedback(parsed.frontmatter);
|
|
533
|
+
candidateDescription = parsed.frontmatter.description ?? "";
|
|
530
534
|
feedbackLines = feedback.lines;
|
|
531
535
|
if (settings().descriptionStrict && feedback.over60) return {
|
|
532
536
|
ok: false,
|
|
@@ -536,8 +540,10 @@ function apply(ctx, rawConfig = {}) {
|
|
|
536
540
|
}
|
|
537
541
|
}
|
|
538
542
|
let result;
|
|
539
|
-
if (action === "create")
|
|
540
|
-
|
|
543
|
+
if (action === "create") {
|
|
544
|
+
feedbackLines.push(...await duplicateHintLines(name, candidateDescription));
|
|
545
|
+
result = await library.create(name, args.content ?? "", origin);
|
|
546
|
+
} else if (action === "edit" || action === "update") result = await library.update(name, args.content ?? "", origin, stagedAnchor);
|
|
541
547
|
else if (action === "patch") result = await library.patch(name, args.old_string ?? "", args.new_string ?? "", args.file_path ?? "", args.replace_all === true, origin);
|
|
542
548
|
else if (action === "delete") result = await library.archive(name, args.absorbed_into ? { absorbedInto: args.absorbed_into } : {});
|
|
543
549
|
else if (action === "write_file") result = await library.writeSupportFile(name, args.file_path ?? "", args.file_content ?? "", origin, stagedAnchor);
|
|
@@ -579,6 +585,39 @@ function apply(ctx, rawConfig = {}) {
|
|
|
579
585
|
skills: []
|
|
580
586
|
};
|
|
581
587
|
}
|
|
588
|
+
/** Batch C (2026-09-27, design §3C-2): the create-time near-duplicate hint — one
|
|
589
|
+
* line naming the existing skill(s) this candidate is about to near-copy, over
|
|
590
|
+
* core's `summary` projection (name + description). Three properties are the
|
|
591
|
+
* design's, not stylistic choices:
|
|
592
|
+
*
|
|
593
|
+
* - It adds NO read of its own. `library.list()` reads each SKILL.md to parse its
|
|
594
|
+
* frontmatter — that listing is what publishes `description` — and the hint never
|
|
595
|
+
* calls `library.read`, never holds a body and never compares content, so a create
|
|
596
|
+
* pays no SECOND whole-tree pass for it.
|
|
597
|
+
* - It is a HINT: the write proceeds either way (G4 — create's foreground
|
|
598
|
+
* behavior is unchanged). The model gets the news, not a refusal.
|
|
599
|
+
* - A failed listing degrades to a NAMED line, never to silence: "nothing
|
|
600
|
+
* similar was checked" and "nothing similar exists" must not look alike
|
|
601
|
+
* (the PLAN-R2 P2-5 posture the review path already takes).
|
|
602
|
+
*
|
|
603
|
+
* The level and the math live in core (`nearDuplicateSummaries`,
|
|
604
|
+
* `SUMMARY_DUPLICATE_HINT_THRESHOLD`) — this function owns the wording only. */
|
|
605
|
+
async function duplicateHintLines(name, description) {
|
|
606
|
+
let matches;
|
|
607
|
+
try {
|
|
608
|
+
matches = nearDuplicateSummaries({
|
|
609
|
+
candidate: {
|
|
610
|
+
name,
|
|
611
|
+
description
|
|
612
|
+
},
|
|
613
|
+
existing: await library.list()
|
|
614
|
+
});
|
|
615
|
+
} catch (error) {
|
|
616
|
+
return [`Duplicate check skipped: the skill listing failed (${error instanceof Error ? error.message : String(error)}).`];
|
|
617
|
+
}
|
|
618
|
+
if (matches.length === 0) return [];
|
|
619
|
+
return [`Near-duplicate of existing skill(s): ${matches.map((match) => `${match.name} (${match.score.toFixed(2)})`).join(", ")} — prefer patch/update on that skill over adding a near-copy.`];
|
|
620
|
+
}
|
|
582
621
|
async function buildSkillReviewText() {
|
|
583
622
|
const list = await library.list();
|
|
584
623
|
const report = await ctx.skillUsage.report();
|
package/lib/types/index.d.ts
CHANGED
|
@@ -46,6 +46,10 @@ export interface Config {
|
|
|
46
46
|
* the family tree) is REFUSED instead of warned about. Default false —
|
|
47
47
|
* warn only, keeping the family tree an autonomous evolution zone. */
|
|
48
48
|
strictCrossSource?: boolean;
|
|
49
|
+
/** Content versions retained per skill (skill-history.ts). E2: a deployment value on this row,
|
|
50
|
+
* like the four caps above — the settings layer deliberately has no card for it (retention is
|
|
51
|
+
* storage policy, not an authoring knob). */
|
|
52
|
+
skillVersionKeep?: number;
|
|
49
53
|
}
|
|
50
54
|
export declare const Config: z<Config>;
|
|
51
55
|
/** Write behaviour a user may change (G3/S3.4). Field names are the CANONICAL
|
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.
|
|
4
|
+
"version": "0.11.1",
|
|
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.
|
|
31
|
-
"@lmzhen/dsh-evolution-core": "^0.
|
|
30
|
+
"@lmzhen/dsh-evolution-approval": "^0.11.1",
|
|
31
|
+
"@lmzhen/dsh-evolution-core": "^0.11.1"
|
|
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.
|
|
39
|
-
"@lmzhen/dsh-skill-usage": "^0.
|
|
38
|
+
"@lmzhen/dsh-evolution-io": "^0.11.1",
|
|
39
|
+
"@lmzhen/dsh-skill-usage": "^0.11.1"
|
|
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.
|
|
47
|
-
"@lmzhen/dsh-evolution-io": "^0.
|
|
48
|
-
"@lmzhen/dsh-skill-usage": "^0.
|
|
46
|
+
"@lmzhen/dsh-evolution-core": "^0.11.1",
|
|
47
|
+
"@lmzhen/dsh-evolution-io": "^0.11.1",
|
|
48
|
+
"@lmzhen/dsh-skill-usage": "^0.11.1"
|
|
49
49
|
}
|
|
50
50
|
}
|