Skip to content

Commit 726034f

Browse files
committed
feat: implement bundled built-in skills
1 parent ffc7682 commit 726034f

19 files changed

Lines changed: 2911 additions & 6 deletions

File tree

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"main": "./dist/cli.js",
1616
"files": [
1717
"dist/cli.js",
18+
"dist/bundled/**",
1819
"templates/tools/**",
1920
"templates/prompts/**",
2021
"templates/skills/**",
@@ -26,7 +27,7 @@
2627
},
2728
"scripts": {
2829
"typecheck": "tsc -p ./ --noEmit",
29-
"bundle": "esbuild ./src/cli.tsx --bundle --platform=node --format=esm --target=node18 --outfile=dist/cli.js --banner:js=\"#!/usr/bin/env node\" --jsx=automatic --jsx-import-source=react --packages=external --log-override:empty-import-meta=silent",
30+
"bundle": "esbuild ./src/cli.tsx --bundle --platform=node --format=esm --target=node18 --outfile=dist/cli.js --banner:js=\"#!/usr/bin/env node\" --jsx=automatic --jsx-import-source=react --packages=external --log-override:empty-import-meta=silent && node scripts/copy_bundle_assets.js",
3031
"lint": "eslint src/",
3132
"lint:fix": "eslint src/ --fix",
3233
"format": "prettier --write 'src/**/*.{ts,tsx}'",

scripts/copy_bundle_assets.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { cpSync, existsSync, mkdirSync, rmSync } from "node:fs";
2+
import { dirname, join } from "node:path";
3+
import { fileURLToPath } from "node:url";
4+
5+
/* global console, process */
6+
7+
const __dirname = dirname(fileURLToPath(import.meta.url));
8+
const root = join(__dirname, "..");
9+
const distDir = join(root, "dist");
10+
const bundledSkillsSrc = join(root, "templates", "skills", "bundled");
11+
const bundledSkillsDest = join(distDir, "bundled");
12+
13+
if (!existsSync(distDir)) {
14+
mkdirSync(distDir, { recursive: true });
15+
}
16+
17+
if (!existsSync(bundledSkillsSrc)) {
18+
console.warn(`Bundled skills directory not found at ${bundledSkillsSrc}`);
19+
process.exit(0);
20+
}
21+
22+
rmSync(bundledSkillsDest, { recursive: true, force: true });
23+
cpSync(bundledSkillsSrc, bundledSkillsDest, {
24+
recursive: true,
25+
dereference: true,
26+
});
27+
console.log("Copied bundled built-in skills to dist/bundled/");

src/session.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -805,9 +805,22 @@ ${agentInstructions}
805805
{ root: path.join(this.projectRoot, ".agents", "skills"), displayRoot: "./.agents/skills" },
806806
{ root: path.join(homeDir, ".deepcode", "skills"), displayRoot: "~/.deepcode/skills" },
807807
{ root: path.join(homeDir, ".agents", "skills"), displayRoot: "~/.agents/skills" },
808+
{ root: this.getBundledSkillsRoot(), displayRoot: "bundled:" },
808809
];
809810
}
810811

812+
private getBundledSkillsRoot(): string {
813+
const extensionRoot = getExtensionRoot();
814+
const sourceRoot = path.join(extensionRoot, "templates", "skills", "bundled");
815+
const distRoot = path.join(extensionRoot, "dist", "bundled");
816+
817+
// Source check keeps local development/tests on the checked-in templates.
818+
if (fs.existsSync(path.join(extensionRoot, "src", "session.ts")) && fs.existsSync(sourceRoot)) {
819+
return sourceRoot;
820+
}
821+
return fs.existsSync(distRoot) ? distRoot : sourceRoot;
822+
}
823+
811824
async listSkills(sessionId?: string): Promise<SkillInfo[]> {
812825
const skillRoots = this.getSkillScanRoots();
813826
const enabledSkills = this.getResolvedSettings().enabledSkills ?? {};
@@ -842,7 +855,9 @@ ${agentInstructions}
842855
} catch {
843856
continue;
844857
}
845-
const skill = this.readSkillInfo(skillPath, `${displayRoot}/${skillName}/SKILL.md`, skillName);
858+
const displayPath =
859+
displayRoot === "bundled:" ? `bundled:${skillName}/SKILL.md` : `${displayRoot}/${skillName}/SKILL.md`;
860+
const skill = this.readSkillInfo(skillPath, displayPath, skillName);
846861
if (enabledSkills[skill.name] === false) {
847862
continue;
848863
}
@@ -872,6 +887,16 @@ ${agentInstructions}
872887
}
873888

