Skip to content

Commit 1969dae

Browse files
authored
Merge pull request #108 from phantom5099/agent-profile
删除冗余skill读取管理逻辑
2 parents 67e7c5b + b03875d commit 1969dae

27 files changed

Lines changed: 141 additions & 446 deletions

docs/skills.md

Lines changed: 14 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,9 @@ Coding Code 支持可插拔的 Markdown 技能包,扩展 Agent 在特定场景
66

77
## 什么是技能
88

9-
技能是一组以 Markdown 编写的指令和资源,在 Agent 调用前注入到 system prompt 中。每个技能包含:
9+
技能是一个包含 `SKILL.md` 的目录。发现阶段只读取少量元数据,具体内容由 Agent 在需要时通过文件工具读取。
1010

11-
- **instruction**:SKILL.md 的 Markdown 正文,作为技能指令注入
12-
- **references**:附带的参考文件(代码片段、文档等)
13-
- **scripts**:附带的脚本文件
14-
- **assets**:附带的二进制资源
11+
发现阶段保存技能名称、描述和 `SKILL.md` 的绝对路径。
1512

1613
---
1714

@@ -29,19 +26,16 @@ Coding Code 支持可插拔的 Markdown 技能包,扩展 Agent 在特定场景
2926
```
3027
.codingcode/skills/
3128
├── code-review/
32-
│ ├── SKILL.md # 技能指令(必需)
33-
│ ├── review-checklist.md # 参考文件
34-
│ └── run-review.sh # 脚本文件
29+
│ └── SKILL.md # 必需
3530
└── api-design/
36-
├── SKILL.md
37-
└── openapi-template.yaml
31+
└── SKILL.md
3832
```
3933

4034
---
4135

4236
## SKILL.md 格式
4337

44-
SKILL.md 是纯 Markdown 文件,正文部分作为技能指令注入 system prompt
38+
SKILL.md 使用 YAML front matter 提供发现元数据。正文不会在发现阶段读取
4539

4640
```markdown
4741
# Code Review Skill
@@ -65,52 +59,30 @@ You are now performing a code review. Follow these steps:
6559

6660
```typescript
6761
interface Skill {
68-
readonly name: string; // 技能名称
69-
readonly description: string; // 技能描述
70-
readonly instruction: string; // SKILL.md 的 Markdown body
71-
readonly references: ReadonlyArray<{ // 参考文件
72-
path: string;
73-
content: string;
74-
}>;
75-
readonly scripts: ReadonlyArray<{ // 脚本文件
76-
path: string;
77-
content: string;
78-
}>;
79-
readonly assets: ReadonlyArray<{ // 二进制资源
80-
path: string;
81-
mimeType: string;
82-
size: number;
83-
}>;
84-
readonly metadata: Record<string, unknown>; // 自定义元数据
62+
readonly name: string;
63+
readonly description: string;
64+
readonly skillPath: string;
8565
}
8666
```
8767

68+
Agent 判断技能相关后,使用 `read_file` 读取 `skillPath`,再按需读取其他文件或执行脚本。
69+
8870
---
8971

90-
## 技能管理 API
72+
## 技能列表 API
9173

92-
通过 `AgentClient` SDK 管理技能
74+
通过 `AgentClient` SDK 读取技能元数据
9375

9476
```typescript
9577
const client = await createHttpClient('http://localhost:8080');
9678

