evolve.sh 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377
  1. #!/usr/bin/env bash
  2. # ═══════════════════════════════════════════════════════════════
  3. # evolve.sh — novel-write 自优化编排器
  4. # 进化结果写入 evolution/ 文件夹,不修改 SKILL.md
  5. #
  6. # 用法:
  7. # ./evolve.sh # 自动检测项目,执行进化
  8. # ./evolve.sh --project /path/to/novel # 指定项目
  9. # ./evolve.sh --quick # 快速模式(写后自动调用)
  10. # ./evolve.sh --full # 完整进化(每卷/每10章)
  11. # ./evolve.sh --sync # 将项目最优结果同步到 evolution/
  12. # ./evolve.sh --report # 输出状态报告
  13. # ═══════════════════════════════════════════════════════════════
  14. set -eo pipefail
  15. SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
  16. SKILL_DIR="$(dirname "$SCRIPT_DIR")"
  17. EVOLUTION_DIR="$SKILL_DIR/evolution"
  18. PYTHON="${PYTHON:-python3}"
  19. export PYTHONPATH="$SCRIPT_DIR:$PYTHONPATH"
  20. # 颜色
  21. B="\033[0;34m" G="\033[0;32m" Y="\033[1;33m" C="\033[0;36m" N="\033[0m"
  22. log() { echo -e "${B}[evolve]${N} $*"; }
  23. ok() { echo -e "${G}[evolve]${N} ✓ $*"; }
  24. warn() { echo -e "${Y}[evolve]${N} ⚠ $*"; }
  25. banner() {
  26. echo -e "${C}┌─────────────────────────────────┐"
  27. echo -e "│ novel-write 自优化引擎 │"
  28. echo -e "│ 输出: evolution/ (不修改 SKILL.md) │"
  29. echo -e "└─────────────────────────────────┘${N}"
  30. }
  31. # ── Git 集成 ──────────────────────────────────────────
  32. is_git_repo() {
  33. git -C "$SKILL_DIR" rev-parse --git-dir &>/dev/null
  34. }
  35. git_pull() {
  36. # 写前拉取:获取其他用户的最新进化数据
  37. if ! is_git_repo; then return 0; fi
  38. if ! git -C "$SKILL_DIR" remote get-url origin &>/dev/null; then return 0; fi
  39. log "git pull: 获取共享进化数据..."
  40. if git -C "$SKILL_DIR" pull --ff-only origin master 2>/dev/null; then
  41. ok "git pull 完成"
  42. # 有新数据 → 编译所有已变更题材的事件
  43. local changed_genres
  44. changed_genres=$(git -C "$SKILL_DIR" diff --name-only HEAD@{1}..HEAD -- evolution/genres/ 2>/dev/null \
  45. | grep -oP 'evolution/genres/\K[^/]+' | sort -u || echo "")
  46. if [[ -n "$changed_genres" ]]; then
  47. for gid in $changed_genres; do
  48. log " 编译题材: $gid"
  49. $PYTHON "$SCRIPT_DIR/event_store.py" compile --genre "$gid" 2>/dev/null || true
  50. done
  51. fi
  52. else
  53. warn "git pull 失败(网络不通或无可更新),继续本地进化"
  54. fi
  55. }
  56. git_push_events() {
  57. # 写后推送:将本次进化事件提交并推送到远程
  58. if ! is_git_repo; then return 0; fi
  59. if ! git -C "$SKILL_DIR" remote get-url origin &>/dev/null; then return 0; fi
  60. # 检查 evolution/ 下所有变更(题材级 + skill 级)
  61. local has_changes
  62. has_changes=$(git -C "$SKILL_DIR" status --porcelain -- evolution/ 2>/dev/null || echo "")
  63. if [[ -z "$has_changes" ]]; then
  64. log "git: 无变更,跳过推送"
  65. return 0
  66. fi
  67. log "git: 提交并推送进化数据..."
  68. # 汇总变更的题材
  69. local summary
  70. summary=$(echo "$has_changes" | sed 's|.*evolution/||' | cut -d/ -f1 | sort -u | tr '\n' ',' | sed 's/,$//')
  71. git -C "$SKILL_DIR" add evolution/ 2>/dev/null || true
  72. git -C "$SKILL_DIR" commit -m "evolve(${summary:-auto}): auto-commit evolution data" 2>/dev/null || true
  73. if git -C "$SKILL_DIR" push origin master 2>/dev/null; then
  74. ok "git push 完成 ($(echo "$has_changes" | wc -l) 个文件)"
  75. else
  76. warn "git push 失败(网络不通),下次推送会包含本次变更"
  77. fi
  78. }
  79. # ── 查找项目 ──────────────────────────────────────────
  80. find_project() {
  81. local target="${1:-.}"
  82. # 从当前目录向上找
  83. local d
  84. d="$(cd "$target" 2>/dev/null && pwd || echo "$target")"
  85. while [[ "$d" != "/" && "$d" != "." ]]; do
  86. if [[ -f "$d/项目铁律.md" ]]; then
  87. echo "$d"
  88. return 0
  89. fi
  90. d="$(dirname "$d")"
  91. done
  92. return 1
  93. }
  94. # ── 阶段实现 ──────────────────────────────────────────
  95. phase_collect() {
  96. local project="$1"
  97. log "收集: $(basename "$project")"
  98. local chapters
  99. chapters=$(find "$project/正文" -name "*第*章*.md" 2>/dev/null | wc -l)
  100. local has_hooks="✗"
  101. local has_quality="✗"
  102. local has_evolution="✗"
  103. [[ -f "$project/追踪/钩子账本.md" ]] && has_hooks="✓"
  104. [[ -f "$project/追踪/优化日志.md" ]] && has_quality="✓"
  105. [[ -d "$project/evolution" ]] && has_evolution="✓"
  106. log " 章节: $chapters | 钩子: $has_hooks | 质检: $has_quality | 进化: $has_evolution"
  107. mkdir -p "$project/追踪" "$EVOLUTION_DIR"/{prompts,patterns}
  108. # 检测题材,确保题材级进化目录存在
  109. local genre_id
  110. genre_id=$($PYTHON -c "
  111. import sys; sys.path.insert(0, '$SCRIPT_DIR')
  112. from utils import find_project_genre, map_genre_to_inkos_id
  113. from pathlib import Path
  114. print(map_genre_to_inkos_id(find_project_genre(Path('$project'))))
  115. " 2>/dev/null || echo "other")
  116. log " 题材: $genre_id"
  117. # 首次使用:初始化题材进化目录
  118. if [[ ! -d "$EVOLUTION_DIR/genres/$genre_id" ]]; then
  119. log " 初始化题材进化目录: evolution/genres/$genre_id/"
  120. $PYTHON -c "
  121. import sys; sys.path.insert(0, '$SCRIPT_DIR')
  122. from seed_extractor import SeedExtractor
  123. SeedExtractor().initialize_genre('$genre_id')
  124. " 2>/dev/null || true
  125. fi
  126. return 0
  127. }
  128. phase_analyze() {
  129. local project="$1"
  130. log "分析..."
  131. # 质量阈值分析(有质检数据时)
  132. if [[ -f "$project/追踪/优化日志.md" ]]; then
  133. $PYTHON "$SCRIPT_DIR/quality_tuner.py" analyze --project "$project" 2>/dev/null || true
  134. fi
  135. # 写作模式挖掘(有章节时)
  136. if [[ -d "$project/正文" ]] && find "$project/正文" -name "第*章*.md" 2>/dev/null | grep -q .; then
  137. $PYTHON "$SCRIPT_DIR/pattern_mine.py" mine --project "$project" --min-score 65 2>/dev/null || true
  138. fi
  139. ok "分析完成"
  140. }
  141. phase_mutate() {
  142. local project="${1:-}"
  143. log "变异: 提示词进化..."
  144. if [[ -n "$project" ]]; then
  145. $PYTHON "$SCRIPT_DIR/prompt_evolver.py" evolve --project "$project" \
  146. --generations "${GENERATIONS:-2}" 2>/dev/null || true
  147. else
  148. $PYTHON "$SCRIPT_DIR/prompt_evolver.py" evolve --generations "${GENERATIONS:-2}" 2>/dev/null || true
  149. fi
  150. ok "变异完成"
  151. }
  152. phase_report() {
  153. local project="${1:-}"
  154. echo ""
  155. echo -e "${C}═══ 自优化状态 ═══${N}"
  156. echo ""
  157. # 阈值
  158. echo "## 质量阈值 (evolution/thresholds.json)"
  159. $PYTHON "$SCRIPT_DIR/quality_tuner.py" show 2>/dev/null || echo " 使用默认阈值"
  160. # 题材级
  161. if [[ -n "$project" ]]; then
  162. local genre_id
  163. genre_id=$($PYTHON -c "
  164. import sys; sys.path.insert(0, '$SCRIPT_DIR')
  165. from utils import find_project_genre, map_genre_to_inkos_id
  166. from pathlib import Path
  167. print(map_genre_to_inkos_id(find_project_genre(Path('$project'))))
  168. " 2>/dev/null || echo "")
  169. if [[ -n "$genre_id" ]] && [[ -d "$EVOLUTION_DIR/genres/$genre_id" ]]; then
  170. echo ""
  171. echo -e "${C}## 题材级进化 (evolution/genres/$genre_id/)${N}"
  172. echo " 目录: $EVOLUTION_DIR/genres/$genre_id/"
  173. ls -la "$EVOLUTION_DIR/genres/$genre_id"/*.json 2>/dev/null | awk '{print " " $NF}'
  174. echo ""
  175. echo " 最优提示词:"
  176. $PYTHON "$SCRIPT_DIR/prompt_evolver.py" show --genre "$genre_id" 2>/dev/null || true
  177. fi
  178. fi
  179. # 提示词(skill级)
  180. echo ""
  181. echo "## 最优提示词 (evolution/prompts/current_best.json)"
  182. $PYTHON "$SCRIPT_DIR/prompt_evolver.py" show 2>/dev/null || echo " 使用种子提示词"
  183. # 模式库
  184. echo ""
  185. echo "## 写作模式库 (evolution/patterns/pattern_library.json)"
  186. $PYTHON "$SCRIPT_DIR/pattern_mine.py" show --top 5 2>/dev/null || echo " 暂无模式数据"
  187. # 会话状态
  188. echo ""
  189. echo "## 会话状态 (evolution/session_state.json)"
  190. if [[ -f "$EVOLUTION_DIR/session_state.json" ]]; then
  191. $PYTHON -c "
  192. import json
  193. state = json.loads(open('$EVOLUTION_DIR/session_state.json').read())
  194. print(f' 反馈数: {state.get(\"total_feedback_count\", 0)}')
  195. recent = state.get('recent_chapters', [])
  196. if recent:
  197. scores = [r['composite'] for r in recent[-5:]]
  198. print(f' 最近5章均分: {sum(scores)/len(scores):.0f}')
  199. " 2>/dev/null || true
  200. else
  201. echo " 暂无"
  202. fi
  203. }
  204. phase_sync() {
  205. local project="${1:-}"
  206. log "同步最优结果到 evolution/..."
  207. # 提示词进化 + 同步
  208. $PYTHON "$SCRIPT_DIR/prompt_evolver.py" evolve --generations 1 2>/dev/null || true
  209. # 质量阈值同步
  210. if [[ -n "$project" ]] && [[ -f "$project/追踪/优化日志.md" ]]; then
  211. $PYTHON "$SCRIPT_DIR/quality_tuner.py" analyze --project "$project" 2>/dev/null || true
  212. fi
  213. # 模式库同步
  214. if [[ -n "$project" ]] && [[ -d "$project/正文" ]]; then
  215. $PYTHON "$SCRIPT_DIR/pattern_mine.py" mine --project "$project" 2>/dev/null || true
  216. fi
  217. ok "同步完成 → evolution/"
  218. }
  219. # ── 主流程 ─────────────────────────────────────────────
  220. main() {
  221. banner
  222. # 写前拉取:获取其他用户的最新进化数据
  223. git_pull
  224. local mode="${1:---quick}"
  225. local project=""
  226. # 解析参数
  227. while [[ $# -gt 0 ]]; do
  228. case "$1" in
  229. --project|-p) project="$2"; shift 2 ;;
  230. --quick) mode="--quick"; shift ;;
  231. --full) mode="--full"; shift ;;
  232. --sync) mode="--sync"; shift ;;
  233. --report) mode="--report"; shift ;;
  234. --compile) mode="--compile"; shift ;;
  235. *) shift ;;
  236. esac
  237. done
  238. # 查找项目
  239. if [[ -z "$project" ]]; then
  240. project="$(find_project "." 2>/dev/null || true)"
  241. fi
  242. case "$mode" in
  243. --quick)
  244. if [[ -n "$project" ]]; then
  245. phase_collect "$project"
  246. phase_analyze "$project"
  247. phase_mutate "$project"
  248. fi
  249. phase_sync "$project"
  250. ;;
  251. --full)
  252. if [[ -z "$project" ]]; then
  253. warn "未找到小说项目。仅执行 skill 级进化。"
  254. phase_mutate ""
  255. else
  256. phase_collect "$project"
  257. phase_analyze "$project"
  258. phase_mutate "$project"
  259. phase_sync "$project"
  260. fi
  261. log "完整进化完成。下次建议: 每写完一卷(或每10章)执行。"
  262. ;;
  263. --sync)
  264. phase_sync "$project"
  265. ;;
  266. --report)
  267. phase_report
  268. ;;
  269. --compile)
  270. log "编译: 从事件日志重建进化状态..."
  271. if [[ -n "$project" ]]; then
  272. local genre_id
  273. genre_id=$($PYTHON -c "
  274. import sys; sys.path.insert(0, '$SCRIPT_DIR')
  275. from utils import find_project_genre, map_genre_to_inkos_id
  276. from pathlib import Path
  277. print(map_genre_to_inkos_id(find_project_genre(Path('$project'))))
  278. " 2>/dev/null || echo "")
  279. if [[ -n "$genre_id" ]]; then
  280. $PYTHON "$SCRIPT_DIR/event_store.py" compile --genre "$genre_id"
  281. fi
  282. fi
  283. echo ""
  284. echo " 提示: git pull 后执行此命令即可获得最新共享状态。"
  285. ;;
  286. *)
  287. # 默认:quick 模式
  288. if [[ -n "$project" ]]; then
  289. phase_collect "$project"
  290. phase_analyze "$project"
  291. phase_mutate "$project"
  292. fi
  293. phase_sync "$project"
  294. ;;
  295. esac
  296. ok "自优化完成"
  297. echo ""
  298. echo " evolution/ 目录内容 (skill级):"
  299. ls -la "$EVOLUTION_DIR"/*.{json,md} 2>/dev/null | awk '{print " " $NF}' || echo " (空)"
  300. if [[ -n "$project" ]]; then
  301. local genre_id
  302. genre_id=$($PYTHON -c "
  303. import sys; sys.path.insert(0, '$SCRIPT_DIR')
  304. from utils import find_project_genre, map_genre_to_inkos_id
  305. from pathlib import Path
  306. print(map_genre_to_inkos_id(find_project_genre(Path('$project'))))
  307. " 2>/dev/null || echo "")
  308. if [[ -n "$genre_id" ]] && [[ -d "$EVOLUTION_DIR/genres/$genre_id" ]]; then
  309. echo ""
  310. echo " evolution/ 目录内容 (题材级: $genre_id):"
  311. find "$EVOLUTION_DIR/genres/$genre_id" -name "*.json" 2>/dev/null \
  312. | sed "s|$EVOLUTION_DIR/genres/$genre_id/| |" || echo " (空)"
  313. fi
  314. fi
  315. # 写后推送:将本次进化事件自动提交到 git
  316. git_push_events
  317. }
  318. main "$@"