#!/usr/bin/env pwsh # ═══════════════════════════════════════════════════════════════ # evolve.ps1 — novel-write 自优化编排器 (PowerShell) # 进化结果写入 evolution/ 文件夹,不修改 SKILL.md # # 用法: # .\evolve.ps1 # 自动检测项目,执行进化 # .\evolve.ps1 -Project /path/to/novel # 指定项目 # .\evolve.ps1 -Quick # 快速模式(写后自动调用) # .\evolve.ps1 -Full # 完整进化(每卷/每10章) # .\evolve.ps1 -Sync # 将项目最优结果同步到 evolution/ # .\evolve.ps1 -Report # 输出状态报告 # .\evolve.ps1 -Compile # 编译:从事件日志重建进化状态 # ═══════════════════════════════════════════════════════════════ param( [string]$Project = "", [switch]$Quick, [switch]$Full, [switch]$Sync, [switch]$Report, [switch]$Compile ) $ErrorActionPreference = "Stop" $SCRIPT_DIR = Split-Path -Parent $MyInvocation.MyCommand.Path $SKILL_DIR = Split-Path -Parent $SCRIPT_DIR $EVOLUTION_DIR = Join-Path $SKILL_DIR "evolution" $PYTHON = if ($env:PYTHON) { $env:PYTHON } else { "python3" } $env:PYTHONPATH = "$SCRIPT_DIR$([IO.Path]::PathSeparator)$($env:PYTHONPATH)" # ── 日志函数 ────────────────────────────────────────── function Write-Log { Write-Host "[evolve] $args" -ForegroundColor Blue } function Write-Ok { Write-Host "[evolve] $([char]0x2713) $args" -ForegroundColor Green } function Write-Warn { Write-Host "[evolve] $([char]0x26A0) $args" -ForegroundColor Yellow } function Write-Banner { Write-Host "┌─────────────────────────────────┐" -ForegroundColor Cyan Write-Host "│ novel-write 自优化引擎 │" -ForegroundColor Cyan Write-Host "│ 输出: evolution/ (不修改 SKILL.md) │" -ForegroundColor Cyan Write-Host "└─────────────────────────────────┘" -ForegroundColor Cyan } # ── Git 集成 ────────────────────────────────────────── function Test-IsGitRepo { try { git -C $SKILL_DIR rev-parse --git-dir 2>$null | Out-Null return $true } catch { return $false } } function Invoke-GitPull { # 写前拉取:获取其他用户的最新进化数据 if (-not (Test-IsGitRepo)) { return } try { git -C $SKILL_DIR remote get-url origin 2>$null | Out-Null } catch { return } Write-Log "git pull: 获取共享进化数据..." try { git -C $SKILL_DIR pull --ff-only origin master 2>$null | Out-Null Write-Ok "git pull 完成" # population/thresholds/current_best 已 gitignore, # 每次 pull 后必须从事件重建(即使无新 commit) $genreDirs = Get-ChildItem -Path "$EVOLUTION_DIR\genres" -Directory -ErrorAction SilentlyContinue | Where-Object { Test-Path (Join-Path $_.FullName "events") } | ForEach-Object { $_.Name } foreach ($gid in $genreDirs) { Write-Log " 编译题材: $gid" try { & $PYTHON "$SCRIPT_DIR\event_store.py" compile --genre $gid 2>$null } catch { } } } catch { Write-Warn "git pull 失败(网络不通或无可更新),继续本地进化" } } function Invoke-GitPushEvents { # 写后推送:将本次进化事件提交并推送到远程 if (-not (Test-IsGitRepo)) { return } try { git -C $SKILL_DIR remote get-url origin 2>$null | Out-Null } catch { return } $hasChanges = git -C $SKILL_DIR status --porcelain -- evolution/ 2>$null if (-not $hasChanges) { Write-Log "git: 无变更,跳过推送" return } Write-Log "git: 提交并推送进化数据..." # 汇总变更的题材 $summary = ($hasChanges | ForEach-Object { $_ -replace '.*evolution/', '' | ForEach-Object { ($_ -split '/')[0] } } | Sort-Object -Unique) -join ',' try { git -C $SKILL_DIR add evolution/ 2>$null | Out-Null git -C $SKILL_DIR commit -m "evolve($($summary)): auto-commit evolution data" 2>$null | Out-Null git -C $SKILL_DIR push origin master 2>$null | Out-Null $fileCount = ($hasChanges | Measure-Object).Count Write-Ok "git push 完成 ($fileCount 个文件)" } catch { Write-Warn "git push 失败(网络不通),下次推送会包含本次变更" } } # ── 查找项目 ────────────────────────────────────────── function Find-Project { param([string]$Target = ".") try { $d = (Resolve-Path $Target -ErrorAction Stop).Path } catch { $d = $Target } while ($d -and $d -ne (Split-Path $d -Qualifier)) { if (Test-Path (Join-Path $d "项目铁律.md") -PathType Leaf) { return $d } $parent = Split-Path $d -Parent if ($parent -eq $d) { break } $d = $parent } return $null } # ── 检测题材ID ──────────────────────────────────────── function Get-GenreId { param([string]$ProjectPath) try { $scriptDirUnix = $SCRIPT_DIR -replace '\\', '/' $projectPathUnix = $ProjectPath -replace '\\', '/' $result = & $PYTHON -c @" import sys; sys.path.insert(0, '$scriptDirUnix') from utils import find_project_genre, map_genre_to_inkos_id from pathlib import Path print(map_genre_to_inkos_id(find_project_genre(Path('$projectPathUnix')))) "@ 2>$null if ($result) { return $result.Trim() } } catch { } return "other" } # ── 阶段实现 ────────────────────────────────────────── function Invoke-PhaseCollect { param([string]$Project) Write-Log "收集: $(Split-Path $Project -Leaf)" $chapterDir = Join-Path $Project "正文" $chapterCount = 0 if (Test-Path $chapterDir) { $chapterCount = (Get-ChildItem -Path $chapterDir -Filter "*第*章*.md" -Recurse -ErrorAction SilentlyContinue | Measure-Object).Count } $hasHooks = if (Test-Path (Join-Path $Project "追踪/钩子账本.md")) { "$([char]0x2713)" } else { "$([char]0x2717)" } $hasQuality = if (Test-Path (Join-Path $Project "追踪/优化日志.md")) { "$([char]0x2713)" } else { "$([char]0x2717)" } $hasEvolution = if (Test-Path (Join-Path $Project "evolution")) { "$([char]0x2713)" } else { "$([char]0x2717)" } Write-Log " 章节: $chapterCount | 钩子: $hasHooks | 质检: $hasQuality | 进化: $hasEvolution" New-Item -ItemType Directory -Force -Path (Join-Path $Project "追踪"), (Join-Path $EVOLUTION_DIR "prompts"), (Join-Path $EVOLUTION_DIR "patterns") | Out-Null # 检测题材,确保题材级进化目录存在 $genreId = Get-GenreId -ProjectPath $Project Write-Log " 题材: $genreId" # 首次使用:初始化题材进化目录 $genreDir = Join-Path (Join-Path $EVOLUTION_DIR "genres") $genreId if (-not (Test-Path $genreDir)) { Write-Log " 初始化题材进化目录: evolution/genres/$genreId/" try { $scriptDirUnix = $SCRIPT_DIR -replace '\\', '/' & $PYTHON -c @" import sys; sys.path.insert(0, '$scriptDirUnix') from seed_extractor import SeedExtractor SeedExtractor().initialize_genre('$genreId') "@ 2>$null } catch { } } } function Invoke-PhaseAnalyze { param([string]$Project) Write-Log "分析..." # 质量阈值分析(有质检数据时) if (Test-Path (Join-Path $Project "追踪/优化日志.md")) { try { & $PYTHON "$SCRIPT_DIR\quality_tuner.py" analyze --project "$Project" 2>$null } catch { } } # 写作模式挖掘(有章节时) $chapterDir = Join-Path $Project "正文" if ((Test-Path $chapterDir) -and (Get-ChildItem -Path $chapterDir -Filter "*第*章*.md" -Recurse -ErrorAction SilentlyContinue | Select-Object -First 1)) { try { & $PYTHON "$SCRIPT_DIR\pattern_mine.py" mine --project "$Project" --min-score 65 2>$null } catch { } } Write-Ok "分析完成" } function Invoke-PhaseMutate { param([string]$Project = "") Write-Log "变异: 提示词进化..." $gen = if ($env:GENERATIONS) { $env:GENERATIONS } else { "2" } if ($Project) { try { & $PYTHON "$SCRIPT_DIR\prompt_evolver.py" evolve --project "$Project" --generations $gen 2>$null } catch { } } else { try { & $PYTHON "$SCRIPT_DIR\prompt_evolver.py" evolve --generations $gen 2>$null } catch { } } Write-Ok "变异完成" } function Invoke-PhaseReport { param([string]$Project = "") Write-Host "" Write-Host "═══ 自优化状态 ═══" -ForegroundColor Cyan Write-Host "" # 阈值 Write-Host "## 质量阈值 (evolution/thresholds.json)" try { & $PYTHON "$SCRIPT_DIR\quality_tuner.py" show 2>$null } catch { Write-Host " 使用默认阈值" } # 题材级 if ($Project) { $genreId = Get-GenreId -ProjectPath $Project if ($genreId) { $genreDir = Join-Path (Join-Path $EVOLUTION_DIR "genres") $genreId if (Test-Path $genreDir) { Write-Host "" Write-Host "## 题材级进化 (evolution/genres/$genreId/)" -ForegroundColor Cyan Write-Host " 目录: $genreDir" Get-ChildItem -Path $genreDir -Filter "*.json" -ErrorAction SilentlyContinue | ForEach-Object { Write-Host " $($_.Name)" } Write-Host "" Write-Host " 最优提示词:" try { & $PYTHON "$SCRIPT_DIR\prompt_evolver.py" show --genre $genreId 2>$null } catch { } } } } # 提示词(skill级) Write-Host "" Write-Host "## 最优提示词 (evolution/prompts/current_best.json)" try { & $PYTHON "$SCRIPT_DIR\prompt_evolver.py" show 2>$null } catch { Write-Host " 使用种子提示词" } # 模式库 Write-Host "" Write-Host "## 写作模式库 (evolution/patterns/pattern_library.json)" try { & $PYTHON "$SCRIPT_DIR\pattern_mine.py" show --top 5 2>$null } catch { Write-Host " 暂无模式数据" } # 会话状态 Write-Host "" Write-Host "## 会话状态 (evolution/session_state.json)" $sessionFile = Join-Path $EVOLUTION_DIR "session_state.json" if (Test-Path $sessionFile) { try { $sessionFileUnix = $sessionFile -replace '\\', '/' & $PYTHON -c @" import json state = json.loads(open('$sessionFileUnix').read()) print(f' 反馈数: {state.get(\"total_feedback_count\", 0)}') recent = state.get('recent_chapters', []) if recent: scores = [r['composite'] for r in recent[-5:]] print(f' 最近5章均分: {sum(scores)/len(scores):.0f}') "@ 2>$null } catch { } } else { Write-Host " 暂无" } } function Invoke-PhaseSync { param([string]$Project = "") Write-Log "同步最优结果到 evolution/..." # 提示词进化 + 同步 try { & $PYTHON "$SCRIPT_DIR\prompt_evolver.py" evolve --generations 1 2>$null } catch { } # 质量阈值同步 if ($Project -and (Test-Path (Join-Path $Project "追踪/优化日志.md"))) { try { & $PYTHON "$SCRIPT_DIR\quality_tuner.py" analyze --project "$Project" 2>$null } catch { } } # 模式库同步 if ($Project -and (Test-Path (Join-Path $Project "正文"))) { try { & $PYTHON "$SCRIPT_DIR\pattern_mine.py" mine --project "$Project" 2>$null } catch { } } Write-Ok "同步完成 → evolution/" } # ── 主流程 ───────────────────────────────────────────── function Main { param( [string]$Project = "", [string]$Mode = "Quick", [switch]$Quick, [switch]$Full, [switch]$Sync, [switch]$Report, [switch]$Compile ) Write-Banner # 写前拉取:获取其他用户的最新进化数据 Invoke-GitPull # 解析模式 if ($Quick) { $Mode = "Quick" } if ($Full) { $Mode = "Full" } if ($Sync) { $Mode = "Sync" } if ($Report) { $Mode = "Report" } if ($Compile) { $Mode = "Compile" } # 查找项目 if (-not $Project) { $Project = Find-Project -Target "." } switch ($Mode) { "Quick" { if ($Project) { Invoke-PhaseCollect -Project $Project Invoke-PhaseAnalyze -Project $Project Invoke-PhaseMutate -Project $Project } Invoke-PhaseSync -Project $Project } "Full" { if (-not $Project) { Write-Warn "未找到小说项目。仅执行 skill 级进化。" Invoke-PhaseMutate -Project "" } else { Invoke-PhaseCollect -Project $Project Invoke-PhaseAnalyze -Project $Project Invoke-PhaseMutate -Project $Project Invoke-PhaseSync -Project $Project } Write-Log "完整进化完成。下次建议: 每写完一卷(或每10章)执行。" } "Sync" { Invoke-PhaseSync -Project $Project } "Report" { Invoke-PhaseReport -Project $Project } "Compile" { Write-Log "编译: 从事件日志重建进化状态..." if ($Project) { $genreId = Get-GenreId -ProjectPath $Project if ($genreId) { & $PYTHON "$SCRIPT_DIR\event_store.py" compile --genre $genreId } } Write-Host "" Write-Host " 提示: git pull 后执行此命令即可获得最新共享状态。" } default { # 默认 Quick if ($Project) { Invoke-PhaseCollect -Project $Project Invoke-PhaseAnalyze -Project $Project Invoke-PhaseMutate -Project $Project } Invoke-PhaseSync -Project $Project } } Write-Ok "自优化完成" Write-Host "" Write-Host " evolution/ 目录内容 (skill级):" Get-ChildItem -Path $EVOLUTION_DIR -Filter "*.json" -ErrorAction SilentlyContinue | ForEach-Object { Write-Host " $($_.Name)" } Get-ChildItem -Path $EVOLUTION_DIR -Filter "*.md" -ErrorAction SilentlyContinue | ForEach-Object { Write-Host " $($_.Name)" } if ($Project) { $genreId = Get-GenreId -ProjectPath $Project if ($genreId) { $genreDir = Join-Path (Join-Path $EVOLUTION_DIR "genres") $genreId if (Test-Path $genreDir) { Write-Host "" Write-Host " evolution/ 目录内容 (题材级: $genreId):" Get-ChildItem -Path $genreDir -Recurse -Filter "*.json" -ErrorAction SilentlyContinue | ForEach-Object { $relPath = $_.FullName.Substring($genreDir.Length + 1) Write-Host " $relPath" } } } } # 写后推送:将本次进化事件自动提交到 git Invoke-GitPushEvents } # ── 入口 ─────────────────────────────────────────────── Main -Project $Project -Quick:$Quick -Full:$Full -Sync:$Sync -Report:$Report -Compile:$Compile