evolve.sh 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  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. # ── 查找项目 ──────────────────────────────────────────
  32. find_project() {
  33. local target="${1:-.}"
  34. # 从当前目录向上找
  35. local d
  36. d="$(cd "$target" 2>/dev/null && pwd || echo "$target")"
  37. while [[ "$d" != "/" && "$d" != "." ]]; do
  38. if [[ -f "$d/项目铁律.md" ]]; then
  39. echo "$d"
  40. return 0
  41. fi
  42. d="$(dirname "$d")"
  43. done
  44. return 1
  45. }
  46. # ── 阶段实现 ──────────────────────────────────────────
  47. phase_collect() {
  48. local project="$1"
  49. log "收集: $(basename "$project")"
  50. local chapters
  51. chapters=$(find "$project/正文" -name "*第*章*.md" 2>/dev/null | wc -l)
  52. local has_hooks="✗"
  53. local has_quality="✗"
  54. local has_evolution="✗"
  55. [[ -f "$project/追踪/钩子账本.md" ]] && has_hooks="✓"
  56. [[ -f "$project/追踪/优化日志.md" ]] && has_quality="✓"
  57. [[ -d "$project/evolution" ]] && has_evolution="✓"
  58. log " 章节: $chapters | 钩子: $has_hooks | 质检: $has_quality | 进化: $has_evolution"
  59. mkdir -p "$project/追踪" "$EVOLUTION_DIR"/{prompts,patterns}
  60. # 检测题材,确保题材级进化目录存在
  61. local genre_id
  62. genre_id=$($PYTHON -c "
  63. import sys; sys.path.insert(0, '$SCRIPT_DIR')
  64. from utils import find_project_genre, map_genre_to_inkos_id
  65. from pathlib import Path
  66. print(map_genre_to_inkos_id(find_project_genre(Path('$project'))))
  67. " 2>/dev/null || echo "other")
  68. log " 题材: $genre_id"
  69. # 首次使用:初始化题材进化目录
  70. if [[ ! -d "$EVOLUTION_DIR/genres/$genre_id" ]]; then
  71. log " 初始化题材进化目录: evolution/genres/$genre_id/"
  72. $PYTHON -c "
  73. import sys; sys.path.insert(0, '$SCRIPT_DIR')
  74. from seed_extractor import SeedExtractor
  75. SeedExtractor().initialize_genre('$genre_id')
  76. " 2>/dev/null || true
  77. fi
  78. return 0
  79. }
  80. phase_analyze() {
  81. local project="$1"
  82. log "分析..."
  83. # 质量阈值分析(有质检数据时)
  84. if [[ -f "$project/追踪/优化日志.md" ]]; then
  85. $PYTHON "$SCRIPT_DIR/quality_tuner.py" analyze --project "$project" 2>/dev/null || true
  86. fi
  87. # 写作模式挖掘(有章节时)
  88. if [[ -d "$project/正文" ]] && find "$project/正文" -name "第*章*.md" 2>/dev/null | grep -q .; then
  89. $PYTHON "$SCRIPT_DIR/pattern_mine.py" mine --project "$project" --min-score 65 2>/dev/null || true
  90. fi
  91. ok "分析完成"
  92. }
  93. phase_mutate() {
  94. local project="${1:-}"
  95. log "变异: 提示词进化..."
  96. if [[ -n "$project" ]]; then
  97. $PYTHON "$SCRIPT_DIR/prompt_evolver.py" evolve --project "$project" \
  98. --generations "${GENERATIONS:-2}" 2>/dev/null || true
  99. else
  100. $PYTHON "$SCRIPT_DIR/prompt_evolver.py" evolve --generations "${GENERATIONS:-2}" 2>/dev/null || true
  101. fi
  102. ok "变异完成"
  103. }
  104. phase_report() {
  105. local project="${1:-}"
  106. echo ""
  107. echo -e "${C}═══ 自优化状态 ═══${N}"
  108. echo ""
  109. # 阈值
  110. echo "## 质量阈值 (evolution/thresholds.json)"
  111. $PYTHON "$SCRIPT_DIR/quality_tuner.py" show 2>/dev/null || echo " 使用默认阈值"
  112. # 题材级
  113. if [[ -n "$project" ]]; then
  114. local genre_id
  115. genre_id=$($PYTHON -c "
  116. import sys; sys.path.insert(0, '$SCRIPT_DIR')
  117. from utils import find_project_genre, map_genre_to_inkos_id
  118. from pathlib import Path
  119. print(map_genre_to_inkos_id(find_project_genre(Path('$project'))))
  120. " 2>/dev/null || echo "")
  121. if [[ -n "$genre_id" ]] && [[ -d "$EVOLUTION_DIR/genres/$genre_id" ]]; then
  122. echo ""
  123. echo -e "${C}## 题材级进化 (evolution/genres/$genre_id/)${N}"
  124. echo " 目录: $EVOLUTION_DIR/genres/$genre_id/"
  125. ls -la "$EVOLUTION_DIR/genres/$genre_id"/*.json 2>/dev/null | awk '{print " " $NF}'
  126. echo ""
  127. echo " 最优提示词:"
  128. $PYTHON "$SCRIPT_DIR/prompt_evolver.py" show --genre "$genre_id" 2>/dev/null || true
  129. fi
  130. fi
  131. # 提示词(skill级)
  132. echo ""
  133. echo "## 最优提示词 (evolution/prompts/current_best.json)"
  134. $PYTHON "$SCRIPT_DIR/prompt_evolver.py" show 2>/dev/null || echo " 使用种子提示词"
  135. # 模式库
  136. echo ""
  137. echo "## 写作模式库 (evolution/patterns/pattern_library.json)"
  138. $PYTHON "$SCRIPT_DIR/pattern_mine.py" show --top 5 2>/dev/null || echo " 暂无模式数据"
  139. # 会话状态
  140. echo ""
  141. echo "## 会话状态 (evolution/session_state.json)"
  142. if [[ -f "$EVOLUTION_DIR/session_state.json" ]]; then
  143. $PYTHON -c "
  144. import json
  145. state = json.loads(open('$EVOLUTION_DIR/session_state.json').read())
  146. print(f' 反馈数: {state.get(\"total_feedback_count\", 0)}')
  147. recent = state.get('recent_chapters', [])
  148. if recent:
  149. scores = [r['composite'] for r in recent[-5:]]
  150. print(f' 最近5章均分: {sum(scores)/len(scores):.0f}')
  151. " 2>/dev/null || true
  152. else
  153. echo " 暂无"
  154. fi
  155. }
  156. phase_sync() {
  157. local project="${1:-}"
  158. log "同步最优结果到 evolution/..."
  159. # 提示词进化 + 同步
  160. $PYTHON "$SCRIPT_DIR/prompt_evolver.py" evolve --generations 1 2>/dev/null || true
  161. # 质量阈值同步
  162. if [[ -n "$project" ]] && [[ -f "$project/追踪/优化日志.md" ]]; then
  163. $PYTHON "$SCRIPT_DIR/quality_tuner.py" analyze --project "$project" 2>/dev/null || true
  164. fi
  165. # 模式库同步
  166. if [[ -n "$project" ]] && [[ -d "$project/正文" ]]; then
  167. $PYTHON "$SCRIPT_DIR/pattern_mine.py" mine --project "$project" 2>/dev/null || true
  168. fi
  169. ok "同步完成 → evolution/"
  170. }
  171. # ── 主流程 ─────────────────────────────────────────────
  172. main() {
  173. banner
  174. local mode="${1:---quick}"
  175. local project=""
  176. # 解析参数
  177. while [[ $# -gt 0 ]]; do
  178. case "$1" in
  179. --project|-p) project="$2"; shift 2 ;;
  180. --quick) mode="--quick"; shift ;;
  181. --full) mode="--full"; shift ;;
  182. --sync) mode="--sync"; shift ;;
  183. --report) mode="--report"; shift ;;
  184. --compile) mode="--compile"; shift ;;
  185. *) shift ;;
  186. esac
  187. done
  188. # 查找项目
  189. if [[ -z "$project" ]]; then
  190. project="$(find_project "." 2>/dev/null || true)"
  191. fi
  192. case "$mode" in
  193. --quick)
  194. if [[ -n "$project" ]]; then
  195. phase_collect "$project"
  196. phase_analyze "$project"
  197. phase_mutate "$project"
  198. fi
  199. phase_sync "$project"
  200. ;;
  201. --full)
  202. if [[ -z "$project" ]]; then
  203. warn "未找到小说项目。仅执行 skill 级进化。"
  204. phase_mutate ""
  205. else
  206. phase_collect "$project"
  207. phase_analyze "$project"
  208. phase_mutate "$project"
  209. phase_sync "$project"
  210. fi
  211. log "完整进化完成。下次建议: 每写完一卷(或每10章)执行。"
  212. ;;
  213. --sync)
  214. phase_sync "$project"
  215. ;;
  216. --report)
  217. phase_report
  218. ;;
  219. --compile)
  220. log "编译: 从事件日志重建进化状态..."
  221. if [[ -n "$project" ]]; then
  222. local genre_id
  223. genre_id=$($PYTHON -c "
  224. import sys; sys.path.insert(0, '$SCRIPT_DIR')
  225. from utils import find_project_genre, map_genre_to_inkos_id
  226. from pathlib import Path
  227. print(map_genre_to_inkos_id(find_project_genre(Path('$project'))))
  228. " 2>/dev/null || echo "")
  229. if [[ -n "$genre_id" ]]; then
  230. $PYTHON "$SCRIPT_DIR/event_store.py" compile --genre "$genre_id"
  231. fi
  232. fi
  233. echo ""
  234. echo " 提示: git pull 后执行此命令即可获得最新共享状态。"
  235. ;;
  236. *)
  237. # 默认:quick 模式
  238. if [[ -n "$project" ]]; then
  239. phase_collect "$project"
  240. phase_analyze "$project"
  241. phase_mutate "$project"
  242. fi
  243. phase_sync "$project"
  244. ;;
  245. esac
  246. ok "自优化完成"
  247. echo ""
  248. echo " evolution/ 目录内容 (skill级):"
  249. ls -la "$EVOLUTION_DIR"/*.{json,md} 2>/dev/null | awk '{print " " $NF}' || echo " (空)"
  250. if [[ -n "$project" ]]; then
  251. local genre_id
  252. genre_id=$($PYTHON -c "
  253. import sys; sys.path.insert(0, '$SCRIPT_DIR')
  254. from utils import find_project_genre, map_genre_to_inkos_id
  255. from pathlib import Path
  256. print(map_genre_to_inkos_id(find_project_genre(Path('$project'))))
  257. " 2>/dev/null || echo "")
  258. if [[ -n "$genre_id" ]] && [[ -d "$EVOLUTION_DIR/genres/$genre_id" ]]; then
  259. echo ""
  260. echo " evolution/ 目录内容 (题材级: $genre_id):"
  261. find "$EVOLUTION_DIR/genres/$genre_id" -name "*.json" 2>/dev/null \
  262. | sed "s|$EVOLUTION_DIR/genres/$genre_id/| |" || echo " (空)"
  263. fi
  264. fi
  265. }
  266. main "$@"