@vima_tech/flywheel 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/commands/flywheel.md +42 -0
- package/.claude/commands/skill.md +36 -0
- package/.claude/settings.local.json +13 -0
- package/.distill-needed/.gitkeep +0 -0
- package/CLAUDE.md +178 -0
- package/README.md +163 -0
- package/agents.md +59 -0
- package/bin/flywheel.js +95 -0
- package/docs/Flywheel_Poster.html +1059 -0
- package/docs/Flywheel_Poster.png +0 -0
- package/episodic-logs/.gitkeep +0 -0
- package/install.sh +194 -0
- package/memory/industry/.gitkeep +0 -0
- package/package.json +28 -0
- package/projects/.gitkeep +0 -0
- package/scripts/auto-distill.sh +154 -0
- package/scripts/bridge-to-coder.sh +139 -0
- package/scripts/feedback-hook.sh +157 -0
- package/scripts/flywheel-install.sh +103 -0
- package/skills/_kernel/distillation.md +254 -0
- package/skills/_template/domain.md +93 -0
- package/skills/_template/feedback-questions.sh +9 -0
- package/skills/_template/skill.yaml +21 -0
- package/skills/req-mining/artifacts.md +185 -0
- package/skills/req-mining/domain.md +243 -0
- package/skills/req-mining/feedback-questions.sh +9 -0
- package/skills/req-mining/industry/erp.md +108 -0
- package/skills/req-mining/industry/retail.md +24 -0
- package/skills/req-mining/memory/failure-patterns.md +84 -0
- package/skills/req-mining/skill.yaml +41 -0
- package/templates/state.json +90 -0
|
Binary file
|
|
File without changes
|
package/install.sh
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Flywheel — 自成长 AI Agent 飞轮框架
|
|
3
|
+
#
|
|
4
|
+
# 一键安装(GitHub 下载方式):
|
|
5
|
+
# curl -sSL https://raw.githubusercontent.com/renmengkai/flywheel/main/install.sh | bash
|
|
6
|
+
#
|
|
7
|
+
# 一键安装(npm 方式):
|
|
8
|
+
# npm install -g @vima_tech/flywheel
|
|
9
|
+
#
|
|
10
|
+
# 安装并指定 skill(仅限 GitHub 方式):
|
|
11
|
+
# curl -sSL https://raw.githubusercontent.com/renmengkai/flywheel/main/install.sh | bash -s req-mining
|
|
12
|
+
|
|
13
|
+
set -e
|
|
14
|
+
|
|
15
|
+
REPO="renmengkai/flywheel"
|
|
16
|
+
BRANCH="main"
|
|
17
|
+
BASE_URL="https://raw.githubusercontent.com/$REPO/$BRANCH"
|
|
18
|
+
INSTALL_DIR="${FLYWHEEL_DIR:-.}"
|
|
19
|
+
SKILLS=()
|
|
20
|
+
|
|
21
|
+
# ── 解析参数 ───────────────────────────────────────────────────
|
|
22
|
+
for arg in "$@"; do
|
|
23
|
+
case "$arg" in
|
|
24
|
+
--list)
|
|
25
|
+
echo "可用 skill 包(通过 PR 添加更多):"
|
|
26
|
+
echo " req-mining 需求挖掘与落地(内置)"
|
|
27
|
+
exit 0
|
|
28
|
+
;;
|
|
29
|
+
--help|-h)
|
|
30
|
+
echo "用法:curl -sSL .../install.sh | bash -s [skill-name...]"
|
|
31
|
+
echo " bash install.sh [--list] [skill-name...]"
|
|
32
|
+
exit 0
|
|
33
|
+
;;
|
|
34
|
+
*) SKILLS+=("$arg") ;;
|
|
35
|
+
esac
|
|
36
|
+
done
|
|
37
|
+
|
|
38
|
+
[ ${#SKILLS[@]} -eq 0 ] && SKILLS=("req-mining")
|
|
39
|
+
|
|
40
|
+
# ── 环境检查 ───────────────────────────────────────────────────
|
|
41
|
+
check_deps() {
|
|
42
|
+
local missing=()
|
|
43
|
+
command -v curl &>/dev/null || missing+=("curl")
|
|
44
|
+
command -v git &>/dev/null || missing+=("git")
|
|
45
|
+
command -v python3 &>/dev/null || missing+=("python3")
|
|
46
|
+
if [ ${#missing[@]} -gt 0 ]; then
|
|
47
|
+
echo "❌ 缺少依赖:${missing[*]}"
|
|
48
|
+
echo " 请先安装后重试"
|
|
49
|
+
exit 1
|
|
50
|
+
fi
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
# ── 文件下载工具 ───────────────────────────────────────────────
|
|
54
|
+
download() {
|
|
55
|
+
local src="$1" # 相对于仓库根的路径
|
|
56
|
+
local dst="$2" # 目标路径(相对于 INSTALL_DIR)
|
|
57
|
+
local full_dst="$INSTALL_DIR/$dst"
|
|
58
|
+
mkdir -p "$(dirname "$full_dst")"
|
|
59
|
+
curl -fsSL "$BASE_URL/$src" -o "$full_dst"
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
# ── 下载内核文件 ───────────────────────────────────────────────
|
|
63
|
+
install_kernel() {
|
|
64
|
+
echo "📦 安装 Flywheel 内核..."
|
|
65
|
+
|
|
66
|
+
local kernel_files=(
|
|
67
|
+
"CLAUDE.md"
|
|
68
|
+
".gitignore"
|
|
69
|
+
".claude/commands/flywheel.md"
|
|
70
|
+
".claude/commands/skill.md"
|
|
71
|
+
"scripts/auto-distill.sh"
|
|
72
|
+
"scripts/feedback-hook.sh"
|
|
73
|
+
"scripts/bridge-to-coder.sh"
|
|
74
|
+
"scripts/flywheel-install.sh"
|
|
75
|
+
"templates/state.json"
|
|
76
|
+
"skills/_kernel/distillation.md"
|
|
77
|
+
"skills/_template/skill.yaml"
|
|
78
|
+
"skills/_template/domain.md"
|
|
79
|
+
"skills/_template/feedback-questions.sh"
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
for f in "${kernel_files[@]}"; do
|
|
83
|
+
download "$f" "$f"
|
|
84
|
+
echo " ✓ $f"
|
|
85
|
+
done
|
|
86
|
+
|
|
87
|
+
# 初始化运行时目录(gitignore 内容)
|
|
88
|
+
mkdir -p "$INSTALL_DIR"/{.distill-needed,episodic-logs,projects,memory}
|
|
89
|
+
touch "$INSTALL_DIR/.distill-needed/.gitkeep"
|
|
90
|
+
touch "$INSTALL_DIR/episodic-logs/.gitkeep"
|
|
91
|
+
touch "$INSTALL_DIR/projects/.gitkeep"
|
|
92
|
+
touch "$INSTALL_DIR/memory/.gitkeep"
|
|
93
|
+
|
|
94
|
+
chmod +x "$INSTALL_DIR"/scripts/*.sh
|
|
95
|
+
echo " ✓ 内核安装完成"
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
# ── 下载 skill 包 ──────────────────────────────────────────────
|
|
99
|
+
install_skill() {
|
|
100
|
+
local skill="$1"
|
|
101
|
+
echo ""
|
|
102
|
+
echo "🎯 安装 skill:$skill"
|
|
103
|
+
|
|
104
|
+
# 读取 skill.yaml 获取文件列表
|
|
105
|
+
local yaml_url="$BASE_URL/skills/$skill/skill.yaml"
|
|
106
|
+
if ! curl -fsSL "$yaml_url" -o /dev/null 2>/dev/null; then
|
|
107
|
+
echo " ❌ skill '$skill' 不存在,可用列表:bash install.sh --list"
|
|
108
|
+
return 1
|
|
109
|
+
fi
|
|
110
|
+
|
|
111
|
+
# 下载核心文件
|
|
112
|
+
local skill_files=(
|
|
113
|
+
"skills/$skill/skill.yaml"
|
|
114
|
+
"skills/$skill/domain.md"
|
|
115
|
+
"skills/$skill/feedback-questions.sh"
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
# 可选文件(不存在则跳过)
|
|
119
|
+
local optional_files=(
|
|
120
|
+
"skills/$skill/artifacts.md"
|
|
121
|
+
"skills/$skill/bridge.sh"
|
|
122
|
+
"skills/$skill/memory/failure-patterns.md"
|
|
123
|
+
"skills/$skill/industry/erp.md"
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
for f in "${skill_files[@]}"; do
|
|
127
|
+
download "$f" "$f" && echo " ✓ $f" || echo " ⚠ 跳过 $f"
|
|
128
|
+
done
|
|
129
|
+
|
|
130
|
+
for f in "${optional_files[@]}"; do
|
|
131
|
+
curl -fsSL "$BASE_URL/$f" -o "$INSTALL_DIR/$f" 2>/dev/null \
|
|
132
|
+
&& echo " ✓ $f" \
|
|
133
|
+
|| true # 可选文件不报错
|
|
134
|
+
done
|
|
135
|
+
|
|
136
|
+
echo " ✓ skill '$skill' 安装完成"
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
# ── 更新 .gitignore ────────────────────────────────────────────
|
|
140
|
+
setup_gitignore() {
|
|
141
|
+
local marker="# flywheel-managed"
|
|
142
|
+
local gitignore="$INSTALL_DIR/.gitignore"
|
|
143
|
+
|
|
144
|
+
# 如果已经有 flywheel 标记则跳过
|
|
145
|
+
grep -q "$marker" "$gitignore" 2>/dev/null && return
|
|
146
|
+
|
|
147
|
+
cat >> "$gitignore" << 'EOF'
|
|
148
|
+
|
|
149
|
+
# flywheel-managed
|
|
150
|
+
projects/*/
|
|
151
|
+
episodic-logs/
|
|
152
|
+
.distill-needed/*
|
|
153
|
+
!.distill-needed/.gitkeep
|
|
154
|
+
memory/.gitkeep
|
|
155
|
+
*.log
|
|
156
|
+
EOF
|
|
157
|
+
echo " ✓ .gitignore 已更新"
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
# ── 主流程 ─────────────────────────────────────────────────────
|
|
161
|
+
main() {
|
|
162
|
+
echo ""
|
|
163
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
164
|
+
echo " Flywheel 安装器 v1.0"
|
|
165
|
+
echo " 自成长 AI Agent 飞轮框架"
|
|
166
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
167
|
+
echo ""
|
|
168
|
+
echo "安装目录:$(realpath "$INSTALL_DIR")"
|
|
169
|
+
echo "Skills:${SKILLS[*]}"
|
|
170
|
+
echo ""
|
|
171
|
+
|
|
172
|
+
check_deps
|
|
173
|
+
install_kernel
|
|
174
|
+
setup_gitignore
|
|
175
|
+
|
|
176
|
+
for skill in "${SKILLS[@]}"; do
|
|
177
|
+
install_skill "$skill"
|
|
178
|
+
done
|
|
179
|
+
|
|
180
|
+
echo ""
|
|
181
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
182
|
+
echo " ✅ 安装完成!"
|
|
183
|
+
echo ""
|
|
184
|
+
echo " 启动方式:"
|
|
185
|
+
echo " cd $(realpath "$INSTALL_DIR") && claude"
|
|
186
|
+
echo " 输入 /flywheel 或 sfw 激活飞轮"
|
|
187
|
+
echo ""
|
|
188
|
+
echo " npm 安装用户可运行:flywheel list"
|
|
189
|
+
echo " 查看可用 skill:bash install.sh --list"
|
|
190
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
191
|
+
echo ""
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
main
|
|
File without changes
|
package/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@vima_tech/flywheel",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "自成长 AI Agent 飞轮框架 — 让任何需要积累迭代的 AI 工作流都能自我进化",
|
|
5
|
+
"main": "bin/flywheel.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"flywheel": "./bin/flywheel.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"postinstall": "chmod +x bin/flywheel.js"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"ai",
|
|
14
|
+
"agent",
|
|
15
|
+
"claude",
|
|
16
|
+
"flywheel",
|
|
17
|
+
"self-evolution",
|
|
18
|
+
"skill"
|
|
19
|
+
],
|
|
20
|
+
"author": "renmengkai",
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"engines": {
|
|
23
|
+
"node": ">=16.0.0"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@playwright/test": "^1.60.0"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# scripts/auto-distill.sh
|
|
3
|
+
# 自动检查 episodic-logs 和 .distill-needed/ 标记,触发蒸馏
|
|
4
|
+
#
|
|
5
|
+
# 用法:
|
|
6
|
+
# ./scripts/auto-distill.sh # 检查所有项目
|
|
7
|
+
# ./scripts/auto-distill.sh <project_id> # 检查指定项目
|
|
8
|
+
# ./scripts/auto-distill.sh --cron # Cron 模式(静默,仅在需要时输出)
|
|
9
|
+
#
|
|
10
|
+
# 设置为 Cron 自动运行(每天早上 9 点检查):
|
|
11
|
+
# 0 9 * * * /path/to/req-miner/scripts/auto-distill.sh --cron
|
|
12
|
+
|
|
13
|
+
set -e
|
|
14
|
+
|
|
15
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
16
|
+
REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
17
|
+
LOGS_DIR="$REPO_DIR/episodic-logs"
|
|
18
|
+
DISTILL_NEEDED_DIR="$REPO_DIR/.distill-needed"
|
|
19
|
+
THRESHOLD=${DISTILL_THRESHOLD:-3}
|
|
20
|
+
CRON_MODE=false
|
|
21
|
+
TARGET_PROJECT=""
|
|
22
|
+
|
|
23
|
+
for arg in "$@"; do
|
|
24
|
+
case "$arg" in
|
|
25
|
+
--cron) CRON_MODE=true ;;
|
|
26
|
+
*) TARGET_PROJECT="$arg" ;;
|
|
27
|
+
esac
|
|
28
|
+
done
|
|
29
|
+
|
|
30
|
+
check_project() {
|
|
31
|
+
local project_id="$1"
|
|
32
|
+
|
|
33
|
+
# 优先检查标记文件(由 feedback-hook.sh --auto 写入)
|
|
34
|
+
if [ -f "$DISTILL_NEEDED_DIR/$project_id" ]; then
|
|
35
|
+
return 0
|
|
36
|
+
fi
|
|
37
|
+
|
|
38
|
+
# 回退:扫描日志文件
|
|
39
|
+
local log_file="$LOGS_DIR/${project_id}.jsonl"
|
|
40
|
+
[ -f "$log_file" ] || return 1
|
|
41
|
+
|
|
42
|
+
local pending
|
|
43
|
+
pending=$(python3 - << PYEOF
|
|
44
|
+
import json
|
|
45
|
+
try:
|
|
46
|
+
with open("$log_file") as f:
|
|
47
|
+
events = [json.loads(l) for l in f if l.strip()]
|
|
48
|
+
pending = sum(1 for e in events
|
|
49
|
+
if e.get("distillation_candidate") and not e.get("distilled")
|
|
50
|
+
and e.get("quality_score", 0) >= 0.6)
|
|
51
|
+
print(pending)
|
|
52
|
+
except:
|
|
53
|
+
print(0)
|
|
54
|
+
PYEOF
|
|
55
|
+
)
|
|
56
|
+
[ "$pending" -ge "$THRESHOLD" ]
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
report_status() {
|
|
60
|
+
local project_id="$1"
|
|
61
|
+
local log_file="$LOGS_DIR/${project_id}.jsonl"
|
|
62
|
+
local has_marker=""
|
|
63
|
+
[ -f "$DISTILL_NEEDED_DIR/$project_id" ] && has_marker=" [标记文件]"
|
|
64
|
+
|
|
65
|
+
python3 - << PYEOF
|
|
66
|
+
import json
|
|
67
|
+
try:
|
|
68
|
+
with open("$log_file") as f:
|
|
69
|
+
events = [json.loads(l) for l in f if l.strip()]
|
|
70
|
+
total = len(events)
|
|
71
|
+
pending = sum(1 for e in events
|
|
72
|
+
if e.get("distillation_candidate") and not e.get("distilled")
|
|
73
|
+
and e.get("quality_score", 0) >= 0.6)
|
|
74
|
+
distilled = sum(1 for e in events if e.get("distilled"))
|
|
75
|
+
print(f" 项目 $project_id$has_marker: 总事件={total}, 待蒸馏={pending}, 已蒸馏={distilled}")
|
|
76
|
+
except:
|
|
77
|
+
print(" 项目 $project_id$has_marker: 无日志文件(由标记文件触发)")
|
|
78
|
+
PYEOF
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
# ── 收集待检查项目 ─────────────────────────────────────────────
|
|
82
|
+
declare -A seen_projects
|
|
83
|
+
|
|
84
|
+
if [ -n "$TARGET_PROJECT" ]; then
|
|
85
|
+
seen_projects["$TARGET_PROJECT"]=1
|
|
86
|
+
else
|
|
87
|
+
# 从标记文件收集
|
|
88
|
+
if [ -d "$DISTILL_NEEDED_DIR" ]; then
|
|
89
|
+
for marker in "$DISTILL_NEEDED_DIR"/*; do
|
|
90
|
+
[ -f "$marker" ] || continue
|
|
91
|
+
project_id=$(basename "$marker")
|
|
92
|
+
[ "$project_id" = ".gitkeep" ] && continue
|
|
93
|
+
seen_projects["$project_id"]=1
|
|
94
|
+
done
|
|
95
|
+
fi
|
|
96
|
+
# 从日志文件收集
|
|
97
|
+
for log_file in "$LOGS_DIR"/*.jsonl; do
|
|
98
|
+
[ -f "$log_file" ] || continue
|
|
99
|
+
project_id=$(basename "$log_file" .jsonl)
|
|
100
|
+
[ "$project_id" = ".gitkeep" ] && continue
|
|
101
|
+
seen_projects["$project_id"]=1
|
|
102
|
+
done
|
|
103
|
+
fi
|
|
104
|
+
|
|
105
|
+
projects=("${!seen_projects[@]}")
|
|
106
|
+
|
|
107
|
+
if [ ${#projects[@]} -eq 0 ]; then
|
|
108
|
+
$CRON_MODE || echo "📭 没有找到任何项目。"
|
|
109
|
+
exit 0
|
|
110
|
+
fi
|
|
111
|
+
|
|
112
|
+
$CRON_MODE || echo "━━━ 蒸馏检查器 ━━━"
|
|
113
|
+
$CRON_MODE || echo "检查阈值:$THRESHOLD 条待蒸馏事件"
|
|
114
|
+
$CRON_MODE || echo ""
|
|
115
|
+
|
|
116
|
+
needs_distill=()
|
|
117
|
+
for project_id in "${projects[@]}"; do
|
|
118
|
+
if check_project "$project_id"; then
|
|
119
|
+
needs_distill+=("$project_id")
|
|
120
|
+
$CRON_MODE || report_status "$project_id"
|
|
121
|
+
$CRON_MODE || echo " ⚡ 需要蒸馏"
|
|
122
|
+
else
|
|
123
|
+
$CRON_MODE || report_status "$project_id"
|
|
124
|
+
fi
|
|
125
|
+
done
|
|
126
|
+
|
|
127
|
+
if [ ${#needs_distill[@]} -eq 0 ]; then
|
|
128
|
+
$CRON_MODE || echo "✅ 所有项目暂无需要蒸馏(待蒸馏事件均 < $THRESHOLD 条)"
|
|
129
|
+
exit 0
|
|
130
|
+
fi
|
|
131
|
+
|
|
132
|
+
# ── 有需要蒸馏的项目 ──────────────────────────────────────────
|
|
133
|
+
echo ""
|
|
134
|
+
echo "━━━ 蒸馏建议 ━━━"
|
|
135
|
+
echo "以下项目已积累足够反馈,建议进行蒸馏:"
|
|
136
|
+
for p in "${needs_distill[@]}"; do
|
|
137
|
+
echo " • $p"
|
|
138
|
+
done
|
|
139
|
+
echo ""
|
|
140
|
+
echo "操作方式:"
|
|
141
|
+
echo " 1. 打开 req-miner 会话(cd $REPO_DIR && claude)"
|
|
142
|
+
echo " 2. 系统将自动检测待蒸馏标记并启动蒸馏"
|
|
143
|
+
echo ""
|
|
144
|
+
|
|
145
|
+
# Cron 模式下发送桌面通知
|
|
146
|
+
if $CRON_MODE; then
|
|
147
|
+
if command -v notify-send &>/dev/null; then
|
|
148
|
+
notify-send "req-miner 蒸馏提醒" "项目 ${needs_distill[*]} 已积累足够反馈,建议蒸馏"
|
|
149
|
+
elif command -v osascript &>/dev/null; then
|
|
150
|
+
osascript -e "display notification \"项目 ${needs_distill[*]} 已积累足够反馈\" with title \"req-miner 蒸馏提醒\""
|
|
151
|
+
fi
|
|
152
|
+
fi
|
|
153
|
+
|
|
154
|
+
exit 0
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# scripts/bridge-to-coder.sh
|
|
3
|
+
# 将 req-miner 产物桥接到 agent 编程工具,工具退出后自动收集反馈
|
|
4
|
+
#
|
|
5
|
+
# 用法:./scripts/bridge-to-coder.sh <project_id> [tool] [impl_dir]
|
|
6
|
+
# project_id : req-miner 项目 ID(如 proj_20260515_01)
|
|
7
|
+
# tool : claude(默认)| opencode | trae | codex
|
|
8
|
+
# impl_dir : 实现目录(默认 ../{project_id}-impl)
|
|
9
|
+
#
|
|
10
|
+
# 前置条件:projects/{project_id}/artifacts/ai-execution-doc.md 已生成
|
|
11
|
+
|
|
12
|
+
set -e
|
|
13
|
+
|
|
14
|
+
PROJECT_ID="${1:?用法: $0 <project_id> [tool] [impl_dir]}"
|
|
15
|
+
TOOL="${2:-claude}"
|
|
16
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
17
|
+
REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
18
|
+
ARTIFACTS_DIR="$REPO_DIR/projects/$PROJECT_ID/artifacts"
|
|
19
|
+
AI_DOC="$ARTIFACTS_DIR/ai-execution-doc.md"
|
|
20
|
+
STATE_FILE="$REPO_DIR/projects/$PROJECT_ID/state.json"
|
|
21
|
+
IMPL_DIR="${3:-$(dirname "$REPO_DIR")/${PROJECT_ID}-impl}"
|
|
22
|
+
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
23
|
+
LOG_FILE="$REPO_DIR/episodic-logs/${PROJECT_ID}.jsonl"
|
|
24
|
+
DISTILL_NEEDED_DIR="$REPO_DIR/.distill-needed"
|
|
25
|
+
|
|
26
|
+
# ── 检查前置条件 ───────────────────────────────────────────────
|
|
27
|
+
if [ ! -f "$AI_DOC" ]; then
|
|
28
|
+
echo "❌ 错误:AI 执行文档不存在:$AI_DOC"
|
|
29
|
+
echo " 请先完成 req-miner 分析并生成产物。"
|
|
30
|
+
exit 1
|
|
31
|
+
fi
|
|
32
|
+
|
|
33
|
+
# ── 创建实现工作区 ─────────────────────────────────────────────
|
|
34
|
+
echo "📁 创建实现目录:$IMPL_DIR"
|
|
35
|
+
mkdir -p "$IMPL_DIR"
|
|
36
|
+
|
|
37
|
+
# ── 注入 AI 执行文档为编程工具上下文 ──────────────────────────
|
|
38
|
+
PROJECT_NAME=$(python3 -c "import json,sys; d=json.load(open('$STATE_FILE')); print(d.get('project_name','未命名项目'))" 2>/dev/null || echo "未命名项目")
|
|
39
|
+
|
|
40
|
+
cat > "$IMPL_DIR/CLAUDE.md" << HEREDOC
|
|
41
|
+
# $PROJECT_NAME — AI 执行规格
|
|
42
|
+
|
|
43
|
+
> 本文档由 req-miner 自动生成,是唯一权威需求来源。
|
|
44
|
+
> **遇到本文档未说明的情况,必须停止并记录,不得自行决定。**
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
$(cat "$AI_DOC")
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## 反馈协议(实现完成后必须执行)
|
|
53
|
+
|
|
54
|
+
实现过程中如遇到以下情况,请记录到 \`.flywheel-feedback.json\`:
|
|
55
|
+
- 需求描述不清晰或有歧义
|
|
56
|
+
- 遇到文档未覆盖的技术决策
|
|
57
|
+
- 发现需求假设与现实不符
|
|
58
|
+
- 验收标准无法被测试
|
|
59
|
+
|
|
60
|
+
记录格式:
|
|
61
|
+
\`\`\`json
|
|
62
|
+
{
|
|
63
|
+
"unclear_specs": ["具体描述哪条规格不清晰"],
|
|
64
|
+
"uncovered_decisions": ["遇到的未覆盖决策"],
|
|
65
|
+
"wrong_assumptions": ["发现的错误假设"],
|
|
66
|
+
"untestable_criteria": ["无法测试的验收标准"],
|
|
67
|
+
"general_feedback": "总体反馈和质量评分(1-10)"
|
|
68
|
+
}
|
|
69
|
+
\`\`\`
|
|
70
|
+
|
|
71
|
+
**重要:** 实现完成后请写入上述文件。退出编程工具后,飞轮系统会自动读取该文件收集反馈,无需手动运行任何命令。
|
|
72
|
+
HEREDOC
|
|
73
|
+
|
|
74
|
+
echo "✅ CLAUDE.md 已生成"
|
|
75
|
+
|
|
76
|
+
# ── 写入桥接事件日志 ──────────────────────────────────────────
|
|
77
|
+
mkdir -p "$(dirname "$LOG_FILE")"
|
|
78
|
+
EVENT_ID="evt_bridge_$(date +%s)"
|
|
79
|
+
echo "{\"event_id\":\"$EVENT_ID\",\"type\":\"bridge_to_coder\",\"project_id\":\"$PROJECT_ID\",\"tool\":\"$TOOL\",\"impl_dir\":\"$IMPL_DIR\",\"timestamp\":\"$TIMESTAMP\"}" >> "$LOG_FILE"
|
|
80
|
+
|
|
81
|
+
# ── 启动编程工具 ───────────────────────────────────────────────
|
|
82
|
+
echo ""
|
|
83
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
84
|
+
echo " 项目:$PROJECT_NAME ($PROJECT_ID)"
|
|
85
|
+
echo " 工具:$TOOL"
|
|
86
|
+
echo " 目录:$IMPL_DIR"
|
|
87
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
88
|
+
echo ""
|
|
89
|
+
|
|
90
|
+
cd "$IMPL_DIR"
|
|
91
|
+
|
|
92
|
+
case "$TOOL" in
|
|
93
|
+
claude)
|
|
94
|
+
echo "🚀 启动 Claude Code..."
|
|
95
|
+
claude
|
|
96
|
+
;;
|
|
97
|
+
opencode)
|
|
98
|
+
echo "🚀 启动 opencode..."
|
|
99
|
+
opencode
|
|
100
|
+
;;
|
|
101
|
+
trae)
|
|
102
|
+
echo "🚀 启动 trae..."
|
|
103
|
+
trae .
|
|
104
|
+
;;
|
|
105
|
+
codex)
|
|
106
|
+
echo "🚀 启动 codex..."
|
|
107
|
+
codex
|
|
108
|
+
;;
|
|
109
|
+
cursor)
|
|
110
|
+
echo "🚀 启动 Cursor..."
|
|
111
|
+
cursor .
|
|
112
|
+
;;
|
|
113
|
+
*)
|
|
114
|
+
echo "⚠️ 未知工具:$TOOL,已准备好实现目录。"
|
|
115
|
+
echo " 手动进入:cd $IMPL_DIR"
|
|
116
|
+
;;
|
|
117
|
+
esac
|
|
118
|
+
|
|
119
|
+
# ── 工具退出后:自动收集反馈 ──────────────────────────────────
|
|
120
|
+
echo ""
|
|
121
|
+
echo "━━━ 编程工具已退出,自动收集反馈... ━━━"
|
|
122
|
+
cd "$REPO_DIR"
|
|
123
|
+
"$SCRIPT_DIR/feedback-hook.sh" "$PROJECT_ID" "$IMPL_DIR" --auto
|
|
124
|
+
|
|
125
|
+
# ── 检查是否需要蒸馏,自动打开蒸馏会话 ───────────────────────
|
|
126
|
+
DISTILL_MARKER="$DISTILL_NEEDED_DIR/$PROJECT_ID"
|
|
127
|
+
if [ -f "$DISTILL_MARKER" ]; then
|
|
128
|
+
echo ""
|
|
129
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
130
|
+
echo " ⚡ 蒸馏条件已满足,自动启动蒸馏会话"
|
|
131
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
132
|
+
echo ""
|
|
133
|
+
echo "即将打开 req-miner 蒸馏会话..."
|
|
134
|
+
echo "AI 将展示所有变更预览,你只需确认一次 (y/n)"
|
|
135
|
+
echo ""
|
|
136
|
+
sleep 1
|
|
137
|
+
cd "$REPO_DIR"
|
|
138
|
+
exec claude
|
|
139
|
+
fi
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# scripts/feedback-hook.sh
|
|
3
|
+
# 从编程工具实现会话收集反馈,写入 episodic-logs 以驱动蒸馏飞轮
|
|
4
|
+
#
|
|
5
|
+
# 用法:./scripts/feedback-hook.sh <project_id> [impl_dir] [--auto]
|
|
6
|
+
# project_id : 飞轮项目 ID
|
|
7
|
+
# impl_dir : 实现目录(用于读取 .flywheel-feedback.json)
|
|
8
|
+
# --auto : 非交互模式,仅读取反馈文件,跳过问答
|
|
9
|
+
#
|
|
10
|
+
# 反馈问题可由 skill 定制:
|
|
11
|
+
# 在 skills/{skill-name}/feedback-questions.sh 中定义
|
|
12
|
+
# QUESTION_1 ~ QUESTION_5 变量
|
|
13
|
+
|
|
14
|
+
set -e
|
|
15
|
+
|
|
16
|
+
PROJECT_ID="${1:?用法: $0 <project_id> [impl_dir] [--auto]}"
|
|
17
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
18
|
+
REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
19
|
+
IMPL_DIR="${2:-$(dirname "$REPO_DIR")/${PROJECT_ID}-impl}"
|
|
20
|
+
LOG_FILE="$REPO_DIR/episodic-logs/${PROJECT_ID}.jsonl"
|
|
21
|
+
FEEDBACK_FILE="$IMPL_DIR/.flywheel-feedback.json"
|
|
22
|
+
DISTILL_NEEDED_DIR="$REPO_DIR/.distill-needed"
|
|
23
|
+
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
24
|
+
EVENT_ID="evt_feedback_$(date +%s)"
|
|
25
|
+
AUTO_MODE=false
|
|
26
|
+
THRESHOLD=${DISTILL_THRESHOLD:-3}
|
|
27
|
+
|
|
28
|
+
# 解析参数(支持 --auto 在任意位置)
|
|
29
|
+
for arg in "$@"; do
|
|
30
|
+
[ "$arg" = "--auto" ] && AUTO_MODE=true
|
|
31
|
+
done
|
|
32
|
+
|
|
33
|
+
$AUTO_MODE || echo "━━━ 需求反馈收集器 ━━━"
|
|
34
|
+
$AUTO_MODE || echo "项目:$PROJECT_ID"
|
|
35
|
+
$AUTO_MODE || echo ""
|
|
36
|
+
|
|
37
|
+
# ── 读取编程工具自动生成的反馈文件 ───────────────────────────
|
|
38
|
+
FEEDBACK_JSON="{}"
|
|
39
|
+
if [ -f "$FEEDBACK_FILE" ]; then
|
|
40
|
+
echo "✅ 读取反馈文件:$FEEDBACK_FILE"
|
|
41
|
+
FEEDBACK_JSON=$(cat "$FEEDBACK_FILE")
|
|
42
|
+
else
|
|
43
|
+
if $AUTO_MODE; then
|
|
44
|
+
echo "ℹ️ 未发现 .flywheel-feedback.json,写入空反馈事件"
|
|
45
|
+
else
|
|
46
|
+
echo "ℹ️ 未发现自动反馈文件,进入交互收集模式..."
|
|
47
|
+
fi
|
|
48
|
+
fi
|
|
49
|
+
|
|
50
|
+
# ── 加载 skill 专属反馈问题(如存在则覆盖默认问题)────────────
|
|
51
|
+
QUESTION_1="执行过程中哪些输入信息不清晰?"
|
|
52
|
+
QUESTION_2="遇到了哪些文档未覆盖的决策点?"
|
|
53
|
+
QUESTION_3="发现哪些假设与实际情况不符?"
|
|
54
|
+
QUESTION_4="哪些产出无法验证质量?"
|
|
55
|
+
QUESTION_5="总体反馈(执行难度、输入质量评分 1-10):"
|
|
56
|
+
|
|
57
|
+
# 从 state.json 读取当前 skill,尝试加载其 feedback-questions.sh
|
|
58
|
+
STATE_FILE="$REPO_DIR/projects/$PROJECT_ID/state.json"
|
|
59
|
+
if [ -f "$STATE_FILE" ]; then
|
|
60
|
+
ACTIVE_SKILL=$(python3 -c "import json; d=json.load(open('$STATE_FILE')); print(d.get('skill',''))" 2>/dev/null || echo "")
|
|
61
|
+
if [ -n "$ACTIVE_SKILL" ] && [ -f "$REPO_DIR/skills/$ACTIVE_SKILL/feedback-questions.sh" ]; then
|
|
62
|
+
source "$REPO_DIR/skills/$ACTIVE_SKILL/feedback-questions.sh"
|
|
63
|
+
fi
|
|
64
|
+
fi
|
|
65
|
+
|
|
66
|
+
# ── 交互补充(仅非 auto 模式)────────────────────────────────
|
|
67
|
+
UNCLEAR=""
|
|
68
|
+
UNCOVERED=""
|
|
69
|
+
WRONG_ASSUMPTIONS=""
|
|
70
|
+
UNTESTABLE=""
|
|
71
|
+
GENERAL=""
|
|
72
|
+
|
|
73
|
+
if ! $AUTO_MODE; then
|
|
74
|
+
echo "请回答以下问题(直接按 Enter 跳过):"
|
|
75
|
+
echo ""
|
|
76
|
+
read -p "1. $QUESTION_1 " UNCLEAR
|
|
77
|
+
read -p "2. $QUESTION_2 " UNCOVERED
|
|
78
|
+
read -p "3. $QUESTION_3 " WRONG_ASSUMPTIONS
|
|
79
|
+
read -p "4. $QUESTION_4 " UNTESTABLE
|
|
80
|
+
read -p "5. $QUESTION_5 " GENERAL
|
|
81
|
+
fi
|
|
82
|
+
|
|
83
|
+
# ── 合并反馈并生成事件 ────────────────────────────────────────
|
|
84
|
+
PENDING=$(python3 - << PYEOF
|
|
85
|
+
import json, os
|
|
86
|
+
|
|
87
|
+
try:
|
|
88
|
+
existing = json.loads('''$FEEDBACK_JSON''')
|
|
89
|
+
except:
|
|
90
|
+
existing = {}
|
|
91
|
+
|
|
92
|
+
feedback = {
|
|
93
|
+
"event_id": "$EVENT_ID",
|
|
94
|
+
"type": "implementation_feedback",
|
|
95
|
+
"project_id": "$PROJECT_ID",
|
|
96
|
+
"timestamp": "$TIMESTAMP",
|
|
97
|
+
"distillation_candidate": True,
|
|
98
|
+
"unclear_specs": existing.get("unclear_specs", []) + (["$UNCLEAR"] if "$UNCLEAR" else []),
|
|
99
|
+
"uncovered_decisions": existing.get("uncovered_decisions", []) + (["$UNCOVERED"] if "$UNCOVERED" else []),
|
|
100
|
+
"wrong_assumptions": existing.get("wrong_assumptions", []) + (["$WRONG_ASSUMPTIONS"] if "$WRONG_ASSUMPTIONS" else []),
|
|
101
|
+
"untestable_criteria": existing.get("untestable_criteria", []) + (["$UNTESTABLE"] if "$UNTESTABLE" else []),
|
|
102
|
+
"general_feedback": existing.get("general_feedback", "") or "$GENERAL",
|
|
103
|
+
"impl_dir": "$IMPL_DIR"
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
# 过滤空字符串
|
|
107
|
+
for key in ["unclear_specs", "uncovered_decisions", "wrong_assumptions", "untestable_criteria"]:
|
|
108
|
+
feedback[key] = [x for x in feedback[key] if x.strip()]
|
|
109
|
+
|
|
110
|
+
# 计算质量分数
|
|
111
|
+
score = 0.0
|
|
112
|
+
if feedback["unclear_specs"]: score += 0.3
|
|
113
|
+
if feedback["uncovered_decisions"]: score += 0.4
|
|
114
|
+
if feedback["wrong_assumptions"]: score += 0.3
|
|
115
|
+
if feedback["general_feedback"]: score += 0.2
|
|
116
|
+
feedback["quality_score"] = min(score, 0.9)
|
|
117
|
+
|
|
118
|
+
log_file = "$LOG_FILE"
|
|
119
|
+
os.makedirs(os.path.dirname(log_file), exist_ok=True)
|
|
120
|
+
with open(log_file, "a") as f:
|
|
121
|
+
f.write(json.dumps(feedback, ensure_ascii=False) + "\n")
|
|
122
|
+
|
|
123
|
+
print(f"✅ 反馈已写入:{log_file}")
|
|
124
|
+
print(f" 质量分数:{feedback['quality_score']:.2f}(≥0.6 将被蒸馏提炼)")
|
|
125
|
+
|
|
126
|
+
# 统计待蒸馏事件数
|
|
127
|
+
try:
|
|
128
|
+
with open(log_file) as f:
|
|
129
|
+
events = [json.loads(l) for l in f if l.strip()]
|
|
130
|
+
pending = sum(1 for e in events
|
|
131
|
+
if e.get("distillation_candidate") and not e.get("distilled")
|
|
132
|
+
and e.get("quality_score", 0) >= 0.6)
|
|
133
|
+
print(f" 待蒸馏事件:{pending} 条(阈值:$THRESHOLD)")
|
|
134
|
+
print(str(pending)) # 最后一行供 bash 读取
|
|
135
|
+
except:
|
|
136
|
+
print("0")
|
|
137
|
+
PYEOF
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
# 读取 python 输出的最后一行(待蒸馏事件数)
|
|
141
|
+
PENDING_COUNT=$(echo "$PENDING" | tail -1)
|
|
142
|
+
echo "$PENDING" | head -n -1 # 打印非数字行
|
|
143
|
+
|
|
144
|
+
# ── 自动触发蒸馏标记 ──────────────────────────────────────────
|
|
145
|
+
if [ "$PENDING_COUNT" -ge "$THRESHOLD" ] 2>/dev/null; then
|
|
146
|
+
mkdir -p "$DISTILL_NEEDED_DIR"
|
|
147
|
+
echo "$PENDING_COUNT" > "$DISTILL_NEEDED_DIR/$PROJECT_ID"
|
|
148
|
+
echo ""
|
|
149
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
150
|
+
echo " ⚡ 蒸馏条件已满足($PENDING_COUNT 条待蒸馏反馈)"
|
|
151
|
+
echo " 已写入标记:$DISTILL_NEEDED_DIR/$PROJECT_ID"
|
|
152
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
153
|
+
else
|
|
154
|
+
$AUTO_MODE || echo ""
|
|
155
|
+
$AUTO_MODE || echo "━━━ 飞轮状态更新完成 ━━━"
|
|
156
|
+
$AUTO_MODE || echo "下一步:积累 ≥$THRESHOLD 条反馈后将自动触发蒸馏"
|
|
157
|
+
fi
|