evolve.ps1 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434
  1. #!/usr/bin/env pwsh
  2. # ═══════════════════════════════════════════════════════════════
  3. # evolve.ps1 — novel-write 自优化编排器 (PowerShell)
  4. # 进化结果写入 evolution/ 文件夹,不修改 SKILL.md
  5. #
  6. # 用法:
  7. # .\evolve.ps1 # 自动检测项目,执行进化
  8. # .\evolve.ps1 -Project /path/to/novel # 指定项目
  9. # .\evolve.ps1 -Quick # 快速模式(写后自动调用)
  10. # .\evolve.ps1 -Full # 完整进化(每卷/每10章)
  11. # .\evolve.ps1 -Sync # 将项目最优结果同步到 evolution/
  12. # .\evolve.ps1 -Report # 输出状态报告
  13. # .\evolve.ps1 -Compile # 编译:从事件日志重建进化状态
  14. # ═══════════════════════════════════════════════════════════════
  15. $ErrorActionPreference = "Stop"
  16. $SCRIPT_DIR = Split-Path -Parent $MyInvocation.MyCommand.Path
  17. $SKILL_DIR = Split-Path -Parent $SCRIPT_DIR
  18. $EVOLUTION_DIR = Join-Path $SKILL_DIR "evolution"
  19. $PYTHON = if ($env:PYTHON) { $env:PYTHON } else { "python3" }
  20. $env:PYTHONPATH = "$SCRIPT_DIR$([IO.Path]::PathSeparator)$($env:PYTHONPATH)"
  21. # ── 日志函数 ──────────────────────────────────────────
  22. function Write-Log { Write-Host "[evolve] $args" -ForegroundColor Blue }
  23. function Write-Ok { Write-Host "[evolve] $([char]0x2713) $args" -ForegroundColor Green }
  24. function Write-Warn { Write-Host "[evolve] $([char]0x26A0) $args" -ForegroundColor Yellow }
  25. function Write-Banner {
  26. Write-Host "┌─────────────────────────────────┐" -ForegroundColor Cyan
  27. Write-Host "│ novel-write 自优化引擎 │" -ForegroundColor Cyan
  28. Write-Host "│ 输出: evolution/ (不修改 SKILL.md) │" -ForegroundColor Cyan
  29. Write-Host "└─────────────────────────────────┘" -ForegroundColor Cyan
  30. }
  31. # ── Git 集成 ──────────────────────────────────────────
  32. function Test-IsGitRepo {
  33. try {
  34. git -C $SKILL_DIR rev-parse --git-dir 2>$null | Out-Null
  35. return $true
  36. } catch {
  37. return $false
  38. }
  39. }
  40. function Invoke-GitPull {
  41. # 写前拉取:获取其他用户的最新进化数据
  42. if (-not (Test-IsGitRepo)) { return }
  43. try {
  44. git -C $SKILL_DIR remote get-url origin 2>$null | Out-Null
  45. } catch {
  46. return
  47. }
  48. Write-Log "git pull: 获取共享进化数据..."
  49. try {
  50. git -C $SKILL_DIR pull --ff-only origin master 2>$null | Out-Null
  51. Write-Ok "git pull 完成"
  52. # population/thresholds/current_best 已 gitignore,
  53. # 每次 pull 后必须从事件重建(即使无新 commit)
  54. $genreDirs = Get-ChildItem -Path "$EVOLUTION_DIR\genres" -Directory -ErrorAction SilentlyContinue |
  55. Where-Object { Test-Path (Join-Path $_.FullName "events") } |
  56. ForEach-Object { $_.Name }
  57. foreach ($gid in $genreDirs) {
  58. Write-Log " 编译题材: $gid"
  59. try { & $PYTHON "$SCRIPT_DIR\event_store.py" compile --genre $gid 2>$null } catch { }
  60. }
  61. } catch {
  62. Write-Warn "git pull 失败(网络不通或无可更新),继续本地进化"
  63. }
  64. }
  65. function Invoke-GitPushEvents {
  66. # 写后推送:将本次进化事件提交并推送到远程
  67. if (-not (Test-IsGitRepo)) { return }
  68. try {
  69. git -C $SKILL_DIR remote get-url origin 2>$null | Out-Null
  70. } catch {
  71. return
  72. }
  73. $hasChanges = git -C $SKILL_DIR status --porcelain -- evolution/ 2>$null
  74. if (-not $hasChanges) {
  75. Write-Log "git: 无变更,跳过推送"
  76. return
  77. }
  78. Write-Log "git: 提交并推送进化数据..."
  79. # 汇总变更的题材
  80. $summary = ($hasChanges | ForEach-Object {
  81. $_ -replace '.*evolution/', '' | ForEach-Object { ($_ -split '/')[0] }
  82. } | Sort-Object -Unique) -join ','
  83. try {
  84. git -C $SKILL_DIR add evolution/ 2>$null | Out-Null
  85. git -C $SKILL_DIR commit -m "evolve($($summary)): auto-commit evolution data" 2>$null | Out-Null
  86. git -C $SKILL_DIR push origin master 2>$null | Out-Null
  87. $fileCount = ($hasChanges | Measure-Object).Count
  88. Write-Ok "git push 完成 ($fileCount 个文件)"
  89. } catch {
  90. Write-Warn "git push 失败(网络不通),下次推送会包含本次变更"
  91. }
  92. }
  93. # ── 查找项目 ──────────────────────────────────────────
  94. function Find-Project {
  95. param([string]$Target = ".")
  96. try {
  97. $d = (Resolve-Path $Target -ErrorAction Stop).Path
  98. } catch {
  99. $d = $Target
  100. }
  101. while ($d -and $d -ne (Split-Path $d -Qualifier)) {
  102. if (Test-Path (Join-Path $d "项目铁律.md") -PathType Leaf) {
  103. return $d
  104. }
  105. $parent = Split-Path $d -Parent
  106. if ($parent -eq $d) { break }
  107. $d = $parent
  108. }
  109. return $null
  110. }
  111. # ── 检测题材ID ────────────────────────────────────────
  112. function Get-GenreId {
  113. param([string]$ProjectPath)
  114. try {
  115. $result = & $PYTHON -c @"
  116. import sys; sys.path.insert(0, '$($SCRIPT_DIR -replace '\\','/')')
  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('$($ProjectPath -replace '\\','/')'))))
  120. "@ 2>$null
  121. if ($result) { return $result.Trim() }
  122. } catch { }
  123. return "other"
  124. }
  125. # ── 阶段实现 ──────────────────────────────────────────
  126. function Invoke-PhaseCollect {
  127. param([string]$Project)
  128. Write-Log "收集: $(Split-Path $Project -Leaf)"
  129. $chapterDir = Join-Path $Project "正文"
  130. $chapterCount = 0
  131. if (Test-Path $chapterDir) {
  132. $chapterCount = (Get-ChildItem -Path $chapterDir -Filter "*第*章*.md" -Recurse -ErrorAction SilentlyContinue | Measure-Object).Count
  133. }
  134. $hasHooks = if (Test-Path (Join-Path $Project "追踪/钩子账本.md")) { "$([char]0x2713)" } else { "$([char]0x2717)" }
  135. $hasQuality = if (Test-Path (Join-Path $Project "追踪/优化日志.md")) { "$([char]0x2713)" } else { "$([char]0x2717)" }
  136. $hasEvolution = if (Test-Path (Join-Path $Project "evolution")) { "$([char]0x2713)" } else { "$([char]0x2717)" }
  137. Write-Log " 章节: $chapterCount | 钩子: $hasHooks | 质检: $hasQuality | 进化: $hasEvolution"
  138. New-Item -ItemType Directory -Force -Path (Join-Path $Project "追踪"), (Join-Path $EVOLUTION_DIR "prompts"), (Join-Path $EVOLUTION_DIR "patterns") | Out-Null
  139. # 检测题材,确保题材级进化目录存在
  140. $genreId = Get-GenreId -ProjectPath $Project
  141. Write-Log " 题材: $genreId"
  142. # 首次使用:初始化题材进化目录
  143. $genreDir = Join-Path $EVOLUTION_DIR "genres" $genreId
  144. if (-not (Test-Path $genreDir)) {
  145. Write-Log " 初始化题材进化目录: evolution/genres/$genreId/"
  146. try {
  147. & $PYTHON -c @"
  148. import sys; sys.path.insert(0, '$($SCRIPT_DIR -replace '\\','/')')
  149. from seed_extractor import SeedExtractor
  150. SeedExtractor().initialize_genre('$genreId')
  151. "@ 2>$null
  152. } catch { }
  153. }
  154. }
  155. function Invoke-PhaseAnalyze {
  156. param([string]$Project)
  157. Write-Log "分析..."
  158. # 质量阈值分析(有质检数据时)
  159. if (Test-Path (Join-Path $Project "追踪/优化日志.md")) {
  160. try { & $PYTHON "$SCRIPT_DIR\quality_tuner.py" analyze --project "$Project" 2>$null } catch { }
  161. }
  162. # 写作模式挖掘(有章节时)
  163. $chapterDir = Join-Path $Project "正文"
  164. if ((Test-Path $chapterDir) -and (Get-ChildItem -Path $chapterDir -Filter "*第*章*.md" -Recurse -ErrorAction SilentlyContinue | Select-Object -First 1)) {
  165. try { & $PYTHON "$SCRIPT_DIR\pattern_mine.py" mine --project "$Project" --min-score 65 2>$null } catch { }
  166. }
  167. Write-Ok "分析完成"
  168. }
  169. function Invoke-PhaseMutate {
  170. param([string]$Project = "")
  171. Write-Log "变异: 提示词进化..."
  172. $gen = if ($env:GENERATIONS) { $env:GENERATIONS } else { "2" }
  173. if ($Project) {
  174. try { & $PYTHON "$SCRIPT_DIR\prompt_evolver.py" evolve --project "$Project" --generations $gen 2>$null } catch { }
  175. } else {
  176. try { & $PYTHON "$SCRIPT_DIR\prompt_evolver.py" evolve --generations $gen 2>$null } catch { }
  177. }
  178. Write-Ok "变异完成"
  179. }
  180. function Invoke-PhaseReport {
  181. param([string]$Project = "")
  182. Write-Host ""
  183. Write-Host "═══ 自优化状态 ═══" -ForegroundColor Cyan
  184. Write-Host ""
  185. # 阈值
  186. Write-Host "## 质量阈值 (evolution/thresholds.json)"
  187. try {
  188. & $PYTHON "$SCRIPT_DIR\quality_tuner.py" show 2>$null
  189. } catch {
  190. Write-Host " 使用默认阈值"
  191. }
  192. # 题材级
  193. if ($Project) {
  194. $genreId = Get-GenreId -ProjectPath $Project
  195. if ($genreId) {
  196. $genreDir = Join-Path $EVOLUTION_DIR "genres" $genreId
  197. if (Test-Path $genreDir) {
  198. Write-Host ""
  199. Write-Host "## 题材级进化 (evolution/genres/$genreId/)" -ForegroundColor Cyan
  200. Write-Host " 目录: $genreDir"
  201. Get-ChildItem -Path $genreDir -Filter "*.json" -ErrorAction SilentlyContinue | ForEach-Object {
  202. Write-Host " $($_.Name)"
  203. }
  204. Write-Host ""
  205. Write-Host " 最优提示词:"
  206. try { & $PYTHON "$SCRIPT_DIR\prompt_evolver.py" show --genre $genreId 2>$null } catch { }
  207. }
  208. }
  209. }
  210. # 提示词(skill级)
  211. Write-Host ""
  212. Write-Host "## 最优提示词 (evolution/prompts/current_best.json)"
  213. try {
  214. & $PYTHON "$SCRIPT_DIR\prompt_evolver.py" show 2>$null
  215. } catch {
  216. Write-Host " 使用种子提示词"
  217. }
  218. # 模式库
  219. Write-Host ""
  220. Write-Host "## 写作模式库 (evolution/patterns/pattern_library.json)"
  221. try {
  222. & $PYTHON "$SCRIPT_DIR\pattern_mine.py" show --top 5 2>$null
  223. } catch {
  224. Write-Host " 暂无模式数据"
  225. }
  226. # 会话状态
  227. Write-Host ""
  228. Write-Host "## 会话状态 (evolution/session_state.json)"
  229. $sessionFile = Join-Path $EVOLUTION_DIR "session_state.json"
  230. if (Test-Path $sessionFile) {
  231. try {
  232. & $PYTHON -c @"
  233. import json
  234. state = json.loads(open('$($sessionFile -replace '\\','/')').read())
  235. print(f' 反馈数: {state.get(\"total_feedback_count\", 0)}')
  236. recent = state.get('recent_chapters', [])
  237. if recent:
  238. scores = [r['composite'] for r in recent[-5:]]
  239. print(f' 最近5章均分: {sum(scores)/len(scores):.0f}')
  240. "@ 2>$null
  241. } catch { }
  242. } else {
  243. Write-Host " 暂无"
  244. }
  245. }
  246. function Invoke-PhaseSync {
  247. param([string]$Project = "")
  248. Write-Log "同步最优结果到 evolution/..."
  249. # 提示词进化 + 同步
  250. try { & $PYTHON "$SCRIPT_DIR\prompt_evolver.py" evolve --generations 1 2>$null } catch { }
  251. # 质量阈值同步
  252. if ($Project -and (Test-Path (Join-Path $Project "追踪/优化日志.md"))) {
  253. try { & $PYTHON "$SCRIPT_DIR\quality_tuner.py" analyze --project "$Project" 2>$null } catch { }
  254. }
  255. # 模式库同步
  256. if ($Project -and (Test-Path (Join-Path $Project "正文"))) {
  257. try { & $PYTHON "$SCRIPT_DIR\pattern_mine.py" mine --project "$Project" 2>$null } catch { }
  258. }
  259. Write-Ok "同步完成 → evolution/"
  260. }
  261. # ── 主流程 ─────────────────────────────────────────────
  262. function Main {
  263. param(
  264. [string]$Project = "",
  265. [string]$Mode = "Quick",
  266. [switch]$Quick,
  267. [switch]$Full,
  268. [switch]$Sync,
  269. [switch]$Report,
  270. [switch]$Compile
  271. )
  272. Write-Banner
  273. # 写前拉取:获取其他用户的最新进化数据
  274. Invoke-GitPull
  275. # 解析模式
  276. if ($Quick) { $Mode = "Quick" }
  277. if ($Full) { $Mode = "Full" }
  278. if ($Sync) { $Mode = "Sync" }
  279. if ($Report) { $Mode = "Report" }
  280. if ($Compile) { $Mode = "Compile" }
  281. # 查找项目
  282. if (-not $Project) {
  283. $Project = Find-Project -Target "."
  284. }
  285. switch ($Mode) {
  286. "Quick" {
  287. if ($Project) {
  288. Invoke-PhaseCollect -Project $Project
  289. Invoke-PhaseAnalyze -Project $Project
  290. Invoke-PhaseMutate -Project $Project
  291. }
  292. Invoke-PhaseSync -Project $Project
  293. }
  294. "Full" {
  295. if (-not $Project) {
  296. Write-Warn "未找到小说项目。仅执行 skill 级进化。"
  297. Invoke-PhaseMutate -Project ""
  298. } else {
  299. Invoke-PhaseCollect -Project $Project
  300. Invoke-PhaseAnalyze -Project $Project
  301. Invoke-PhaseMutate -Project $Project
  302. Invoke-PhaseSync -Project $Project
  303. }
  304. Write-Log "完整进化完成。下次建议: 每写完一卷(或每10章)执行。"
  305. }
  306. "Sync" {
  307. Invoke-PhaseSync -Project $Project
  308. }
  309. "Report" {
  310. Invoke-PhaseReport -Project $Project
  311. }
  312. "Compile" {
  313. Write-Log "编译: 从事件日志重建进化状态..."
  314. if ($Project) {
  315. $genreId = Get-GenreId -ProjectPath $Project
  316. if ($genreId) {
  317. & $PYTHON "$SCRIPT_DIR\event_store.py" compile --genre $genreId
  318. }
  319. }
  320. Write-Host ""
  321. Write-Host " 提示: git pull 后执行此命令即可获得最新共享状态。"
  322. }
  323. default {
  324. # 默认 Quick
  325. if ($Project) {
  326. Invoke-PhaseCollect -Project $Project
  327. Invoke-PhaseAnalyze -Project $Project
  328. Invoke-PhaseMutate -Project $Project
  329. }
  330. Invoke-PhaseSync -Project $Project
  331. }
  332. }
  333. Write-Ok "自优化完成"
  334. Write-Host ""
  335. Write-Host " evolution/ 目录内容 (skill级):"
  336. Get-ChildItem -Path $EVOLUTION_DIR -Filter "*.json" -ErrorAction SilentlyContinue | ForEach-Object {
  337. Write-Host " $($_.Name)"
  338. }
  339. Get-ChildItem -Path $EVOLUTION_DIR -Filter "*.md" -ErrorAction SilentlyContinue | ForEach-Object {
  340. Write-Host " $($_.Name)"
  341. }
  342. if ($Project) {
  343. $genreId = Get-GenreId -ProjectPath $Project
  344. if ($genreId) {
  345. $genreDir = Join-Path $EVOLUTION_DIR "genres" $genreId
  346. if (Test-Path $genreDir) {
  347. Write-Host ""
  348. Write-Host " evolution/ 目录内容 (题材级: $genreId):"
  349. Get-ChildItem -Path $genreDir -Recurse -Filter "*.json" -ErrorAction SilentlyContinue | ForEach-Object {
  350. $relPath = $_.FullName.Substring($genreDir.Length + 1)
  351. Write-Host " $relPath"
  352. }
  353. }
  354. }
  355. }
  356. # 写后推送:将本次进化事件自动提交到 git
  357. Invoke-GitPushEvents
  358. }
  359. # ── 入口 ───────────────────────────────────────────────
  360. param(
  361. [string]$Project = "",
  362. [switch]$Quick,
  363. [switch]$Full,
  364. [switch]$Sync,
  365. [switch]$Report,
  366. [switch]$Compile
  367. )
  368. Main -Project $Project -Quick:$Quick -Full:$Full -Sync:$Sync -Report:$Report -Compile:$Compile