9779
// 列出所有技能
9880
const skills = await client.listSkills();
99-
// 返回:Array<{ name: string, description: string, enabled: boolean }>
100-
101-
// 启用/禁用技能
102-
await client.toggleSkill({ name: 'code-review', enabled: true });
81+
// 返回:Array<{ name: string, description: string, skillPath: string }>
10382
```
10483

10584
也可通过 HTTP API:
10685

10786
| 路由 | 方法 | 说明 |
10887
|------|------|------|
109-
| `/api/settings/skills` | GET | 列出所有技能 |
110-
| `/api/settings/skills/toggle` | POST | 启用/禁用技能 |
111-
112-
---
113-
114-
## 配置
115-
116-
技能的启用/禁用状态持久化在项目配置中。禁用的技能不会被注入 system prompt,但仍保留在技能目录中,可随时重新启用。
88+
| `/api/settings/skills` | GET | 列出所有技能元数据 |

packages/codingcode/src/client/http.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -226,10 +226,6 @@ export async function createHttpClient(serverUrl: string): Promise<AgentClient>
226226
return clients.settings.listSkills();
227227
},
228228

229-
async toggleSkill(body: { name: string; enabled: boolean; cwd: string }) {
230-
await clients.settings.toggleSkill(body);
231-
},
232-
233229
async createMcpServer(server: McpServerConfig, { cwd }: { cwd: string }) {
234230
await clients.settings.createMcpServer({ cwd, server });
235231
},

packages/codingcode/src/client/http/settings.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,7 @@ export interface SettingsClient {
2424
createMcpServer(input: { cwd: string; server: McpServerConfig }): Promise<void>;
2525
updateMcpServer(input: { cwd: string; name: string; server: McpServerConfig }): Promise<void>;
2626
deleteMcpServer(input: { cwd: string; name: string }): Promise<void>;
27-
listSkills(): Promise<Array<{ name: string; description: string; enabled: boolean }>>;
28-
toggleSkill(body: { name: string; enabled: boolean; cwd: string }): Promise<void>;
27+
listSkills(): Promise<Array<{ name: string; description: string; skillPath: string }>>;
2928
listHooks(input: { cwd: string }): Promise<UserHookConfig[]>;
3029
createHook(input: { cwd: string; hook: UserHookConfig }): Promise<void>;
3130
updateHook(input: { cwd: string; name: string; hook: UserHookConfig }): Promise<void>;
@@ -124,10 +123,6 @@ export function createHttpSettingsClient(
124123
return apiGet('/api/settings/skills');
125124
},
126125

127-
async toggleSkill({ name, enabled, cwd }) {
128-
await apiPost(`/api/settings/skills${qsCwd(cwd)}`, { name, enabled });
129-
},
130-
131126
async listHooks({ cwd }) {
132127
return apiGet(`/api/settings/hooks${qsCwd(cwd)}`);
133128
},

packages/codingcode/src/client/types.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,7 @@ export interface AgentClient {
7777
deleteMcpServer(name: string, query: { cwd: string }): Promise<void>;
7878
setMcpDisabled(body: { name: string; disabled: boolean; cwd: string }): Promise<void>;
7979
resetMcpDisabled(body: { name: string; cwd: string }): Promise<void>;
80-
listSkills(): Promise<Array<{ name: string; description: string; enabled: boolean }>>;
81-
toggleSkill(body: { name: string; enabled: boolean; cwd: string }): Promise<void>;
80+
listSkills(): Promise<Array<{ name: string; description: string; skillPath: string }>>;
8281
listHooks(query: { cwd: string }): Promise<UserHookConfig[]>;
8382
setHookDisabled(body: { name: string; disabled: boolean; cwd: string }): Promise<void>;
8483
resetHookDisabled(body: { name: string; cwd: string }): Promise<void>;

packages/codingcode/src/direct/settings.ts

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,7 @@ export interface SettingsClient {
6565
createMcpServer(input: { cwd: string; server: McpServerConfig }): Promise<void>;
6666
updateMcpServer(input: { cwd: string; name: string; server: McpServerConfig }): Promise<void>;
6767
deleteMcpServer(input: { cwd: string; name: string }): Promise<void>;
68-
listSkills(): Promise<Array<{ name: string; description: string; enabled: boolean }>>;
69-
toggleSkill(body: { name: string; enabled: boolean; cwd: string }): Promise<void>;
68+
listSkills(): Promise<Array<{ name: string; description: string; skillPath: string }>>;
7069
listHooks(input: { cwd: string }): Promise<UserHookConfig[]>;
7170
createHook(input: { cwd: string; hook: UserHookConfig }): Promise<void>;
7271
updateHook(input: { cwd: string; name: string; hook: UserHookConfig }): Promise<void>;
@@ -385,21 +384,7 @@ export function createDirectSettingsClient(rt: AppRuntime): SettingsClient {
385384
return rt.runPromise(
386385
Effect.gen(function* () {
387386
const skill = yield* SkillService;
388-
return yield* skill.listWithStatus(process.cwd());
389-
})
390-
);
391-
},
392-
393-
async toggleSkill({ name, enabled, cwd }) {
394-
const skillCwd = cwd || process.cwd();
395-
await rt.runPromise(
396-
Effect.gen(function* () {
397-
const skill = yield* SkillService;
398-
if (enabled) {
399-
yield* skill.enableSkill(skillCwd, name);
400-
} else {
401-
yield* skill.disableSkill(skillCwd, name);
402-
}
387+
return yield* skill.getAll(process.cwd());
403388
})
404389
);
405390
},

packages/codingcode/src/server/routes/settings.ts

Lines changed: 3 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,7 @@ import {
2929
resetProjectHookDisabledState,
3030
} from '../../hooks/config.js';
3131
import { setHookRuntimeEnabled } from '../../hooks/executor.js';
32-
import {
33-
setGlobalSkillDisabledState,
34-
setProjectSkillDisabledState,
35-
discoverGlobalSkillDirs,
36-
discoverProjectSkillDirs,
37-
} from '../../skills/source.js';
32+
import { discoverGlobalSkillDirs, discoverProjectSkillDirs } from '../../skills/source.js';
3833
import {
3934
getMemoryConfig,
4035
getAllTypesWithStatus,
@@ -471,7 +466,7 @@ export async function createSettingsRouter(rt: ManagedRt): Promise<Hono> {
471466
const result = await runWithLayer(
472467
Effect.gen(function* () {
473468
const skill = yield* SkillService;
474-
return yield* skill.listWithStatus(cwd);
469+
return yield* skill.getAll(cwd);
475470
})
476471
);
477472
const skills = result.ok ? result.value : [];
@@ -490,7 +485,7 @@ export async function createSettingsRouter(rt: ManagedRt): Promise<Hono> {
490485
const result = await runWithLayer(
491486
Effect.gen(function* () {
492487
const skill = yield* SkillService;
493-
return yield* skill.listWithStatus(cwd);
488+
return yield* skill.getAll(cwd);
494489
})
495490
);
496491
const skills = result.ok ? result.value : [];
@@ -508,17 +503,5 @@ export async function createSettingsRouter(rt: ManagedRt): Promise<Hono> {
508503
);
509504
});
510505

511-
settingsRouter.post('/skills', async (c) => {
512-
const body = (await c.req.json()) as { name: string; enabled: boolean };
513-
const rawCwd = c.req.query('cwd');
514-
if (isGlobalCwd(rawCwd)) {
515-
setGlobalSkillDisabledState(body.name, !body.enabled);
516-
return c.json({ ok: true });
517-
}
518-
const cwd = resolveWorkspaceCwd(rawCwd);
519-
setProjectSkillDisabledState(cwd, body.name, !body.enabled);
520-
return c.json({ ok: true });
521-
});
522-
523506
return settingsRouter;
524507
}
Lines changed: 10 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,14 @@
1-
import { statSync } from 'fs';
2-
import { basename } from 'path';
1+
import { basename, resolve } from 'path';
32
import type { Skill } from './types.js';
4-
import { readSkillMd, readFileContent, getFilesInDir, getMimeType } from './source.js';
3+
import { readSkillFrontMatter } from './source.js';
54

65
export function loadSkill(dirPath: string): Skill | null {
7-
const parsed = readSkillMd(dirPath);
8-
if (!parsed) return null;
9-
10-
const { frontMatter, body } = parsed;
11-
12-
const name = frontMatter.name || basename(dirPath);
13-
const description = frontMatter.description || '';
14-
const instruction = body;
15-
16-
// Extract metadata (everything except name and description)
17-
const metadata: Record<string, unknown> = {};
18-
for (const [key, value] of Object.entries(frontMatter)) {
19-
if (key !== 'name' && key !== 'description') {
20-
metadata[key] = value;
21-
}
22-
}
23-
24-
// Load references
25-
const refsDir = `${dirPath}/references`;
26-
const refs: Array<{ path: string; content: string }> = [];
27-
for (const refPath of getFilesInDir(refsDir)) {
28-
const content = readFileContent(refPath);
29-
if (content !== null) {
30-
refs.push({ path: refPath, content });
31-
}
32-
}
33-
34-
// Load scripts
35-
const scriptsDir = `${dirPath}/scripts`;
36-
const scripts: Array<{ path: string; content: string }> = [];
37-
for (const scriptPath of getFilesInDir(scriptsDir)) {
38-
const content = readFileContent(scriptPath);
39-
if (content !== null) {
40-
scripts.push({ path: scriptPath, content });
41-
}
42-
}
43-
44-
// Load assets (metadata only, not binary content)
45-
const assetsDir = `${dirPath}/assets`;
46-
const assets: Array<{ path: string; mimeType: string; size: number }> = [];
47-
for (const assetPath of getFilesInDir(assetsDir)) {
48-
try {
49-
const st = statSync(assetPath);
50-
assets.push({
51-
path: assetPath,
52-
mimeType: getMimeType(assetPath),
53-
size: st.size,
54-
});
55-
} catch {
56-
// skip
57-
}
58-
}
59-
60-
return { name, description, instruction, references: refs, scripts, assets, metadata };
6+
const frontMatter = readSkillFrontMatter(dirPath);
7+
if (!frontMatter) return null;
8+
9+
return {
10+
name: frontMatter.name || basename(dirPath),
11+
description: frontMatter.description || '',
12+
skillPath: resolve(dirPath, 'SKILL.md'),
13+
};
6114
}

packages/codingcode/src/skills/service.ts

Lines changed: 5 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,8 @@
11
import { Effect } from 'effect';
2-
import { discoverSkillDirs, resolveSkillDisabled, setProjectSkillDisabledState } from './source.js';
2+
import { discoverSkillDirs } from './source.js';
33
import { loadSkill } from './loader.js';
44
import type { Skill } from './types.js';
55

6-
function filterEnabled(projectPath: string, skills: Skill[]): Skill[] {
7-
return skills.filter((s) => !resolveSkillDisabled(projectPath, s.name));
8-
}
9-
106
export class SkillService extends Effect.Service<SkillService>()('Skill', {
117
effect: Effect.gen(function* () {
128
const cachedByProject = new Map<string, Skill[]>();
@@ -25,21 +21,16 @@ export class SkillService extends Effect.Service<SkillService>()('Skill', {
2521
}
2622

2723
return {
28-
getAll: (projectPath: string) =>
29-
Effect.sync(() => filterEnabled(projectPath, readAll(projectPath))),
24+
getAll: (projectPath: string) => Effect.sync(() => readAll(projectPath)),
3025

3126
findByName: (projectPath: string, name: string) =>
32-
Effect.sync(() => {
33-
if (resolveSkillDisabled(projectPath, name)) return undefined;
34-
return readAll(projectPath).find((s) => s.name === name);
35-
}),
27+
Effect.sync(() => readAll(projectPath).find((s) => s.name === name)),
3628

3729
select: (projectPath: string, query: string) =>
3830
Effect.sync(() => {
3931
const match = query.match(/^@([a-zA-Z0-9-]+)(?:\s+|$)/);
4032
if (!match) return undefined;
4133
const name = match[1]!;
42-
if (resolveSkillDisabled(projectPath, name)) return undefined;
4334
return readAll(projectPath).find((s) => s.name === name);
4435
}),
4536

@@ -49,10 +40,9 @@ export class SkillService extends Effect.Service<SkillService>()('Skill', {
4940
matcher: (all: readonly Skill[], q: string) => Effect.Effect<string | undefined>
5041
): Effect.Effect<Skill | undefined> =>
5142
Effect.gen(function* () {
52-
const all = filterEnabled(projectPath, readAll(projectPath));
43+
const all = readAll(projectPath);
5344
const name = yield* matcher(all, query);
5445
if (!name) return undefined;
55-
if (resolveSkillDisabled(projectPath, name)) return undefined;
5646
return all.find((s) => s.name === name);
5747
}),
5848

@@ -62,29 +52,12 @@ export class SkillService extends Effect.Service<SkillService>()('Skill', {
6252
let skill: Skill | undefined;
6353
if (match) {
6454
const name = match[1]!;
65-
if (!resolveSkillDisabled(projectPath, name)) {
66-
skill = readAll(projectPath).find((s) => s.name === name);
67-
}
55+
skill = readAll(projectPath).find((s) => s.name === name);
6856
}
6957
const actualQuery = query.replace(/^@[a-zA-Z0-9-]+\s*/, '');
7058
return [skill, actualQuery] as [Skill | undefined, string];
7159
}),
7260

73-
disableSkill: (projectPath: string, name: string) =>
74-
Effect.sync(() => setProjectSkillDisabledState(projectPath, name, true)),
75-
76-
enableSkill: (projectPath: string, name: string) =>
77-
Effect.sync(() => setProjectSkillDisabledState(projectPath, name, false)),
78-
79-
listWithStatus: (projectPath: string) =>
80-
Effect.sync(() =>
81-
readAll(projectPath).map((s) => ({
82-
name: s.name,
83-
description: s.description,
84-
enabled: !resolveSkillDisabled(projectPath, s.name),
85-
}))
86-
),
87-
8861
evictProject: (projectPath: string) =>
8962
Effect.sync(() => {
9063
cachedByProject.delete(projectPath);

0 commit comments

Comments
 (0)