mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-05 01:50:27 +08:00
feat: 为 skill-generator 添加脚本执行能力
- 默认创建 scripts/ 目录用于存放确定性脚本 - 新增 specs/scripting-integration.md 脚本集成规范 - 新增 templates/script-python.md 和 script-bash.md 脚本模板 - 模板中添加 ## Scripts 声明和 ExecuteScript 调用示例 - 支持命名即ID、扩展名即运行时的约定 - 参数自动转换: snake_case → kebab-case - Bash 模板使用 jq 构建 JSON 输出 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -34,10 +34,8 @@ if (config.execution_mode === 'autonomous' || config.execution_mode === 'hybrid'
|
|||||||
Bash(`mkdir -p "${skillDir}/phases/actions"`);
|
Bash(`mkdir -p "${skillDir}/phases/actions"`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 可选: scripts 目录
|
// scripts 目录(默认创建,用于存放确定性脚本)
|
||||||
if (config.needs_scripts) {
|
Bash(`mkdir -p "${skillDir}/scripts"`);
|
||||||
Bash(`mkdir -p "${skillDir}/scripts"`);
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Step 3: 生成 SKILL.md
|
### Step 3: 生成 SKILL.md
|
||||||
@@ -197,11 +195,12 @@ function generateReferenceTable(config) {
|
|||||||
## Output
|
## Output
|
||||||
|
|
||||||
- **Directory**: `.claude/skills/{skill-name}/`
|
- **Directory**: `.claude/skills/{skill-name}/`
|
||||||
- **Files**:
|
- **Files**:
|
||||||
- `SKILL.md` (入口文件)
|
- `SKILL.md` (入口文件)
|
||||||
- `phases/` (空目录)
|
- `phases/` (执行阶段目录)
|
||||||
- `specs/` (空目录)
|
- `specs/` (规范文档目录)
|
||||||
- `templates/` (空目录)
|
- `templates/` (模板目录)
|
||||||
|
- `scripts/` (脚本目录,存放 Python/Bash 确定性脚本)
|
||||||
|
|
||||||
## Next Phase
|
## Next Phase
|
||||||
|
|
||||||
|
|||||||
265
.claude/skills/skill-generator/specs/scripting-integration.md
Normal file
265
.claude/skills/skill-generator/specs/scripting-integration.md
Normal file
@@ -0,0 +1,265 @@
|
|||||||
|
# Scripting Integration Spec
|
||||||
|
|
||||||
|
技能脚本集成规范,定义如何在技能中使用外部脚本执行确定性任务。
|
||||||
|
|
||||||
|
## 核心原则
|
||||||
|
|
||||||
|
1. **约定优于配置**:命名即 ID,扩展名即运行时
|
||||||
|
2. **极简调用**:一行完成脚本调用
|
||||||
|
3. **标准输入输出**:命令行参数输入,JSON 标准输出
|
||||||
|
|
||||||
|
## 目录结构
|
||||||
|
|
||||||
|
```
|
||||||
|
.claude/skills/<skill-name>/
|
||||||
|
├── scripts/ # 脚本专用目录
|
||||||
|
│ ├── process-data.py # id: process-data
|
||||||
|
│ ├── validate-output.sh # id: validate-output
|
||||||
|
│ └── transform-json.js # id: transform-json
|
||||||
|
├── phases/
|
||||||
|
└── specs/
|
||||||
|
```
|
||||||
|
|
||||||
|
## 命名约定
|
||||||
|
|
||||||
|
| 扩展名 | 运行时 | 执行命令 |
|
||||||
|
|--------|--------|----------|
|
||||||
|
| `.py` | python | `python scripts/{id}.py` |
|
||||||
|
| `.sh` | bash | `bash scripts/{id}.sh` |
|
||||||
|
| `.js` | node | `node scripts/{id}.js` |
|
||||||
|
|
||||||
|
## 声明格式
|
||||||
|
|
||||||
|
在 Phase 或 Action 文件的 `## Scripts` 部分声明:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
## Scripts
|
||||||
|
|
||||||
|
- process-data
|
||||||
|
- validate-output
|
||||||
|
```
|
||||||
|
|
||||||
|
## 调用语法
|
||||||
|
|
||||||
|
### 基础调用
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const result = await ExecuteScript('script-id', { key: value });
|
||||||
|
```
|
||||||
|
|
||||||
|
### 参数命名转换
|
||||||
|
|
||||||
|
调用时 JS 对象中的键会**自动转换**为 `kebab-case` 命令行参数:
|
||||||
|
|
||||||
|
| JS 键名 | 转换后参数 |
|
||||||
|
|---------|-----------|
|
||||||
|
| `input_path` | `--input-path` |
|
||||||
|
| `output_dir` | `--output-dir` |
|
||||||
|
| `max_count` | `--max-count` |
|
||||||
|
|
||||||
|
脚本中使用 `--input-path` 接收,调用时使用 `input_path` 传入。
|
||||||
|
|
||||||
|
### 完整调用(含错误处理)
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const result = await ExecuteScript('process-data', {
|
||||||
|
input_path: `${workDir}/data.json`,
|
||||||
|
threshold: 0.9
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!result.success) {
|
||||||
|
throw new Error(`脚本执行失败: ${result.stderr}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { output_file, count } = result.outputs;
|
||||||
|
```
|
||||||
|
|
||||||
|
## 返回格式
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface ScriptResult {
|
||||||
|
success: boolean; // exit code === 0
|
||||||
|
stdout: string; // 完整标准输出
|
||||||
|
stderr: string; // 完整标准错误
|
||||||
|
outputs: { // 从 stdout 最后一行解析的 JSON
|
||||||
|
[key: string]: any;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 脚本编写规范
|
||||||
|
|
||||||
|
### 输入:命令行参数
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Python: argparse
|
||||||
|
--input-path /path/to/file --threshold 0.9
|
||||||
|
|
||||||
|
# Bash: 手动解析
|
||||||
|
--input-path /path/to/file
|
||||||
|
```
|
||||||
|
|
||||||
|
### 输出:标准输出 JSON
|
||||||
|
|
||||||
|
脚本最后一行必须打印单行 JSON:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{"output_file": "/tmp/result.json", "count": 42}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Python 模板
|
||||||
|
|
||||||
|
```python
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument('--input-path', required=True)
|
||||||
|
parser.add_argument('--threshold', type=float, default=0.9)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
# 执行逻辑...
|
||||||
|
result_path = "/tmp/result.json"
|
||||||
|
|
||||||
|
# 输出 JSON
|
||||||
|
print(json.dumps({
|
||||||
|
"output_file": result_path,
|
||||||
|
"items_processed": 100
|
||||||
|
}))
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
|
```
|
||||||
|
|
||||||
|
### Bash 模板
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# 解析参数
|
||||||
|
while [[ "$#" -gt 0 ]]; do
|
||||||
|
case $1 in
|
||||||
|
--input-path) INPUT_PATH="$2"; shift ;;
|
||||||
|
*) echo "Unknown: $1" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
|
||||||
|
# 执行逻辑...
|
||||||
|
LOG_FILE="/tmp/process.log"
|
||||||
|
echo "Processing $INPUT_PATH" > "$LOG_FILE"
|
||||||
|
|
||||||
|
# 输出 JSON
|
||||||
|
echo "{\"log_file\": \"$LOG_FILE\", \"status\": \"done\"}"
|
||||||
|
```
|
||||||
|
|
||||||
|
## ExecuteScript 实现
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
async function ExecuteScript(scriptId, inputs = {}) {
|
||||||
|
const skillDir = GetSkillDir();
|
||||||
|
|
||||||
|
// 查找脚本文件
|
||||||
|
const extensions = ['.py', '.sh', '.js'];
|
||||||
|
let scriptPath, runtime;
|
||||||
|
|
||||||
|
for (const ext of extensions) {
|
||||||
|
const path = `${skillDir}/scripts/${scriptId}${ext}`;
|
||||||
|
if (FileExists(path)) {
|
||||||
|
scriptPath = path;
|
||||||
|
runtime = ext === '.py' ? 'python' : ext === '.sh' ? 'bash' : 'node';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!scriptPath) {
|
||||||
|
throw new Error(`Script not found: ${scriptId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建命令行参数
|
||||||
|
const args = Object.entries(inputs)
|
||||||
|
.map(([k, v]) => `--${k.replace(/_/g, '-')} "${v}"`)
|
||||||
|
.join(' ');
|
||||||
|
|
||||||
|
// 执行脚本
|
||||||
|
const cmd = `${runtime} "${scriptPath}" ${args}`;
|
||||||
|
const { stdout, stderr, exitCode } = await Bash(cmd);
|
||||||
|
|
||||||
|
// 解析输出
|
||||||
|
let outputs = {};
|
||||||
|
try {
|
||||||
|
const lastLine = stdout.trim().split('\n').pop();
|
||||||
|
outputs = JSON.parse(lastLine);
|
||||||
|
} catch (e) {
|
||||||
|
// 无法解析 JSON,保持空对象
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: exitCode === 0,
|
||||||
|
stdout,
|
||||||
|
stderr,
|
||||||
|
outputs
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 使用场景
|
||||||
|
|
||||||
|
### 适合脚本化的任务
|
||||||
|
|
||||||
|
- 数据处理和转换
|
||||||
|
- 文件格式转换
|
||||||
|
- 批量文件操作
|
||||||
|
- 复杂计算逻辑
|
||||||
|
- 调用外部工具/库
|
||||||
|
|
||||||
|
### 不适合脚本化的任务
|
||||||
|
|
||||||
|
- 需要用户交互的任务
|
||||||
|
- 需要访问 Claude 工具的任务
|
||||||
|
- 简单的文件读写
|
||||||
|
- 需要动态决策的任务
|
||||||
|
|
||||||
|
## 路径约定
|
||||||
|
|
||||||
|
### 脚本路径
|
||||||
|
|
||||||
|
脚本路径相对于 `SKILL.md` 所在目录(技能根目录):
|
||||||
|
|
||||||
|
```
|
||||||
|
.claude/skills/<skill-name>/ # 技能根目录(SKILL.md 所在位置)
|
||||||
|
├── SKILL.md
|
||||||
|
├── scripts/ # 脚本目录
|
||||||
|
│ └── process-data.py # 相对路径: scripts/process-data.py
|
||||||
|
└── phases/
|
||||||
|
```
|
||||||
|
|
||||||
|
`ExecuteScript` 自动从技能根目录查找脚本:
|
||||||
|
```javascript
|
||||||
|
// 实际执行: python .claude/skills/<skill-name>/scripts/process-data.py
|
||||||
|
await ExecuteScript('process-data', { ... });
|
||||||
|
```
|
||||||
|
|
||||||
|
### 输出目录
|
||||||
|
|
||||||
|
**推荐**:由调用方传递输出目录,而非脚本默认 `/tmp`:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// 调用时指定输出目录(在工作流工作目录内)
|
||||||
|
const result = await ExecuteScript('process-data', {
|
||||||
|
input_path: `${workDir}/data.json`,
|
||||||
|
output_dir: `${workDir}/output` // 明确指定输出位置
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
脚本应接受 `--output-dir` 参数,而非硬编码输出路径。
|
||||||
|
|
||||||
|
## 最佳实践
|
||||||
|
|
||||||
|
1. **单一职责**:每个脚本只做一件事
|
||||||
|
2. **无副作用**:脚本不应修改全局状态
|
||||||
|
3. **幂等性**:相同输入产生相同输出
|
||||||
|
4. **错误明确**:错误信息写入 stderr,正常输出写入 stdout
|
||||||
|
5. **快速失败**:参数验证失败立即退出
|
||||||
|
6. **路径参数化**:输出路径由调用方指定,不硬编码
|
||||||
@@ -17,11 +17,22 @@
|
|||||||
|
|
||||||
{{preconditions_list}}
|
{{preconditions_list}}
|
||||||
|
|
||||||
|
## Scripts
|
||||||
|
|
||||||
|
\`\`\`yaml
|
||||||
|
# 声明本动作使用的脚本(可选)
|
||||||
|
# - script-id # 对应 scripts/script-id.py 或 .sh
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
## Execution
|
## Execution
|
||||||
|
|
||||||
\`\`\`javascript
|
\`\`\`javascript
|
||||||
async function execute(state) {
|
async function execute(state) {
|
||||||
{{execution_code}}
|
{{execution_code}}
|
||||||
|
|
||||||
|
// 调用脚本示例
|
||||||
|
// const result = await ExecuteScript('script-id', { input: state.context.data });
|
||||||
|
// if (!result.success) throw new Error(result.stderr);
|
||||||
}
|
}
|
||||||
\`\`\`
|
\`\`\`
|
||||||
|
|
||||||
|
|||||||
277
.claude/skills/skill-generator/templates/script-bash.md
Normal file
277
.claude/skills/skill-generator/templates/script-bash.md
Normal file
@@ -0,0 +1,277 @@
|
|||||||
|
# Bash Script Template
|
||||||
|
|
||||||
|
Bash 脚本模板,用于生成技能中的确定性脚本。
|
||||||
|
|
||||||
|
## 模板代码
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/bin/bash
|
||||||
|
# {{script_description}}
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# 参数解析
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
INPUT_PATH=""
|
||||||
|
OUTPUT_DIR="" # 由调用方指定,不设默认值
|
||||||
|
|
||||||
|
show_help() {
|
||||||
|
echo "用法: $0 --input-path <path> --output-dir <dir>"
|
||||||
|
echo ""
|
||||||
|
echo "参数:"
|
||||||
|
echo " --input-path 输入文件路径 (必需)"
|
||||||
|
echo " --output-dir 输出目录 (必需,由调用方指定)"
|
||||||
|
echo " --help 显示帮助信息"
|
||||||
|
}
|
||||||
|
|
||||||
|
while [[ "$#" -gt 0 ]]; do
|
||||||
|
case $1 in
|
||||||
|
--input-path)
|
||||||
|
INPUT_PATH="$2"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--output-dir)
|
||||||
|
OUTPUT_DIR="$2"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--help)
|
||||||
|
show_help
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "错误: 未知参数 $1" >&2
|
||||||
|
show_help >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# 参数验证
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
if [[ -z "$INPUT_PATH" ]]; then
|
||||||
|
echo "错误: --input-path 是必需参数" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "$OUTPUT_DIR" ]]; then
|
||||||
|
echo "错误: --output-dir 是必需参数" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -f "$INPUT_PATH" ]]; then
|
||||||
|
echo "错误: 输入文件不存在: $INPUT_PATH" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 检查 jq 是否可用(用于 JSON 输出)
|
||||||
|
if ! command -v jq &> /dev/null; then
|
||||||
|
echo "错误: 需要安装 jq" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$OUTPUT_DIR"
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# 核心逻辑
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
OUTPUT_FILE="$OUTPUT_DIR/result.txt"
|
||||||
|
ITEMS_COUNT=0
|
||||||
|
|
||||||
|
# TODO: 实现处理逻辑
|
||||||
|
# 示例:处理输入文件
|
||||||
|
while IFS= read -r line; do
|
||||||
|
echo "$line" >> "$OUTPUT_FILE"
|
||||||
|
((ITEMS_COUNT++))
|
||||||
|
done < "$INPUT_PATH"
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# 输出 JSON 结果(使用 jq 构建,避免特殊字符问题)
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
jq -n \
|
||||||
|
--arg output_file "$OUTPUT_FILE" \
|
||||||
|
--argjson items_processed "$ITEMS_COUNT" \
|
||||||
|
'{output_file: $output_file, items_processed: $items_processed, status: "success"}'
|
||||||
|
```
|
||||||
|
|
||||||
|
## 变量说明
|
||||||
|
|
||||||
|
| 变量 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| `{{script_description}}` | 脚本功能描述 |
|
||||||
|
|
||||||
|
## 使用规范
|
||||||
|
|
||||||
|
### 脚本头部
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/bin/bash
|
||||||
|
set -euo pipefail # 严格模式:出错退出、未定义变量报错、管道错误传递
|
||||||
|
```
|
||||||
|
|
||||||
|
### 参数解析模式
|
||||||
|
|
||||||
|
```bash
|
||||||
|
while [[ "$#" -gt 0 ]]; do
|
||||||
|
case $1 in
|
||||||
|
--param-name)
|
||||||
|
PARAM_VAR="$2"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--flag)
|
||||||
|
FLAG_VAR=true
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Unknown: $1" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
```
|
||||||
|
|
||||||
|
### 输出格式
|
||||||
|
|
||||||
|
- 最后一行打印单行 JSON
|
||||||
|
- **强烈推荐使用 `jq`**:自动处理转义和类型
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 推荐:使用 jq 构建(安全、可靠)
|
||||||
|
jq -n \
|
||||||
|
--arg file "$FILE" \
|
||||||
|
--argjson count "$COUNT" \
|
||||||
|
'{output_file: $file, items_processed: $count}'
|
||||||
|
|
||||||
|
# 备选:简单场景手动拼接(注意特殊字符转义)
|
||||||
|
echo "{\"file\": \"$FILE\", \"count\": $COUNT}"
|
||||||
|
```
|
||||||
|
|
||||||
|
**jq 参数类型**:
|
||||||
|
- `--arg name value`:字符串类型
|
||||||
|
- `--argjson name value`:数字/布尔/null 类型
|
||||||
|
|
||||||
|
### 错误处理
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 验证错误
|
||||||
|
if [[ -z "$PARAM" ]]; then
|
||||||
|
echo "错误: 参数不能为空" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 命令错误
|
||||||
|
if ! command -v jq &> /dev/null; then
|
||||||
|
echo "错误: 需要安装 jq" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 运行时错误
|
||||||
|
if ! some_command; then
|
||||||
|
echo "错误: 命令执行失败" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
```
|
||||||
|
|
||||||
|
## 常用模式
|
||||||
|
|
||||||
|
### 文件遍历
|
||||||
|
|
||||||
|
```bash
|
||||||
|
for file in "$INPUT_DIR"/*.json; do
|
||||||
|
[[ -f "$file" ]] || continue
|
||||||
|
echo "处理: $file"
|
||||||
|
# 处理逻辑...
|
||||||
|
done
|
||||||
|
```
|
||||||
|
|
||||||
|
### 临时文件
|
||||||
|
|
||||||
|
```bash
|
||||||
|
TEMP_FILE=$(mktemp)
|
||||||
|
trap "rm -f $TEMP_FILE" EXIT
|
||||||
|
|
||||||
|
echo "data" > "$TEMP_FILE"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 调用其他工具
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 检查工具存在
|
||||||
|
require_command() {
|
||||||
|
if ! command -v "$1" &> /dev/null; then
|
||||||
|
echo "错误: 需要 $1" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
require_command jq
|
||||||
|
require_command curl
|
||||||
|
```
|
||||||
|
|
||||||
|
### JSON 处理(使用 jq)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 读取 JSON 字段
|
||||||
|
VALUE=$(jq -r '.field' "$INPUT_PATH")
|
||||||
|
|
||||||
|
# 修改 JSON
|
||||||
|
jq '.field = "new_value"' "$INPUT_PATH" > "$OUTPUT_FILE"
|
||||||
|
|
||||||
|
# 合并 JSON 文件
|
||||||
|
jq -s 'add' file1.json file2.json > merged.json
|
||||||
|
```
|
||||||
|
|
||||||
|
## 生成函数
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
function generateBashScript(scriptConfig) {
|
||||||
|
return `#!/bin/bash
|
||||||
|
# ${scriptConfig.description}
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# 参数定义
|
||||||
|
${scriptConfig.inputs.map(i =>
|
||||||
|
`${i.name.toUpperCase().replace(/-/g, '_')}="${i.default || ''}"`
|
||||||
|
).join('\n')}
|
||||||
|
|
||||||
|
# 参数解析
|
||||||
|
while [[ "$#" -gt 0 ]]; do
|
||||||
|
case $1 in
|
||||||
|
${scriptConfig.inputs.map(i =>
|
||||||
|
` --${i.name})
|
||||||
|
${i.name.toUpperCase().replace(/-/g, '_')}="$2"
|
||||||
|
shift
|
||||||
|
;;`
|
||||||
|
).join('\n')}
|
||||||
|
*)
|
||||||
|
echo "未知参数: $1" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
|
||||||
|
# 参数验证
|
||||||
|
${scriptConfig.inputs.filter(i => i.required).map(i =>
|
||||||
|
`if [[ -z "$${i.name.toUpperCase().replace(/-/g, '_')}" ]]; then
|
||||||
|
echo "错误: --${i.name} 是必需参数" >&2
|
||||||
|
exit 1
|
||||||
|
fi`
|
||||||
|
).join('\n\n')}
|
||||||
|
|
||||||
|
# TODO: 实现处理逻辑
|
||||||
|
|
||||||
|
# 输出结果
|
||||||
|
echo "{${scriptConfig.outputs.map(o =>
|
||||||
|
`\\"${o.name}\\": \\"\\$${o.name.toUpperCase().replace(/-/g, '_')}\\"`
|
||||||
|
).join(', ')}}"
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
```
|
||||||
198
.claude/skills/skill-generator/templates/script-python.md
Normal file
198
.claude/skills/skill-generator/templates/script-python.md
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
# Python Script Template
|
||||||
|
|
||||||
|
Python 脚本模板,用于生成技能中的确定性脚本。
|
||||||
|
|
||||||
|
## 模板代码
|
||||||
|
|
||||||
|
```python
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
{{script_description}}
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
# 1. 定义参数
|
||||||
|
parser = argparse.ArgumentParser(description='{{script_description}}')
|
||||||
|
parser.add_argument('--input-path', type=str, required=True,
|
||||||
|
help='输入文件路径')
|
||||||
|
parser.add_argument('--output-dir', type=str, required=True,
|
||||||
|
help='输出目录(由调用方指定)')
|
||||||
|
# 添加更多参数...
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
# 2. 验证输入
|
||||||
|
input_path = Path(args.input_path)
|
||||||
|
if not input_path.exists():
|
||||||
|
print(f"错误: 输入文件不存在: {input_path}", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
output_dir = Path(args.output_dir)
|
||||||
|
output_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
# 3. 执行核心逻辑
|
||||||
|
try:
|
||||||
|
result = process(input_path, output_dir)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"错误: {e}", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# 4. 输出 JSON 结果
|
||||||
|
print(json.dumps(result))
|
||||||
|
|
||||||
|
|
||||||
|
def process(input_path: Path, output_dir: Path) -> dict:
|
||||||
|
"""
|
||||||
|
核心处理逻辑
|
||||||
|
|
||||||
|
Args:
|
||||||
|
input_path: 输入文件路径
|
||||||
|
output_dir: 输出目录
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: 包含输出结果的字典
|
||||||
|
"""
|
||||||
|
# TODO: 实现处理逻辑
|
||||||
|
|
||||||
|
output_file = output_dir / 'result.json'
|
||||||
|
|
||||||
|
# 示例:读取并处理数据
|
||||||
|
with open(input_path, 'r', encoding='utf-8') as f:
|
||||||
|
data = json.load(f)
|
||||||
|
|
||||||
|
# 处理数据...
|
||||||
|
processed_count = len(data) if isinstance(data, list) else 1
|
||||||
|
|
||||||
|
# 写入输出
|
||||||
|
with open(output_file, 'w', encoding='utf-8') as f:
|
||||||
|
json.dump(data, f, indent=2, ensure_ascii=False)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'output_file': str(output_file),
|
||||||
|
'items_processed': processed_count,
|
||||||
|
'status': 'success'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
|
```
|
||||||
|
|
||||||
|
## 变量说明
|
||||||
|
|
||||||
|
| 变量 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| `{{script_description}}` | 脚本功能描述 |
|
||||||
|
|
||||||
|
## 使用规范
|
||||||
|
|
||||||
|
### 输入参数
|
||||||
|
|
||||||
|
- 使用 `argparse` 定义参数
|
||||||
|
- 参数名使用 kebab-case:`--input-path`
|
||||||
|
- 必需参数设置 `required=True`
|
||||||
|
- 可选参数提供 `default` 值
|
||||||
|
|
||||||
|
### 输出格式
|
||||||
|
|
||||||
|
- 最后一行打印单行 JSON
|
||||||
|
- 包含所有输出文件路径和关键数据
|
||||||
|
- 错误信息输出到 stderr
|
||||||
|
|
||||||
|
### 错误处理
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 验证错误 - 直接退出
|
||||||
|
if not valid:
|
||||||
|
print("错误信息", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# 运行时错误 - 捕获并退出
|
||||||
|
try:
|
||||||
|
result = process()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"错误: {e}", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 常用模式
|
||||||
|
|
||||||
|
### 文件处理
|
||||||
|
|
||||||
|
```python
|
||||||
|
def process_files(input_dir: Path, pattern: str = '*.json') -> list:
|
||||||
|
results = []
|
||||||
|
for file in input_dir.glob(pattern):
|
||||||
|
with open(file, 'r') as f:
|
||||||
|
data = json.load(f)
|
||||||
|
results.append({'file': str(file), 'data': data})
|
||||||
|
return results
|
||||||
|
```
|
||||||
|
|
||||||
|
### 数据转换
|
||||||
|
|
||||||
|
```python
|
||||||
|
def transform_data(data: dict) -> dict:
|
||||||
|
return {
|
||||||
|
'id': data.get('id'),
|
||||||
|
'name': data.get('name', '').strip(),
|
||||||
|
'timestamp': datetime.now().isoformat()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 调用外部命令
|
||||||
|
|
||||||
|
```python
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
def run_command(cmd: list) -> str:
|
||||||
|
result = subprocess.run(cmd, capture_output=True, text=True)
|
||||||
|
if result.returncode != 0:
|
||||||
|
raise RuntimeError(result.stderr)
|
||||||
|
return result.stdout
|
||||||
|
```
|
||||||
|
|
||||||
|
## 生成函数
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
function generatePythonScript(scriptConfig) {
|
||||||
|
return `#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
${scriptConfig.description}
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(description='${scriptConfig.description}')
|
||||||
|
${scriptConfig.inputs.map(i =>
|
||||||
|
` parser.add_argument('--${i.name}', type=${i.type || 'str'}, ${i.required ? 'required=True' : `default='${i.default}'`},
|
||||||
|
help='${i.description}')`
|
||||||
|
).join('\n')}
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
# TODO: 实现处理逻辑
|
||||||
|
result = {
|
||||||
|
${scriptConfig.outputs.map(o =>
|
||||||
|
` '${o.name}': None # ${o.description}`
|
||||||
|
).join(',\n')}
|
||||||
|
}
|
||||||
|
|
||||||
|
print(json.dumps(result))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -18,6 +18,13 @@
|
|||||||
- 依赖: `{{input_dependency}}`
|
- 依赖: `{{input_dependency}}`
|
||||||
- 配置: `{workDir}/skill-config.json`
|
- 配置: `{workDir}/skill-config.json`
|
||||||
|
|
||||||
|
## Scripts
|
||||||
|
|
||||||
|
\`\`\`yaml
|
||||||
|
# 声明本阶段使用的脚本(可选)
|
||||||
|
# - script-id # 对应 scripts/script-id.py 或 .sh
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
## Execution Steps
|
## Execution Steps
|
||||||
|
|
||||||
### Step 1: {{step_1_name}}
|
### Step 1: {{step_1_name}}
|
||||||
@@ -32,10 +39,13 @@
|
|||||||
{{step_2_code}}
|
{{step_2_code}}
|
||||||
\`\`\`
|
\`\`\`
|
||||||
|
|
||||||
### Step 3: {{step_3_name}}
|
### Step 3: 执行脚本(可选)
|
||||||
|
|
||||||
\`\`\`javascript
|
\`\`\`javascript
|
||||||
{{step_3_code}}
|
// 调用脚本示例
|
||||||
|
// const result = await ExecuteScript('script-id', { input_path: `${workDir}/data.json` });
|
||||||
|
// if (!result.success) throw new Error(result.stderr);
|
||||||
|
// console.log(result.outputs.output_file);
|
||||||
\`\`\`
|
\`\`\`
|
||||||
|
|
||||||
## Output
|
## Output
|
||||||
@@ -68,6 +78,44 @@
|
|||||||
| `{{quality_checklist}}` | 质量检查项 |
|
| `{{quality_checklist}}` | 质量检查项 |
|
||||||
| `{{next_phase_link}}` | 下一阶段链接 |
|
| `{{next_phase_link}}` | 下一阶段链接 |
|
||||||
|
|
||||||
|
## 脚本调用说明
|
||||||
|
|
||||||
|
### 目录约定
|
||||||
|
|
||||||
|
```
|
||||||
|
scripts/
|
||||||
|
├── process-data.py # id: process-data, runtime: python
|
||||||
|
├── validate.sh # id: validate, runtime: bash
|
||||||
|
└── transform.js # id: transform, runtime: node
|
||||||
|
```
|
||||||
|
|
||||||
|
- **命名即 ID**:文件名(不含扩展名)= 脚本 ID
|
||||||
|
- **扩展名即运行时**:`.py` → python, `.sh` → bash, `.js` → node
|
||||||
|
|
||||||
|
### 调用语法
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// 一行调用
|
||||||
|
const result = await ExecuteScript('script-id', { key: value });
|
||||||
|
|
||||||
|
// 检查结果
|
||||||
|
if (!result.success) throw new Error(result.stderr);
|
||||||
|
|
||||||
|
// 获取输出
|
||||||
|
const { output_file } = result.outputs;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 返回格式
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface ScriptResult {
|
||||||
|
success: boolean; // exit code === 0
|
||||||
|
stdout: string; // 标准输出
|
||||||
|
stderr: string; // 标准错误
|
||||||
|
outputs: object; // 从 stdout 解析的 JSON 输出
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Phase 类型模板
|
## Phase 类型模板
|
||||||
|
|
||||||
### 1. 收集型 Phase (Collection)
|
### 1. 收集型 Phase (Collection)
|
||||||
|
|||||||
Reference in New Issue
Block a user