@gpzhang2001/sharpkit-preset 0.2.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/LICENSE +201 -0
- package/README.md +18 -0
- package/THIRD_PARTY_NOTICES.md +48 -0
- package/lib/index.d.ts +145 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +548 -0
- package/lib/index.js.map +1 -0
- package/lib/system_prompt.jinja +545 -0
- package/package.json +45 -0
- package/src/index.ts +154 -0
- package/src/jinja.ts +298 -0
- package/src/skills.ts +172 -0
- package/src/system_prompt.jinja +545 -0
package/lib/index.js
ADDED
|
@@ -0,0 +1,548 @@
|
|
|
1
|
+
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import z from "@deepseek-ai/schemastery";
|
|
5
|
+
import { bundledSkillsRoot as bundledSkillsRoot$1 } from "@gpzhang2001/sharpkit-skills";
|
|
6
|
+
//#region src/jinja.ts
|
|
7
|
+
/** Truthiness per Jinja: undefined/null/false/empty string/empty array are falsy. */
|
|
8
|
+
function isTruthy(value) {
|
|
9
|
+
if (value === void 0 || value === null || value === false) return false;
|
|
10
|
+
if (typeof value === "string") return value !== "";
|
|
11
|
+
if (Array.isArray(value)) return value.length > 0;
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
/** Resolve a dotted path — or a zero/one-arg call like get_skill(x) — against the scope chain. */
|
|
15
|
+
function lookup(scopes, path) {
|
|
16
|
+
const call = /^([^(]+)\(([^)]*)\)$/.exec(path);
|
|
17
|
+
if (call !== null) {
|
|
18
|
+
const fn = resolveName(scopes, (call[1] ?? "").trim());
|
|
19
|
+
if (typeof fn !== "function") return void 0;
|
|
20
|
+
const argExpression = (call[2] ?? "").trim();
|
|
21
|
+
return fn(argExpression === "" ? void 0 : lookup(scopes, argExpression));
|
|
22
|
+
}
|
|
23
|
+
return resolveName(scopes, path);
|
|
24
|
+
}
|
|
25
|
+
/** Resolve a dotted name against a scope chain (top of stack first). */
|
|
26
|
+
function resolveName(scopes, path) {
|
|
27
|
+
const parts = path.split(".");
|
|
28
|
+
const head = parts[0] ?? "";
|
|
29
|
+
const rest = parts.slice(1);
|
|
30
|
+
for (let index = scopes.length - 1; index >= 0; index--) {
|
|
31
|
+
const scope = scopes[index];
|
|
32
|
+
if (scope === void 0 || !(head in scope)) continue;
|
|
33
|
+
let current = scope[head];
|
|
34
|
+
for (const part of rest) {
|
|
35
|
+
if (typeof current !== "object" || current === null) return void 0;
|
|
36
|
+
current = current[part];
|
|
37
|
+
}
|
|
38
|
+
return current;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/** Evaluate one condition expression: `a`, `a.b`, `x and y`. */
|
|
42
|
+
function evaluateCondition(scopes, expression) {
|
|
43
|
+
return expression.split(" and ").map((part) => part.trim()).every((part) => isTruthy(lookup(scopes, part)));
|
|
44
|
+
}
|
|
45
|
+
/** Interpolate {{ expr }} occurrences (dotted lookup; undefined renders as empty per strix usage guard). */
|
|
46
|
+
function interpolate(scopes, text) {
|
|
47
|
+
return text.replace(/\{\{\s*([^}]+?)\s*\}\}/g, (_match, expression) => {
|
|
48
|
+
const value = lookup(scopes, expression.trim());
|
|
49
|
+
return value === void 0 || value === null ? "" : String(value);
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
/** dictsort: sort object entries by key (jinja dictsort default). */
|
|
53
|
+
function dictSort(value) {
|
|
54
|
+
if (typeof value !== "object" || value === null) return [];
|
|
55
|
+
return Object.entries(value).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([key, entryValue]) => ({
|
|
56
|
+
key,
|
|
57
|
+
value: entryValue
|
|
58
|
+
}));
|
|
59
|
+
}
|
|
60
|
+
/** Parse template text into lines, splitting inline {% if %}/{% endif %} fragments. */
|
|
61
|
+
function parseLines(text) {
|
|
62
|
+
const lines = [];
|
|
63
|
+
for (const rawLine of text.split("\n")) {
|
|
64
|
+
const trimmed = rawLine.trim();
|
|
65
|
+
const loneIf = /^\{%-?\s*if\s+(.+?)\s*-?%\}$/.exec(trimmed);
|
|
66
|
+
const loneElse = /^\{%-?\s*else\s*-?%\}$/.test(trimmed);
|
|
67
|
+
const loneEndif = /^\{%-?\s*endif\s*-?%\}$/.test(trimmed);
|
|
68
|
+
const loneFor = /^\{%-?\s*for\s+(.+?)\s*-?%\}$/.exec(trimmed);
|
|
69
|
+
const loneEndfor = /^\{%-?\s*endfor\s*-?%\}$/.test(trimmed);
|
|
70
|
+
if (loneIf !== null) {
|
|
71
|
+
lines.push({
|
|
72
|
+
kind: "if",
|
|
73
|
+
condition: loneIf[1] ?? ""
|
|
74
|
+
});
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
if (loneElse) {
|
|
78
|
+
lines.push({ kind: "else" });
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
if (loneEndif) {
|
|
82
|
+
lines.push({ kind: "endif" });
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
if (loneFor !== null) {
|
|
86
|
+
const rightTrim = loneFor[0].endsWith("-%}");
|
|
87
|
+
const declaration = loneFor[1] ?? "";
|
|
88
|
+
const two = /^(\w+),\s*(\w+)\s+in\s+(.+?)\s*\|\s*(\w+)$/.exec(declaration);
|
|
89
|
+
const one = /^(\w+)\s+in\s+(.+?)\s*\|\s*(\w+)$/.exec(declaration);
|
|
90
|
+
const onePlain = /^(\w+)\s+in\s+(.+?)$/.exec(declaration);
|
|
91
|
+
if (two !== null) lines.push({
|
|
92
|
+
kind: "for",
|
|
93
|
+
variable: `${two[1]},${two[2]}`,
|
|
94
|
+
iterable: `${two[3]}|${two[4] ?? ""}`,
|
|
95
|
+
rightTrim
|
|
96
|
+
});
|
|
97
|
+
else if (one !== null) lines.push({
|
|
98
|
+
kind: "for",
|
|
99
|
+
variable: one[1] ?? "",
|
|
100
|
+
iterable: `${one[2] ?? ""}|${one[3] ?? ""}`,
|
|
101
|
+
rightTrim
|
|
102
|
+
});
|
|
103
|
+
else if (onePlain !== null) lines.push({
|
|
104
|
+
kind: "for",
|
|
105
|
+
variable: onePlain[1] ?? "",
|
|
106
|
+
iterable: onePlain[2] ?? "",
|
|
107
|
+
rightTrim
|
|
108
|
+
});
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
if (loneEndfor) {
|
|
112
|
+
lines.push({ kind: "endfor" });
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
const inlineParts = rawLine.split(/(\{%-?\s*(?:if|endif)\s+[^%]*?-?%\}|\{\{-?[^-]*?-?\}\})/g);
|
|
116
|
+
if (inlineParts.length > 1 && inlineParts.some((part) => part.includes("{%"))) {
|
|
117
|
+
let buffer = "";
|
|
118
|
+
const segments = [];
|
|
119
|
+
for (const part of inlineParts) {
|
|
120
|
+
const ifMatch = /^\{%-?\s*if\s+(.+?)\s*-?%\}$/.exec(part);
|
|
121
|
+
const endifMatch = /^\{%-?\s*endif\s*-?%\}$/.exec(part);
|
|
122
|
+
if (ifMatch !== null) {
|
|
123
|
+
if (buffer !== "") segments.push({
|
|
124
|
+
tag: null,
|
|
125
|
+
text: buffer
|
|
126
|
+
});
|
|
127
|
+
buffer = "";
|
|
128
|
+
segments.push({
|
|
129
|
+
tag: `if ${ifMatch[1]}`,
|
|
130
|
+
text: ""
|
|
131
|
+
});
|
|
132
|
+
} else if (endifMatch !== null) {
|
|
133
|
+
if (buffer !== "") segments.push({
|
|
134
|
+
tag: null,
|
|
135
|
+
text: buffer
|
|
136
|
+
});
|
|
137
|
+
buffer = "";
|
|
138
|
+
segments.push({
|
|
139
|
+
tag: "endif",
|
|
140
|
+
text: ""
|
|
141
|
+
});
|
|
142
|
+
} else buffer += part;
|
|
143
|
+
}
|
|
144
|
+
if (buffer !== "") segments.push({
|
|
145
|
+
tag: null,
|
|
146
|
+
text: buffer
|
|
147
|
+
});
|
|
148
|
+
lines.push({
|
|
149
|
+
kind: "text",
|
|
150
|
+
text: `\u0000SEG${JSON.stringify(segments)}\u0000`
|
|
151
|
+
});
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
const ifMatch = /^\{%-?\s*if\s+(.+?)\s*-?%\}$/.exec(rawLine.trim());
|
|
155
|
+
if (ifMatch !== null) {
|
|
156
|
+
lines.push({
|
|
157
|
+
kind: "if",
|
|
158
|
+
condition: ifMatch[1] ?? ""
|
|
159
|
+
});
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
if (/^\{%-?\s*else\s*-?%\}$/.test(rawLine.trim())) {
|
|
163
|
+
lines.push({ kind: "else" });
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
if (/^\{%-?\s*endif\s*-?%\}$/.test(rawLine.trim())) {
|
|
167
|
+
lines.push({ kind: "endif" });
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
lines.push({
|
|
171
|
+
kind: "text",
|
|
172
|
+
text: rawLine
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
return lines;
|
|
176
|
+
}
|
|
177
|
+
/** Render segment-marked text lines (inline if fragments). */
|
|
178
|
+
function renderSegments(text, scopes) {
|
|
179
|
+
const payload = text.slice(4, -1);
|
|
180
|
+
const segments = JSON.parse(payload);
|
|
181
|
+
let out = "";
|
|
182
|
+
let include = true;
|
|
183
|
+
for (const segment of segments) {
|
|
184
|
+
if (segment.tag !== null) {
|
|
185
|
+
include = segment.tag.startsWith("if") ? evaluateCondition(scopes, segment.tag.slice(3)) : false;
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
if (include) out += interpolate(scopes, segment.text);
|
|
189
|
+
}
|
|
190
|
+
return out;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Render the template text with a variable scope.
|
|
194
|
+
* @param template - the raw jinja template text.
|
|
195
|
+
* @param variables - top-level template variables.
|
|
196
|
+
* @returns the rendered output.
|
|
197
|
+
*/
|
|
198
|
+
function renderTemplate(template, variables) {
|
|
199
|
+
const scopes = [variables];
|
|
200
|
+
const lines = parseLines(template.replace(/\n$/, ""));
|
|
201
|
+
const output = [];
|
|
202
|
+
const emit = (from, to) => {
|
|
203
|
+
let index = from;
|
|
204
|
+
while (index < to) {
|
|
205
|
+
const line = lines[index];
|
|
206
|
+
index++;
|
|
207
|
+
if (line === void 0) break;
|
|
208
|
+
if (line.kind === "text") {
|
|
209
|
+
output.push(line.text.startsWith("\0SEG") ? renderSegments(line.text, scopes) : interpolate(scopes, line.text));
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
if (line.kind === "if") {
|
|
213
|
+
const taken = evaluateCondition(scopes, line.condition);
|
|
214
|
+
let depth = 0;
|
|
215
|
+
let elseAt = -1;
|
|
216
|
+
let endifAt = -1;
|
|
217
|
+
let cursor = index;
|
|
218
|
+
while (cursor < to) {
|
|
219
|
+
const probe = lines[cursor];
|
|
220
|
+
if (probe?.kind === "if") depth++;
|
|
221
|
+
if (probe?.kind === "endif") {
|
|
222
|
+
if (depth === 0) {
|
|
223
|
+
endifAt = cursor;
|
|
224
|
+
break;
|
|
225
|
+
}
|
|
226
|
+
depth--;
|
|
227
|
+
}
|
|
228
|
+
if (probe?.kind === "else" && depth === 0 && elseAt === -1) elseAt = cursor;
|
|
229
|
+
cursor++;
|
|
230
|
+
}
|
|
231
|
+
if (endifAt === -1) endifAt = to;
|
|
232
|
+
if (taken) {
|
|
233
|
+
output.push("");
|
|
234
|
+
emit(index, elseAt === -1 ? endifAt : elseAt);
|
|
235
|
+
} else if (elseAt !== -1) {
|
|
236
|
+
output.push("");
|
|
237
|
+
emit(elseAt + 1, endifAt);
|
|
238
|
+
}
|
|
239
|
+
output.push("");
|
|
240
|
+
index = endifAt + 1;
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
if (line.kind === "for") {
|
|
244
|
+
const [iterableSource, filter = ""] = line.iterable.split("|");
|
|
245
|
+
const raw = lookup(scopes, iterableSource ?? "");
|
|
246
|
+
let depth = 0;
|
|
247
|
+
let endforAt = to;
|
|
248
|
+
let cursor = index;
|
|
249
|
+
while (cursor < to) {
|
|
250
|
+
const probe = lines[cursor];
|
|
251
|
+
if (probe?.kind === "for") depth++;
|
|
252
|
+
if (probe?.kind === "endfor") {
|
|
253
|
+
if (depth === 0) {
|
|
254
|
+
endforAt = cursor;
|
|
255
|
+
break;
|
|
256
|
+
}
|
|
257
|
+
depth--;
|
|
258
|
+
}
|
|
259
|
+
cursor++;
|
|
260
|
+
}
|
|
261
|
+
const [nameA, nameB] = line.variable.split(",");
|
|
262
|
+
const items = nameB !== void 0 && nameA !== void 0 ? dictSort(raw) : (Array.isArray(raw) ? raw : []).map((item) => ({
|
|
263
|
+
key: "",
|
|
264
|
+
value: item
|
|
265
|
+
}));
|
|
266
|
+
if (items.length > 0) {
|
|
267
|
+
for (const entry of items) {
|
|
268
|
+
if (!line.rightTrim) output.push("");
|
|
269
|
+
if (nameB !== void 0 && nameA !== void 0) {
|
|
270
|
+
const scope = {};
|
|
271
|
+
scope[nameA] = entry.key;
|
|
272
|
+
scope[nameB] = entry.value;
|
|
273
|
+
scopes.push(scope);
|
|
274
|
+
} else scopes.push({ [line.variable]: entry.value });
|
|
275
|
+
emit(index, endforAt);
|
|
276
|
+
scopes.pop();
|
|
277
|
+
}
|
|
278
|
+
if (!line.rightTrim) output.push("");
|
|
279
|
+
}
|
|
280
|
+
index = endforAt + 1;
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
};
|
|
285
|
+
emit(0, lines.length);
|
|
286
|
+
return output.join("\n");
|
|
287
|
+
}
|
|
288
|
+
//#endregion
|
|
289
|
+
//#region src/skills.ts
|
|
290
|
+
/**
|
|
291
|
+
* Skill resolution — port of strix skills/__init__.py `_resolve_skills`,
|
|
292
|
+
* `load_skills`, `get_available_skills`, and `validate_requested_skills`
|
|
293
|
+
* over a filesystem skills root with the `<root>/<category>/<name>.md`
|
|
294
|
+
* layout (the bundled pentest-suite skills tree, or the strix original for
|
|
295
|
+
* golden tests).
|
|
296
|
+
* @module @gpzhang2001/sharpkit-preset/skills
|
|
297
|
+
*/
|
|
298
|
+
/** Internal categories excluded from the selectable catalog (strix parity). */
|
|
299
|
+
const INTERNAL_SKILL_CATEGORIES = /* @__PURE__ */ new Set([
|
|
300
|
+
"scan_modes",
|
|
301
|
+
"coordination",
|
|
302
|
+
"analysis"
|
|
303
|
+
]);
|
|
304
|
+
/** Frontmatter regex (strix `_FRONTMATTER_PATTERN`). */
|
|
305
|
+
const FRONTMATTER = /^---\s*\n([\s\S]*?)\n---\s*\n/;
|
|
306
|
+
/** Parse frontmatter and body (strix `_parse_skill_content`). */
|
|
307
|
+
function parseSkillContent(content) {
|
|
308
|
+
const match = FRONTMATTER.exec(content);
|
|
309
|
+
if (match === null) return {
|
|
310
|
+
metadata: {},
|
|
311
|
+
body: content.replace(/^\s+/, "")
|
|
312
|
+
};
|
|
313
|
+
const body = match[1] ?? "";
|
|
314
|
+
const metadata = {};
|
|
315
|
+
for (const line of body.split("\n")) {
|
|
316
|
+
const colon = line.indexOf(":");
|
|
317
|
+
if (colon === -1) continue;
|
|
318
|
+
const key = line.slice(0, colon).trim();
|
|
319
|
+
let value = line.slice(colon + 1).trim();
|
|
320
|
+
if (value.startsWith("\"") && value.endsWith("\"")) value = value.slice(1, -1);
|
|
321
|
+
if (value.startsWith("'") && value.endsWith("'")) value = value.slice(1, -1);
|
|
322
|
+
metadata[key] = value;
|
|
323
|
+
}
|
|
324
|
+
return {
|
|
325
|
+
metadata,
|
|
326
|
+
body: content.slice(match[0].length).replace(/^\s+/, "")
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* List selectable skills (strix `_iter_user_skill_files` + frontmatter):
|
|
331
|
+
* root-level `*.md` plus category directories, excluding internal
|
|
332
|
+
* categories from the catalog.
|
|
333
|
+
* @param skillsRoot - the skills tree root.
|
|
334
|
+
*/
|
|
335
|
+
function getAvailableSkills(skillsRoot) {
|
|
336
|
+
const grouped = {};
|
|
337
|
+
if (!existsSync(skillsRoot)) return grouped;
|
|
338
|
+
const seen = /* @__PURE__ */ new Set();
|
|
339
|
+
const consider = (category, name) => {
|
|
340
|
+
const key = `${category}/${name}`;
|
|
341
|
+
if (seen.has(key)) return;
|
|
342
|
+
const path = category === "root" ? join(skillsRoot, `${name}.md`) : join(skillsRoot, category, `${name}.md`);
|
|
343
|
+
if (!existsSync(path)) return;
|
|
344
|
+
const { metadata } = parseSkillContent(readFileSync(path, "utf8"));
|
|
345
|
+
const description = (metadata["description"] ?? "").split(/\s+/).filter(Boolean).join(" ");
|
|
346
|
+
grouped[category] ??= [];
|
|
347
|
+
grouped[category]?.push({
|
|
348
|
+
name,
|
|
349
|
+
description
|
|
350
|
+
});
|
|
351
|
+
seen.add(key);
|
|
352
|
+
};
|
|
353
|
+
const entries = readdirSync(skillsRoot, { withFileTypes: true });
|
|
354
|
+
const rootFiles = entries.filter((entry) => entry.isFile() && entry.name.endsWith(".md") && !entry.name.startsWith("__") && entry.name !== "README.md").map((entry) => entry.name).sort();
|
|
355
|
+
for (const name of rootFiles) consider("root", name.replace(/\.md$/, ""));
|
|
356
|
+
const categoryDirs = entries.filter((entry) => entry.isDirectory() && !entry.name.startsWith("__")).map((entry) => entry.name).sort();
|
|
357
|
+
for (const category of categoryDirs) {
|
|
358
|
+
if (INTERNAL_SKILL_CATEGORIES.has(category)) continue;
|
|
359
|
+
const files = readdirSync(join(skillsRoot, category)).filter((name) => name.endsWith(".md")).sort();
|
|
360
|
+
for (const file of files) consider(category, file.replace(/\.md$/, ""));
|
|
361
|
+
}
|
|
362
|
+
return grouped;
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* Resolve the deduped, ordered skills list (strix `_resolve_skills`).
|
|
366
|
+
* @param options - requested skills and the shape flags.
|
|
367
|
+
*/
|
|
368
|
+
function resolveSkills(options) {
|
|
369
|
+
const ordered = [...options.requested ?? []];
|
|
370
|
+
ordered.push(`scan_modes/${options.scanMode}`);
|
|
371
|
+
if (options.isDiffScoped) ordered.push("scan_modes/diff");
|
|
372
|
+
ordered.push("tooling/agent_browser");
|
|
373
|
+
ordered.push("tooling/python");
|
|
374
|
+
ordered.push("analysis/counterevidence");
|
|
375
|
+
ordered.push("analysis/severity_calibration");
|
|
376
|
+
if (options.isRoot) ordered.push("coordination/root_agent");
|
|
377
|
+
if (options.isWhitebox) {
|
|
378
|
+
ordered.push("coordination/source_aware_whitebox");
|
|
379
|
+
ordered.push("custom/source_aware_sast");
|
|
380
|
+
ordered.push("analysis/source_aware_discovery");
|
|
381
|
+
ordered.push("analysis/fix_verification");
|
|
382
|
+
}
|
|
383
|
+
const deduped = [];
|
|
384
|
+
const seen = /* @__PURE__ */ new Set();
|
|
385
|
+
for (const skill of ordered) if (skill !== "" && !seen.has(skill)) {
|
|
386
|
+
deduped.push(skill);
|
|
387
|
+
seen.add(skill);
|
|
388
|
+
}
|
|
389
|
+
return deduped;
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Load skill bodies keyed by their BARE names (strix `load_skills` returns
|
|
393
|
+
* {'deep': ...}, not {'scan_modes/deep': ...} — the `<skill_name>` tags in
|
|
394
|
+
* the prompt use bare names). Missing skills resolve to ''.
|
|
395
|
+
* @param names - qualified skill names (category/name).
|
|
396
|
+
* @param skillsRoot - the skills tree root.
|
|
397
|
+
*/
|
|
398
|
+
function loadSkills(names, skillsRoot) {
|
|
399
|
+
const content = {};
|
|
400
|
+
for (const name of names) {
|
|
401
|
+
const [category, fileName] = name.split("/");
|
|
402
|
+
if (category === void 0 || fileName === void 0) {
|
|
403
|
+
content[name] = "";
|
|
404
|
+
continue;
|
|
405
|
+
}
|
|
406
|
+
const path = join(skillsRoot, category, `${fileName}.md`);
|
|
407
|
+
if (existsSync(path)) content[fileName] = parseSkillContent(readFileSync(path, "utf8")).body;
|
|
408
|
+
}
|
|
409
|
+
return content;
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* Validate a requested skill list (strix `validate_requested_skills`).
|
|
413
|
+
* @param skillList - the requested names.
|
|
414
|
+
* @param skillsRoot - the skills tree root.
|
|
415
|
+
* @returns an error message, or null when valid.
|
|
416
|
+
*/
|
|
417
|
+
function validateRequestedSkills(skillList, skillsRoot, maxSkills = 5) {
|
|
418
|
+
if (skillList.length > maxSkills) return `Cannot specify more than ${String(maxSkills)} skills per agent; got ${String(skillList.length)}. Aim for 1-3 related skills per specialist.`;
|
|
419
|
+
if (skillList.length === 0) return null;
|
|
420
|
+
const catalog = getAvailableSkills(skillsRoot);
|
|
421
|
+
const availableNames = new Set(Object.values(catalog).flat().map((entry) => entry.name));
|
|
422
|
+
const availableKeys = new Set(Object.entries(catalog).flatMap(([category, entries]) => entries.map((entry) => `${category}/${entry.name}`)));
|
|
423
|
+
const invalid = [...new Set(skillList.filter((skill) => !availableNames.has(skill) && !availableKeys.has(skill)))].sort();
|
|
424
|
+
if (invalid.length > 0) return `Invalid skill name(s): ${JSON.stringify(invalid)}. Available skills: ${JSON.stringify(Object.values(catalog).flat().map((entry) => entry.name).sort())}`;
|
|
425
|
+
const ambiguousNames = /* @__PURE__ */ new Set();
|
|
426
|
+
const nameCounts = /* @__PURE__ */ new Map();
|
|
427
|
+
for (const entries of Object.values(catalog)) for (const entry of entries) nameCounts.set(entry.name, (nameCounts.get(entry.name) ?? 0) + 1);
|
|
428
|
+
for (const [name, count] of nameCounts) if (count > 1) ambiguousNames.add(name);
|
|
429
|
+
const ambiguous = skillList.filter((skill) => !skill.includes("/") && ambiguousNames.has(skill)).sort();
|
|
430
|
+
if (ambiguous.length > 0) return `Ambiguous skill name(s): ${JSON.stringify(ambiguous)}. Use category-qualified names from: ${JSON.stringify([...availableKeys].sort())}`;
|
|
431
|
+
return null;
|
|
432
|
+
}
|
|
433
|
+
//#endregion
|
|
434
|
+
//#region src/index.ts
|
|
435
|
+
/**
|
|
436
|
+
* Pentest preset — the M3 prompt/knowledge layer: renders the strix system
|
|
437
|
+
* prompt (VERBATIM template + jinja-subset renderer + the skill selection
|
|
438
|
+
* policy) from scan configuration, registers it through the dsh
|
|
439
|
+
* system-prompt seam, and exposes scan-mode semantics (budget/turn
|
|
440
|
+
* defaults) as `pentestPreset` for the orchestrator. Golden-locked against
|
|
441
|
+
* renders from the original jinja template.
|
|
442
|
+
* @module @gpzhang2001/sharpkit-preset
|
|
443
|
+
*/
|
|
444
|
+
/** The template file shipped verbatim from strix. */
|
|
445
|
+
const TEMPLATE = readFileSync(join(dirname(fileURLToPath(import.meta.url)), "system_prompt.jinja"), "utf8");
|
|
446
|
+
/** The quick/deep budget matrix (initial values; M6 tunes). */
|
|
447
|
+
const SCAN_MODE_SEMANTICS = {
|
|
448
|
+
quick: {
|
|
449
|
+
maxTurns: 60,
|
|
450
|
+
maxBudgetUsd: 5
|
|
451
|
+
},
|
|
452
|
+
deep: {
|
|
453
|
+
maxTurns: 250,
|
|
454
|
+
maxBudgetUsd: 25
|
|
455
|
+
}
|
|
456
|
+
};
|
|
457
|
+
const name = "pentest-preset";
|
|
458
|
+
const inject = [];
|
|
459
|
+
const Config = z.object({
|
|
460
|
+
scanMode: z.string().default("deep"),
|
|
461
|
+
isRoot: z.boolean().default(true),
|
|
462
|
+
isWhitebox: z.boolean().default(false),
|
|
463
|
+
interactive: z.boolean().default(true),
|
|
464
|
+
isDiffScoped: z.boolean().default(false),
|
|
465
|
+
skills: z.array(z.string()),
|
|
466
|
+
authorizedTargets: z.array(z.object({
|
|
467
|
+
type: z.string(),
|
|
468
|
+
value: z.string(),
|
|
469
|
+
workspace_path: z.string()
|
|
470
|
+
})),
|
|
471
|
+
scopeSource: z.string(),
|
|
472
|
+
authorizationSource: z.string(),
|
|
473
|
+
mcpConnections: z.array(z.object({
|
|
474
|
+
name: z.string(),
|
|
475
|
+
tool_count: z.number(),
|
|
476
|
+
purpose: z.string()
|
|
477
|
+
})),
|
|
478
|
+
skillsRoot: z.string()
|
|
479
|
+
});
|
|
480
|
+
/** Default skills root: the skills package's bundled corpus (npm-layout safe). */
|
|
481
|
+
function bundledSkillsRoot() {
|
|
482
|
+
return bundledSkillsRoot$1();
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* Render the system prompt for one configuration (pure given the inputs).
|
|
486
|
+
* @param config - resolved preset config.
|
|
487
|
+
* @returns the rendered markdown prompt.
|
|
488
|
+
*/
|
|
489
|
+
function renderPresetPrompt(config) {
|
|
490
|
+
const skillsRoot = config.skillsRoot ?? bundledSkillsRoot();
|
|
491
|
+
const skillContent = loadSkills(resolveSkills({
|
|
492
|
+
requested: config.skills ?? [],
|
|
493
|
+
scanMode: config.scanMode ?? "deep",
|
|
494
|
+
isRoot: config.isRoot ?? true,
|
|
495
|
+
isWhitebox: config.isWhitebox ?? false,
|
|
496
|
+
isDiffScoped: config.isDiffScoped ?? false
|
|
497
|
+
}), skillsRoot);
|
|
498
|
+
const targets = (config.authorizedTargets ?? []).map((target) => ({ ...target }));
|
|
499
|
+
const hasScope = targets.length > 0 || config.scopeSource !== void 0;
|
|
500
|
+
const mcpConnections = (config.mcpConnections ?? []).map((connection) => ({ ...connection }));
|
|
501
|
+
return renderTemplate(TEMPLATE, {
|
|
502
|
+
is_root: config.isRoot ?? true,
|
|
503
|
+
interactive: config.interactive ?? true,
|
|
504
|
+
loaded_skill_names: [...Object.keys(skillContent)],
|
|
505
|
+
available_skills: getAvailableSkills(skillsRoot),
|
|
506
|
+
get_skill: (name) => skillContent[name] ?? "",
|
|
507
|
+
system_prompt_context: {
|
|
508
|
+
...hasScope ? {
|
|
509
|
+
authorized_targets: targets,
|
|
510
|
+
scope_source: config.scopeSource,
|
|
511
|
+
authorization_source: config.authorizationSource
|
|
512
|
+
} : {},
|
|
513
|
+
...config.mcpConnections !== void 0 && config.mcpConnections.length > 0 ? {
|
|
514
|
+
mcp_available: true,
|
|
515
|
+
mcp_connections: mcpConnections
|
|
516
|
+
} : {}
|
|
517
|
+
},
|
|
518
|
+
...skillContent
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
/** Scan-mode semantics lookup (quick default when the mode is unknown). */
|
|
522
|
+
function scanModeSemantics(scanMode) {
|
|
523
|
+
return SCAN_MODE_SEMANTICS[scanMode] ?? {
|
|
524
|
+
maxTurns: 60,
|
|
525
|
+
maxBudgetUsd: 5
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
function apply(ctx, config = {}) {
|
|
529
|
+
const prompt = renderPresetPrompt(config);
|
|
530
|
+
ctx.inject(["systemPrompt"], (promptCtx) => {
|
|
531
|
+
promptCtx.systemPrompt.section({
|
|
532
|
+
name: "deployment:persona",
|
|
533
|
+
order: promptCtx.systemPrompt.getSectionOrder("DEPLOYMENT_PERSONA"),
|
|
534
|
+
text: prompt
|
|
535
|
+
});
|
|
536
|
+
});
|
|
537
|
+
ctx.provide("pentestPreset", {
|
|
538
|
+
scanMode: config.scanMode ?? "deep",
|
|
539
|
+
isRoot: config.isRoot ?? true,
|
|
540
|
+
...scanModeSemantics(config.scanMode ?? "deep"),
|
|
541
|
+
/** Consumed by tool-proxy's repeat_request target enforcement. */
|
|
542
|
+
authorizedTargets: (config.authorizedTargets ?? []).map((target) => ({ ...target }))
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
//#endregion
|
|
546
|
+
export { Config, apply, bundledSkillsRoot, getAvailableSkills, inject, loadSkills, name, parseSkillContent, renderPresetPrompt, renderTemplate, resolveSkills, scanModeSemantics, validateRequestedSkills };
|
|
547
|
+
|
|
548
|
+
//# sourceMappingURL=index.js.map
|
package/lib/index.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":["skillsPackageRoot"],"sources":["../src/jinja.ts","../src/skills.ts","../src/index.ts"],"sourcesContent":["/**\n * A minimal Jinja-subset renderer covering exactly the constructs the\n * strix system prompt template uses: line-level {% if %}/{% else %}/\n * {% endif %}/{% for x in y %}/{% endfor %}, inline {% if %}...{% endif %},\n * and {{ dotted.expr }} interpolation (with `a and b` truthiness and the\n * `available_skills | dictsort` filter). The template file ships VERBATIM\n * from strix — no hand transcription — and whitespace semantics follow\n * default Jinja (a lone tag line renders as an empty line).\n * @module @gpzhang2001/sharpkit-preset/jinja\n */\n\n/** A rendered value: strings, numbers, booleans, null, arrays, objects. */\nexport type JinjaValue = string | number | boolean | null | JinjaValue[] | { [key: string]: JinvaRecordValue } | JinvaRecordValue[]\ntype JinvaRecordValue = string | number | boolean | null | JinvaRecordValue[] | { [key: string]: JinvaRecordValue }\n\n/** Truthiness per Jinja: undefined/null/false/empty string/empty array are falsy. */\nfunction isTruthy(value: unknown): boolean {\n if (value === undefined || value === null || value === false) return false\n if (typeof value === 'string') return value !== ''\n if (Array.isArray(value)) return value.length > 0\n return true\n}\n\n/** Resolve a dotted path — or a zero/one-arg call like get_skill(x) — against the scope chain. */\nfunction lookup(scopes: ReadonlyArray<Record<string, unknown>>, path: string): unknown {\n const call = /^([^(]+)\\(([^)]*)\\)$/.exec(path)\n if (call !== null) {\n const fn = resolveName(scopes, (call[1] ?? '').trim())\n if (typeof fn !== 'function') return undefined\n const argExpression = (call[2] ?? '').trim()\n const arg = argExpression === '' ? undefined : lookup(scopes, argExpression)\n return (fn as (value?: unknown) => unknown)(arg)\n }\n return resolveName(scopes, path)\n}\n\n/** Resolve a dotted name against a scope chain (top of stack first). */\nfunction resolveName(scopes: ReadonlyArray<Record<string, unknown>>, path: string): unknown {\n const parts = path.split('.')\n const head = parts[0] ?? ''\n const rest = parts.slice(1)\n for (let index = scopes.length - 1; index >= 0; index--) {\n const scope = scopes[index]\n if (scope === undefined || !(head in scope)) continue\n let current: unknown = scope[head]\n for (const part of rest) {\n if (typeof current !== 'object' || current === null) return undefined\n current = (current as Record<string, unknown>)[part]\n }\n return current\n }\n return undefined\n}\n\n/** Evaluate one condition expression: `a`, `a.b`, `x and y`. */\nfunction evaluateCondition(scopes: ReadonlyArray<Record<string, unknown>>, expression: string): boolean {\n const conjuncts = expression.split(' and ').map(part => part.trim())\n return conjuncts.every(part => isTruthy(lookup(scopes, part)))\n}\n\n/** Interpolate {{ expr }} occurrences (dotted lookup; undefined renders as empty per strix usage guard). */\nfunction interpolate(scopes: ReadonlyArray<Record<string, unknown>>, text: string): string {\n return text.replace(/\\{\\{\\s*([^}]+?)\\s*\\}\\}/g, (_match, expression: string) => {\n const value = lookup(scopes, expression.trim())\n return value === undefined || value === null ? '' : String(value)\n })\n}\n\n/** dictsort: sort object entries by key (jinja dictsort default). */\nfunction dictSort(value: unknown): Array<{ key: string; value: unknown }> {\n if (typeof value !== 'object' || value === null) return []\n return Object.entries(value as Record<string, unknown>)\n .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))\n .map(([key, entryValue]) => ({ key, value: entryValue }))\n}\n\n/** One parsed template directive. */\ntype Line =\n | { readonly kind: 'text'; readonly text: string }\n | { readonly kind: 'if'; readonly condition: string }\n | { readonly kind: 'else' }\n | { readonly kind: 'endif' }\n | { readonly kind: 'for'; readonly variable: string; readonly iterable: string; readonly rightTrim: boolean }\n | { readonly kind: 'endfor' }\n\n/** Parse template text into lines, splitting inline {% if %}/{% endif %} fragments. */\nfunction parseLines(text: string): Line[] {\n const lines: Line[] = []\n for (const rawLine of text.split('\\n')) {\n // Line-alone block tags take precedence over inline-fragment splitting.\n const trimmed = rawLine.trim()\n const loneIf = /^\\{%-?\\s*if\\s+(.+?)\\s*-?%\\}$/.exec(trimmed)\n const loneElse = /^\\{%-?\\s*else\\s*-?%\\}$/.test(trimmed)\n const loneEndif = /^\\{%-?\\s*endif\\s*-?%\\}$/.test(trimmed)\n const loneFor = /^\\{%-?\\s*for\\s+(.+?)\\s*-?%\\}$/.exec(trimmed)\n const loneEndfor = /^\\{%-?\\s*endfor\\s*-?%\\}$/.test(trimmed)\n if (loneIf !== null) {\n lines.push({ kind: 'if', condition: loneIf[1] ?? '' })\n continue\n }\n if (loneElse) {\n lines.push({ kind: 'else' })\n continue\n }\n if (loneEndif) {\n lines.push({ kind: 'endif' })\n continue\n }\n if (loneFor !== null) {\n const rightTrim = loneFor[0].endsWith('-%}')\n const declaration = loneFor[1] ?? ''\n const two = /^(\\w+),\\s*(\\w+)\\s+in\\s+(.+?)\\s*\\|\\s*(\\w+)$/.exec(declaration)\n const one = /^(\\w+)\\s+in\\s+(.+?)\\s*\\|\\s*(\\w+)$/.exec(declaration)\n const onePlain = /^(\\w+)\\s+in\\s+(.+?)$/.exec(declaration)\n if (two !== null) {\n lines.push({ kind: 'for', variable: `${two[1]},${two[2]}`, iterable: `${two[3]}|${two[4] ?? ''}`, rightTrim })\n } else if (one !== null) {\n lines.push({ kind: 'for', variable: one[1] ?? '', iterable: `${one[2] ?? ''}|${one[3] ?? ''}`, rightTrim })\n } else if (onePlain !== null) {\n lines.push({ kind: 'for', variable: onePlain[1] ?? '', iterable: onePlain[2] ?? '', rightTrim })\n }\n continue\n }\n if (loneEndfor) {\n lines.push({ kind: 'endfor' })\n continue\n }\n const inlineParts = rawLine.split(/(\\{%-?\\s*(?:if|endif)\\s+[^%]*?-?%\\}|\\{\\{-?[^-]*?-?\\}\\})/g)\n if (inlineParts.length > 1 && inlineParts.some(part => part.includes('{%'))) {\n // A line mixing text and inline if/endif tags: expand into sub-lines\n // while preserving the join (rendered pieces re-join with '').\n let buffer = ''\n const segments: Array<{ readonly tag: string | null; readonly text: string }> = []\n for (const part of inlineParts) {\n const ifMatch = /^\\{%-?\\s*if\\s+(.+?)\\s*-?%\\}$/.exec(part)\n const endifMatch = /^\\{%-?\\s*endif\\s*-?%\\}$/.exec(part)\n if (ifMatch !== null) {\n if (buffer !== '') segments.push({ tag: null, text: buffer })\n buffer = ''\n segments.push({ tag: `if ${ifMatch[1]}`, text: '' })\n } else if (endifMatch !== null) {\n if (buffer !== '') segments.push({ tag: null, text: buffer })\n buffer = ''\n segments.push({ tag: 'endif', text: '' })\n } else {\n buffer += part\n }\n }\n if (buffer !== '') segments.push({ tag: null, text: buffer })\n // Emit as a synthetic single line handled by renderSegments.\n lines.push({ kind: 'text', text: `\\u0000SEG${JSON.stringify(segments)}\\u0000` })\n continue\n }\n const ifMatch = /^\\{%-?\\s*if\\s+(.+?)\\s*-?%\\}$/.exec(rawLine.trim())\n if (ifMatch !== null) {\n lines.push({ kind: 'if', condition: ifMatch[1] ?? '' })\n continue\n }\n if (/^\\{%-?\\s*else\\s*-?%\\}$/.test(rawLine.trim())) {\n lines.push({ kind: 'else' })\n continue\n }\n if (/^\\{%-?\\s*endif\\s*-?%\\}$/.test(rawLine.trim())) {\n lines.push({ kind: 'endif' })\n continue\n }\n lines.push({ kind: 'text', text: rawLine })\n }\n return lines\n}\n\n/** Render segment-marked text lines (inline if fragments). */\nfunction renderSegments(text: string, scopes: ReadonlyArray<Record<string, unknown>>): string {\n const payload = text.slice(4, -1)\n const segments = JSON.parse(payload) as Array<{ tag: string | null; text: string }>\n let out = ''\n let include = true\n for (const segment of segments) {\n if (segment.tag !== null) {\n include = segment.tag.startsWith('if') ? evaluateCondition(scopes, segment.tag.slice(3)) : false\n continue\n }\n if (include) out += interpolate(scopes, segment.text)\n }\n return out\n}\n\n/**\n * Render the template text with a variable scope.\n * @param template - the raw jinja template text.\n * @param variables - top-level template variables.\n * @returns the rendered output.\n */\nexport function renderTemplate(template: string, variables: Record<string, unknown>): string {\n const scopes: Record<string, unknown>[] = [variables]\n // Jinja default keep_trailing_newline=False: the template's final newline\n // is removed before rendering.\n const lines = parseLines(template.replace(/\\n$/, ''))\n const output: string[] = []\n // Block stack for if/for with instruction pointers.\n const emit = (from: number, to: number): void => {\n let index = from\n while (index < to) {\n const line = lines[index]\n index++\n if (line === undefined) break\n if (line.kind === 'text') {\n output.push(line.text.startsWith('\\u0000SEG') ? renderSegments(line.text, scopes) : interpolate(scopes, line.text))\n continue\n }\n if (line.kind === 'if') {\n const taken = evaluateCondition(scopes, line.condition)\n // Find matching else/endif at the same depth.\n let depth = 0\n let elseAt = -1\n let endifAt = -1\n let cursor = index\n while (cursor < to) {\n const probe = lines[cursor]\n if (probe?.kind === 'if') depth++\n if (probe?.kind === 'endif') {\n if (depth === 0) {\n endifAt = cursor\n break\n }\n depth--\n }\n if (probe?.kind === 'else' && depth === 0 && elseAt === -1) elseAt = cursor\n cursor++\n }\n if (endifAt === -1) endifAt = to\n // Jinja default whitespace: a block tag adjacent to the TAKEN side\n // keeps its line's newline (renders as one empty line); everything\n // inside a FALSE region — including its opening tag — vanishes,\n // except the closing endif, which always leaves one blank line.\n if (taken) {\n output.push('')\n emit(index, elseAt === -1 ? endifAt : elseAt)\n } else if (elseAt !== -1) {\n // The else tag's trailing newline belongs to the taken side.\n output.push('')\n emit(elseAt + 1, endifAt)\n }\n output.push('')\n index = endifAt + 1\n continue\n }\n if (line.kind === 'for') {\n const [iterableSource, filter = ''] = line.iterable.split('|')\n const raw = lookup(scopes, iterableSource ?? '')\n void filter\n // Find endfor at the same depth.\n let depth = 0\n let endforAt = to\n let cursor = index\n while (cursor < to) {\n const probe = lines[cursor]\n if (probe?.kind === 'for') depth++\n if (probe?.kind === 'endfor') {\n if (depth === 0) {\n endforAt = cursor\n break\n }\n depth--\n }\n cursor++\n }\n const [nameA, nameB] = line.variable.split(',')\n const items: Array<{ readonly key: string; readonly value: unknown }> = nameB !== undefined && nameA !== undefined\n ? dictSort(raw)\n : (Array.isArray(raw) ? raw : []).map(item => ({ key: '', value: item }))\n if (items.length > 0) {\n for (const entry of items) {\n // The for tag's trailing newline is INSIDE the loop body (default\n // Jinja); `-%}` right-trims it away for every iteration.\n if (!line.rightTrim) output.push('')\n if (nameB !== undefined && nameA !== undefined) {\n const scope: Record<string, unknown> = {}\n scope[nameA] = entry.key\n scope[nameB] = entry.value\n scopes.push(scope)\n } else {\n scopes.push({ [line.variable]: entry.value })\n }\n emit(index, endforAt)\n scopes.pop()\n }\n if (!line.rightTrim) output.push('')\n }\n index = endforAt + 1\n continue\n }\n }\n }\n emit(0, lines.length)\n return output.join('\\n')\n}\n\n","/**\n * Skill resolution — port of strix skills/__init__.py `_resolve_skills`,\n * `load_skills`, `get_available_skills`, and `validate_requested_skills`\n * over a filesystem skills root with the `<root>/<category>/<name>.md`\n * layout (the bundled pentest-suite skills tree, or the strix original for\n * golden tests).\n * @module @gpzhang2001/sharpkit-preset/skills\n */\n\nimport { existsSync, readdirSync, readFileSync } from 'node:fs'\nimport { join } from 'node:path'\n\n/** Internal categories excluded from the selectable catalog (strix parity). */\nconst INTERNAL_SKILL_CATEGORIES = new Set(['scan_modes', 'coordination', 'analysis'])\n\n/** Frontmatter regex (strix `_FRONTMATTER_PATTERN`). */\nconst FRONTMATTER = /^---\\s*\\n([\\s\\S]*?)\\n---\\s*\\n/\n\n/** Parsed skill metadata + body. */\nexport interface SkillFile {\n readonly metadata: Record<string, string>\n readonly body: string\n}\n\n/** Parse frontmatter and body (strix `_parse_skill_content`). */\nexport function parseSkillContent(content: string): SkillFile {\n const match = FRONTMATTER.exec(content)\n if (match === null) return { metadata: {}, body: content.replace(/^\\s+/, '') }\n const body = match[1] ?? ''\n const metadata: Record<string, string> = {}\n for (const line of body.split('\\n')) {\n const colon = line.indexOf(':')\n if (colon === -1) continue\n const key = line.slice(0, colon).trim()\n let value = line.slice(colon + 1).trim()\n if (value.startsWith('\"') && value.endsWith('\"')) value = value.slice(1, -1)\n if (value.startsWith(\"'\") && value.endsWith(\"'\")) value = value.slice(1, -1)\n metadata[key] = value\n }\n const rest = content.slice(match[0].length)\n return { metadata, body: rest.replace(/^\\s+/, '') }\n}\n\n/** One selectable skill in the catalog. */\nexport interface SkillCatalogEntry {\n readonly category: string\n readonly name: string\n readonly description: string\n}\n\n/**\n * List selectable skills (strix `_iter_user_skill_files` + frontmatter):\n * root-level `*.md` plus category directories, excluding internal\n * categories from the catalog.\n * @param skillsRoot - the skills tree root.\n */\nexport function getAvailableSkills(skillsRoot: string): Record<string, Array<{ name: string; description: string }>> {\n const grouped: Record<string, Array<{ name: string; description: string }>> = {}\n if (!existsSync(skillsRoot)) return grouped\n const seen = new Set<string>()\n const consider = (category: string, name: string): void => {\n const key = `${category}/${name}`\n if (seen.has(key)) return\n const path = category === 'root' ? join(skillsRoot, `${name}.md`) : join(skillsRoot, category, `${name}.md`)\n if (!existsSync(path)) return\n const { metadata } = parseSkillContent(readFileSync(path, 'utf8'))\n const description = (metadata['description'] ?? '').split(/\\s+/).filter(Boolean).join(' ')\n grouped[category] ??= []\n grouped[category]?.push({ name, description })\n seen.add(key)\n }\n const entries = readdirSync(skillsRoot, { withFileTypes: true })\n const rootFiles = entries.filter(entry => entry.isFile() && entry.name.endsWith('.md') && !entry.name.startsWith('__') && entry.name !== 'README.md').map(entry => entry.name).sort()\n for (const name of rootFiles) consider('root', name.replace(/\\.md$/, ''))\n const categoryDirs = entries.filter(entry => entry.isDirectory() && !entry.name.startsWith('__')).map(entry => entry.name).sort()\n for (const category of categoryDirs) {\n if (INTERNAL_SKILL_CATEGORIES.has(category)) continue\n const files = readdirSync(join(skillsRoot, category)).filter(name => name.endsWith('.md')).sort()\n for (const file of files) consider(category, file.replace(/\\.md$/, ''))\n }\n return grouped\n}\n\n/**\n * Resolve the deduped, ordered skills list (strix `_resolve_skills`).\n * @param options - requested skills and the shape flags.\n */\nexport function resolveSkills(options: {\n readonly requested?: readonly string[]\n readonly scanMode: string\n readonly isRoot: boolean\n readonly isWhitebox: boolean\n readonly isDiffScoped: boolean\n}): string[] {\n const ordered: string[] = [...(options.requested ?? [])]\n ordered.push(`scan_modes/${options.scanMode}`)\n if (options.isDiffScoped) ordered.push('scan_modes/diff')\n ordered.push('tooling/agent_browser')\n ordered.push('tooling/python')\n ordered.push('analysis/counterevidence')\n ordered.push('analysis/severity_calibration')\n if (options.isRoot) ordered.push('coordination/root_agent')\n if (options.isWhitebox) {\n ordered.push('coordination/source_aware_whitebox')\n ordered.push('custom/source_aware_sast')\n ordered.push('analysis/source_aware_discovery')\n ordered.push('analysis/fix_verification')\n }\n const deduped: string[] = []\n const seen = new Set<string>()\n for (const skill of ordered) {\n if (skill !== '' && !seen.has(skill)) {\n deduped.push(skill)\n seen.add(skill)\n }\n }\n return deduped\n}\n\n/**\n * Load skill bodies keyed by their BARE names (strix `load_skills` returns\n * {'deep': ...}, not {'scan_modes/deep': ...} — the `<skill_name>` tags in\n * the prompt use bare names). Missing skills resolve to ''.\n * @param names - qualified skill names (category/name).\n * @param skillsRoot - the skills tree root.\n */\nexport function loadSkills(names: readonly string[], skillsRoot: string): Record<string, string> {\n const content: Record<string, string> = {}\n for (const name of names) {\n const [category, fileName] = name.split('/')\n if (category === undefined || fileName === undefined) {\n content[name] = ''\n continue\n }\n const path = join(skillsRoot, category, `${fileName}.md`)\n if (existsSync(path)) content[fileName] = parseSkillContent(readFileSync(path, 'utf8')).body\n }\n return content\n}\n\n/**\n * Validate a requested skill list (strix `validate_requested_skills`).\n * @param skillList - the requested names.\n * @param skillsRoot - the skills tree root.\n * @returns an error message, or null when valid.\n */\nexport function validateRequestedSkills(skillList: readonly string[], skillsRoot: string, maxSkills = 5): string | null {\n if (skillList.length > maxSkills) {\n return `Cannot specify more than ${String(maxSkills)} skills per agent; got ${String(skillList.length)}. Aim for 1-3 related skills per specialist.`\n }\n if (skillList.length === 0) return null\n const catalog = getAvailableSkills(skillsRoot)\n const availableNames = new Set(Object.values(catalog).flat().map(entry => entry.name))\n const availableKeys = new Set(Object.entries(catalog).flatMap(([category, entries]) => entries.map(entry => `${category}/${entry.name}`)))\n const invalid = [...new Set(skillList.filter(skill => !availableNames.has(skill) && !availableKeys.has(skill)))].sort()\n if (invalid.length > 0) {\n return `Invalid skill name(s): ${JSON.stringify(invalid)}. Available skills: ${JSON.stringify(Object.values(catalog).flat().map(entry => entry.name).sort())}`\n }\n const ambiguousNames = new Set<string>()\n const nameCounts = new Map<string, number>()\n for (const entries of Object.values(catalog)) {\n for (const entry of entries) nameCounts.set(entry.name, (nameCounts.get(entry.name) ?? 0) + 1)\n }\n for (const [name, count] of nameCounts) {\n if (count > 1) ambiguousNames.add(name)\n }\n const ambiguous = skillList.filter(skill => !skill.includes('/') && ambiguousNames.has(skill)).sort()\n if (ambiguous.length > 0) {\n return `Ambiguous skill name(s): ${JSON.stringify(ambiguous)}. Use category-qualified names from: ${JSON.stringify([...availableKeys].sort())}`\n }\n return null\n}\n","/**\n * Pentest preset — the M3 prompt/knowledge layer: renders the strix system\n * prompt (VERBATIM template + jinja-subset renderer + the skill selection\n * policy) from scan configuration, registers it through the dsh\n * system-prompt seam, and exposes scan-mode semantics (budget/turn\n * defaults) as `pentestPreset` for the orchestrator. Golden-locked against\n * renders from the original jinja template.\n * @module @gpzhang2001/sharpkit-preset\n */\n\nimport { readFileSync } from 'node:fs'\nimport { dirname, join } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport type { Context } from '@deepseek-ai/cordis'\nimport type {} from '@deepseek-ai/dsh-system-prompt'\nimport type Schema from '@deepseek-ai/schemastery'\nimport z from '@deepseek-ai/schemastery'\nimport { bundledSkillsRoot as skillsPackageRoot } from '@gpzhang2001/sharpkit-skills'\nimport { renderTemplate } from './jinja.ts'\nimport { getAvailableSkills, loadSkills, resolveSkills } from './skills.ts'\n\nexport { renderTemplate } from './jinja.ts'\nexport { getAvailableSkills, loadSkills, resolveSkills, validateRequestedSkills, parseSkillContent } from './skills.ts'\n\n/** The template file shipped verbatim from strix. */\nconst TEMPLATE = readFileSync(join(dirname(fileURLToPath(import.meta.url)), 'system_prompt.jinja'), 'utf8')\n\n/** Scan-mode budget semantics (manual §6 M3; orchestrator consumes). */\nexport interface ScanModeSemantics {\n readonly maxTurns: number\n readonly maxBudgetUsd: number\n}\n\n/** The quick/deep budget matrix (initial values; M6 tunes). */\nconst SCAN_MODE_SEMANTICS: Record<string, ScanModeSemantics> = {\n quick: { maxTurns: 60, maxBudgetUsd: 5 },\n deep: { maxTurns: 250, maxBudgetUsd: 25 },\n}\n\n/** Deployment-tunable configuration (cordis resolves defaults before apply). */\nexport interface Config {\n /** Scan mode: quick or deep. */\n readonly scanMode?: string\n /** Whether this agent is the root orchestrator. */\n readonly isRoot?: boolean\n /** Whether the scan has source access (whitebox). */\n readonly isWhitebox?: boolean\n /** Whether the session is interactive. */\n readonly interactive?: boolean\n /** Whether the scan is scoped to a change set. */\n readonly isDiffScoped?: boolean\n /** Requested skills (≤5, category-qualified). */\n readonly skills?: readonly string[]\n /** Scan targets rendered into the scope block. */\n readonly authorizedTargets?: ReadonlyArray<{ readonly type: string; readonly value: string; readonly workspace_path?: string }>\n readonly scopeSource?: string\n readonly authorizationSource?: string\n /** MCP connections rendered into the mcp block. */\n readonly mcpConnections?: ReadonlyArray<{ readonly name: string; readonly tool_count: number; readonly purpose?: string }>\n /** Skills tree root; defaults to the bundled pentest-suite skills. */\n readonly skillsRoot?: string\n}\n\nexport const name = 'pentest-preset'\n\nexport const inject: string[] = []\n\nexport const Config: Schema<Config> = z.object({\n scanMode: z.string().default('deep'),\n isRoot: z.boolean().default(true),\n isWhitebox: z.boolean().default(false),\n interactive: z.boolean().default(true),\n isDiffScoped: z.boolean().default(false),\n skills: z.array(z.string()),\n authorizedTargets: z.array(z.object({\n type: z.string(),\n value: z.string(),\n workspace_path: z.string(),\n })),\n scopeSource: z.string(),\n authorizationSource: z.string(),\n mcpConnections: z.array(z.object({\n name: z.string(),\n tool_count: z.number(),\n purpose: z.string(),\n })),\n skillsRoot: z.string(),\n}) as unknown as Schema<Config>\n\n/** Default skills root: the skills package's bundled corpus (npm-layout safe). */\nexport function bundledSkillsRoot(): string {\n return skillsPackageRoot()\n}\n\n/**\n * Render the system prompt for one configuration (pure given the inputs).\n * @param config - resolved preset config.\n * @returns the rendered markdown prompt.\n */\nexport function renderPresetPrompt(config: Config): string {\n const skillsRoot = config.skillsRoot ?? bundledSkillsRoot()\n const requested = config.skills ?? []\n const skillNames = resolveSkills({\n requested,\n scanMode: config.scanMode ?? 'deep',\n isRoot: config.isRoot ?? true,\n isWhitebox: config.isWhitebox ?? false,\n isDiffScoped: config.isDiffScoped ?? false,\n })\n const skillContent = loadSkills(skillNames, skillsRoot)\n const targets = (config.authorizedTargets ?? []).map(target => ({ ...target }))\n const hasScope = targets.length > 0 || config.scopeSource !== undefined\n const mcpConnections = (config.mcpConnections ?? []).map(connection => ({ ...connection }))\n return renderTemplate(TEMPLATE, {\n is_root: config.isRoot ?? true,\n interactive: config.interactive ?? true,\n loaded_skill_names: [...Object.keys(skillContent)],\n available_skills: getAvailableSkills(skillsRoot),\n get_skill: (name: string): string => skillContent[name] ?? '',\n system_prompt_context: {\n ...(hasScope ? { authorized_targets: targets, scope_source: config.scopeSource, authorization_source: config.authorizationSource } : {}),\n ...(config.mcpConnections !== undefined && config.mcpConnections.length > 0 ? { mcp_available: true, mcp_connections: mcpConnections } : {}),\n },\n ...skillContent,\n })\n}\n\n/** Scan-mode semantics lookup (quick default when the mode is unknown). */\nexport function scanModeSemantics(scanMode: string): ScanModeSemantics {\n return SCAN_MODE_SEMANTICS[scanMode] ?? { maxTurns: 60, maxBudgetUsd: 5 }\n}\n\nexport function apply(ctx: Context, config: Config = {}): void {\n const prompt = renderPresetPrompt(config)\n // The persona section exists on host compositions; the headless one-shot\n // tree mounts no system-prompt service, where the preset still provides\n // pentestPreset (the rendered text is available to SDK consumers).\n void ctx.inject(['systemPrompt'], (promptCtx: unknown) => {\n ;(promptCtx as Context).systemPrompt.section({\n // PERSONA_SECTION is the slot an agent preset shadows (system-prompt docs).\n name: 'deployment:persona',\n order: (promptCtx as Context).systemPrompt.getSectionOrder('DEPLOYMENT_PERSONA'),\n text: prompt,\n })\n })\n ctx.provide('pentestPreset', {\n scanMode: config.scanMode ?? 'deep',\n isRoot: config.isRoot ?? true,\n ...scanModeSemantics(config.scanMode ?? 'deep'),\n /** Consumed by tool-proxy's repeat_request target enforcement. */\n authorizedTargets: (config.authorizedTargets ?? []).map(target => ({ ...target })),\n })\n}\n\n"],"mappings":"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"}
|