evolve.ps1 16 KB

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