874889
private resolveSkillPath(skillPath: string): string {
890+
if (skillPath.startsWith("bundled:")) {
891+
const relativePath = skillPath.slice("bundled:".length);
892+
const root = this.getBundledSkillsRoot();
893+
const resolvedPath = path.resolve(root, relativePath);
894+
const resolvedRoot = path.resolve(root);
895+
if (resolvedPath === resolvedRoot || !resolvedPath.startsWith(`${resolvedRoot}${path.sep}`)) {
896+
return path.join(root, "__invalid_bundled_skill__");
897+
}
898+
return resolvedPath;
899+
}
875900
if (skillPath.startsWith("~/")) {
876901
return path.join(os.homedir(), skillPath.slice(2));
877902
}

src/tests/session.test.ts

Lines changed: 77 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -471,6 +471,57 @@ 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 lists bundled skills at lowest priority", async () => {
475+
const workspace = createTempDir("deepcode-bundled-skills-workspace-");
476+
const home = createTempDir("deepcode-bundled-skills-home-");
477+
setHomeDir(home);
478+
479+
const manager = createSessionManager(workspace, "machine-id-bundled-skills");
480+
const skills = await manager.listSkills();
481+
const skillWriter = skills.find((skill) => skill.name === "skill-writer");
482+
const selfRefer = skills.find((skill) => skill.name === "deepcode-self-refer");
483+
484+
assert.equal(skillWriter?.path, "bundled:skill-writer/SKILL.md");
485+
assert.equal(selfRefer?.path, "bundled:deepcode-self-refer/SKILL.md");
486+
assert.match(skillWriter?.description ?? "", /Guide users through creating/);
487+
});
488+
489+
test("SessionManager lets project skills override bundled skills", async () => {
490+
const workspace = createTempDir("deepcode-bundled-override-workspace-");
491+
const home = createTempDir("deepcode-bundled-override-home-");
492+
setHomeDir(home);
493+
494+
const projectSkillDir = path.join(workspace, ".deepcode", "skills", "skill-writer");
495+
fs.mkdirSync(projectSkillDir, { recursive: true });
496+
fs.writeFileSync(
497+
path.join(projectSkillDir, "SKILL.md"),
498+
"---\nname: skill-writer\ndescription: Project override skill writer\n---\n# Project Skill Writer\n",
499+
"utf8"
500+
);
501+
502+
const manager = createSessionManager(workspace, "machine-id-bundled-override");
503+
const skillWriter = (await manager.listSkills()).find((skill) => skill.name === "skill-writer");
504+
505+
assert.equal(skillWriter?.path, "./.deepcode/skills/skill-writer/SKILL.md");
506+
assert.equal(skillWriter?.description, "Project override skill writer");
507+
});
508+
509+
test("SessionManager resolves bundled skill prompts", () => {
510+
const workspace = createTempDir("deepcode-bundled-prompt-workspace-");
511+
const home = createTempDir("deepcode-bundled-prompt-home-");
512+
setHomeDir(home);
513+
514+
const manager = createSessionManager(workspace, "machine-id-bundled-prompt");
515+
const prompt = (manager as any).buildSkillPrompt({
516+
name: "skill-writer",
517+
path: "bundled:skill-writer/SKILL.md",
518+
description: "Write skills",
519+
});
520+
521+
assert.match(prompt, /<skill-writer-skill/);
522+
assert.match(prompt, /# Skill Writer/);
523+
});
524+
474525
test("SessionManager excludes disabled skills by resolved skill name", async () => {
475526
const workspace = createTempDir("deepcode-disabled-skills-workspace-");
476527
const home = createTempDir("deepcode-disabled-skills-home-");
@@ -511,6 +562,8 @@ test("SessionManager excludes disabled skills by resolved skill name", async ()
511562
enabledSkills: {
512563
"skill-writer": false,
513564
"renamed-disabled": false,
565+
"deepcode-self-refer": false,
566+
"skill-digester": false,
514567
"enabled-skill": true,
515568
},
516569
}),
@@ -2814,7 +2867,10 @@ test("SessionManager stores usage per model across model changes", async () => {
28142867
const client = {
28152868
chat: {
28162869
completions: {
2817-
create: async () => {
2870+
create: async (request: any) => {
2871+
if (isSkillMatchingRequest(request)) {
2872+
return createSkillMatchingResponse();
2873+
}
28182874
const response = responses.shift();
28192875
assert.ok(response, "expected a queued chat response");
28202876
return response;
@@ -3353,7 +3409,10 @@ function createNotifyingSessionManager(
33533409
const client = {
33543410
chat: {
33553411
completions: {
3356-
create: async () => {
3412+
create: async (request: any) => {
3413+
if (isSkillMatchingRequest(request)) {
3414+
return createSkillMatchingResponse();
3415+
}
33573416
const response = responses.shift();
33583417
assert.ok(response, "expected a queued chat response");
33593418
if (response instanceof Error) {
@@ -3391,7 +3450,10 @@ function createMockedClientSessionManager(projectRoot: string, responses: unknow
33913450
const client = {
33923451
chat: {
33933452
completions: {
3394-
create: async () => {
3453+
create: async (request: any) => {
3454+
if (isSkillMatchingRequest(request)) {
3455+
return createSkillMatchingResponse();
3456+
}
33953457
const response = responses.shift();
33963458
assert.ok(response, "expected a queued chat response");
33973459
return response;
@@ -3427,7 +3489,10 @@ function createPermissionSessionManager(
34273489
const client = {
34283490
chat: {
34293491
completions: {
3430-
create: async () => {
3492+
create: async (request: any) => {
3493+
if (isSkillMatchingRequest(request)) {
3494+
return createSkillMatchingResponse();
3495+
}
34313496
const response = responses.shift();
34323497
assert.ok(response, "expected a queued chat response");
34333498
return response;
@@ -3467,6 +3532,14 @@ function createMockedClientSessionManagerWithClient(projectRoot: string, client:
34673532

34683533
class APIUserAbortError extends Error {}
34693534

3535+
function isSkillMatchingRequest(request: any): boolean {
3536+
return request?.response_format?.type === "json_object";
3537+
}
3538+
3539+
function createSkillMatchingResponse(): unknown {
3540+
return { choices: [{ message: { content: '{"skillNames":[]}' } }] };
3541+
}
3542+
34703543
function createChatResponse(content: string, usage: Record<string, unknown>): unknown {
34713544
return {
34723545
choices: [{ message: { content } }],
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
---
2+
name: deepcode-self-refer
3+
description: 回答关于 Deep Code CLI 本身的问题——包括功能特性、配置项、斜杠命令、Skills、MCP 集成、权限、通知、会话持久化及故障排查。当用户询问如何配置或使用 Deep Code、如何设置 MCP 服务器、配置通知(如 Slack/飞书)、管理权限、查看可用技能、理解斜杠命令、配置思考模式、使用 Undo 功能,或咨询 Deep Code 与 VSCode 集成等场景时使用。
4+
---
5+
6+
# Deep Code Self-Refer
7+
8+
This Skill helps you answer user questions about Deep Code CLI itself by consulting the reference documentation bundled with this Skill. All docs live in the `references/` subdirectory — always refer to them for authoritative answers.
9+
10+
## When to use this Skill
11+
12+
Use this Skill when the user asks any question about Deep Code itself, such as:
13+
14+
- "列出可用的 skills"
15+
- "如何配置 MCP?"
16+
- "给当前项目配置 playwright mcp"
17+
- "怎么启用搜索功能?"
18+
- "支持哪些模型?"
19+
- "如何配置思考模式?"
20+
- "怎么设置权限?"
21+
- "任务完成后怎么发通知?"
22+
- "支持哪些斜杠命令?"
23+
- "会话历史保存在哪里?"
24+
- "/undo 是怎么工作的?"
25+
- "Deep Code 和 VSCode 插件怎么配合?"
26+
- Any other question about Deep Code CLI's features, configuration, or usage.
27+
28+
## Instructions
29+
30+
### Step 1: Identify the topic
31+
32+
Map the user's question to the appropriate document(s):
33+
34+
| Topic | Document | Key contents |
35+
|-------|----------|-------------|
36+
| **Overview, features, quick start** | `references/README.md` | Installation, slash commands, keyboard shortcuts, supported models, FAQ |
37+
| **Configuration & settings** | `references/configuration.md` | `settings.json` fields, config hierarchy, env vars, thinking mode, reasoning effort, webSearchTool, enabledSkills |
38+
| **MCP setup & usage** | `references/mcp.md` | MCP server config format, GitHub/Playwright/Filesystem examples, tool naming (`mcp__<name>__<tool>`), troubleshooting |
39+
| **Permissions** | `references/permission.md` | Permission scopes (10 types), allow/deny/ask/defaultMode config, priority rules, persistence |
40+
| **Notifications** | `references/notify.md` | Notify script path, injected env vars, Slack/Feishu/iTerm2/macOS/Linux/Windows examples |
41+
| **Session persistence** | `references/session-persistence.md` | Storage paths, JSONL format, session index, compaction, `/undo` mechanics, code snapshots |
42+
43+
### Step 2: Read the relevant document(s)
44+
45+
Use the `Read` tool to read the appropriate document(s) from the list above. All paths are relative to this Skill's loaded root directory, where the `references/` subdirectory lives.
46+
47+
- If the question spans multiple topics, read multiple documents.
48+
- If a document doesn't exist in the user's preferred language (e.g., Chinese), try the other language variant (e.g., `references/configuration_en.md`).
49+
- When answering from references/README.md, focus on the relevant sections.
50+
51+
### Step 3: Answer with precision
52+
53+
- **Quote the doc directly** for config examples, JSON snippets, or command syntax.
54+
- **Don't guess** — if the answer isn't in the docs, say so and suggest checking GitHub Issues.
55+
- **Provide copy-paste-ready configurations** when the user asks to set something up (e.g., MCP servers, notify scripts, permissions).
56+
- **Mention related docs** when appropriate (e.g., MCP setup references `references/mcp.md`, the permissions section references `references/permission.md`).
57+
58+
### Step 4: Handle common request patterns
59+
60+
**"列出/查看可用的 skills":**
61+
- Explain the skill scanning paths from references/README.md (`./.deepcode/skills/`, `./.agents/skills/`, `~/.deepcode/skills/`, `~/.agents/skills/`, and bundled built-in skills)
62+
- Explain that `/skills` slash command lists available skills
63+
- Mention `enabledSkills` in `settings.json` for enabling/disabling specific skills
64+
65+
**"配置 <X> MCP":**
66+
- Read `references/mcp.md` for the MCP format and examples
67+
- Ask the user for any required credentials (e.g., GitHub token)
68+
- Provide the exact `mcpServers` JSON block to add to `settings.json`
69+
- Mention using `/mcp` to verify the setup afterwards
70+
71+
**"如何配置/修改 <设置项>":**
72+
- Read `references/configuration.md`
73+
- Explain which `settings.json` field controls the setting
74+
- Clarify user-level (`~/.deepcode/settings.json`) vs project-level (`.deepcode/settings.json`)
75+
- Provide the exact JSON snippet
76+
77+
**"<斜杠命令> 是做什么的?":**
78+
- Read the slash command table from references/README.md
79+
- Provide a brief explanation with any additional context from relevant docs
80+
81+
### Best practices
82+
83+
1. **Always consult the docs first** — never answer from memory alone; the docs are the source of truth.
84+
2. **Provide copy-paste-ready JSON** — users want to copy config blocks directly into their `settings.json`.
85+
3. **Be specific about file paths** — always specify whether it's `~/.deepcode/settings.json` or `.deepcode/settings.json`.
86+
4. **Mention `/mcp` verification** — after any MCP configuration change, remind users to use `/mcp` to verify.
87+
5. **Acknowledge both Chinese and English docs** — the project has docs in both languages (`references/xxx.md` for Chinese, `references/xxx_en.md` for English).
88+
89+
## Examples
90+
91+
### Example 1: "列出可用的skills"
92+
93+
Read references/README.md, locate the Skills section. Answer:
94+
95+
- Skills are discovered from: `./.deepcode/skills/`, `./.agents/skills/`, `~/.deepcode/skills/`, `~/.agents/skills/`, and bundled built-in skills such as `bundled:deepcode-self-refer/SKILL.md`.
96+
- Use `/skills` slash command in the Deep Code CLI to list all available skills
97+
- Use `enabledSkills` in `settings.json` to enable/disable skills by name
98+
99+
### Example 2: "给当前项目配置playwright mcp"
100+
101+
Read `references/mcp.md`, locate the Playwright example. Answer:
102+
103+
- Add to `settings.json` (user-level `~/.deepcode/settings.json` or project-level `.deepcode/settings.json`):
104+
105+
```json
106+
{
107+
"mcpServers": {
108+
"playwright": {
109+
"command": "npx",
110+
"args": ["@playwright/mcp@latest"]
111+
}
112+
}
113+
}
114+
```
115+
116+
- If merging with existing config, add the `"playwright"` entry into the existing `mcpServers` object
117+
- After saving, use `/mcp` in Deep Code to verify the server is running
118+
119+
### Example 3: "怎么设置通知到Slack?"
120+
121+
Read `references/notify.md`, locate the Slack section. Answer with the script + config.
122+
123+
### Example 4: "如何只允许AI读写当前目录?"
124+
125+
Read `references/permission.md`, locate the strict mode example. Provide the exact JSON.

0 commit comments

Comments
 (0)