@@ -26,14 +26,36 @@ function findSkill(skills: QaSkillStatusEntry[], name: string) {
|
26 | 26 | return skills.find((skill) => skill.name === name); |
27 | 27 | } |
28 | 28 | |
| 29 | +function resolveWorkspaceSkillPath(workspaceDir: string, name: string) { |
| 30 | +const trimmed = name.trim(); |
| 31 | +if ( |
| 32 | +!trimmed || |
| 33 | +trimmed !== name || |
| 34 | +trimmed === "." || |
| 35 | +trimmed === ".." || |
| 36 | +trimmed.includes("\0") || |
| 37 | +/[\\/]/u.test(trimmed) |
| 38 | +) { |
| 39 | +throw new Error(`invalid QA workspace skill name: ${JSON.stringify(name)}`); |
| 40 | +} |
| 41 | + |
| 42 | +const skillsDir = path.resolve(workspaceDir, "skills"); |
| 43 | +const skillDir = path.resolve(skillsDir, trimmed); |
| 44 | +const relative = path.relative(skillsDir, skillDir); |
| 45 | +if (!relative || relative.startsWith("..") || path.isAbsolute(relative)) { |
| 46 | +throw new Error(`invalid QA workspace skill name: ${JSON.stringify(name)}`); |
| 47 | +} |
| 48 | +return path.join(skillDir, "SKILL.md"); |
| 49 | +} |
| 50 | + |
29 | 51 | async function writeWorkspaceSkill(params: { |
30 | 52 | env: Pick<QaSuiteRuntimeEnv, "gateway">; |
31 | 53 | name: string; |
32 | 54 | body: string; |
33 | 55 | }) { |
34 | | -const skillDir = path.join(params.env.gateway.workspaceDir, "skills", params.name); |
| 56 | +const skillPath = resolveWorkspaceSkillPath(params.env.gateway.workspaceDir, params.name); |
| 57 | +const skillDir = path.dirname(skillPath); |
35 | 58 | await fs.mkdir(skillDir, { recursive: true }); |
36 | | -const skillPath = path.join(skillDir, "SKILL.md"); |
37 | 59 | await fs.writeFile(skillPath, `${params.body.trim()}\n`, "utf8"); |
38 | 60 | return skillPath; |
39 | 61 | } |
@@ -113,4 +135,10 @@ async function handleQaAction(params: {
|
113 | 135 | return extractQaToolPayload(result as Parameters<typeof extractQaToolPayload>[0]); |
114 | 136 | } |
115 | 137 | |
116 | | -export { callPluginToolsMcp, findSkill, handleQaAction, writeWorkspaceSkill }; |
| 138 | +export { |
| 139 | +callPluginToolsMcp, |
| 140 | +findSkill, |
| 141 | +handleQaAction, |
| 142 | +resolveWorkspaceSkillPath, |
| 143 | +writeWorkspaceSkill, |
| 144 | +}; |