Skip to content

Commit 4b33392

Browse files
committed
feat: implement enabledSkills support in settings.json
1 parent fc1223b commit 4b33392

6 files changed

Lines changed: 160 additions & 1 deletion

File tree

docs/configuration.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ Deep Code 使用 `settings.json` 设置文件进行持久化配置,支持两
3636
| `webSearchTool` | string | 自定义联网搜索脚本的完整路径 |
3737
| `mcpServers` | object | MCP 服务器配置(键为服务名,值为 McpServerConfig 对象) |
3838
| `temperature` | number | 模型采样温度,范围 `0``2` |
39+
| `enabledSkills` | object | 按 skill 名称启用或禁用 skill 的配置 |
3940

4041
#### `env` 子字段
4142

@@ -101,6 +102,23 @@ Deep Code 内置免费可用的 Web Search 工具。如果需要自定义搜索
101102

102103
脚本接收一个搜索查询参数,输出 JSON 格式的结果供 AI 使用。
103104

105+
#### `enabledSkills` — Skill 启用配置
106+
107+
控制 skill 扫描时是否包含指定 skill。键是解析后的 skill 名称,值必须是布尔值:
108+
109+
```json
110+
{
111+
"enabledSkills": {
112+
"skill-writer": false,
113+
"code-review": true
114+
}
115+
}
116+
```
117+
118+
- 未配置的 skill 默认启用。
119+
- 将某个 skill 设置为 `false` 后,所有项目级和用户级目录中解析名称相同的 skill 都会被隐藏。
120+
- 项目设置会按 skill 覆盖用户设置。如果项目设置没有配置某个 skill,则使用用户设置。
121+
104122
#### `mcpServers` — MCP 服务器
105123

106124
MCP(Model Context Protocol)服务器配置。值是键值对,键为服务名称,值为服务器配置对象。

docs/configuration_en.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ The following are all the top-level fields supported in `settings.json`, along w
3636
| `webSearchTool` | string | Full path to a custom web search script |
3737
| `mcpServers` | object | MCP server configurations (keys are service names, values are McpServerConfig objects) |
3838
| `temperature` | number | Sampling temperature for LLM, from `0` to `2` |
39+
| `enabledSkills` | object | Per-skill enable/disable map, keyed by skill name |
3940

4041
#### `env` Sub-fields
4142

@@ -101,6 +102,23 @@ Deep Code has a built-in, free-to-use Web Search tool. If you need custom search
101102

102103
The script receives a search query as an argument and outputs results in JSON format for the AI.
103104

105+
#### `enabledSkills` — Skill Enablement
106+
107+
Controls whether skills are included during skill scanning. Keys are resolved skill names, and values must be booleans:
108+
109+
```json
110+
{
111+
"enabledSkills": {
112+
"skill-writer": false,
113+
"code-review": true
114+
}
115+
}
116+
```
117+
118+
- Missing entries are enabled by default.
119+
- Setting a skill to `false` hides every skill with that resolved `name`, across project and user skill roots.
120+
- Project settings override user settings per skill. If the project setting omits a skill, the user setting is used.
121+
104122
#### `mcpServers` — MCP Servers
105123

106124
Configuration for MCP (Model Context Protocol) servers. The value is a key-value pair, where the key is the service name and the value is a server configuration object.

src/session.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,7 @@ type SessionManagerOptions = {
298298
webSearchTool?: string;
299299
mcpServers?: Record<string, McpServerConfig>;
300300
permissions?: Required<PermissionSettings>;
301+
enabledSkills?: Record<string, boolean>;
301302
};
302303
renderMarkdown: (text: string) => string;
303304
onAssistantMessage: (message: SessionMessage, shouldConnect: boolean) => void;
@@ -324,6 +325,7 @@ export class SessionManager {
324325
webSearchTool?: string;
325326
mcpServers?: Record<string, McpServerConfig>;
326327
permissions?: Required<PermissionSettings>;
328+
enabledSkills?: Record<string, boolean>;
327329
};
328330
private readonly onAssistantMessage: (message: SessionMessage, shouldConnect: boolean) => void;
329331
private readonly onSessionEntryUpdated?: (entry: SessionEntry) => void;
@@ -808,6 +810,7 @@ ${agentInstructions}
808810

