Add directory-based project skills
This commit is contained in:
@@ -2082,7 +2082,7 @@ class LocalCodingAgent:
|
||||
args = str(args) if args is not None else ''
|
||||
|
||||
# 1. Check bundled skills first
|
||||
bundled = find_bundled_skill(skill_name)
|
||||
bundled = find_bundled_skill(skill_name, cwd=self.runtime_config.cwd)
|
||||
if bundled is not None:
|
||||
prompt = bundled.get_prompt(self, args.strip())
|
||||
return ToolExecutionResult(
|
||||
@@ -2092,7 +2092,8 @@ class LocalCodingAgent:
|
||||
metadata={
|
||||
'action': 'skill',
|
||||
'skill_name': skill_name,
|
||||
'source': 'bundled',
|
||||
'source': bundled.source,
|
||||
'skill_path': bundled.path,
|
||||
'should_query': True,
|
||||
},
|
||||
)
|
||||
@@ -2105,7 +2106,7 @@ class LocalCodingAgent:
|
||||
for s in get_slash_command_specs()
|
||||
for name in s.names
|
||||
)
|
||||
available_skills = [sk.name for sk in get_bundled_skills()]
|
||||
available_skills = [sk.name for sk in get_bundled_skills(self.runtime_config.cwd)]
|
||||
all_available = sorted(set(available_cmds + available_skills))
|
||||
return ToolExecutionResult(
|
||||
name='Skill',
|
||||
|
||||
@@ -1671,7 +1671,7 @@ def _handle_skills(agent: 'LocalCodingAgent', _args: str, input_text: str) -> Sl
|
||||
from .bundled_skills import get_bundled_skills
|
||||
|
||||
lines = ['## Available Skills', '']
|
||||
for skill in get_bundled_skills():
|
||||
for skill in get_bundled_skills(agent.runtime_config.cwd):
|
||||
if not skill.user_invocable:
|
||||
continue
|
||||
header = f'- `{skill.name}`'
|
||||
|
||||
+100
-100
@@ -13,6 +13,7 @@ from __future__ import annotations
|
||||
|
||||
import subprocess
|
||||
from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Callable
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -29,6 +30,8 @@ class BundledSkill:
|
||||
aliases: tuple[str, ...] = ()
|
||||
allowed_tools: tuple[str, ...] = ()
|
||||
user_invocable: bool = True
|
||||
source: str = 'python'
|
||||
path: str | None = None
|
||||
get_prompt: Callable[['LocalCodingAgent', str], str] = lambda _a, _args: ''
|
||||
|
||||
|
||||
@@ -98,33 +101,6 @@ def _simplify_prompt(agent: 'LocalCodingAgent', args: str) -> str:
|
||||
{f'Additional context: {args}' if args.strip() else ''}"""
|
||||
|
||||
|
||||
def _verify_prompt(agent: 'LocalCodingAgent', args: str) -> str:
|
||||
"""Generate the verify prompt."""
|
||||
return f"""Verify that the recent code changes work correctly.
|
||||
|
||||
## Instructions
|
||||
|
||||
1. Identify what was changed (check `git diff` and `git status`)
|
||||
2. Determine the appropriate verification strategy:
|
||||
- **Unit tests**: Run existing tests, check for failures
|
||||
- **Integration tests**: Run broader test suites if available
|
||||
- **Manual verification**: Start the app/server and test the feature
|
||||
3. Run the verification
|
||||
4. Report the result clearly:
|
||||
- PASS: All checks passed, feature works as expected
|
||||
- FAIL: Describe what failed and why
|
||||
- PARTIAL: Some checks passed, some need attention
|
||||
|
||||
## Verification Strategy
|
||||
|
||||
- For CLI tools: Run the command with test inputs
|
||||
- For servers: Start the server, make test requests
|
||||
- For libraries: Run the test suite
|
||||
- For config changes: Validate the config loads correctly
|
||||
|
||||
{f'Specific focus: {args}' if args.strip() else 'Verify the most recent changes.'}"""
|
||||
|
||||
|
||||
def _debug_prompt(agent: 'LocalCodingAgent', args: str) -> str:
|
||||
"""Generate the debug prompt."""
|
||||
import os
|
||||
@@ -154,71 +130,94 @@ def _debug_prompt(agent: 'LocalCodingAgent', args: str) -> str:
|
||||
return '\n'.join(lines)
|
||||
|
||||
|
||||
def _update_config_prompt(agent: 'LocalCodingAgent', args: str) -> str:
|
||||
"""Generate the update-config prompt."""
|
||||
return f"""Help configure the agent settings.
|
||||
def _directory_skill_prompt(body: str) -> Callable[['LocalCodingAgent', str], str]:
|
||||
def _prompt(_agent: 'LocalCodingAgent', args: str) -> str:
|
||||
if args.strip():
|
||||
return f'{body.rstrip()}\n\n## Invocation Arguments\n\n{args.strip()}'
|
||||
return body
|
||||
|
||||
## Settings File Locations
|
||||
return _prompt
|
||||
|
||||
- **Global**: `~/.claude/settings.json` — applies to all projects
|
||||
- **Project**: `.claude/settings.json` — project-specific, committed to git
|
||||
- **Local**: `.claude/settings.local.json` — project-specific, gitignored
|
||||
|
||||
## Configurable Settings
|
||||
def _parse_front_matter(text: str) -> tuple[dict[str, str], str]:
|
||||
if not text.startswith('---\n'):
|
||||
return {}, text
|
||||
end_marker = text.find('\n---\n', 4)
|
||||
if end_marker == -1:
|
||||
return {}, text
|
||||
raw_meta = text[4:end_marker]
|
||||
body = text[end_marker + len('\n---\n') :]
|
||||
metadata: dict[str, str] = {}
|
||||
for line in raw_meta.splitlines():
|
||||
if not line.strip() or line.lstrip().startswith('#') or ':' not in line:
|
||||
continue
|
||||
key, value = line.split(':', 1)
|
||||
metadata[key.strip()] = value.strip()
|
||||
return metadata, body
|
||||
|
||||
### Hooks
|
||||
Event-driven shell commands that run on tool use or lifecycle events:
|
||||
- `PreToolUse` — runs before a tool executes (can block with exit code 2)
|
||||
- `PostToolUse` — runs after a tool completes
|
||||
- `PreCompact` — runs before conversation compaction
|
||||
|
||||
Hook format:
|
||||
```json
|
||||
{{
|
||||
"hooks": {{
|
||||
"PreToolUse": [
|
||||
{{
|
||||
"matcher": "Bash",
|
||||
"hooks": [
|
||||
{{
|
||||
"type": "command",
|
||||
"command": "echo 'tool: $TOOL_NAME'"
|
||||
}}
|
||||
]
|
||||
}}
|
||||
]
|
||||
}}
|
||||
}}
|
||||
```
|
||||
def _split_csv(value: str | None) -> tuple[str, ...]:
|
||||
if not value:
|
||||
return ()
|
||||
return tuple(item.strip() for item in value.split(',') if item.strip())
|
||||
|
||||
### Permissions
|
||||
Tool permission rules:
|
||||
```json
|
||||
{{
|
||||
"permissions": {{
|
||||
"allow": ["Read", "Grep", "Glob"],
|
||||
"deny": ["Bash(rm:*)"]
|
||||
}}
|
||||
}}
|
||||
```
|
||||
|
||||
### Environment Variables
|
||||
```json
|
||||
{{
|
||||
"env": {{
|
||||
"MY_VAR": "value"
|
||||
}}
|
||||
}}
|
||||
```
|
||||
def _parse_bool(value: str | None, *, default: bool = True) -> bool:
|
||||
if value is None or not value.strip():
|
||||
return default
|
||||
return value.strip().lower() not in {'0', 'false', 'no', 'off'}
|
||||
|
||||
{f'User request: {args}' if args.strip() else 'What would you like to configure?'}"""
|
||||
|
||||
def _load_directory_skill(path: Path, *, source: str = 'directory') -> BundledSkill | None:
|
||||
try:
|
||||
text = path.read_text(encoding='utf-8')
|
||||
except OSError:
|
||||
return None
|
||||
metadata, body = _parse_front_matter(text)
|
||||
name = metadata.get('name') or path.parent.name
|
||||
description = metadata.get('description', '').strip()
|
||||
if not name.strip() or not description:
|
||||
return None
|
||||
return BundledSkill(
|
||||
name=name.strip(),
|
||||
description=description,
|
||||
when_to_use=metadata.get('when_to_use', '').strip(),
|
||||
aliases=_split_csv(metadata.get('aliases')),
|
||||
allowed_tools=_split_csv(metadata.get('allowed_tools')),
|
||||
user_invocable=_parse_bool(metadata.get('user_invocable'), default=True),
|
||||
source=source,
|
||||
path=str(path),
|
||||
get_prompt=_directory_skill_prompt(body.strip()),
|
||||
)
|
||||
|
||||
|
||||
def load_directory_skills(
|
||||
root: Path | None = None,
|
||||
*,
|
||||
source: str = 'directory',
|
||||
) -> tuple[BundledSkill, ...]:
|
||||
skill_root = root or (Path(__file__).resolve().parent / 'skills' / 'bundled')
|
||||
if not skill_root.exists() or not skill_root.is_dir():
|
||||
return ()
|
||||
skills: list[BundledSkill] = []
|
||||
for path in sorted(skill_root.glob('*/SKILL.md')):
|
||||
skill = _load_directory_skill(path, source=source)
|
||||
if skill is not None:
|
||||
skills.append(skill)
|
||||
return tuple(skills)
|
||||
|
||||
|
||||
def load_project_skills(cwd: Path | str | None) -> tuple[BundledSkill, ...]:
|
||||
if cwd is None:
|
||||
return ()
|
||||
return load_directory_skills(Path(cwd).resolve() / 'skills', source='project')
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Skill registry
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
BUNDLED_SKILLS: tuple[BundledSkill, ...] = (
|
||||
PYTHON_BUNDLED_SKILLS: tuple[BundledSkill, ...] = (
|
||||
BundledSkill(
|
||||
name='simplify',
|
||||
description='Review changed code for reuse, quality, and efficiency, then fix issues.',
|
||||
@@ -226,13 +225,6 @@ BUNDLED_SKILLS: tuple[BundledSkill, ...] = (
|
||||
allowed_tools=('read_file', 'edit_file', 'write_file', 'bash', 'grep_search', 'glob_search'),
|
||||
get_prompt=_simplify_prompt,
|
||||
),
|
||||
BundledSkill(
|
||||
name='verify',
|
||||
description='Verify a code change works by running the app and tests.',
|
||||
when_to_use='When the user asks to verify, test, or check that recent changes work.',
|
||||
allowed_tools=('read_file', 'bash', 'grep_search', 'glob_search'),
|
||||
get_prompt=_verify_prompt,
|
||||
),
|
||||
BundledSkill(
|
||||
name='debug',
|
||||
description='Debug the current session — show diagnostics, token usage, and config.',
|
||||
@@ -240,32 +232,40 @@ BUNDLED_SKILLS: tuple[BundledSkill, ...] = (
|
||||
user_invocable=True,
|
||||
get_prompt=_debug_prompt,
|
||||
),
|
||||
BundledSkill(
|
||||
name='update-config',
|
||||
description='Configure settings via settings.json — hooks, permissions, env vars.',
|
||||
when_to_use='When the user wants to configure hooks, permissions, or settings.',
|
||||
aliases=('config-help',),
|
||||
allowed_tools=('read_file',),
|
||||
get_prompt=_update_config_prompt,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def get_bundled_skills() -> tuple[BundledSkill, ...]:
|
||||
"""Return all registered bundled skills."""
|
||||
return BUNDLED_SKILLS
|
||||
def get_bundled_skills(cwd: Path | str | None = None) -> tuple[BundledSkill, ...]:
|
||||
"""Return registered skills, with project skills taking precedence."""
|
||||
skills = [
|
||||
*load_project_skills(cwd),
|
||||
*load_directory_skills(),
|
||||
*PYTHON_BUNDLED_SKILLS,
|
||||
]
|
||||
seen: set[str] = set()
|
||||
deduped: list[BundledSkill] = []
|
||||
for skill in skills:
|
||||
lowered = skill.name.lower()
|
||||
if lowered in seen:
|
||||
continue
|
||||
seen.add(lowered)
|
||||
deduped.append(skill)
|
||||
return tuple(deduped)
|
||||
|
||||
|
||||
def find_bundled_skill(name: str) -> BundledSkill | None:
|
||||
def find_bundled_skill(name: str, cwd: Path | str | None = None) -> BundledSkill | None:
|
||||
"""Look up a bundled skill by name or alias."""
|
||||
lowered = name.lower()
|
||||
for skill in BUNDLED_SKILLS:
|
||||
for skill in get_bundled_skills(cwd):
|
||||
if lowered == skill.name or lowered in skill.aliases:
|
||||
return skill
|
||||
return None
|
||||
|
||||
|
||||
def format_skills_for_system_prompt(max_chars: int = 8000) -> str:
|
||||
def format_skills_for_system_prompt(
|
||||
max_chars: int = 8000,
|
||||
cwd: Path | str | None = None,
|
||||
) -> str:
|
||||
"""Format bundled skills for inclusion in system-reminder messages.
|
||||
|
||||
The model discovers available skills through this listing.
|
||||
@@ -273,7 +273,7 @@ def format_skills_for_system_prompt(max_chars: int = 8000) -> str:
|
||||
lines = ['Available skills (invoke via Skill tool):']
|
||||
char_count = len(lines[0])
|
||||
|
||||
for skill in BUNDLED_SKILLS:
|
||||
for skill in get_bundled_skills(cwd):
|
||||
if not skill.user_invocable:
|
||||
continue
|
||||
entry = f'- {skill.name}: {skill.description}'
|
||||
|
||||
+1
-1
@@ -204,7 +204,7 @@ def create_app(state: AgentState) -> FastAPI:
|
||||
'aliases': list(skill.aliases),
|
||||
'allowed_tools': list(skill.allowed_tools),
|
||||
}
|
||||
for skill in get_bundled_skills()
|
||||
for skill in get_bundled_skills(state.cwd)
|
||||
if skill.user_invocable
|
||||
]
|
||||
|
||||
|
||||
+57
-13
@@ -232,6 +232,61 @@ function appendToolCall({ name, args, result, isError }) {
|
||||
els.chat.scrollTop = els.chat.scrollHeight;
|
||||
}
|
||||
|
||||
function parseAssistantToolCalls(entry) {
|
||||
if (!Array.isArray(entry?.tool_calls)) return [];
|
||||
return entry.tool_calls.map((tc) => {
|
||||
const fn = tc?.function || {};
|
||||
let args = fn.arguments ?? tc?.arguments ?? "";
|
||||
try {
|
||||
if (typeof args === "string") args = JSON.parse(args);
|
||||
} catch {}
|
||||
return {
|
||||
id: tc?.id || "",
|
||||
name: fn.name || tc?.name || "tool",
|
||||
args,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function renderTranscriptEntries(entries) {
|
||||
const pendingToolCalls = new Map();
|
||||
for (const entry of entries || []) {
|
||||
if (!entry || entry.role === "system") continue;
|
||||
if (entry.role === "assistant") {
|
||||
if (entry.content && entry.content.trim()) {
|
||||
appendMessage({ role: "assistant", content: entry.content });
|
||||
}
|
||||
for (const call of parseAssistantToolCalls(entry)) {
|
||||
if (call.id) pendingToolCalls.set(call.id, call);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (entry.role === "tool") {
|
||||
const call = pendingToolCalls.get(entry.tool_call_id) || {};
|
||||
let parsedResult = null;
|
||||
try {
|
||||
parsedResult = JSON.parse(entry.content || "{}");
|
||||
} catch {}
|
||||
appendToolCall({
|
||||
name: call.name || entry.name || parsedResult?.tool || "tool",
|
||||
args: call.args ?? "",
|
||||
result: entry.content || "",
|
||||
isError: parsedResult?.ok === false || entry.metadata?.error_kind,
|
||||
});
|
||||
if (entry.tool_call_id) pendingToolCalls.delete(entry.tool_call_id);
|
||||
continue;
|
||||
}
|
||||
renderTranscriptEntry(entry);
|
||||
}
|
||||
for (const call of pendingToolCalls.values()) {
|
||||
appendToolCall({
|
||||
name: call.name,
|
||||
args: call.args,
|
||||
result: "(pending — no tool result message found)",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Settings
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -314,9 +369,7 @@ async function openSession(sessionId) {
|
||||
const session = await apiGet(`/api/sessions/${encodeURIComponent(sessionId)}`);
|
||||
State.activeSessionId = sessionId;
|
||||
clearChat();
|
||||
for (const msg of session.messages) {
|
||||
renderTranscriptEntry(msg);
|
||||
}
|
||||
renderTranscriptEntries(session.messages);
|
||||
renderSessions();
|
||||
setStatus("ready", `Session ${sessionId.slice(0, 8)}…`);
|
||||
} catch (e) {
|
||||
@@ -494,16 +547,7 @@ function renderRunResult(data) {
|
||||
}
|
||||
}
|
||||
const tail = transcript.slice(lastUserIndex + 1);
|
||||
for (const entry of tail) {
|
||||
if (entry.role === "assistant") {
|
||||
// Only render if it has visible content; tool_calls already rendered.
|
||||
if (Array.isArray(entry.tool_calls) && entry.tool_calls.length) {
|
||||
// Skip — corresponding tool messages will follow.
|
||||
continue;
|
||||
}
|
||||
}
|
||||
renderTranscriptEntry(entry);
|
||||
}
|
||||
renderTranscriptEntries(tail);
|
||||
// Always show the canonical final output if it isn't already present.
|
||||
const lastMsg = els.chat.querySelector(".message.assistant:last-of-type .body");
|
||||
if (!lastMsg || lastMsg.textContent.trim() !== (data.final_output || "").trim()) {
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
---
|
||||
name: data-record-format
|
||||
description: 定义、检查、校验并准备标准数据记录,作为导出前的统一中间格式。
|
||||
when_to_use: 当用户想讨论、创建、校验、归一化或转换标准数据记录格式时使用。
|
||||
aliases: canonical-record, record-format
|
||||
allowed_tools: read_file, write_file, edit_file, grep_search, glob_search, ask_user_question
|
||||
---
|
||||
|
||||
使用这个 skill 处理共享的数据格式层。目标是让生成、挖掘、历史导入的数据都先进入同一个 canonical record 格式,再导出为训练、评测、prompt 或展示格式。
|
||||
|
||||
## 当前 Canonical Record 状态
|
||||
|
||||
TODO:canonical record v1 还没有最终确定。
|
||||
|
||||
在最终确定前,先把下面结构当作工作草案:
|
||||
|
||||
```json
|
||||
{
|
||||
"record_id": "optional-stable-id",
|
||||
"conversation": [
|
||||
{
|
||||
"query": "...",
|
||||
"tts": "...",
|
||||
"metadata": {}
|
||||
}
|
||||
],
|
||||
"label": {
|
||||
"type": "function_or_agent",
|
||||
"name": "...",
|
||||
"arguments": {}
|
||||
},
|
||||
"source": {
|
||||
"type": "generated|mined|eval_error|manual",
|
||||
"task_id": "...",
|
||||
"notes": "..."
|
||||
},
|
||||
"metadata": {}
|
||||
}
|
||||
```
|
||||
|
||||
## 必要工作循环
|
||||
|
||||
1. 先判断用户讨论的是 canonical record 层,还是下游导出格式层。
|
||||
2. 如果用户提供了文件,先检查文件结构,再提出转换或校验方案。
|
||||
3. 明确记录必填字段中的不确定点,尤其是 `tts`、标签类型、标签名称、多轮结构和来源元数据。
|
||||
4. 如果当前 schema 信息不足以继续,提出简短问题,或把问题写入任务笔记。
|
||||
5. 在 canonical records 存在之前,不要直接创建最终训练、评测或展示格式。
|
||||
|
||||
## 工具使用建议
|
||||
|
||||
- 用 `read_file` 读取用户提供的样例或 schema 笔记。
|
||||
- 用 `write_file` 或 `edit_file` 起草 schema 笔记、样例记录或 open questions。
|
||||
- 用 `grep_search` 和 `glob_search` 查找已有 data-agent 文档和样例。
|
||||
- TODO:工具实现后,使用 `validate_dataset_records`、`normalize_dataset_records`、`convert_dataset_format` 和 `export_dataset`。
|
||||
|
||||
## 推荐产物
|
||||
|
||||
优先把过程沉淀到这些路径:
|
||||
|
||||
```text
|
||||
tasks/{task_id}/memory/open_questions.md
|
||||
tasks/{task_id}/artifacts/canonical_record_notes.md
|
||||
tasks/{task_id}/artifacts/sample_records.jsonl
|
||||
```
|
||||
|
||||
最终回复保持简短,并引用创建或更新过的文件路径。
|
||||
@@ -0,0 +1,71 @@
|
||||
---
|
||||
name: eval-error-data-generation
|
||||
description: 分析评测错误,并为针对性训练集/评测集补充生成可 review 的数据计划。
|
||||
when_to_use: 当用户提供评测结果、模型判错样本或带标签错误 case,并希望补充训练/评测数据时使用。
|
||||
aliases: eval-error-generation, error-driven-data
|
||||
allowed_tools: read_file, write_file, edit_file, grep_search, glob_search, ask_user_question
|
||||
---
|
||||
|
||||
使用这个 skill 处理“已有评测错误 -> 错误分析 -> 人工 review 问题类别 -> 数据生成计划 -> canonical records -> 导出”的工作流。
|
||||
|
||||
## 输入假设
|
||||
|
||||
用户可能会提供一个或多个文件,里面包含:
|
||||
|
||||
- query 或多轮对话
|
||||
- 期望/正确 label
|
||||
- 模型预测 label
|
||||
- 可选的 tts
|
||||
- 可选的原因、垂域、模型版本、设备或其他元数据
|
||||
|
||||
输入格式可能每次不同。不要在检查文件前假设列名。
|
||||
|
||||
## 必要工作流
|
||||
|
||||
1. 读取用户提供的评测/错误文件;如果没有路径,先询问。
|
||||
2. 识别已有字段,以及缺失的 canonical record 必填字段。
|
||||
3. 生成错误分析笔记,至少包含:
|
||||
- top 混淆对
|
||||
- 反复出现的 query 模式
|
||||
- 代表性样例
|
||||
- 缺失或不明确的字段
|
||||
4. 提出可供人工 review 的问题类别。
|
||||
5. 如果类别边界不清,先请用户确认,再制定生成计划。
|
||||
6. 创建生成计划,说明:
|
||||
- 目标问题类别
|
||||
- 目标 label
|
||||
- 计划生成数量
|
||||
- 要生成的正例、反例、边界例
|
||||
- 必要元数据
|
||||
7. 只有在计划清楚后,才生成或请求 canonical records。
|
||||
8. TODO:工具实现后,在预览或导出前运行 `validate_dataset_records`。
|
||||
9. TODO:工具实现后,运行 `render_dataset_preview` 和 `export_dataset`。
|
||||
|
||||
## 当前工具状态
|
||||
|
||||
当前先使用已有工具完成文件检查和产物写入:
|
||||
|
||||
- `read_file`
|
||||
- `write_file`
|
||||
- `edit_file`
|
||||
- `grep_search`
|
||||
- `glob_search`
|
||||
- `ask_user_question`
|
||||
|
||||
规划中的数据工具不一定已经实现。除非用户确认当前分支已经实现,否则不要直接调用不存在的工具。
|
||||
|
||||
## 推荐产物
|
||||
|
||||
```text
|
||||
tasks/{task_id}/context/eval_errors.*
|
||||
tasks/{task_id}/artifacts/error_analysis.md
|
||||
tasks/{task_id}/artifacts/generation_plan.md
|
||||
tasks/{task_id}/artifacts/generated_candidates.jsonl
|
||||
tasks/{task_id}/memory/open_questions.md
|
||||
```
|
||||
|
||||
## 约束
|
||||
|
||||
- 在 canonical records 达成一致并通过校验前,不要直接导出最终训练/评测格式。
|
||||
- 不要把自动聚类当成最终事实,聚类和类别命名必须可 review。
|
||||
- 面向人的总结要简洁,并尽量用样例支撑。
|
||||
@@ -0,0 +1,88 @@
|
||||
---
|
||||
name: online-badcase-mining
|
||||
description: 分析 badcase,并通过可 review 的策略迭代挖掘线上相似 case。
|
||||
when_to_use: 当用户提供线上 badcase 或产品/标签定义,并希望查找相似线上问题或构建专项集时使用。
|
||||
aliases: badcase-mining, online-mining
|
||||
allowed_tools: read_file, write_file, edit_file, grep_search, glob_search, ask_user_question, mcp_list_tools, mcp_call_tool
|
||||
---
|
||||
|
||||
使用这个 skill 处理“badcase 或标签定义 -> 挖掘策略 -> 候选召回 -> 抽样 review -> 策略迭代 -> mined dataset”的工作流。
|
||||
|
||||
## 输入假设
|
||||
|
||||
用户可能会提供:
|
||||
|
||||
- 产品或标签定义
|
||||
- 线上 badcase 样例
|
||||
- 期望/正确 label
|
||||
- 线上预测 label
|
||||
- query、tts、agent type、function name、垂域、设备、日期、模型版本或其他元数据
|
||||
|
||||
线上字段和允许使用的筛选条件还没有最终确定。
|
||||
|
||||
## 必要工作流
|
||||
|
||||
1. 读取用户提供的 badcase 或定义文件。
|
||||
2. 分析 badcase 共性:
|
||||
- query 模式
|
||||
- label 混淆
|
||||
- agent/function 类型
|
||||
- 垂域
|
||||
- 如存在,分析设备/日期/模型等元数据
|
||||
3. 起草带明确筛选条件的挖掘策略。
|
||||
4. 当筛选条件不清或影响较大时,先请用户 review,再做宽泛线上召回。
|
||||
5. 只使用批准的工具检索或请求候选数据。
|
||||
6. 先抽取小批 review 样本,通常约 100 条。
|
||||
7. 总结样本命中率和主要 false positive 模式。
|
||||
8. 根据结果调整策略,并按需重复。
|
||||
9. 最终产出以下一种或多种:
|
||||
- 线上问题评估报告
|
||||
- 专项评测集候选
|
||||
- 专项 badcase 集合
|
||||
- 训练候选数据
|
||||
|
||||
## 当前工具状态
|
||||
|
||||
当前先使用已有工具完成本地分析和策略起草:
|
||||
|
||||
- `read_file`
|
||||
- `write_file`
|
||||
- `edit_file`
|
||||
- `grep_search`
|
||||
- `glob_search`
|
||||
- `ask_user_question`
|
||||
|
||||
如果线上挖掘工具通过 MCP 暴露,先用 `mcp_list_tools` 查看可用工具,再决定是否调用 `mcp_call_tool`。
|
||||
|
||||
TODO:规划中的专用工具:
|
||||
|
||||
- `analyze_badcases`
|
||||
- `build_mining_strategy`
|
||||
- `search_online_sessions`
|
||||
- `sample_online_candidates`
|
||||
- `create_annotation_batch`
|
||||
- `read_annotation_result`
|
||||
- `evaluate_mining_precision`
|
||||
- `refine_mining_strategy`
|
||||
- `export_mined_dataset`
|
||||
|
||||
不要手写 raw SQL,也不要用宽泛 shell 命令进行数据检索。
|
||||
|
||||
## 推荐产物
|
||||
|
||||
```text
|
||||
tasks/{task_id}/context/badcases.*
|
||||
tasks/{task_id}/artifacts/badcase_analysis.md
|
||||
tasks/{task_id}/artifacts/mining_strategy.md
|
||||
tasks/{task_id}/artifacts/candidate_sample.jsonl
|
||||
tasks/{task_id}/artifacts/review_report.md
|
||||
tasks/{task_id}/artifacts/mined_candidates.jsonl
|
||||
tasks/{task_id}/memory/open_questions.md
|
||||
tasks/{task_id}/memory/failed_attempts.md
|
||||
```
|
||||
|
||||
## 约束
|
||||
|
||||
- 除非批准的工具输出已经脱敏,否则不要导出敏感线上原始字段。
|
||||
- 不要把第一版挖掘策略当成最终策略。
|
||||
- 始终让筛选条件和抽样决策可 review。
|
||||
@@ -0,0 +1,70 @@
|
||||
---
|
||||
name: product-definition-data-generation
|
||||
description: 从产品或标签定义文档中提取边界,并生成边界感知的 canonical data records。
|
||||
when_to_use: 当用户提供产品定义、标签规则或路由边界文档,并希望生成训练/评测数据时使用。
|
||||
aliases: definition-data-generation, label-definition-generation
|
||||
allowed_tools: read_file, write_file, edit_file, grep_search, glob_search, ask_user_question
|
||||
---
|
||||
|
||||
使用这个 skill 处理“产品/标签定义 -> 边界总结 -> review 后的数据生成计划 -> canonical records -> 导出”的工作流。
|
||||
|
||||
## 输入假设
|
||||
|
||||
用户可能会提供:
|
||||
|
||||
- 产品定义文档
|
||||
- 标签定义文档
|
||||
- 正例和反例
|
||||
- 路由、agent 或 function 选择规则
|
||||
- 已知边界冲突
|
||||
|
||||
文档可能不完整或存在歧义。保留不确定性,不要自行发明隐藏规则。
|
||||
|
||||
## 必要工作流
|
||||
|
||||
1. 读取用户提供的定义文档;如果没有路径,先询问。
|
||||
2. 总结目标标签/类型及其预期边界。
|
||||
3. 提取:
|
||||
- 正向规则
|
||||
- 负向规则
|
||||
- 模糊边界
|
||||
- 示例和反例
|
||||
- 缺失假设
|
||||
4. 写出可 review 的边界总结。
|
||||
5. 对重要的模糊边界,先请用户确认,再进行大规模生成。
|
||||
6. 创建生成计划,覆盖:
|
||||
- 直接正例
|
||||
- hard negative
|
||||
- 边界样例
|
||||
- 必要时包含单轮和多轮样例
|
||||
7. 只有在边界总结和计划清楚后,才生成 canonical records。
|
||||
8. TODO:工具实现后,校验 records、生成预览并导出目标格式。
|
||||
|
||||
## 当前工具状态
|
||||
|
||||
当前先使用已有工具完成文件检查和产物写入:
|
||||
|
||||
- `read_file`
|
||||
- `write_file`
|
||||
- `edit_file`
|
||||
- `grep_search`
|
||||
- `glob_search`
|
||||
- `ask_user_question`
|
||||
|
||||
规划中的定义类工具,例如 `extract_label_definition` 和 `validate_label_coverage`,不一定已经实现。
|
||||
|
||||
## 推荐产物
|
||||
|
||||
```text
|
||||
tasks/{task_id}/context/product_definition.*
|
||||
tasks/{task_id}/artifacts/label_boundary_summary.md
|
||||
tasks/{task_id}/artifacts/generation_plan.md
|
||||
tasks/{task_id}/artifacts/generated_candidates.jsonl
|
||||
tasks/{task_id}/memory/open_questions.md
|
||||
```
|
||||
|
||||
## 约束
|
||||
|
||||
- 不要静默解决产品或标签歧义。
|
||||
- canonical records 通过校验前,不要生成最终导出格式。
|
||||
- 除非用户明确要求,否则不要把“修改标签定义”和“生成数据”混在一起做。
|
||||
@@ -0,0 +1,24 @@
|
||||
---
|
||||
name: tool-smoke-test
|
||||
description: Exercise a small existing-tool chain for skill/tool debugging.
|
||||
when_to_use: When the user wants to verify directory skills and tool-call tracing in the Web UI.
|
||||
aliases: smoke-tools
|
||||
allowed_tools: list_dir, write_file, read_file, grep_search
|
||||
---
|
||||
|
||||
Use this skill to verify that directory-based skills and basic tool chaining work.
|
||||
|
||||
## Required Flow
|
||||
|
||||
1. Briefly state that this is a tool smoke test.
|
||||
2. Call `list_dir` on `.` with a small `max_entries` value.
|
||||
3. Call `write_file` to create `tasks/task-001-smoke/artifacts/smoke.md` with a short markdown note that includes the exact phrase `tool-smoke-ok`.
|
||||
4. Call `read_file` on `tasks/task-001-smoke/artifacts/smoke.md`.
|
||||
5. Call `grep_search` for `tool-smoke-ok` under `tasks/task-001-smoke`.
|
||||
6. Summarize whether all tool calls succeeded and include the created file path.
|
||||
|
||||
## Constraints
|
||||
|
||||
- Do not use `bash`.
|
||||
- Do not edit unrelated files.
|
||||
- Keep the final response short.
|
||||
@@ -0,0 +1,68 @@
|
||||
---
|
||||
name: update-config
|
||||
description: Configure settings via settings.json - hooks, permissions, env vars.
|
||||
when_to_use: When the user wants to configure hooks, permissions, or settings.
|
||||
aliases: config-help
|
||||
allowed_tools: read_file
|
||||
---
|
||||
|
||||
Help configure the agent settings.
|
||||
|
||||
## Settings File Locations
|
||||
|
||||
- Global: `~/.claude/settings.json` applies to all projects.
|
||||
- Project: `.claude/settings.json` is project-specific and committed to git.
|
||||
- Local: `.claude/settings.local.json` is project-specific and gitignored.
|
||||
|
||||
## Configurable Settings
|
||||
|
||||
### Hooks
|
||||
|
||||
Event-driven shell commands that run on tool use or lifecycle events:
|
||||
|
||||
- `PreToolUse` runs before a tool executes and can block with exit code 2.
|
||||
- `PostToolUse` runs after a tool completes.
|
||||
- `PreCompact` runs before conversation compaction.
|
||||
|
||||
Hook format:
|
||||
|
||||
```json
|
||||
{
|
||||
"hooks": {
|
||||
"PreToolUse": [
|
||||
{
|
||||
"matcher": "Bash",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "echo 'tool: $TOOL_NAME'"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Permissions
|
||||
|
||||
Tool permission rules:
|
||||
|
||||
```json
|
||||
{
|
||||
"permissions": {
|
||||
"allow": ["Read", "Grep", "Glob"],
|
||||
"deny": ["Bash(rm:*)"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Environment Variables
|
||||
|
||||
```json
|
||||
{
|
||||
"env": {
|
||||
"MY_VAR": "value"
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,28 @@
|
||||
---
|
||||
name: verify
|
||||
description: Verify a code change works by running the app and tests.
|
||||
when_to_use: When the user asks to verify, test, or check that recent changes work.
|
||||
allowed_tools: read_file, bash, grep_search, glob_search
|
||||
---
|
||||
|
||||
Verify that the recent code changes work correctly.
|
||||
|
||||
## Instructions
|
||||
|
||||
1. Identify what was changed by checking `git diff` and `git status`.
|
||||
2. Determine the appropriate verification strategy:
|
||||
- Unit tests: run existing tests and check for failures.
|
||||
- Integration tests: run broader test suites if available.
|
||||
- Manual verification: start the app/server and test the feature when needed.
|
||||
3. Run the verification.
|
||||
4. Report the result clearly:
|
||||
- PASS: all checks passed and the feature works as expected.
|
||||
- FAIL: describe what failed and why.
|
||||
- PARTIAL: some checks passed and some still need attention.
|
||||
|
||||
## Verification Strategy
|
||||
|
||||
- For CLI tools: run the command with test inputs.
|
||||
- For servers: start the server and make test requests.
|
||||
- For libraries: run the test suite.
|
||||
- For config changes: validate that the config loads correctly.
|
||||
Reference in New Issue
Block a user