Add directory-based project skills
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
__pycache__/
|
__pycache__/
|
||||||
*.py[cod]
|
*.py[cod]
|
||||||
|
.DS_Store
|
||||||
.pytest_cache/
|
.pytest_cache/
|
||||||
.mypy_cache/
|
.mypy_cache/
|
||||||
.ruff_cache/
|
.ruff_cache/
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
# 数据 Agent 实现任务表
|
||||||
|
|
||||||
|
范围:先在现有 agent loop 上开发和调试 data-agent 的 tools 与 skills,暂时不改主运行链路。
|
||||||
|
|
||||||
|
## 极简任务表
|
||||||
|
|
||||||
|
| 顺序 | 任务 | 产物 | 验证方式 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 1 | 定义 canonical record v1 | `src/data_agent_schema.py` 或 `src/data_agent/records.py` | 单测覆盖合法和非法样本 |
|
||||||
|
| 2 | 实现 `validate_dataset_records` 工具 | 新增 `AgentTool` 注册和 handler | 用 JSONL 样例跑工具,检查结构化错误 |
|
||||||
|
| 3 | 实现 `render_dataset_preview` 工具 | 生成面向人工 review 的 Markdown 预览 | 用 3 条样本生成预览 |
|
||||||
|
| 4 | 实现 `convert_dataset_format` 工具 | 至少支持 `training_jsonl`、`eval_prompt_csv`、`review_table_csv` | 输入 canonical JSONL,检查导出文件 |
|
||||||
|
| 5 | 实现 `export_dataset` 工具 | 多格式导出和 manifest | 校验失败时拒绝导出 |
|
||||||
|
| 6 | 增加最小 data skill | 新增类似 `dataset-construction` 的目录化 skill | Web UI 中可见,并指导 validate/export 循环 |
|
||||||
|
| 7 | 准备调试样例 | `test_data/data_agent/*.jsonl` | 通过 Web UI 跑 skill + tools |
|
||||||
|
| 8 | 实现 `normalize_dataset_records` 工具 | 支持简单 CSV/JSONL 字段映射到 canonical JSONL | 输入 CSV,输出 canonical JSONL |
|
||||||
|
| 9 | 实现 `dedup_dataset_records` 工具 | 按 query + label 做精确去重 | 输出去重后 JSONL 和重复样本报告 |
|
||||||
|
| 10 | 按需增强 Web UI trace | 更清晰展示 tool 参数和结果 | 人工 UI 验证 |
|
||||||
|
|
||||||
|
## 第一批实现切片
|
||||||
|
|
||||||
|
先做任务 1、2、6、7:
|
||||||
|
|
||||||
|
```text
|
||||||
|
skill 指导
|
||||||
|
-> Agent 生成 canonical JSONL
|
||||||
|
-> validate_dataset_records 校验
|
||||||
|
-> Web UI 展示工具调用参数和校验错误
|
||||||
|
```
|
||||||
|
|
||||||
|
## 预计第一批文件
|
||||||
|
|
||||||
|
```text
|
||||||
|
src/data_agent_schema.py
|
||||||
|
src/agent_tools.py
|
||||||
|
src/bundled_skills.py
|
||||||
|
tests/test_data_agent_schema.py
|
||||||
|
tests/test_data_agent_tools.py
|
||||||
|
test_data/data_agent/valid_records.jsonl
|
||||||
|
test_data/data_agent/invalid_records.jsonl
|
||||||
|
```
|
||||||
@@ -0,0 +1,730 @@
|
|||||||
|
下面是中文翻译:
|
||||||
|
|
||||||
|
# Data Agent 新工具提案
|
||||||
|
|
||||||
|
本文档列出了 data-agent 改造中建议新增的工具。范围刻意聚焦在第一阶段实现:数据 schema、生成、校验、格式转换、评审,以及后续在线挖掘。
|
||||||
|
|
||||||
|
## 设计原则
|
||||||
|
|
||||||
|
* 优先使用窄口径的数据工具,而不是通用 shell、SQL 或临时代码。
|
||||||
|
* 工具应该稳定数据格式和可重复操作。
|
||||||
|
* 开放式推理、聚类解释和生成策略可以保留在 Agent loop 中;工具负责提供结构化输入、校验和可重复转换。
|
||||||
|
* 每个工具都应该返回机器可读的 metadata,以及人类可读的摘要。
|
||||||
|
* 生成的数据集必须在导出或持久化前完成校验。
|
||||||
|
|
||||||
|
## 优先级
|
||||||
|
|
||||||
|
* `P0`:短期立即实现所必需。
|
||||||
|
* `P1`:很快需要,但可以在第一条 schema / 生成链路跑通后再加。
|
||||||
|
* `P2`:用于在线挖掘或生产级加固。
|
||||||
|
|
||||||
|
# P0:数据集 Schema 与格式工具
|
||||||
|
|
||||||
|
这些是基础工具,避免 Agent 手写脆弱的格式转换逻辑。
|
||||||
|
|
||||||
|
数据格式设计分两层:
|
||||||
|
|
||||||
|
1. **标准数据记录层**:验证数据要素是否完整。该层回答一个 record 是否具备所需的对话轮次、`query`、`tts` 和最终 label。数据生成和在线挖掘工具应该直接产出这一标准层,或者在进一步使用前先 normalize 到这一层。
|
||||||
|
2. **导出格式层**:把标准 records 转换成固定的下游格式。一旦标准 metadata 格式稳定,转换到 training JSONL、prompt/eval CSV、review tables、display markdown 和其他消费格式,就应该成为确定性的工具逻辑。
|
||||||
|
|
||||||
|
实际规则是:
|
||||||
|
|
||||||
|
```text
|
||||||
|
generation/mining output
|
||||||
|
-> canonical records
|
||||||
|
-> validation
|
||||||
|
-> dedup/review
|
||||||
|
-> deterministic exports
|
||||||
|
```
|
||||||
|
|
||||||
|
Agent 应该负责理解数据意图和编写计划,但 schema 校验和格式转换应该由工具负责。
|
||||||
|
|
||||||
|
## 标准 Record 层
|
||||||
|
|
||||||
|
这一层定义生成或挖掘数据的内部事实源。第一版应该足够严格,避免缺失关键元素;同时也要足够灵活,以支持单轮和多轮对话。
|
||||||
|
|
||||||
|
建议的标准 record 结构:
|
||||||
|
|
||||||
|
```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": {}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
待确认的 schema 问题:
|
||||||
|
|
||||||
|
* `tts` 是否应该每一轮都必填?还是当源数据没有 TTS 时可以为空?
|
||||||
|
* label type 是否应该标准化为 `function` 和 `agent` 这样的 enum?
|
||||||
|
* label arguments 应该是必填、可选,还是由目标格式决定?
|
||||||
|
* record-level metadata 是否应该包含 model version、device、date、source table、mining strategy、reviewer decision 等信息?
|
||||||
|
|
||||||
|
## `validate_dataset_records`
|
||||||
|
|
||||||
|
用途:校验 records 是否符合标准数据 schema。
|
||||||
|
|
||||||
|
建议输入:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"input_path": "artifacts/candidates.jsonl",
|
||||||
|
"schema_name": "router_conversation_v1",
|
||||||
|
"max_errors": 50
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
建议输出:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"valid": true,
|
||||||
|
"record_count": 120,
|
||||||
|
"error_count": 0,
|
||||||
|
"errors": []
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
说明:
|
||||||
|
|
||||||
|
* 检查多轮对话、`query`、`tts`、最终 `label` 等必填字段。
|
||||||
|
* 应同时支持单轮和多轮 records。
|
||||||
|
* 应在生成 / 挖掘之后运行,并在 dedup、export 或 persistence 之前运行。
|
||||||
|
* 这是完整性和结构校验,不是下游格式校验。
|
||||||
|
|
||||||
|
## `normalize_dataset_records`
|
||||||
|
|
||||||
|
用途:把历史数据、生成数据、挖掘数据或 eval result 中略有差异的 record 形态转换成标准 record schema。
|
||||||
|
|
||||||
|
建议输入:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"input_path": "context/raw_cases.csv",
|
||||||
|
"output_path": "artifacts/normalized_cases.jsonl",
|
||||||
|
"source_format": "auto",
|
||||||
|
"schema_name": "router_conversation_v1",
|
||||||
|
"field_mapping": {
|
||||||
|
"query": "query",
|
||||||
|
"tts": "tts",
|
||||||
|
"label": "correct_label"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
说明:
|
||||||
|
|
||||||
|
* 现有 eval 文件、文档和表格可能存在格式差异,因此这个工具有用。
|
||||||
|
* 不应该过度猜测。如果必填字段含义不明确,应返回 mapping error,让 Agent 向用户确认。
|
||||||
|
* 对于在线挖掘,挖掘工具最好直接输出标准 records。这个 normalizer 主要适合 legacy 文件和用户提供的表格。
|
||||||
|
|
||||||
|
## `generate_record_ids`
|
||||||
|
|
||||||
|
用途:为标准 records 填充稳定的 `record_id`。
|
||||||
|
|
||||||
|
建议输入:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"input_path": "artifacts/normalized_cases.jsonl",
|
||||||
|
"output_path": "artifacts/normalized_cases.with_ids.jsonl",
|
||||||
|
"id_strategy": "hash"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
说明:
|
||||||
|
|
||||||
|
* 稳定 ID 会让 review、dedup、export manifest 和 git diff 更容易处理。
|
||||||
|
* 默认策略可以对标准化后的 conversation 和 label 做 hash。
|
||||||
|
|
||||||
|
# 数据集质量层
|
||||||
|
|
||||||
|
这些工具在导出前作用于标准 records。
|
||||||
|
|
||||||
|
## `dedup_dataset_records`
|
||||||
|
|
||||||
|
用途:导出前删除或标记重复、近重复 records。
|
||||||
|
|
||||||
|
建议输入:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"input_path": "artifacts/eval_candidates.jsonl",
|
||||||
|
"output_path": "artifacts/eval_candidates_deduped.jsonl",
|
||||||
|
"keys": ["conversation.query", "label.name"],
|
||||||
|
"mode": "flag"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
说明:
|
||||||
|
|
||||||
|
* 在 eval set 中,重复样本会抬高指标,或让专项集看起来比实际更大。
|
||||||
|
* 在 training set 中,重复样本可能会无意中加重某种 pattern 的权重。
|
||||||
|
* MVP 可以先从精确重复检测开始。近重复检测可以放到 P1 / P2。
|
||||||
|
* `mode="flag"` 比直接删除更安全,因为 reviewer 可以检查哪些内容被移除了。
|
||||||
|
|
||||||
|
# 导出格式层
|
||||||
|
|
||||||
|
## `convert_dataset_format`
|
||||||
|
|
||||||
|
用途:把已校验的标准 records 转换成一个批准的下游格式。
|
||||||
|
|
||||||
|
建议格式:
|
||||||
|
|
||||||
|
```text
|
||||||
|
training_jsonl
|
||||||
|
eval_prompt_csv
|
||||||
|
review_table_csv
|
||||||
|
markdown_preview
|
||||||
|
```
|
||||||
|
|
||||||
|
建议输入:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"input_path": "artifacts/eval_candidates.jsonl",
|
||||||
|
"output_path": "artifacts/eval_candidates.csv",
|
||||||
|
"target_format": "eval_prompt_csv",
|
||||||
|
"schema_name": "router_conversation_v1"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
说明:
|
||||||
|
|
||||||
|
* 这个工具和 validation 分开,因为不同转换目标可能有不同的列要求和布局要求。
|
||||||
|
* 它不应该改变语义内容。如果目标格式无法表达某个字段,工具应该在 metadata 中说明哪些字段被省略。
|
||||||
|
|
||||||
|
## `render_dataset_preview`
|
||||||
|
|
||||||
|
用途:把 records 渲染成人类评审用的格式。
|
||||||
|
|
||||||
|
建议输入:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"input_path": "artifacts/eval_candidates.jsonl",
|
||||||
|
"output_path": "artifacts/eval_candidates_preview.md",
|
||||||
|
"max_records": 50,
|
||||||
|
"group_by": "label"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
说明:
|
||||||
|
|
||||||
|
* Review 格式应该对人友好,但不应被当作训练 / 评测的标准格式。
|
||||||
|
|
||||||
|
## `export_dataset`
|
||||||
|
|
||||||
|
用途:把已校验的标准 records 导出为一个或多个批准的 artifact 文件,并写入 export manifest。
|
||||||
|
|
||||||
|
建议输入:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"input_path": "artifacts/eval_candidates_deduped.jsonl",
|
||||||
|
"exports": [
|
||||||
|
{
|
||||||
|
"target_format": "training_jsonl",
|
||||||
|
"output_path": "artifacts/export/train.jsonl"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"target_format": "eval_prompt_csv",
|
||||||
|
"output_path": "artifacts/export/eval.csv"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"target_format": "markdown_preview",
|
||||||
|
"output_path": "artifacts/export/review.md"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"require_valid": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
说明:
|
||||||
|
|
||||||
|
* 这个工具可以在写入最终输出前内部调用 validation。
|
||||||
|
* 应生成 manifest,包含 record 数量、schema version、export paths 和 validation status。
|
||||||
|
* 这是基于确定性转换的便利性 / 编排工具。调试时,仍然可以直接调用 `convert_dataset_format`。
|
||||||
|
|
||||||
|
# P1:Eval 输入与错误分析工具
|
||||||
|
|
||||||
|
这些工具支撑任务 1:利用现有 eval errors 规划并生成针对性数据。
|
||||||
|
|
||||||
|
## `load_eval_results`
|
||||||
|
|
||||||
|
用途:从 CSV、JSONL 或类 spreadsheet 导出文件中加载 eval result,并转换成标准内部表。
|
||||||
|
|
||||||
|
建议输入:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"input_path": "context/eval_errors.csv",
|
||||||
|
"output_path": "artifacts/eval_results.normalized.jsonl",
|
||||||
|
"field_mapping": {
|
||||||
|
"query": "query",
|
||||||
|
"expected_label": "correct_label",
|
||||||
|
"predicted_label": "model_label"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
说明:
|
||||||
|
|
||||||
|
* 应支持显式 field mapping。
|
||||||
|
* 可以支持 `field_mapping="auto"`,但应返回低置信度 mapping warning,而不是静默猜测。
|
||||||
|
|
||||||
|
## `profile_error_cases`
|
||||||
|
|
||||||
|
用途:从 eval errors 中产出结构化统计和切片分析。
|
||||||
|
|
||||||
|
这是比全自动聚类更安全的第一版。
|
||||||
|
|
||||||
|
建议输出:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"total_errors": 328,
|
||||||
|
"by_expected_label": {},
|
||||||
|
"by_predicted_label": {},
|
||||||
|
"confusion_pairs": [],
|
||||||
|
"top_terms": [],
|
||||||
|
"sample_records": []
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
说明:
|
||||||
|
|
||||||
|
* 这个工具给 Agent 提供分析证据。
|
||||||
|
* 随后 Agent 可以写 `artifacts/error_analysis.md`。
|
||||||
|
|
||||||
|
## `cluster_error_cases`
|
||||||
|
|
||||||
|
用途:把相似错误样本分组,便于分析。
|
||||||
|
|
||||||
|
处置:`P1`,但应被视为辅助聚类,而不是最终事实。
|
||||||
|
|
||||||
|
难点:
|
||||||
|
|
||||||
|
* 输入文件格式不一致。
|
||||||
|
* 好的 cluster 经常需要人工解释。
|
||||||
|
* 之前的聚类是交互式的,这一点应该保留。
|
||||||
|
|
||||||
|
建议设计:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"input_path": "artifacts/eval_results.normalized.jsonl",
|
||||||
|
"output_path": "artifacts/error_clusters.json",
|
||||||
|
"features": ["query", "expected_label", "predicted_label"],
|
||||||
|
"method": "heuristic",
|
||||||
|
"max_clusters": 20,
|
||||||
|
"include_examples": 10
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
推荐 MVP:
|
||||||
|
|
||||||
|
* 从 `profile_error_cases` 开始,再基于 confusion pair、label、keywords 或显式字段做简单分组。
|
||||||
|
* 由 Agent 在 markdown 中产出 cluster 名称和假设。
|
||||||
|
* 由人工评审、合并、拆分 clusters。
|
||||||
|
|
||||||
|
## `summarize_error_clusters`
|
||||||
|
|
||||||
|
用途:把 cluster 数据转换成人类可评审的报告。
|
||||||
|
|
||||||
|
与 `cluster_error_cases` 的关系:
|
||||||
|
|
||||||
|
* `cluster_error_cases` 创建结构化分组和代表样例。
|
||||||
|
* `summarize_error_clusters` 把这些分组转换成报告:cluster 标题、疑似根因、示例、建议的数据生成方向。
|
||||||
|
|
||||||
|
建议输入:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"clusters_path": "artifacts/error_clusters.json",
|
||||||
|
"output_path": "artifacts/error_cluster_summary.md"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
说明:
|
||||||
|
|
||||||
|
* 这个工具可能部分由 Agent 编写。工具可以渲染基础确定性报告;Agent 可以在其上补充推理。
|
||||||
|
|
||||||
|
## `create_review_packet`
|
||||||
|
|
||||||
|
用途:把分析结果打包成一个紧凑的人类评审 artifact。
|
||||||
|
|
||||||
|
建议输入:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"inputs": [
|
||||||
|
"artifacts/error_analysis.md",
|
||||||
|
"artifacts/error_cluster_summary.md"
|
||||||
|
],
|
||||||
|
"output_path": "artifacts/review_packet.md",
|
||||||
|
"questions_path": "memory/open_questions.md"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
说明:
|
||||||
|
|
||||||
|
* 在调用 `ask_user_question` 前有用。
|
||||||
|
* 可以让交互式评审始终基于文件内容展开。
|
||||||
|
|
||||||
|
# P1:数据规划与生成工具
|
||||||
|
|
||||||
|
这些工具支撑任务 1 和任务 2。
|
||||||
|
|
||||||
|
## `generate_data_plan`
|
||||||
|
|
||||||
|
用途:基于已评审的错误 clusters 或标签定义创建结构化生成计划。
|
||||||
|
|
||||||
|
建议输入:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"analysis_path": "artifacts/error_cluster_summary.md",
|
||||||
|
"constraints_path": "context/data_requirements.md",
|
||||||
|
"output_path": "artifacts/generation_plan.json"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
建议输出形态:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"batches": [
|
||||||
|
{
|
||||||
|
"name": "hard_negative_fast_direct",
|
||||||
|
"target_label": "SLOW_FILTER_RANK",
|
||||||
|
"count": 50,
|
||||||
|
"scenario": "...",
|
||||||
|
"requirements": [],
|
||||||
|
"negative_constraints": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
说明:
|
||||||
|
|
||||||
|
* 可以由 Agent 生成,再由工具校验。
|
||||||
|
* 短期内,这个工具可以先负责校验和规范化计划,而不是完整生成计划。
|
||||||
|
|
||||||
|
## `validate_data_plan`
|
||||||
|
|
||||||
|
用途:校验 generation plan 是否完整、可执行。
|
||||||
|
|
||||||
|
说明:
|
||||||
|
|
||||||
|
* 检查数量、标签、必填字段、schema names 和不支持的指令。
|
||||||
|
* 有助于保持交互式 plan review 稳定。
|
||||||
|
|
||||||
|
## `generate_dataset_records`
|
||||||
|
|
||||||
|
用途:根据已校验的 plan 和 data schema 生成 records。
|
||||||
|
|
||||||
|
建议输入:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"plan_path": "artifacts/generation_plan.json",
|
||||||
|
"output_path": "artifacts/generated_candidates.jsonl",
|
||||||
|
"schema_name": "router_conversation_v1",
|
||||||
|
"generation_mode": "llm_assisted"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
说明:
|
||||||
|
|
||||||
|
* 如果内部使用 LLM,工具输出仍然需要校验。
|
||||||
|
* 更简单的 MVP 是:Agent 生成候选 JSONL,然后调用 `validate_dataset_records`。
|
||||||
|
* 长期看,这个工具可以负责生成,减少格式漂移。
|
||||||
|
|
||||||
|
## `validate_label_coverage`
|
||||||
|
|
||||||
|
用途:检查生成数据是否覆盖了计划要求的 labels、scenarios 和 boundary types。
|
||||||
|
|
||||||
|
建议输入:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"dataset_path": "artifacts/generated_candidates.jsonl",
|
||||||
|
"plan_path": "artifacts/generation_plan.json",
|
||||||
|
"output_path": "artifacts/coverage_report.md"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
说明:
|
||||||
|
|
||||||
|
* 这和 schema validation 不同。
|
||||||
|
* Schema validation 问的是:“record 结构是否合法?”
|
||||||
|
* Coverage validation 问的是:“是否生成了计划中想要的数据?”
|
||||||
|
|
||||||
|
# P1:产品或标签定义工具
|
||||||
|
|
||||||
|
这些工具支撑任务 2。
|
||||||
|
|
||||||
|
## `load_definition_document`
|
||||||
|
|
||||||
|
用途:把产品或标签定义文档加载为标准 markdown / text。
|
||||||
|
|
||||||
|
建议输入:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"input_path": "context/product_definition.md",
|
||||||
|
"output_path": "artifacts/definition.normalized.md"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
说明:
|
||||||
|
|
||||||
|
* 可以支持 markdown、txt、csv,之后按需支持 docx / pdf。
|
||||||
|
|
||||||
|
## `extract_label_definition`
|
||||||
|
|
||||||
|
用途:抽取候选标签定义、正例、反例和模糊边界。
|
||||||
|
|
||||||
|
建议输出:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"labels": [],
|
||||||
|
"positive_rules": [],
|
||||||
|
"negative_rules": [],
|
||||||
|
"ambiguous_boundaries": [],
|
||||||
|
"examples": []
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
说明:
|
||||||
|
|
||||||
|
* 可以由 LLM 辅助,但应输出结构化 JSON 和 markdown summary。
|
||||||
|
|
||||||
|
## `render_label_boundary_summary`
|
||||||
|
|
||||||
|
用途:基于抽取出的定义创建可评审的标签边界摘要。
|
||||||
|
|
||||||
|
建议输出:
|
||||||
|
|
||||||
|
```text
|
||||||
|
artifacts/label_boundary_summary.md
|
||||||
|
```
|
||||||
|
|
||||||
|
说明:
|
||||||
|
|
||||||
|
* 应接入 `ask_user_question` 或人工评审流程。
|
||||||
|
|
||||||
|
# P2:在线挖掘工具
|
||||||
|
|
||||||
|
这些工具支撑任务 3。应在 schema / generation 工具稳定后再添加。
|
||||||
|
|
||||||
|
## `analyze_badcases`
|
||||||
|
|
||||||
|
用途:总结输入 badcases 的共性特征。
|
||||||
|
|
||||||
|
建议输出:
|
||||||
|
|
||||||
|
* 常见 query patterns
|
||||||
|
* 涉及 labels
|
||||||
|
* 候选过滤条件
|
||||||
|
* 高风险 / 模糊字段
|
||||||
|
* 代表样例
|
||||||
|
|
||||||
|
## `build_mining_strategy`
|
||||||
|
|
||||||
|
用途:把 badcase 分析转换成结构化在线数据搜索策略。
|
||||||
|
|
||||||
|
建议输出形态:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"filters": {
|
||||||
|
"date_range": {},
|
||||||
|
"device": [],
|
||||||
|
"agent_type": [],
|
||||||
|
"keywords": [],
|
||||||
|
"regex": [],
|
||||||
|
"domain": []
|
||||||
|
},
|
||||||
|
"sampling": {
|
||||||
|
"limit": 100,
|
||||||
|
"method": "diverse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
说明:
|
||||||
|
|
||||||
|
* 大规模查询前,应支持人工评审。
|
||||||
|
|
||||||
|
## `search_online_sessions`
|
||||||
|
|
||||||
|
用途:通过受控、可审计的 filters 查询线上 sessions。
|
||||||
|
|
||||||
|
说明:
|
||||||
|
|
||||||
|
* Agent 不应该写原始 SQL。
|
||||||
|
* 工具 schema 应只暴露被批准的字段和过滤操作符。
|
||||||
|
* 敏感字段应在工具边界完成脱敏。
|
||||||
|
|
||||||
|
## `sample_online_candidates`
|
||||||
|
|
||||||
|
用途:从检索到的线上 sessions 中采样候选样本,供评审使用。
|
||||||
|
|
||||||
|
说明:
|
||||||
|
|
||||||
|
* 支持“先标注 100 条”的循环。
|
||||||
|
* 应包含确定性 sampling metadata,保证可复现。
|
||||||
|
|
||||||
|
## `create_annotation_batch`
|
||||||
|
|
||||||
|
用途:从采样候选中创建 review / annotation packet。
|
||||||
|
|
||||||
|
说明:
|
||||||
|
|
||||||
|
* MVP 可以是本地 markdown / CSV。
|
||||||
|
* 之后可以接入标注系统。
|
||||||
|
|
||||||
|
## `read_annotation_result`
|
||||||
|
|
||||||
|
用途:把人工评审结果读回 task workspace。
|
||||||
|
|
||||||
|
说明:
|
||||||
|
|
||||||
|
* 应规范化 review labels 和 comments。
|
||||||
|
|
||||||
|
## `evaluate_mining_precision`
|
||||||
|
|
||||||
|
用途:评估当前 mining strategy 的精度是否足够。
|
||||||
|
|
||||||
|
建议输出:
|
||||||
|
|
||||||
|
* 已评审数量
|
||||||
|
* 正样本数量
|
||||||
|
* Precision
|
||||||
|
* 主要误召 pattern
|
||||||
|
* 推荐的下一步过滤条件调整
|
||||||
|
|
||||||
|
## `refine_mining_strategy`
|
||||||
|
|
||||||
|
用途:根据评估结果生成修订版 mining strategy。
|
||||||
|
|
||||||
|
说明:
|
||||||
|
|
||||||
|
* 初期可以保留为 Agent 编写;工具负责校验 strategy JSON。
|
||||||
|
|
||||||
|
## `export_mined_dataset`
|
||||||
|
|
||||||
|
用途:把挖掘数据导出成 badcase set、eval set 或 training set 格式。
|
||||||
|
|
||||||
|
说明:
|
||||||
|
|
||||||
|
* 应调用或复用 validation、dedup、conversion 和 export 逻辑。
|
||||||
|
|
||||||
|
# 持久化与 Git 工具
|
||||||
|
|
||||||
|
## `persist_dataset_artifact`
|
||||||
|
|
||||||
|
用途:持久化已批准的数据集导出,并写入 manifest。
|
||||||
|
|
||||||
|
建议输入:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"artifact_paths": [
|
||||||
|
"artifacts/export/train.jsonl",
|
||||||
|
"artifacts/export/eval.csv"
|
||||||
|
],
|
||||||
|
"manifest_path": "artifacts/export/manifest.json",
|
||||||
|
"destination": "workspace"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
说明:
|
||||||
|
|
||||||
|
* `destination="workspace"` 可作为 MVP。
|
||||||
|
* 之后 destination 可以包括对象存储、dataset registry 或内部平台。
|
||||||
|
|
||||||
|
## `prepare_dataset_git_commit`
|
||||||
|
|
||||||
|
用途:stage 已批准的数据集 artifacts,并生成 commit summary。
|
||||||
|
|
||||||
|
处置:`P1/P2`。
|
||||||
|
|
||||||
|
说明:
|
||||||
|
|
||||||
|
* 实际 git push 应需要人工确认。
|
||||||
|
* 这个工具应和 dataset export 分开。Export 负责创建文件;git persistence 负责发布或版本化。
|
||||||
|
|
||||||
|
# 建议的首批实现集合
|
||||||
|
|
||||||
|
当前短期工作建议先实现这些:
|
||||||
|
|
||||||
|
```text
|
||||||
|
validate_dataset_records
|
||||||
|
normalize_dataset_records
|
||||||
|
generate_record_ids
|
||||||
|
convert_dataset_format
|
||||||
|
render_dataset_preview
|
||||||
|
dedup_dataset_records
|
||||||
|
export_dataset
|
||||||
|
load_eval_results
|
||||||
|
profile_error_cases
|
||||||
|
validate_data_plan
|
||||||
|
persist_dataset_artifact
|
||||||
|
```
|
||||||
|
|
||||||
|
然后再添加:
|
||||||
|
|
||||||
|
```text
|
||||||
|
cluster_error_cases
|
||||||
|
summarize_error_clusters
|
||||||
|
generate_data_plan
|
||||||
|
generate_dataset_records
|
||||||
|
validate_label_coverage
|
||||||
|
load_definition_document
|
||||||
|
extract_label_definition
|
||||||
|
render_label_boundary_summary
|
||||||
|
```
|
||||||
|
|
||||||
|
最后添加在线挖掘工具:
|
||||||
|
|
||||||
|
```text
|
||||||
|
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
|
||||||
|
```
|
||||||
|
|
||||||
|
# 开放问题
|
||||||
|
|
||||||
|
* 对话 records 的 canonical schema name 和 version 是什么?
|
||||||
|
* 确切接受哪些 export formats?需要哪些必填列?
|
||||||
|
* 生成 records 中的 `tts` 应该是必填、可选,还是派生字段?
|
||||||
|
* 哪些 labels 是合法的?label registry 存在哪里?
|
||||||
|
* 在线挖掘时,哪些字段允许用于过滤和导出?
|
||||||
|
* 哪些动作在持久化或 git push 前需要人工确认?
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
# 数据 Agent Skill 地图
|
||||||
|
|
||||||
|
这是一份第一批 data-agent 目录化 skill 的工作索引。
|
||||||
|
|
||||||
|
## Skill 列表
|
||||||
|
|
||||||
|
| Skill | 主要用途 | 当前状态 |
|
||||||
|
|---|---|---|
|
||||||
|
| `data-record-format` | 讨论和起草 canonical record 规则、样例记录、格式相关 open questions | 草案;canonical record v1 仍待确定 |
|
||||||
|
| `eval-error-data-generation` | 分析评测错误,并规划针对性训练/评测数据生成 | 草案;数据工具仍待实现 |
|
||||||
|
| `product-definition-data-generation` | 从产品或标签定义文档生成边界感知的数据计划和候选记录 | 草案;定义抽取工具仍待实现 |
|
||||||
|
| `online-badcase-mining` | 分析 badcase,并起草可迭代的线上挖掘策略 | 草案;线上挖掘工具仍待实现 |
|
||||||
|
| `tool-smoke-test` | 验证目录化 skill 加载和基础工具 trace | 可用 smoke test |
|
||||||
|
|
||||||
|
## 示例触发 Query
|
||||||
|
|
||||||
|
```text
|
||||||
|
Use the data-record-format skill. 帮我 review 这个样例文件:test_data/data_agent/sample.jsonl
|
||||||
|
```
|
||||||
|
|
||||||
|
```text
|
||||||
|
Use the eval-error-data-generation skill. 输入文件:tasks/debug/context/eval_errors.csv。请先起草错误分析和数据生成计划。
|
||||||
|
```
|
||||||
|
|
||||||
|
```text
|
||||||
|
Use the product-definition-data-generation skill. 输入文件:tasks/debug/context/product_definition.md。请总结标签边界并起草数据生成计划。
|
||||||
|
```
|
||||||
|
|
||||||
|
```text
|
||||||
|
Use the online-badcase-mining skill. 输入文件:tasks/debug/context/badcases.jsonl。请分析共性模式并起草挖掘策略。
|
||||||
|
```
|
||||||
@@ -0,0 +1,189 @@
|
|||||||
|
# Data Agent 工具清单
|
||||||
|
|
||||||
|
本文档是当前 `claw-code-agent` 工具能力面的工作清单,用于未来的数据 Agent 改造。
|
||||||
|
|
||||||
|
范围:
|
||||||
|
|
||||||
|
* 源注册表:`src.agent_tools.default_tool_registry()`
|
||||||
|
* 工具形态:`AgentTool(name, description, parameters, handler)`
|
||||||
|
* 运行时执行:模型输出 `tool_calls`,随后 `LocalCodingAgent` 执行匹配的 handler,并把结果作为 tool message 写回。
|
||||||
|
|
||||||
|
## 当前工具生命周期
|
||||||
|
|
||||||
|
1. `default_tool_registry()` 构建基础注册表。
|
||||||
|
2. 在 `LocalCodingAgent.__post_init__` 阶段,可能会把插件别名和虚拟工具合并进注册表。
|
||||||
|
3. 每个 `AgentTool` 会通过 `to_openai_tool()` 转换成 OpenAI 兼容的 function schema。
|
||||||
|
4. 模型接收 `messages` 和 `tool_specs`。
|
||||||
|
5. 如果模型返回 `tool_calls`,运行时会执行每个指定名称的工具。
|
||||||
|
6. 工具 handler 接收 `(arguments, ToolExecutionContext)`。
|
||||||
|
7. handler 返回字符串,或返回 `(content, metadata)`。
|
||||||
|
8. 运行时序列化结果,并把它作为 `role="tool"` 的消息追加进去,供下一轮模型调用使用。
|
||||||
|
|
||||||
|
## Data-Agent 处置标记说明
|
||||||
|
|
||||||
|
* `Keep`:适合首版 data-agent MVP 使用。
|
||||||
|
* `Candidate`:可能有用,但应只在具体数据工作流需要时启用。
|
||||||
|
* `Wrap`:能力有用,但应该通过数据专用工具名或更窄的 schema 暴露。
|
||||||
|
* `Defer`:首版 data-agent MVP 暂不需要。
|
||||||
|
* `Disable`:对首版 data-agent MVP 来说太宽泛,或过于面向代码。
|
||||||
|
* `Special`:由 agent loop 特殊处理,不是普通业务工具。
|
||||||
|
|
||||||
|
## 工作区与文件工具
|
||||||
|
|
||||||
|
| 工具 | 当前描述 | Data-Agent 处置 | 备注 |
|
||||||
|
| --------------- | ------------------------------------------------------- | ------------- | ----------------------------------------------- |
|
||||||
|
| `list_dir` | 列出 workspace 路径下的文件和目录。 | Keep | 用于任务工作区检查。 |
|
||||||
|
| `read_file` | 读取 workspace 内 UTF-8 文本文件内容。 | Keep | Markdown 优先流程和 artifact 读取的核心工具。 |
|
||||||
|
| `write_file` | 在 workspace 内完整写入文件;必要时创建父目录。 | Keep | 用于 `goal.md`、memory 文件、报告、JSONL artifact。需要写权限。 |
|
||||||
|
| `edit_file` | 使用精确字符串匹配替换 workspace 文件中的文本。 | Keep | 适合增量更新 memory / artifact。 |
|
||||||
|
| `notebook_edit` | 通过替换或追加 source 来编辑 `.ipynb` 文件中的 Jupyter notebook cell。 | Defer | data-agent MVP 应优先使用 Markdown、JSONL、CSV 和报告。 |
|
||||||
|
| `glob_search` | 在 workspace 内查找匹配 glob pattern 的文件。 | Keep | 用于发现任务文件和 skill 文件。 |
|
||||||
|
| `grep_search` | 在 workspace 文件中搜索字符串或正则表达式。 | Keep | 用于本地标签定义、历史笔记和 skill 查找。 |
|
||||||
|
|
||||||
|
## Shell 与代码智能工具
|
||||||
|
|
||||||
|
| 工具 | 当前描述 | Data-Agent 处置 | 备注 |
|
||||||
|
| ------ | ------------------------- | ------------- | --------------------------------------- |
|
||||||
|
| `bash` | 在 workspace 内运行 shell 命令。 | Disable | 对 data-agent MVP 来说过于宽泛。优先使用专用数据工具和校验器。 |
|
||||||
|
| `LSP` | 使用本地 LSP 风格的代码智能能力。 | Disable | 面向代码的能力;数据任务不需要。 |
|
||||||
|
|
||||||
|
## Web 与搜索工具
|
||||||
|
|
||||||
|
| 工具 | 当前描述 | Data-Agent 处置 | 备注 |
|
||||||
|
| -------------------------- | ------------------------------- | ------------- | ---------------------------------------- |
|
||||||
|
| `web_fetch` | 从 HTTP、HTTPS 或 file URL 获取文本资源。 | Defer | 可能用于文档读取,但不是核心数据工作流。 |
|
||||||
|
| `search_status` | 显示本地搜索运行时摘要。 | Candidate | 仅当在线 session / 搜索 provider 被建模为搜索运行时时有用。 |
|
||||||
|
| `search_list_providers` | 列出已配置的本地搜索 provider。 | Candidate | 同上。 |
|
||||||
|
| `search_activate_provider` | 设置当前激活的本地搜索 provider。 | Defer | 属于配置动作,不应是普通 agent 任务行为。 |
|
||||||
|
| `web_search` | 通过已配置后端执行真实 Web 搜索。 | Defer | 外部 Web 搜索不同于内部数据挖掘。 |
|
||||||
|
| `tool_search` | 搜索当前激活的工具注册表。 | Keep | 在工具面持续演进时有用。之后进入严格生产模式可以移除。 |
|
||||||
|
| `sleep` | 暂停执行一小段时间。 | Defer | 适合轮询,但在存在异步任务之前不需要。 |
|
||||||
|
|
||||||
|
## 人工评审与决策工具
|
||||||
|
|
||||||
|
| 工具 | 当前描述 | Data-Agent 处置 | 备注 |
|
||||||
|
| ------------------- | ---------------------------- | ------------- | -------------- |
|
||||||
|
| `ask_user_question` | 向本地 ask-user runtime 请求用户回答。 | Keep | 对边界澄清和人工裁决很重要。 |
|
||||||
|
|
||||||
|
## 账号与配置工具
|
||||||
|
|
||||||
|
| 工具 | 当前描述 | Data-Agent 处置 | 备注 |
|
||||||
|
| ----------------------- | ------------------------------------ | ------------- | --------------------------------- |
|
||||||
|
| `account_status` | 显示本地账号运行时摘要。 | Candidate | 如果数据系统需要账号 profile,会有用。 |
|
||||||
|
| `account_list_profiles` | 列出已配置的本地账号 profile。 | Candidate | 用于调试访问配置。 |
|
||||||
|
| `account_login` | 激活本地账号 profile 或临时身份。 | Defer | 不应出现在常规自主数据任务循环里。 |
|
||||||
|
| `account_logout` | 清空当前激活账号 session 状态。 | Defer | 运维动作。 |
|
||||||
|
| `config_list` | 列出合并后的或特定来源的 workspace 配置 key。 | Candidate | 用于检查 data-agent 配置。 |
|
||||||
|
| `config_get` | 通过 dotted key path 读取 workspace 配置值。 | Candidate | 用于 data-agent policy / config 查询。 |
|
||||||
|
| `config_set` | 写入 workspace 配置值。 | Disable | 配置变更应由人控制。 |
|
||||||
|
|
||||||
|
## MCP 工具
|
||||||
|
|
||||||
|
| 工具 | 当前描述 | Data-Agent 处置 | 备注 |
|
||||||
|
| -------------------- | ------------------------------- | ------------- | ----------------------------------------------------------------------- |
|
||||||
|
| `mcp_list_resources` | 列出本地 MCP resources。 | Candidate | 适合 skill / resource 发现或内部文档读取。 |
|
||||||
|
| `mcp_read_resource` | 通过 URI 读取本地 MCP resource。 | Candidate | 如果标签、策略或历史决策以 resource 形式暴露,会有用。 |
|
||||||
|
| `mcp_list_tools` | 列出已配置 MCP server 暴露的 MCP tools。 | Candidate | 用于发现数据系统工具。 |
|
||||||
|
| `mcp_call_tool` | 调用已配置 MCP server 暴露的 MCP tool。 | Wrap | 这是强大的通用桥接能力。常规 agent 使用时,应优先使用 `search_online_sessions` 这类数据专用 wrapper。 |
|
||||||
|
|
||||||
|
## Remote、Worktree、Workflow 与 Trigger 工具
|
||||||
|
|
||||||
|
| 工具 | 当前描述 | Data-Agent 处置 | 备注 |
|
||||||
|
| ---------------------- | -------------------------------- | ------------- | --------------------------------- |
|
||||||
|
| `remote_status` | 显示本地 remote 运行时摘要。 | Defer | 运维相关。 |
|
||||||
|
| `remote_list_profiles` | 列出已配置的本地 remote profile。 | Defer | 运维相关。 |
|
||||||
|
| `remote_connect` | 激活本地 remote target / profile。 | Disable | 对 data-agent 自主模式来说过于宽泛。 |
|
||||||
|
| `remote_disconnect` | 清空当前激活 remote connection。 | Disable | 运维变更。 |
|
||||||
|
| `worktree_status` | 显示当前受管 git worktree session 状态。 | Disable | 面向代码。 |
|
||||||
|
| `worktree_enter` | 创建并进入隔离 git worktree。 | Disable | 面向代码。 |
|
||||||
|
| `worktree_exit` | 离开当前受管 worktree session。 | Disable | 面向代码。 |
|
||||||
|
| `workflow_list` | 列出本地 workflow 定义。 | Candidate | 如果数据 pipeline 以 workflow 表示,可能有用。 |
|
||||||
|
| `workflow_get` | 显示某个本地 workflow 定义。 | Candidate | 同上。 |
|
||||||
|
| `workflow_run` | 记录并渲染一次 workflow 执行请求。 | Wrap | 应变成明确的数据动作,而不是任意 workflow 执行。 |
|
||||||
|
| `remote_trigger` | 列出、查看、创建、更新或运行本地 remote trigger。 | Defer | 更偏自动化,首版 data-agent MVP 暂不需要。 |
|
||||||
|
|
||||||
|
## Planning、Task、Team 与后台工具
|
||||||
|
|
||||||
|
| 工具 | 当前描述 | Data-Agent 处置 | 备注 |
|
||||||
|
| --------------- | ----------------------------- | ------------- | -------------------------------------- |
|
||||||
|
| `plan_get` | 显示当前本地运行时 plan。 | Candidate | 对结构化长周期数据任务有用。 |
|
||||||
|
| `update_plan` | 替换当前运行时 plan。 | Candidate | 有用,但不能替代任务 workspace memory。 |
|
||||||
|
| `plan_clear` | 清空当前运行时 plan。 | Defer | 运维动作。 |
|
||||||
|
| `task_next` | 显示下一批可执行 runtime task。 | Candidate | 之后可映射到数据任务 backlog。 |
|
||||||
|
| `task_list` | 列出本地存储的 runtime task。 | Candidate | 可支持数据任务状态。 |
|
||||||
|
| `task_get` | 通过 id 显示本地存储的 runtime task。 | Candidate | 同上。 |
|
||||||
|
| `task_create` | 创建本地存储的 runtime task。 | Defer | 优先使用 task workspace。 |
|
||||||
|
| `task_update` | 更新本地存储的 runtime task。 | Defer | 优先使用 task workspace。 |
|
||||||
|
| `task_start` | 标记任务为进行中。 | Defer | 优先使用 task workspace。 |
|
||||||
|
| `task_complete` | 标记任务为完成。 | Defer | 优先使用 task workspace。 |
|
||||||
|
| `task_block` | 标记任务被阻塞。 | Defer | 优先使用 `open_questions.md`。 |
|
||||||
|
| `task_cancel` | 标记任务被取消。 | Defer | 运维动作。 |
|
||||||
|
| `team_list` | 列出本地配置的协作团队。 | Defer | 非核心 MVP。 |
|
||||||
|
| `team_get` | 显示某个本地配置的协作团队。 | Defer | 非核心 MVP。 |
|
||||||
|
| `team_create` | 创建本地存储的协作团队。 | Disable | 非核心 MVP;属于变更动作。 |
|
||||||
|
| `team_delete` | 删除本地存储的协作团队。 | Disable | 破坏性运维动作。 |
|
||||||
|
| `send_message` | 发送本地协作消息。 | Defer | 之后在评审路由中可能有用。 |
|
||||||
|
| `team_messages` | 显示已记录的协作消息。 | Defer | 之后在评审路由中可能有用。 |
|
||||||
|
| `todo_write` | 用结构化 todo list 替换当前本地运行时任务列表。 | Candidate | 可用于 agent 自组织;但 task workspace 仍应是事实源。 |
|
||||||
|
| `TaskOutput` | 通过 ID 获取后台任务输出。 | Defer | 等异步数据任务存在后有用。 |
|
||||||
|
| `TaskStop` | 通过 ID 停止运行中的后台任务。 | Defer | 等异步数据任务存在后有用。 |
|
||||||
|
|
||||||
|
## Loop 特殊工具
|
||||||
|
|
||||||
|
| 工具 | 当前描述 | Data-Agent 处置 | 备注 |
|
||||||
|
| ---------------- | ------------------- | ------------- | ----------------------------------------- |
|
||||||
|
| `EnterPlanMode` | 进入 plan mode。 | Special | coding-agent 行为;可能会被 data-agent 任务规划指导替代。 |
|
||||||
|
| `ExitPlanMode` | 退出 plan mode。 | Special | 同上。 |
|
||||||
|
| `Agent` | 为复杂任务启动一个新 agent。 | Defer | 之后可用于并行挖掘 / 评审,但首版 MVP 应保持单 agent loop。 |
|
||||||
|
| `delegate_agent` | 旧版:把子任务委托给嵌套 agent。 | Disable | 如果之后需要委托,优先使用 `Agent`。 |
|
||||||
|
| `Skill` | 在主对话中执行 skill。 | Special | 作为概念入口保留,但基于目录的数据 skills 需要单独设计。 |
|
||||||
|
|
||||||
|
## 建议的 MVP Data Tool Registry
|
||||||
|
|
||||||
|
首版 data-agent 实验建议从这个最小注册表开始:
|
||||||
|
|
||||||
|
```text
|
||||||
|
list_dir
|
||||||
|
read_file
|
||||||
|
write_file
|
||||||
|
edit_file
|
||||||
|
glob_search
|
||||||
|
grep_search
|
||||||
|
ask_user_question
|
||||||
|
tool_search
|
||||||
|
```
|
||||||
|
|
||||||
|
原型阶段可选 MCP 桥接:
|
||||||
|
|
||||||
|
```text
|
||||||
|
mcp_list_resources
|
||||||
|
mcp_read_resource
|
||||||
|
mcp_list_tools
|
||||||
|
mcp_call_tool
|
||||||
|
```
|
||||||
|
|
||||||
|
当内部数据工具准备好后,应优先添加窄口径的数据专用工具,而不是暴露宽泛工具:
|
||||||
|
|
||||||
|
```text
|
||||||
|
search_online_sessions
|
||||||
|
sample_top_queries
|
||||||
|
find_similar_cases
|
||||||
|
mine_by_pattern
|
||||||
|
mine_by_model_disagreement
|
||||||
|
cluster_and_dedup
|
||||||
|
generate_eval_candidates
|
||||||
|
validate_eval_jsonl
|
||||||
|
run_router_eval
|
||||||
|
run_online_replay
|
||||||
|
create_annotation_queue
|
||||||
|
read_human_review_result
|
||||||
|
persist_dataset_artifact
|
||||||
|
```
|
||||||
|
|
||||||
|
## Data-Agent 改造实现说明
|
||||||
|
|
||||||
|
* 新增 `default_data_tool_registry()`,不要直接修改 `default_tool_registry()`。
|
||||||
|
* 第一阶段保留文件工具、人工评审工具,以及可选的 MCP discovery。
|
||||||
|
* 内部数据系统应通过稳定、可审计的数据工具暴露,而不是通过 `bash`、原始 SQL 或通用 `mcp_call_tool` 暴露。
|
||||||
|
* 将 `mcp_call_tool` 视为开发桥接;生产 data-agent 流程应使用面向目的构建的 wrapper tools。
|
||||||
|
* 工具权限应与数据治理动作对齐,而不只是与底层执行机制对齐。
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
# Task 001:目录化 Skills
|
||||||
|
|
||||||
|
## 目标
|
||||||
|
|
||||||
|
把 skill 内容从 Python 内置字符串迁移到文件目录中,让 data-agent 工作流可以用文件形式开发、review 和维护。
|
||||||
|
|
||||||
|
## 范围
|
||||||
|
|
||||||
|
- 保持当前 `Skill` 工具和 agent loop 不变。
|
||||||
|
- 支持从 `src/skills/bundled/*/SKILL.md` 加载内置目录化 skill。
|
||||||
|
- 保持 Python 动态 skill 可用。
|
||||||
|
- 使用已有内置 skill 作为迁移样例。
|
||||||
|
|
||||||
|
## 第一批切片
|
||||||
|
|
||||||
|
- `simplify` 和 `debug` 继续保留 Python 实现,因为它们需要读取动态运行时数据。
|
||||||
|
- `verify` 和 `update-config` 迁移到目录化 skill。
|
||||||
|
- `SKILL.md` 支持简单 front matter:
|
||||||
|
|
||||||
|
```text
|
||||||
|
---
|
||||||
|
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
|
||||||
|
---
|
||||||
|
|
||||||
|
Skill prompt body...
|
||||||
|
```
|
||||||
|
|
||||||
|
## 成功标准
|
||||||
|
|
||||||
|
- `/skills` 仍然能列出迁移后的 skills。
|
||||||
|
- Web UI `/api/skills` 仍然能返回迁移后的 skills。
|
||||||
|
- `Skill({"skill": "verify"})` 返回 `SKILL.md` 中的 prompt 正文。
|
||||||
|
- 现有 Python 动态 skills 仍然可用。
|
||||||
|
|
||||||
|
## 当前状态
|
||||||
|
|
||||||
|
- 目录化 skill loader 已在 `src/bundled_skills.py` 实现。
|
||||||
|
- `verify` 已迁移到 `src/skills/bundled/verify/SKILL.md`。
|
||||||
|
- `update-config` 已迁移到 `src/skills/bundled/update-config/SKILL.md`。
|
||||||
|
- 动态 skills `simplify` 和 `debug` 继续保持 Python 实现。
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
# Task 002:项目级 Skills
|
||||||
|
|
||||||
|
## 目标
|
||||||
|
|
||||||
|
允许项目维护者直接在 workspace 根目录添加 skills:
|
||||||
|
|
||||||
|
```text
|
||||||
|
skills/
|
||||||
|
my-skill/
|
||||||
|
SKILL.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## 查找顺序
|
||||||
|
|
||||||
|
当存在 workspace `cwd` 时:
|
||||||
|
|
||||||
|
1. `cwd/skills/*/SKILL.md`
|
||||||
|
2. `src/skills/bundled/*/SKILL.md`
|
||||||
|
3. Python 动态 skills
|
||||||
|
|
||||||
|
项目级 skills 优先于同名的内置目录 skill 或 Python 动态 skill。
|
||||||
|
|
||||||
|
## 接入点
|
||||||
|
|
||||||
|
- `Skill` 工具执行时,会通过当前 runtime `cwd` 解析项目级 skills。
|
||||||
|
- `/skills` 会列出当前 workspace 下的项目级 skills。
|
||||||
|
- Web UI `/api/skills` 会列出当前 Working dir 下的项目级 skills。
|
||||||
|
|
||||||
|
## 说明
|
||||||
|
|
||||||
|
- 项目级 skill 与内置目录化 skill 共用同一套 `SKILL.md` front matter 格式。
|
||||||
|
- `name`、`aliases`、`allowed_tools` 保持面向机器的英文字段。
|
||||||
|
- skill 正文可以使用中文或英文维护。
|
||||||
@@ -57,4 +57,5 @@ include = ["src*"]
|
|||||||
src = [
|
src = [
|
||||||
"reference_data/*.json",
|
"reference_data/*.json",
|
||||||
"gui/static/*",
|
"gui/static/*",
|
||||||
|
"skills/bundled/*/SKILL.md",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -2082,7 +2082,7 @@ class LocalCodingAgent:
|
|||||||
args = str(args) if args is not None else ''
|
args = str(args) if args is not None else ''
|
||||||
|
|
||||||
# 1. Check bundled skills first
|
# 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:
|
if bundled is not None:
|
||||||
prompt = bundled.get_prompt(self, args.strip())
|
prompt = bundled.get_prompt(self, args.strip())
|
||||||
return ToolExecutionResult(
|
return ToolExecutionResult(
|
||||||
@@ -2092,7 +2092,8 @@ class LocalCodingAgent:
|
|||||||
metadata={
|
metadata={
|
||||||
'action': 'skill',
|
'action': 'skill',
|
||||||
'skill_name': skill_name,
|
'skill_name': skill_name,
|
||||||
'source': 'bundled',
|
'source': bundled.source,
|
||||||
|
'skill_path': bundled.path,
|
||||||
'should_query': True,
|
'should_query': True,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -2105,7 +2106,7 @@ class LocalCodingAgent:
|
|||||||
for s in get_slash_command_specs()
|
for s in get_slash_command_specs()
|
||||||
for name in s.names
|
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))
|
all_available = sorted(set(available_cmds + available_skills))
|
||||||
return ToolExecutionResult(
|
return ToolExecutionResult(
|
||||||
name='Skill',
|
name='Skill',
|
||||||
|
|||||||
@@ -1671,7 +1671,7 @@ def _handle_skills(agent: 'LocalCodingAgent', _args: str, input_text: str) -> Sl
|
|||||||
from .bundled_skills import get_bundled_skills
|
from .bundled_skills import get_bundled_skills
|
||||||
|
|
||||||
lines = ['## Available 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:
|
if not skill.user_invocable:
|
||||||
continue
|
continue
|
||||||
header = f'- `{skill.name}`'
|
header = f'- `{skill.name}`'
|
||||||
|
|||||||
+100
-100
@@ -13,6 +13,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import subprocess
|
import subprocess
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
|
from pathlib import Path
|
||||||
from typing import TYPE_CHECKING, Callable
|
from typing import TYPE_CHECKING, Callable
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@@ -29,6 +30,8 @@ class BundledSkill:
|
|||||||
aliases: tuple[str, ...] = ()
|
aliases: tuple[str, ...] = ()
|
||||||
allowed_tools: tuple[str, ...] = ()
|
allowed_tools: tuple[str, ...] = ()
|
||||||
user_invocable: bool = True
|
user_invocable: bool = True
|
||||||
|
source: str = 'python'
|
||||||
|
path: str | None = None
|
||||||
get_prompt: Callable[['LocalCodingAgent', str], str] = lambda _a, _args: ''
|
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 ''}"""
|
{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:
|
def _debug_prompt(agent: 'LocalCodingAgent', args: str) -> str:
|
||||||
"""Generate the debug prompt."""
|
"""Generate the debug prompt."""
|
||||||
import os
|
import os
|
||||||
@@ -154,71 +130,94 @@ def _debug_prompt(agent: 'LocalCodingAgent', args: str) -> str:
|
|||||||
return '\n'.join(lines)
|
return '\n'.join(lines)
|
||||||
|
|
||||||
|
|
||||||
def _update_config_prompt(agent: 'LocalCodingAgent', args: str) -> str:
|
def _directory_skill_prompt(body: str) -> Callable[['LocalCodingAgent', str], str]:
|
||||||
"""Generate the update-config prompt."""
|
def _prompt(_agent: 'LocalCodingAgent', args: str) -> str:
|
||||||
return f"""Help configure the agent settings.
|
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:
|
def _split_csv(value: str | None) -> tuple[str, ...]:
|
||||||
```json
|
if not value:
|
||||||
{{
|
return ()
|
||||||
"hooks": {{
|
return tuple(item.strip() for item in value.split(',') if item.strip())
|
||||||
"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
|
def _parse_bool(value: str | None, *, default: bool = True) -> bool:
|
||||||
```json
|
if value is None or not value.strip():
|
||||||
{{
|
return default
|
||||||
"env": {{
|
return value.strip().lower() not in {'0', 'false', 'no', 'off'}
|
||||||
"MY_VAR": "value"
|
|
||||||
}}
|
|
||||||
}}
|
|
||||||
```
|
|
||||||
|
|
||||||
{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
|
# Skill registry
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
BUNDLED_SKILLS: tuple[BundledSkill, ...] = (
|
PYTHON_BUNDLED_SKILLS: tuple[BundledSkill, ...] = (
|
||||||
BundledSkill(
|
BundledSkill(
|
||||||
name='simplify',
|
name='simplify',
|
||||||
description='Review changed code for reuse, quality, and efficiency, then fix issues.',
|
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'),
|
allowed_tools=('read_file', 'edit_file', 'write_file', 'bash', 'grep_search', 'glob_search'),
|
||||||
get_prompt=_simplify_prompt,
|
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(
|
BundledSkill(
|
||||||
name='debug',
|
name='debug',
|
||||||
description='Debug the current session — show diagnostics, token usage, and config.',
|
description='Debug the current session — show diagnostics, token usage, and config.',
|
||||||
@@ -240,32 +232,40 @@ BUNDLED_SKILLS: tuple[BundledSkill, ...] = (
|
|||||||
user_invocable=True,
|
user_invocable=True,
|
||||||
get_prompt=_debug_prompt,
|
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, ...]:
|
def get_bundled_skills(cwd: Path | str | None = None) -> tuple[BundledSkill, ...]:
|
||||||
"""Return all registered bundled skills."""
|
"""Return registered skills, with project skills taking precedence."""
|
||||||
return BUNDLED_SKILLS
|
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."""
|
"""Look up a bundled skill by name or alias."""
|
||||||
lowered = name.lower()
|
lowered = name.lower()
|
||||||
for skill in BUNDLED_SKILLS:
|
for skill in get_bundled_skills(cwd):
|
||||||
if lowered == skill.name or lowered in skill.aliases:
|
if lowered == skill.name or lowered in skill.aliases:
|
||||||
return skill
|
return skill
|
||||||
return None
|
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.
|
"""Format bundled skills for inclusion in system-reminder messages.
|
||||||
|
|
||||||
The model discovers available skills through this listing.
|
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):']
|
lines = ['Available skills (invoke via Skill tool):']
|
||||||
char_count = len(lines[0])
|
char_count = len(lines[0])
|
||||||
|
|
||||||
for skill in BUNDLED_SKILLS:
|
for skill in get_bundled_skills(cwd):
|
||||||
if not skill.user_invocable:
|
if not skill.user_invocable:
|
||||||
continue
|
continue
|
||||||
entry = f'- {skill.name}: {skill.description}'
|
entry = f'- {skill.name}: {skill.description}'
|
||||||
|
|||||||
+1
-1
@@ -204,7 +204,7 @@ def create_app(state: AgentState) -> FastAPI:
|
|||||||
'aliases': list(skill.aliases),
|
'aliases': list(skill.aliases),
|
||||||
'allowed_tools': list(skill.allowed_tools),
|
'allowed_tools': list(skill.allowed_tools),
|
||||||
}
|
}
|
||||||
for skill in get_bundled_skills()
|
for skill in get_bundled_skills(state.cwd)
|
||||||
if skill.user_invocable
|
if skill.user_invocable
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
+57
-13
@@ -232,6 +232,61 @@ function appendToolCall({ name, args, result, isError }) {
|
|||||||
els.chat.scrollTop = els.chat.scrollHeight;
|
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
|
// Settings
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -314,9 +369,7 @@ async function openSession(sessionId) {
|
|||||||
const session = await apiGet(`/api/sessions/${encodeURIComponent(sessionId)}`);
|
const session = await apiGet(`/api/sessions/${encodeURIComponent(sessionId)}`);
|
||||||
State.activeSessionId = sessionId;
|
State.activeSessionId = sessionId;
|
||||||
clearChat();
|
clearChat();
|
||||||
for (const msg of session.messages) {
|
renderTranscriptEntries(session.messages);
|
||||||
renderTranscriptEntry(msg);
|
|
||||||
}
|
|
||||||
renderSessions();
|
renderSessions();
|
||||||
setStatus("ready", `Session ${sessionId.slice(0, 8)}…`);
|
setStatus("ready", `Session ${sessionId.slice(0, 8)}…`);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -494,16 +547,7 @@ function renderRunResult(data) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
const tail = transcript.slice(lastUserIndex + 1);
|
const tail = transcript.slice(lastUserIndex + 1);
|
||||||
for (const entry of tail) {
|
renderTranscriptEntries(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);
|
|
||||||
}
|
|
||||||
// Always show the canonical final output if it isn't already present.
|
// Always show the canonical final output if it isn't already present.
|
||||||
const lastMsg = els.chat.querySelector(".message.assistant:last-of-type .body");
|
const lastMsg = els.chat.querySelector(".message.assistant:last-of-type .body");
|
||||||
if (!lastMsg || lastMsg.textContent.trim() !== (data.final_output || "").trim()) {
|
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.
|
||||||
@@ -0,0 +1,147 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import tempfile
|
||||||
|
import unittest
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from src.bundled_skills import (
|
||||||
|
find_bundled_skill,
|
||||||
|
format_skills_for_system_prompt,
|
||||||
|
get_bundled_skills,
|
||||||
|
load_directory_skills,
|
||||||
|
load_project_skills,
|
||||||
|
)
|
||||||
|
from src.agent_runtime import LocalCodingAgent
|
||||||
|
from src.agent_types import AgentRuntimeConfig, ModelConfig
|
||||||
|
|
||||||
|
|
||||||
|
class BundledSkillsTests(unittest.TestCase):
|
||||||
|
def test_directory_skills_are_loaded_with_metadata(self) -> None:
|
||||||
|
skills = {skill.name: skill for skill in get_bundled_skills()}
|
||||||
|
self.assertIn('verify', skills)
|
||||||
|
self.assertEqual(skills['verify'].source, 'directory')
|
||||||
|
self.assertIn('bash', skills['verify'].allowed_tools)
|
||||||
|
|
||||||
|
def test_directory_skill_aliases_are_resolved(self) -> None:
|
||||||
|
skill = find_bundled_skill('config-help')
|
||||||
|
self.assertIsNotNone(skill)
|
||||||
|
assert skill is not None
|
||||||
|
self.assertEqual(skill.name, 'update-config')
|
||||||
|
self.assertEqual(skill.source, 'directory')
|
||||||
|
|
||||||
|
def test_directory_skill_prompt_appends_invocation_args(self) -> None:
|
||||||
|
skill = find_bundled_skill('verify')
|
||||||
|
self.assertIsNotNone(skill)
|
||||||
|
assert skill is not None
|
||||||
|
prompt = skill.get_prompt(None, 'focus: tests') # type: ignore[arg-type]
|
||||||
|
self.assertIn('Verify that the recent code changes work correctly.', prompt)
|
||||||
|
self.assertIn('## Invocation Arguments', prompt)
|
||||||
|
self.assertIn('focus: tests', prompt)
|
||||||
|
|
||||||
|
def test_custom_directory_skill_loader(self) -> None:
|
||||||
|
with tempfile.TemporaryDirectory() as tmp_dir:
|
||||||
|
root = Path(tmp_dir)
|
||||||
|
skill_dir = root / 'sample'
|
||||||
|
skill_dir.mkdir()
|
||||||
|
(skill_dir / 'SKILL.md').write_text(
|
||||||
|
(
|
||||||
|
'---\n'
|
||||||
|
'name: sample\n'
|
||||||
|
'description: Sample directory skill.\n'
|
||||||
|
'aliases: s1, s2\n'
|
||||||
|
'allowed_tools: read_file, write_file\n'
|
||||||
|
'---\n'
|
||||||
|
'Use sample instructions.\n'
|
||||||
|
),
|
||||||
|
encoding='utf-8',
|
||||||
|
)
|
||||||
|
skills = load_directory_skills(root)
|
||||||
|
self.assertEqual(len(skills), 1)
|
||||||
|
self.assertEqual(skills[0].name, 'sample')
|
||||||
|
self.assertEqual(skills[0].aliases, ('s1', 's2'))
|
||||||
|
self.assertEqual(skills[0].allowed_tools, ('read_file', 'write_file'))
|
||||||
|
|
||||||
|
def test_skills_prompt_includes_directory_skill(self) -> None:
|
||||||
|
rendered = format_skills_for_system_prompt()
|
||||||
|
self.assertIn('verify', rendered)
|
||||||
|
self.assertIn('update-config', rendered)
|
||||||
|
|
||||||
|
def test_project_skills_are_loaded_from_workspace_skills_directory(self) -> None:
|
||||||
|
with tempfile.TemporaryDirectory() as tmp_dir:
|
||||||
|
workspace = Path(tmp_dir)
|
||||||
|
skill_dir = workspace / 'skills' / 'project-skill'
|
||||||
|
skill_dir.mkdir(parents=True)
|
||||||
|
(skill_dir / 'SKILL.md').write_text(
|
||||||
|
(
|
||||||
|
'---\n'
|
||||||
|
'name: project-skill\n'
|
||||||
|
'description: Project-maintained skill.\n'
|
||||||
|
'aliases: local-skill\n'
|
||||||
|
'allowed_tools: read_file\n'
|
||||||
|
'---\n'
|
||||||
|
'Project skill body.\n'
|
||||||
|
),
|
||||||
|
encoding='utf-8',
|
||||||
|
)
|
||||||
|
skills = get_bundled_skills(workspace)
|
||||||
|
resolved = find_bundled_skill('local-skill', cwd=workspace)
|
||||||
|
by_name = {skill.name: skill for skill in skills}
|
||||||
|
self.assertIn('project-skill', by_name)
|
||||||
|
self.assertEqual(by_name['project-skill'].source, 'project')
|
||||||
|
self.assertIsNotNone(resolved)
|
||||||
|
assert resolved is not None
|
||||||
|
self.assertEqual(resolved.name, 'project-skill')
|
||||||
|
|
||||||
|
def test_project_skills_override_bundled_directory_skills(self) -> None:
|
||||||
|
with tempfile.TemporaryDirectory() as tmp_dir:
|
||||||
|
workspace = Path(tmp_dir)
|
||||||
|
skill_dir = workspace / 'skills' / 'verify'
|
||||||
|
skill_dir.mkdir(parents=True)
|
||||||
|
(skill_dir / 'SKILL.md').write_text(
|
||||||
|
(
|
||||||
|
'---\n'
|
||||||
|
'name: verify\n'
|
||||||
|
'description: Project override for verify.\n'
|
||||||
|
'---\n'
|
||||||
|
'Project verify body.\n'
|
||||||
|
),
|
||||||
|
encoding='utf-8',
|
||||||
|
)
|
||||||
|
skill = find_bundled_skill('verify', cwd=workspace)
|
||||||
|
self.assertIsNotNone(skill)
|
||||||
|
assert skill is not None
|
||||||
|
self.assertEqual(skill.source, 'project')
|
||||||
|
self.assertIn('Project verify body.', skill.get_prompt(None, '')) # type: ignore[arg-type]
|
||||||
|
|
||||||
|
def test_load_project_skills_returns_empty_without_skills_directory(self) -> None:
|
||||||
|
with tempfile.TemporaryDirectory() as tmp_dir:
|
||||||
|
self.assertEqual(load_project_skills(Path(tmp_dir)), ())
|
||||||
|
|
||||||
|
def test_agent_skill_tool_executes_project_skill_from_cwd(self) -> None:
|
||||||
|
with tempfile.TemporaryDirectory() as tmp_dir:
|
||||||
|
workspace = Path(tmp_dir)
|
||||||
|
skill_dir = workspace / 'skills' / 'project-run'
|
||||||
|
skill_dir.mkdir(parents=True)
|
||||||
|
(skill_dir / 'SKILL.md').write_text(
|
||||||
|
(
|
||||||
|
'---\n'
|
||||||
|
'name: project-run\n'
|
||||||
|
'description: Project runnable skill.\n'
|
||||||
|
'---\n'
|
||||||
|
'Run project skill body.\n'
|
||||||
|
),
|
||||||
|
encoding='utf-8',
|
||||||
|
)
|
||||||
|
agent = LocalCodingAgent(
|
||||||
|
model_config=ModelConfig(model='test'),
|
||||||
|
runtime_config=AgentRuntimeConfig(cwd=workspace),
|
||||||
|
)
|
||||||
|
result = agent._execute_skill({'skill': 'project-run', 'args': 'demo'})
|
||||||
|
self.assertTrue(result.ok)
|
||||||
|
self.assertIn('Run project skill body.', result.content)
|
||||||
|
self.assertEqual(result.metadata.get('source'), 'project')
|
||||||
|
self.assertIn('SKILL.md', str(result.metadata.get('skill_path')))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
||||||
Reference in New Issue
Block a user