omk-agent-core 0.80.8 → 0.90.3
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/dist/agent-loop.d.ts.map +1 -1
- package/dist/agent-loop.js +34 -6
- package/dist/agent-loop.js.map +1 -1
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +16 -1
- package/dist/agent.js.map +1 -1
- package/dist/harness/reverse-skill.d.ts +86 -0
- package/dist/harness/reverse-skill.d.ts.map +1 -0
- package/dist/harness/reverse-skill.js +647 -0
- package/dist/harness/reverse-skill.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +3 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,647 @@
|
|
|
1
|
+
const ROUTE_SCORE_TARGET_WEIGHT = 4;
|
|
2
|
+
const ROUTE_SCORE_INTENT_WEIGHT = 3;
|
|
3
|
+
const ROUTE_SCORE_TOOL_WEIGHT = 2;
|
|
4
|
+
const ROUTE_SCORE_KEYWORD_WEIGHT = 1;
|
|
5
|
+
const ROUTE_SCORE_FULL_TRIAD_BONUS = 4;
|
|
6
|
+
const ROUTE_SCORE_CONFIDENCE_DENOMINATOR = 14;
|
|
7
|
+
export const REVERSE_SKILL_ROUTES = [
|
|
8
|
+
{
|
|
9
|
+
id: "apk-reverse",
|
|
10
|
+
label: "APK / Android reverse engineering",
|
|
11
|
+
skillPath: "skills/apk-reverse/SKILL.md",
|
|
12
|
+
targetPatterns: ["apk", "android", "aab", "smali", "jni", "dex", "안드로이드", "反编译"],
|
|
13
|
+
intentPatterns: [
|
|
14
|
+
"decompile",
|
|
15
|
+
"unpack",
|
|
16
|
+
"repack",
|
|
17
|
+
"manifest",
|
|
18
|
+
"frida",
|
|
19
|
+
"hook",
|
|
20
|
+
"ssl pinning",
|
|
21
|
+
"root detection",
|
|
22
|
+
"rebuild",
|
|
23
|
+
"sign",
|
|
24
|
+
"설치",
|
|
25
|
+
"리패키징",
|
|
26
|
+
],
|
|
27
|
+
toolPatterns: ["jadx", "apktool", "adb", "frida", "apksigner", "zipalign", "objection"],
|
|
28
|
+
keywords: ["classes.dex", "androidmanifest", "native library", "lib/", ".so", "certificate pinning", "smali"],
|
|
29
|
+
requiredTools: ["jadx", "apktool"],
|
|
30
|
+
optionalTools: ["adb", "frida", "apksigner", "zipalign", "objection"],
|
|
31
|
+
skillHints: ["apk-reverse", "reverse-engineering", "docs-generator"],
|
|
32
|
+
mcpHints: ["filesystem", "playwright for app docs only", "idapro if native .so becomes dominant"],
|
|
33
|
+
hookHints: ["pre-shell-guard", "protect-secrets", "subagent-stop-audit"],
|
|
34
|
+
acceptance: [
|
|
35
|
+
"Target package structure is identified",
|
|
36
|
+
"Static findings cite class/method/file paths",
|
|
37
|
+
"Any dynamic step is scoped to a local test device or emulator",
|
|
38
|
+
],
|
|
39
|
+
firstActions: [
|
|
40
|
+
"Confirm target artifact path",
|
|
41
|
+
"Decode APK metadata",
|
|
42
|
+
"Route native .so files to binary analysis when needed",
|
|
43
|
+
],
|
|
44
|
+
risk: "local-tooling",
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
id: "ida-reverse",
|
|
48
|
+
label: "Binary reverse engineering with decompiler-first workflow",
|
|
49
|
+
skillPath: "skills/ida-reverse/SKILL.md",
|
|
50
|
+
targetPatterns: [
|
|
51
|
+
"exe",
|
|
52
|
+
"dll",
|
|
53
|
+
"elf",
|
|
54
|
+
"so",
|
|
55
|
+
"dylib",
|
|
56
|
+
"macho",
|
|
57
|
+
"pe",
|
|
58
|
+
"binary",
|
|
59
|
+
"firmware blob",
|
|
60
|
+
"바이너리",
|
|
61
|
+
"二进制",
|
|
62
|
+
],
|
|
63
|
+
intentPatterns: [
|
|
64
|
+
"decompile",
|
|
65
|
+
"disassemble",
|
|
66
|
+
"xref",
|
|
67
|
+
"call graph",
|
|
68
|
+
"data flow",
|
|
69
|
+
"rename",
|
|
70
|
+
"symbol",
|
|
71
|
+
"recover",
|
|
72
|
+
"pseudocode",
|
|
73
|
+
"함수",
|
|
74
|
+
"反汇编",
|
|
75
|
+
],
|
|
76
|
+
toolPatterns: ["ida", "idapro", "idalib", "ida-pro-mcp", "ghidra", "r2", "radare2"],
|
|
77
|
+
keywords: ["imports", "exports", "strings", "function", "offset", "xref", "pdb", "symbols"],
|
|
78
|
+
requiredTools: ["idapro"],
|
|
79
|
+
optionalTools: ["idalib-mcp", "ghidra", "radare2", "rabin2"],
|
|
80
|
+
skillHints: ["ida-reverse", "radare2", "reverse-engineering", "binary-diff"],
|
|
81
|
+
mcpHints: ["idapro", "filesystem", "serena"],
|
|
82
|
+
hookHints: ["pre-shell-guard", "protect-secrets", "stop-verify"],
|
|
83
|
+
acceptance: [
|
|
84
|
+
"Functions are cited by address/name",
|
|
85
|
+
"Hypotheses cite disassembly, decompiler, xref, or string evidence",
|
|
86
|
+
"Output distinguishes evidence from inference",
|
|
87
|
+
],
|
|
88
|
+
firstActions: [
|
|
89
|
+
"Collect file metadata",
|
|
90
|
+
"Run strings/imports reconnaissance",
|
|
91
|
+
"Choose IDA or CLI path based on available tools",
|
|
92
|
+
],
|
|
93
|
+
risk: "passive-analysis",
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
id: "radare2",
|
|
97
|
+
label: "Binary reconnaissance with radare2 CLI",
|
|
98
|
+
skillPath: "skills/radare2/SKILL.md",
|
|
99
|
+
targetPatterns: ["binary", "elf", "pe", "macho", "so", "dll", "firmware", "바이너리"],
|
|
100
|
+
intentPatterns: ["quick recon", "strings", "imports", "exports", "patch", "offset", "cli", "headless"],
|
|
101
|
+
toolPatterns: ["radare2", "r2", "rabin2", "rasm2", "radiff2"],
|
|
102
|
+
keywords: ["entrypoint", "section", "symbol", "hex", "disassembly", "patch bytes"],
|
|
103
|
+
requiredTools: ["radare2", "rabin2"],
|
|
104
|
+
optionalTools: ["rasm2", "radiff2", "r2pipe"],
|
|
105
|
+
skillHints: ["radare2", "ida-reverse", "reverse-engineering"],
|
|
106
|
+
mcpHints: ["filesystem"],
|
|
107
|
+
hookHints: ["pre-shell-guard", "protect-secrets"],
|
|
108
|
+
acceptance: [
|
|
109
|
+
"Recon output names sections, imports, symbols, and notable strings",
|
|
110
|
+
"Patch guidance includes offset and before/after bytes",
|
|
111
|
+
],
|
|
112
|
+
firstActions: [
|
|
113
|
+
"Run file metadata",
|
|
114
|
+
"Run rabin2-style reconnaissance",
|
|
115
|
+
"Escalate to decompiler if CLI evidence is insufficient",
|
|
116
|
+
],
|
|
117
|
+
risk: "passive-analysis",
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
id: "js-reverse",
|
|
121
|
+
label: "Frontend JavaScript signature and request reconstruction",
|
|
122
|
+
skillPath: "skills/js-reverse/SKILL.md",
|
|
123
|
+
targetPatterns: [
|
|
124
|
+
"javascript",
|
|
125
|
+
"js",
|
|
126
|
+
"frontend",
|
|
127
|
+
"webpack",
|
|
128
|
+
"source map",
|
|
129
|
+
"sourcemap",
|
|
130
|
+
"web",
|
|
131
|
+
"browser",
|
|
132
|
+
"前端",
|
|
133
|
+
"프론트",
|
|
134
|
+
],
|
|
135
|
+
intentPatterns: [
|
|
136
|
+
"signature",
|
|
137
|
+
"encrypted params",
|
|
138
|
+
"request replay",
|
|
139
|
+
"token",
|
|
140
|
+
"decrypt",
|
|
141
|
+
"environment patch",
|
|
142
|
+
"ast",
|
|
143
|
+
"breakpoint",
|
|
144
|
+
"hook",
|
|
145
|
+
"서명",
|
|
146
|
+
"암호화",
|
|
147
|
+
],
|
|
148
|
+
toolPatterns: ["playwright", "chrome", "cdp", "jshook", "jshookmcp", "node", "anything-analyzer"],
|
|
149
|
+
keywords: [
|
|
150
|
+
"sign",
|
|
151
|
+
"nonce",
|
|
152
|
+
"timestamp",
|
|
153
|
+
"crypto",
|
|
154
|
+
"axios",
|
|
155
|
+
"fetch",
|
|
156
|
+
"xhr",
|
|
157
|
+
"webpackJsonp",
|
|
158
|
+
"localStorage",
|
|
159
|
+
"cookie",
|
|
160
|
+
],
|
|
161
|
+
requiredTools: ["node"],
|
|
162
|
+
optionalTools: ["playwright", "jshookmcp", "anything-analyzer", "chrome"],
|
|
163
|
+
skillHints: ["js-reverse", "browser-automation", "docs-generator"],
|
|
164
|
+
mcpHints: ["chrome-devtools", "playwright", "fetch", "filesystem"],
|
|
165
|
+
hookHints: ["pre-shell-guard", "protect-secrets", "session-context"],
|
|
166
|
+
acceptance: [
|
|
167
|
+
"Target request and parameter names are identified",
|
|
168
|
+
"Recovered algorithm has a local reproduction or explicit blocker",
|
|
169
|
+
"Browser/runtime observations are tied to request evidence",
|
|
170
|
+
],
|
|
171
|
+
firstActions: ["Identify target request", "Find producer function", "Rebuild the minimum local reproduction"],
|
|
172
|
+
risk: "local-tooling",
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
id: "browser-automation",
|
|
176
|
+
label: "Browser or desktop evidence collection",
|
|
177
|
+
skillPath: "skills/browser-automation/SKILL.md",
|
|
178
|
+
targetPatterns: ["website", "browser", "web app", "desktop app", "page", "form", "웹", "브라우저"],
|
|
179
|
+
intentPatterns: [
|
|
180
|
+
"open",
|
|
181
|
+
"screenshot",
|
|
182
|
+
"fill",
|
|
183
|
+
"crawl",
|
|
184
|
+
"capture",
|
|
185
|
+
"network",
|
|
186
|
+
"login",
|
|
187
|
+
"observe",
|
|
188
|
+
"click",
|
|
189
|
+
"샷",
|
|
190
|
+
"스크린샷",
|
|
191
|
+
],
|
|
192
|
+
toolPatterns: ["playwright", "chrome-devtools", "agent-browser", "openreverse", "anything-analyzer"],
|
|
193
|
+
keywords: ["dom", "selector", "network log", "console", "cookie", "screenshot", "trace"],
|
|
194
|
+
requiredTools: ["playwright"],
|
|
195
|
+
optionalTools: ["chrome", "agent-browser", "anything-analyzer"],
|
|
196
|
+
skillHints: ["browser-automation", "js-reverse", "docs-generator"],
|
|
197
|
+
mcpHints: ["playwright", "chrome-devtools", "filesystem"],
|
|
198
|
+
hookHints: ["pre-shell-guard", "protect-secrets"],
|
|
199
|
+
acceptance: [
|
|
200
|
+
"Browser state is captured with URL and selector evidence",
|
|
201
|
+
"Screenshots or logs are saved as artifacts when visual proof matters",
|
|
202
|
+
],
|
|
203
|
+
firstActions: [
|
|
204
|
+
"Open the target in an isolated browser context",
|
|
205
|
+
"Capture visible state",
|
|
206
|
+
"Record console/network evidence",
|
|
207
|
+
],
|
|
208
|
+
risk: "local-tooling",
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
id: "ctf-sandbox-orchestrator",
|
|
212
|
+
label: "CTF challenge routing",
|
|
213
|
+
skillPath: "../CTF-Sandbox-Orchestrator/ctf-sandbox-orchestrator/SKILL.md",
|
|
214
|
+
targetPatterns: ["ctf", "challenge", "flag", "pwn", "crypto challenge", "forensics", "misc", "rev", "대회"],
|
|
215
|
+
intentPatterns: ["solve", "find flag", "exploit", "writeup", "sandbox", "analyze challenge"],
|
|
216
|
+
toolPatterns: ["pwntools", "gdb", "python", "sage", "z3", "binwalk", "wireshark"],
|
|
217
|
+
keywords: ["flag{", "ctf", "nc ", "remote", "proof", "challenge files"],
|
|
218
|
+
requiredTools: [],
|
|
219
|
+
optionalTools: ["python", "gdb", "pwntools", "z3", "binwalk"],
|
|
220
|
+
skillHints: ["ctf-sandbox-orchestrator", "reverse-engineering", "pwn-chain", "docs-generator"],
|
|
221
|
+
mcpHints: ["filesystem", "memory"],
|
|
222
|
+
hookHints: ["pre-shell-guard", "protect-secrets", "stop-verify"],
|
|
223
|
+
acceptance: [
|
|
224
|
+
"Challenge category is justified by evidence",
|
|
225
|
+
"Solution artifacts are reproducible",
|
|
226
|
+
"Writeup includes the shortest verified path",
|
|
227
|
+
],
|
|
228
|
+
firstActions: [
|
|
229
|
+
"Inventory challenge files",
|
|
230
|
+
"Classify dominant evidence",
|
|
231
|
+
"Dispatch to the narrowest CTF sub-skill",
|
|
232
|
+
],
|
|
233
|
+
risk: "exploit-development",
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
id: "api-security",
|
|
237
|
+
label: "API security review and authorized test planning",
|
|
238
|
+
skillPath: "skills/api-security/SKILL.md",
|
|
239
|
+
targetPatterns: ["api", "rest", "graphql", "websocket", "jwt", "oauth", "endpoint"],
|
|
240
|
+
intentPatterns: ["review", "test", "auth", "idor", "bola", "bfla", "rate limit", "schema", "권한", "越权"],
|
|
241
|
+
toolPatterns: ["burp", "postman", "nuclei", "zap", "curl", "graphql"],
|
|
242
|
+
keywords: ["authorization", "authentication", "tenant", "object id", "jwt", "scope", "graphql"],
|
|
243
|
+
requiredTools: [],
|
|
244
|
+
optionalTools: ["burpsuite", "nuclei", "zap", "node", "python"],
|
|
245
|
+
skillHints: ["api-security", "pentest-tools", "docs-generator"],
|
|
246
|
+
mcpHints: ["filesystem", "github", "playwright"],
|
|
247
|
+
hookHints: ["pre-shell-guard", "protect-secrets", "stop-verify"],
|
|
248
|
+
acceptance: [
|
|
249
|
+
"Scope boundary is explicit",
|
|
250
|
+
"Findings include request/response evidence or code references",
|
|
251
|
+
"High-risk active steps require explicit target scope",
|
|
252
|
+
],
|
|
253
|
+
firstActions: [
|
|
254
|
+
"Identify API surface",
|
|
255
|
+
"Map auth and tenant boundaries",
|
|
256
|
+
"Choose static review or scoped dynamic testing",
|
|
257
|
+
],
|
|
258
|
+
risk: "active-security",
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
id: "supply-chain-security",
|
|
262
|
+
label: "Supply-chain and CI/CD security analysis",
|
|
263
|
+
skillPath: "skills/supply-chain-security/SKILL.md",
|
|
264
|
+
targetPatterns: [
|
|
265
|
+
"dependency",
|
|
266
|
+
"package",
|
|
267
|
+
"sbom",
|
|
268
|
+
"container",
|
|
269
|
+
"docker",
|
|
270
|
+
"ci",
|
|
271
|
+
"workflow",
|
|
272
|
+
"github actions",
|
|
273
|
+
"supply chain",
|
|
274
|
+
],
|
|
275
|
+
intentPatterns: ["scan", "audit", "gitleaks", "trivy", "osv", "syft", "secret", "provenance", "attestation"],
|
|
276
|
+
toolPatterns: ["trivy", "syft", "gitleaks", "osv-scanner", "npm audit", "semgrep"],
|
|
277
|
+
keywords: ["package-lock", "npm-shrinkwrap", "dockerfile", "workflow", "actions", "secret", "sbom"],
|
|
278
|
+
requiredTools: [],
|
|
279
|
+
optionalTools: ["trivy", "syft", "gitleaks", "osv-scanner", "semgrep"],
|
|
280
|
+
skillHints: ["supply-chain-security", "security-review", "docs-generator"],
|
|
281
|
+
mcpHints: ["filesystem", "github"],
|
|
282
|
+
hookHints: ["protect-secrets", "pre-shell-guard", "npm-audit-summary"],
|
|
283
|
+
acceptance: [
|
|
284
|
+
"Dependency, workflow, and secret surfaces are separated",
|
|
285
|
+
"Findings cite exact files and lockfile evidence",
|
|
286
|
+
],
|
|
287
|
+
firstActions: [
|
|
288
|
+
"Inventory package managers and workflows",
|
|
289
|
+
"Run non-mutating scans first",
|
|
290
|
+
"Triangulate tool output with source evidence",
|
|
291
|
+
],
|
|
292
|
+
risk: "passive-analysis",
|
|
293
|
+
},
|
|
294
|
+
{
|
|
295
|
+
id: "docs-generator",
|
|
296
|
+
label: "Security or reverse-engineering report generation",
|
|
297
|
+
skillPath: "skills/docs-generator/SKILL.md",
|
|
298
|
+
targetPatterns: ["report", "writeup", "documentation", "diagram", "flowchart", "보고서", "문서"],
|
|
299
|
+
intentPatterns: ["write", "summarize", "generate", "document", "explain", "diagram", "publish"],
|
|
300
|
+
toolPatterns: ["markdown", "mermaid", "graphviz", "plantuml", "pdf"],
|
|
301
|
+
keywords: ["executive summary", "reproduction", "impact", "timeline", "artifact", "evidence"],
|
|
302
|
+
requiredTools: [],
|
|
303
|
+
optionalTools: ["graphviz", "plantuml", "pandoc"],
|
|
304
|
+
skillHints: ["docs-generator", "diagram-generator"],
|
|
305
|
+
mcpHints: ["filesystem"],
|
|
306
|
+
hookHints: ["protect-secrets"],
|
|
307
|
+
acceptance: [
|
|
308
|
+
"Report separates facts, analysis, and recommendations",
|
|
309
|
+
"Artifacts and commands are reproducible",
|
|
310
|
+
"Sensitive values are redacted",
|
|
311
|
+
],
|
|
312
|
+
firstActions: [
|
|
313
|
+
"Collect evidence paths",
|
|
314
|
+
"Choose report template",
|
|
315
|
+
"Add at least one flow or evidence diagram when useful",
|
|
316
|
+
],
|
|
317
|
+
risk: "passive-analysis",
|
|
318
|
+
},
|
|
319
|
+
];
|
|
320
|
+
const TOOL_ALIASES = {
|
|
321
|
+
"anything-analyzer": ["anything-analyzer"],
|
|
322
|
+
adb: ["adb"],
|
|
323
|
+
apktool: ["apktool"],
|
|
324
|
+
apksigner: ["apksigner"],
|
|
325
|
+
binwalk: ["binwalk"],
|
|
326
|
+
burpsuite: ["burpsuite"],
|
|
327
|
+
chrome: ["google-chrome", "chromium", "chrome"],
|
|
328
|
+
frida: ["frida", "frida-ps"],
|
|
329
|
+
gdb: ["gdb"],
|
|
330
|
+
ghidra: ["ghidraRun", "analyzeHeadless"],
|
|
331
|
+
gitleaks: ["gitleaks"],
|
|
332
|
+
graphviz: ["dot"],
|
|
333
|
+
idapro: ["ida", "idat", "idat64"],
|
|
334
|
+
"idalib-mcp": ["ida-pro-mcp", "idalib-mcp"],
|
|
335
|
+
jadx: ["jadx"],
|
|
336
|
+
jshookmcp: ["jshook", "npx"],
|
|
337
|
+
node: ["node"],
|
|
338
|
+
nuclei: ["nuclei"],
|
|
339
|
+
pandoc: ["pandoc"],
|
|
340
|
+
plantuml: ["plantuml"],
|
|
341
|
+
playwright: ["playwright", "npx"],
|
|
342
|
+
pwntools: ["python"],
|
|
343
|
+
python: ["python3", "python"],
|
|
344
|
+
radare2: ["radare2", "r2"],
|
|
345
|
+
rabin2: ["rabin2"],
|
|
346
|
+
radiff2: ["radiff2"],
|
|
347
|
+
rasm2: ["rasm2"],
|
|
348
|
+
semgrep: ["semgrep"],
|
|
349
|
+
syft: ["syft"],
|
|
350
|
+
trivy: ["trivy"],
|
|
351
|
+
zap: ["zap.sh", "zap-baseline.py"],
|
|
352
|
+
zipalign: ["zipalign"],
|
|
353
|
+
z3: ["z3"],
|
|
354
|
+
};
|
|
355
|
+
export function getReverseSkillToolAliases(tool) {
|
|
356
|
+
return TOOL_ALIASES[tool] ?? [tool];
|
|
357
|
+
}
|
|
358
|
+
export function normalizeReverseSkillText(text) {
|
|
359
|
+
return (text ?? "")
|
|
360
|
+
.normalize("NFKC")
|
|
361
|
+
.toLowerCase()
|
|
362
|
+
.replace(/[\u2018\u2019]/g, "'")
|
|
363
|
+
.replace(/[\u201c\u201d]/g, '"')
|
|
364
|
+
.replace(/[_./\\:-]+/g, " ")
|
|
365
|
+
.replace(/\s+/g, " ")
|
|
366
|
+
.trim();
|
|
367
|
+
}
|
|
368
|
+
function findPatternMatches(text, patterns) {
|
|
369
|
+
const matches = [];
|
|
370
|
+
for (const pattern of patterns) {
|
|
371
|
+
const normalizedPattern = normalizeReverseSkillText(pattern);
|
|
372
|
+
if (!normalizedPattern)
|
|
373
|
+
continue;
|
|
374
|
+
if (text.includes(normalizedPattern))
|
|
375
|
+
matches.push(pattern);
|
|
376
|
+
}
|
|
377
|
+
return matches;
|
|
378
|
+
}
|
|
379
|
+
function unique(items) {
|
|
380
|
+
return Array.from(new Set(items.filter((item) => item.trim().length > 0)));
|
|
381
|
+
}
|
|
382
|
+
function clampConfidence(score) {
|
|
383
|
+
if (score <= 0)
|
|
384
|
+
return 0;
|
|
385
|
+
return Math.min(0.99, Number((score / ROUTE_SCORE_CONFIDENCE_DENOMINATOR).toFixed(2)));
|
|
386
|
+
}
|
|
387
|
+
function scoreRoute(route, input, normalizedQuery) {
|
|
388
|
+
const targetText = normalizeReverseSkillText(`${input.targetType ?? ""} ${normalizedQuery}`);
|
|
389
|
+
const intentText = normalizeReverseSkillText(`${input.intent ?? ""} ${normalizedQuery}`);
|
|
390
|
+
const toolText = normalizeReverseSkillText(`${input.toolchain ?? ""} ${normalizedQuery}`);
|
|
391
|
+
const target = findPatternMatches(targetText, route.targetPatterns);
|
|
392
|
+
const intent = findPatternMatches(intentText, route.intentPatterns);
|
|
393
|
+
const toolchain = findPatternMatches(toolText, route.toolPatterns);
|
|
394
|
+
const keywords = findPatternMatches(normalizedQuery, route.keywords);
|
|
395
|
+
let score = target.length * ROUTE_SCORE_TARGET_WEIGHT +
|
|
396
|
+
intent.length * ROUTE_SCORE_INTENT_WEIGHT +
|
|
397
|
+
toolchain.length * ROUTE_SCORE_TOOL_WEIGHT +
|
|
398
|
+
keywords.length * ROUTE_SCORE_KEYWORD_WEIGHT;
|
|
399
|
+
if (target.length > 0 && intent.length > 0 && toolchain.length > 0) {
|
|
400
|
+
score += ROUTE_SCORE_FULL_TRIAD_BONUS;
|
|
401
|
+
}
|
|
402
|
+
const missingDimensions = [];
|
|
403
|
+
if (target.length === 0)
|
|
404
|
+
missingDimensions.push("target");
|
|
405
|
+
if (intent.length === 0)
|
|
406
|
+
missingDimensions.push("intent");
|
|
407
|
+
if (toolchain.length === 0)
|
|
408
|
+
missingDimensions.push("toolchain");
|
|
409
|
+
return {
|
|
410
|
+
route,
|
|
411
|
+
score,
|
|
412
|
+
confidence: clampConfidence(score),
|
|
413
|
+
matched: { target, intent, toolchain, keywords },
|
|
414
|
+
missingDimensions,
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
export function routeReverseSkill(input) {
|
|
418
|
+
const normalizedQuery = normalizeReverseSkillText(input.query);
|
|
419
|
+
const platform = input.platform ?? "unknown";
|
|
420
|
+
const maxAlternatives = input.maxAlternatives ?? 3;
|
|
421
|
+
const scored = REVERSE_SKILL_ROUTES.map((route) => scoreRoute(route, input, normalizedQuery))
|
|
422
|
+
.filter((candidate) => candidate.score > 0)
|
|
423
|
+
.sort((a, b) => b.score - a.score || REVERSE_SKILL_ROUTES.indexOf(a.route) - REVERSE_SKILL_ROUTES.indexOf(b.route));
|
|
424
|
+
const primary = scored[0];
|
|
425
|
+
const alternatives = scored.slice(1, maxAlternatives + 1);
|
|
426
|
+
const unmatched = !primary || primary.score < ROUTE_SCORE_TARGET_WEIGHT || primary.confidence < 0.18;
|
|
427
|
+
const nextAction = unmatched
|
|
428
|
+
? "No strong route matched. Create or extend a reverse-skill route before executing specialized tooling."
|
|
429
|
+
: `Read ${primary.route.skillPath}, then execute first action: ${primary.route.firstActions[0] ?? "classify target"}.`;
|
|
430
|
+
return {
|
|
431
|
+
query: input.query,
|
|
432
|
+
platform,
|
|
433
|
+
normalizedQuery,
|
|
434
|
+
primary: unmatched ? undefined : primary,
|
|
435
|
+
alternatives,
|
|
436
|
+
unmatched,
|
|
437
|
+
nextAction,
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
function formatMatches(score) {
|
|
441
|
+
const parts = [
|
|
442
|
+
`target=${score.matched.target.join(", ") || "—"}`,
|
|
443
|
+
`intent=${score.matched.intent.join(", ") || "—"}`,
|
|
444
|
+
`toolchain=${score.matched.toolchain.join(", ") || "—"}`,
|
|
445
|
+
`keywords=${score.matched.keywords.join(", ") || "—"}`,
|
|
446
|
+
];
|
|
447
|
+
return parts.join("; ");
|
|
448
|
+
}
|
|
449
|
+
export function formatReverseSkillRouteDecision(decision) {
|
|
450
|
+
if (decision.unmatched || !decision.primary) {
|
|
451
|
+
return [
|
|
452
|
+
"# Reverse Skill Route",
|
|
453
|
+
"",
|
|
454
|
+
`Query: ${decision.query}`,
|
|
455
|
+
"",
|
|
456
|
+
"No strong route matched.",
|
|
457
|
+
"Next action: create or extend a route before specialized execution.",
|
|
458
|
+
].join("\n");
|
|
459
|
+
}
|
|
460
|
+
const primary = decision.primary;
|
|
461
|
+
const lines = [
|
|
462
|
+
"# Reverse Skill Route",
|
|
463
|
+
"",
|
|
464
|
+
`Query: ${decision.query}`,
|
|
465
|
+
`Platform: ${decision.platform}`,
|
|
466
|
+
"",
|
|
467
|
+
`## Primary: ${primary.route.label}`,
|
|
468
|
+
`- Route ID: ${primary.route.id}`,
|
|
469
|
+
`- Skill path: ${primary.route.skillPath}`,
|
|
470
|
+
`- Confidence: ${primary.confidence}`,
|
|
471
|
+
`- Score: ${primary.score}`,
|
|
472
|
+
`- Matches: ${formatMatches(primary)}`,
|
|
473
|
+
`- Risk: ${primary.route.risk}`,
|
|
474
|
+
`- Required tools: ${primary.route.requiredTools.join(", ") || "none"}`,
|
|
475
|
+
`- Optional tools: ${primary.route.optionalTools.join(", ") || "none"}`,
|
|
476
|
+
`- Skill hints: ${primary.route.skillHints.join(", ")}`,
|
|
477
|
+
`- MCP hints: ${primary.route.mcpHints.join(", ")}`,
|
|
478
|
+
`- Hooks: ${primary.route.hookHints.join(", ")}`,
|
|
479
|
+
"",
|
|
480
|
+
"## First actions",
|
|
481
|
+
...primary.route.firstActions.map((action, index) => `${index + 1}. ${action}`),
|
|
482
|
+
"",
|
|
483
|
+
"## Acceptance",
|
|
484
|
+
...primary.route.acceptance.map((item) => `- ${item}`),
|
|
485
|
+
];
|
|
486
|
+
if (decision.alternatives.length > 0) {
|
|
487
|
+
lines.push("", "## Alternatives");
|
|
488
|
+
for (const alternative of decision.alternatives) {
|
|
489
|
+
lines.push(`- ${alternative.route.id} (${alternative.confidence}): ${formatMatches(alternative)}`);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
lines.push("", `Next action: ${decision.nextAction}`);
|
|
493
|
+
return lines.join("\n");
|
|
494
|
+
}
|
|
495
|
+
export function planReverseSkillToolChecks(decision, maxTools = 12) {
|
|
496
|
+
const routes = [decision.primary, ...decision.alternatives].filter((score) => Boolean(score));
|
|
497
|
+
const tools = [];
|
|
498
|
+
for (const score of routes) {
|
|
499
|
+
tools.push(...score.route.requiredTools, ...score.route.optionalTools);
|
|
500
|
+
}
|
|
501
|
+
return unique(tools).slice(0, maxTools);
|
|
502
|
+
}
|
|
503
|
+
export function normalizeReverseSkillName(name) {
|
|
504
|
+
const normalized = normalizeReverseSkillText(name)
|
|
505
|
+
.replace(/[^a-z0-9-]+/g, "-")
|
|
506
|
+
.replace(/-+/g, "-")
|
|
507
|
+
.replace(/^-|-$/g, "")
|
|
508
|
+
.slice(0, 64)
|
|
509
|
+
.replace(/-$/g, "");
|
|
510
|
+
return normalized || "reverse-skill";
|
|
511
|
+
}
|
|
512
|
+
function yamlString(value) {
|
|
513
|
+
return JSON.stringify(value);
|
|
514
|
+
}
|
|
515
|
+
function compactDescription(input, routes) {
|
|
516
|
+
const fallback = `Use when ${input.triggerSummary}`;
|
|
517
|
+
const base = input.description?.trim() || fallback;
|
|
518
|
+
const routeNames = routes.map((route) => route.id).join(", ");
|
|
519
|
+
const suffix = routeNames ? ` Routes through ${routeNames}.` : "";
|
|
520
|
+
const description = `${base}${suffix}`;
|
|
521
|
+
return description.length <= 1024 ? description : `${description.slice(0, 1021).trimEnd()}...`;
|
|
522
|
+
}
|
|
523
|
+
function routesFromIds(routeIds) {
|
|
524
|
+
if (!routeIds)
|
|
525
|
+
return [];
|
|
526
|
+
return routeIds
|
|
527
|
+
.map((routeId) => REVERSE_SKILL_ROUTES.find((route) => route.id === routeId))
|
|
528
|
+
.filter((route) => Boolean(route));
|
|
529
|
+
}
|
|
530
|
+
export function formatReverseSkillMarkdown(input) {
|
|
531
|
+
const routes = routesFromIds(input.routeIds);
|
|
532
|
+
const name = normalizeReverseSkillName(input.name);
|
|
533
|
+
const description = compactDescription(input, routes);
|
|
534
|
+
const workflowSteps = input.workflowSteps?.length
|
|
535
|
+
? input.workflowSteps
|
|
536
|
+
: [
|
|
537
|
+
"Classify target, intent, and available toolchain before execution",
|
|
538
|
+
"Read the routed sub-skill or reference",
|
|
539
|
+
"Collect evidence and record blockers",
|
|
540
|
+
"Verify output against acceptance criteria",
|
|
541
|
+
];
|
|
542
|
+
const tools = unique([
|
|
543
|
+
...(input.tools ?? []),
|
|
544
|
+
...routes.flatMap((route) => [...route.requiredTools, ...route.optionalTools]),
|
|
545
|
+
]);
|
|
546
|
+
const mcpServers = unique([...(input.mcpServers ?? []), ...routes.flatMap((route) => route.mcpHints)]);
|
|
547
|
+
const hooks = unique([...(input.hooks ?? []), ...routes.flatMap((route) => route.hookHints)]);
|
|
548
|
+
const acceptance = unique([...(input.acceptance ?? []), ...routes.flatMap((route) => route.acceptance)]);
|
|
549
|
+
const safetyNotes = input.safetyNotes?.length
|
|
550
|
+
? input.safetyNotes
|
|
551
|
+
: [
|
|
552
|
+
"Operate only on user-provided local artifacts or explicitly scoped targets",
|
|
553
|
+
"Do not write secrets, tokens, credentials, or raw private data into skill artifacts",
|
|
554
|
+
];
|
|
555
|
+
const lines = [
|
|
556
|
+
"---",
|
|
557
|
+
`name: ${name}`,
|
|
558
|
+
`description: ${yamlString(description)}`,
|
|
559
|
+
"---",
|
|
560
|
+
"",
|
|
561
|
+
`# ${name}`,
|
|
562
|
+
"",
|
|
563
|
+
"## Routing contract",
|
|
564
|
+
"",
|
|
565
|
+
`Trigger this skill when ${input.triggerSummary}.`,
|
|
566
|
+
"",
|
|
567
|
+
"1. Classify the target type, user intent, and toolchain signals before choosing tools.",
|
|
568
|
+
"2. Read the narrowest referenced sub-skill or reference before running specialized tooling.",
|
|
569
|
+
"3. Prefer passive/local evidence collection first; escalate only when the task scope requires it.",
|
|
570
|
+
"4. Save evidence paths and blockers in the final response.",
|
|
571
|
+
"",
|
|
572
|
+
"## Workflow",
|
|
573
|
+
"",
|
|
574
|
+
...workflowSteps.map((step, index) => `${index + 1}. ${step}`),
|
|
575
|
+
"",
|
|
576
|
+
"## Tool selection",
|
|
577
|
+
"",
|
|
578
|
+
`- Tools: ${tools.join(", ") || "none required"}`,
|
|
579
|
+
`- MCP: ${mcpServers.join(", ") || "filesystem/read-only"}`,
|
|
580
|
+
`- Hooks: ${hooks.join(", ") || "pre-shell-guard, protect-secrets"}`,
|
|
581
|
+
"",
|
|
582
|
+
"## Acceptance",
|
|
583
|
+
"",
|
|
584
|
+
...(acceptance.length > 0
|
|
585
|
+
? acceptance.map((item) => `- ${item}`)
|
|
586
|
+
: ["- Output includes routed skill, evidence, commands, and remaining blockers"]),
|
|
587
|
+
"",
|
|
588
|
+
"## Safety constraints",
|
|
589
|
+
"",
|
|
590
|
+
...safetyNotes.map((note) => `- ${note}`),
|
|
591
|
+
];
|
|
592
|
+
if (routes.length > 0) {
|
|
593
|
+
lines.push("", "## Built-in routes", "");
|
|
594
|
+
for (const route of routes) {
|
|
595
|
+
lines.push(`- ${route.id}: ${route.skillPath}`);
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
if (input.references?.length) {
|
|
599
|
+
lines.push("", "## References", "");
|
|
600
|
+
for (const reference of input.references)
|
|
601
|
+
lines.push(`- ${reference}`);
|
|
602
|
+
}
|
|
603
|
+
return `${lines.join("\n").trimEnd()}\n`;
|
|
604
|
+
}
|
|
605
|
+
export function extractReverseSkillFactsFromMarkdown(sourceText) {
|
|
606
|
+
const headings = unique(Array.from(sourceText.matchAll(/^#{1,4}\s+(.+)$/gm)).map((match) => match[1]?.trim() ?? "")).slice(0, 24);
|
|
607
|
+
const skillPaths = unique(Array.from(sourceText.matchAll(/[`(\s]([^`\s)]+SKILL\.md)/g)).map((match) => match[1] ?? ""));
|
|
608
|
+
const scriptPaths = unique(Array.from(sourceText.matchAll(/[`(\s]([^`\s)]+\.(?:ps1|sh|py|js|ts))/g)).map((match) => match[1] ?? ""));
|
|
609
|
+
const knownTools = Object.keys(TOOL_ALIASES);
|
|
610
|
+
const normalizedSource = normalizeReverseSkillText(sourceText);
|
|
611
|
+
const tools = knownTools.filter((tool) => normalizedSource.includes(normalizeReverseSkillText(tool)));
|
|
612
|
+
const mcpServers = unique(Array.from(sourceText.matchAll(/\b([a-z0-9-]+)\s+MCP\b/gi)).map((match) => match[1]?.toLowerCase() ?? ""));
|
|
613
|
+
const triggerTerms = unique(REVERSE_SKILL_ROUTES.flatMap((route) => [
|
|
614
|
+
...route.targetPatterns,
|
|
615
|
+
...route.intentPatterns,
|
|
616
|
+
...route.toolPatterns,
|
|
617
|
+
]).filter((term) => normalizedSource.includes(normalizeReverseSkillText(term)))).slice(0, 40);
|
|
618
|
+
return { headings, skillPaths, scriptPaths, tools, mcpServers, triggerTerms };
|
|
619
|
+
}
|
|
620
|
+
export function formatReverseSkillFromSource(input) {
|
|
621
|
+
const facts = extractReverseSkillFactsFromMarkdown(input.sourceText);
|
|
622
|
+
const query = [input.triggerSummary, facts.triggerTerms.slice(0, 12).join(" "), facts.headings.slice(0, 8).join(" ")]
|
|
623
|
+
.filter(Boolean)
|
|
624
|
+
.join(" ");
|
|
625
|
+
const decision = routeReverseSkill({ query: query || input.sourceText.slice(0, 500) });
|
|
626
|
+
const routeIds = [decision.primary?.route.id, ...decision.alternatives.map((item) => item.route.id)].filter((routeId) => Boolean(routeId));
|
|
627
|
+
const name = input.name ?? (decision.primary ? `${decision.primary.route.id}-workflow` : "reverse-skill-workflow");
|
|
628
|
+
const triggerSummary = input.triggerSummary ??
|
|
629
|
+
(facts.triggerTerms.slice(0, 8).join(", ") || "reverse-engineering or security routing is needed");
|
|
630
|
+
const workflowSteps = [
|
|
631
|
+
"Classify the incoming request against target, intent, and toolchain signals",
|
|
632
|
+
"Load the routed sub-skill before running specialized tooling",
|
|
633
|
+
"Check local tool availability only for tools required by the selected route",
|
|
634
|
+
"Return evidence paths, commands, confidence, and blockers",
|
|
635
|
+
];
|
|
636
|
+
return formatReverseSkillMarkdown({
|
|
637
|
+
name,
|
|
638
|
+
description: input.description,
|
|
639
|
+
triggerSummary,
|
|
640
|
+
routeIds,
|
|
641
|
+
workflowSteps,
|
|
642
|
+
tools: facts.tools,
|
|
643
|
+
mcpServers: facts.mcpServers,
|
|
644
|
+
references: unique([...facts.skillPaths, ...facts.scriptPaths]).slice(0, 20),
|
|
645
|
+
});
|
|
646
|
+
}
|
|
647
|
+
//# sourceMappingURL=reverse-skill.js.map
|