@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.
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