809811
async listSkills(sessionId?: string): Promise<SkillInfo[]> {
810812
const skillRoots = this.getSkillScanRoots();
813+
const enabledSkills = this.getResolvedSettings().enabledSkills ?? {};
811814
const skillsByName = new Map<string, SkillInfo>();
812815

813816
const collectSkills = (root: string, displayRoot: string): SkillInfo[] => {
@@ -839,7 +842,11 @@ ${agentInstructions}
839842
} catch {
840843
continue;
841844
}
842-
results.push(this.readSkillInfo(skillPath, `${displayRoot}/${skillName}/SKILL.md`, skillName));
845+
const skill = this.readSkillInfo(skillPath, `${displayRoot}/${skillName}/SKILL.md`, skillName);
846+
if (enabledSkills[skill.name] === false) {
847+
continue;
848+
}
849+
results.push(skill);
843850
}
844851
return results;
845852
};

src/settings.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ export type PermissionSettings = {
4343
defaultMode?: PermissionDefaultMode;
4444
};
4545

46+
export type EnabledSkillsSettings = Record<string, boolean>;
47+
4648
export type DeepcodingSettings = {
4749
env?: DeepcodingEnv;
4850
model?: string;
@@ -55,6 +57,7 @@ export type DeepcodingSettings = {
5557
webSearchTool?: string;
5658
mcpServers?: Record<string, McpServerConfig>;
5759
permissions?: PermissionSettings;
60+
enabledSkills?: EnabledSkillsSettings;
5861
};
5962

6063
export type ResolvedDeepcodingSettings = {
@@ -71,6 +74,7 @@ export type ResolvedDeepcodingSettings = {
7174
webSearchTool?: string;
7275
mcpServers?: Record<string, McpServerConfig>;
7376
permissions: Required<PermissionSettings>;
77+
enabledSkills: EnabledSkillsSettings;
7478
};
7579

7680
export type ModelConfigSelection = {
@@ -188,6 +192,30 @@ function mergePermissions(
188192
};
189193
}
190194

195+
function normalizeEnabledSkills(value: unknown): EnabledSkillsSettings {
196+
if (!value || typeof value !== "object" || Array.isArray(value)) {
197+
return {};
198+
}
199+
const result: EnabledSkillsSettings = {};
200+
for (const [name, enabled] of Object.entries(value)) {
201+
if (!name || typeof enabled !== "boolean") {
202+
continue;
203+
}
204+
result[name] = enabled;
205+
}
206+
return result;
207+
}
208+
209+
function mergeEnabledSkills(
210+
userSettings: DeepcodingSettings | null | undefined,
211+
projectSettings: DeepcodingSettings | null | undefined
212+
): EnabledSkillsSettings {
213+
return {
214+
...normalizeEnabledSkills(userSettings?.enabledSkills),
215+
...normalizeEnabledSkills(projectSettings?.enabledSkills),
216+
};
217+
}
218+
191219
function normalizeEnv(env: DeepcodingSettings["env"]): Record<string, string> {
192220
const result: Record<string, string> = {};
193221
if (!env) {
@@ -364,6 +392,7 @@ export function resolveSettingsSources(
364392
webSearchTool: webSearchTool || undefined,
365393
mcpServers: mergeMcpServers(userSettings, projectSettings, userEnv, projectEnv, systemEnv),
366394
permissions: mergePermissions(userSettings, projectSettings),
395+
enabledSkills: mergeEnabledSkills(userSettings, projectSettings),
367396
};
368397
}
369398

src/tests/session.test.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -471,6 +471,60 @@ test("SessionManager lists skills from Deep Code and .agents roots by priority",
471471
assert.equal(sharedSkill?.description, "Project .deepcode skill");
472472
});
473473

474+
test("SessionManager excludes disabled skills by resolved skill name", async () => {
475+
const workspace = createTempDir("deepcode-disabled-skills-workspace-");
476+
const home = createTempDir("deepcode-disabled-skills-home-");
477+
setHomeDir(home);
478+
479+
const writeSkill = (root: string, dirName: string, skillName: string): void => {
480+
const skillDir = path.join(root, dirName);
481+
fs.mkdirSync(skillDir, { recursive: true });
482+
fs.writeFileSync(
483+
path.join(skillDir, "SKILL.md"),
484+
`---\nname: ${skillName}\ndescription: ${skillName} description\n---\n# ${skillName}\n`,
485+
"utf8"
486+
);
487+
};
488+
489+
for (const root of [
490+
path.join(workspace, ".deepcode", "skills"),
491+
path.join(workspace, ".agents", "skills"),
492+
path.join(home, ".deepcode", "skills"),
493+
path.join(home, ".agents", "skills"),
494+
]) {
495+
writeSkill(root, "skill-writer", "skill-writer");
496+
}
497+
writeSkill(path.join(workspace, ".deepcode", "skills"), "frontmatter-disabled", "renamed-disabled");
498+
writeSkill(path.join(workspace, ".deepcode", "skills"), "enabled-skill", "enabled-skill");
499+
500+
const manager = new SessionManager({
501+
projectRoot: workspace,
502+
createOpenAIClient: () => ({
503+
client: null,
504+
model: "test-model",
505+
baseURL: "https://api.deepseek.com",
506+
thinkingEnabled: false,
507+
machineId: "machine-id-disabled-skills",
508+
}),
509+
getResolvedSettings: () => ({
510+
model: "test-model",
511+
enabledSkills: {
512+
"skill-writer": false,
513+
"renamed-disabled": false,
514+
"enabled-skill": true,
515+
},
516+
}),
517+
renderMarkdown: (text) => text,
518+
onAssistantMessage: () => {},
519+
});
520+
521+
const skills = await manager.listSkills();
522+
const skillNames = skills.map((skill) => skill.name);
523+
524+
assert.deepEqual(skillNames, ["enabled-skill"]);
525+
assert.equal(skills[0]?.path, "./.deepcode/skills/enabled-skill/SKILL.md");
526+
});
527+
474528
test("SessionManager dispose disconnects MCP servers", async () => {
475529
const workspace = createTempDir("deepcode-mcp-dispose-workspace-");
476530
const serverPath = path.join(workspace, "mcp-server.cjs");

src/tests/settings-and-notify.test.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,39 @@ test("resolveSettingsSources merges permission settings", () => {
220220
assert.equal(resolved.permissions.defaultMode, "allowAll");
221221
});
222222

223+
test("resolveSettingsSources merges enabledSkills with project precedence", () => {
224+
const resolved = resolveSettingsSources(
225+
{
226+
enabledSkills: {
227+
inherited: false,
228+
"project-enabled": false,
229+
"project-disabled": true,
230+
invalid: "false" as never,
231+
},
232+
},
233+
{
234+
enabledSkills: {
235+
"project-enabled": true,
236+
"project-disabled": false,
237+
projectOnly: true,
238+
ignored: null as never,
239+
},
240+
},
241+
{
242+
model: "default-model",
243+
baseURL: "https://default.example.com",
244+
},
245+
TEST_PROCESS_ENV
246+
);
247+
248+
assert.deepEqual(resolved.enabledSkills, {
249+
inherited: false,
250+
"project-enabled": true,
251+
"project-disabled": false,
252+
projectOnly: true,
253+
});
254+
});
255+
223256
test("resolveSettingsSources merges MCP env with documented priority", () => {
224257
const resolved = resolveSettingsSources(
225258
{

0 commit comments

Comments
 (0)