|
|
@@ -0,0 +1,436 @@
|
|
|
+#!/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 # 编译:从事件日志重建进化状态
|
|
|
+# ═══════════════════════════════════════════════════════════════
|
|
|
+
|
|
|
+$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 完成"
|
|
|
+
|
|
|
+ # 有新数据 → 编译所有已变更题材的事件
|
|
|
+ $diffOutput = git -C $SKILL_DIR diff --name-only 'HEAD@{1}..HEAD' -- evolution/genres/ 2>$null
|
|
|
+ if ($diffOutput) {
|
|
|
+ $changedGenres = $diffOutput | ForEach-Object {
|
|
|
+ if ($_ -match 'evolution/genres/([^/]+)') { $matches[1] }
|
|
|
+ } | Sort-Object -Unique
|
|
|
+ foreach ($gid in $changedGenres) {
|
|
|
+ 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 {
|
|
|
+ $result = & $PYTHON -c @"
|
|
|
+import sys; sys.path.insert(0, '$($SCRIPT_DIR -replace '\\','/')')
|
|
|
+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('$($ProjectPath -replace '\\','/')'))))
|
|
|
+"@ 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 $EVOLUTION_DIR "genres" $genreId
|
|
|
+ if (-not (Test-Path $genreDir)) {
|
|
|
+ Write-Log " 初始化题材进化目录: evolution/genres/$genreId/"
|
|
|
+ try {
|
|
|
+ & $PYTHON -c @"
|
|
|
+import sys; sys.path.insert(0, '$($SCRIPT_DIR -replace '\\','/')')
|
|
|
+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 $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 {
|
|
|
+ & $PYTHON -c @"
|
|
|
+import json
|
|
|
+state = json.loads(open('$($sessionFile -replace '\\','/')').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 $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
|
|
|
+}
|
|
|
+
|
|
|
+# ── 入口 ───────────────────────────────────────────────
|
|
|
+
|
|
|
+param(
|
|
|
+ [string]$Project = "",
|
|
|
+ [switch]$Quick,
|
|
|
+ [switch]$Full,
|
|
|
+ [switch]$Sync,
|
|
|
+ [switch]$Report,
|
|
|
+ [switch]$Compile
|
|
|
+)
|
|
|
+
|
|
|
+Main -Project $Project -Quick:$Quick -Full:$Full -Sync:$Sync -Report:$Report -Compile:$Compile
|