#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Update State - 状态更新脚本 用途: - 更新 state.json 中的各种状态信息 - 支持多种更新操作:添加审查报告、更新实体、修改进度等 - 提供 CLI 接口供 Skills 调用 典型用法: python update_state.py --project-root --add-review "1-10" "审查报告/第 1-10 章审查报告.md" python update_state.py --project-root --update-entity 角色 萧炎 '{"境界": "斗皇"}' python update_state.py --project-root --set-chapter-meta 100 '{"word_count": 2300}' """ from __future__ import annotations import argparse import json import os import sys from pathlib import Path from datetime import datetime # 添加 scripts 目录到 sys.path scripts_dir = Path(__file__).resolve().parent sys.path.insert(0, str(scripts_dir)) from runtime_compat import enable_windows_utf8_stdio, normalize_windows_path def load_state(state_file: Path) -> dict: """加载 state.json""" if not state_file.exists(): raise FileNotFoundError(f"state.json not found: {state_file}") with open(state_file, 'r', encoding='utf-8') as f: return json.load(f) def save_state(state_file: Path, state: dict) -> None: """保存 state.json(带文件锁保护)""" try: from filelock import FileLock except ImportError: FileLock = None lock_file = state_file.with_suffix('.lock') if FileLock: with FileLock(str(lock_file)): _write_state(state_file, state) else: _write_state(state_file, state) def _write_state(state_file: Path, state: dict) -> None: """实际写入文件""" import tempfile import shutil # 原子写入:先写临时文件,再替换 dir_path = state_file.parent dir_path.mkdir(parents=True, exist_ok=True) fd, tmp_path = tempfile.mkstemp(dir=str(dir_path), suffix='.tmp') try: with os.fdopen(fd, 'w', encoding='utf-8') as f: json.dump(state, f, ensure_ascii=False, indent=2) f.write('\n') shutil.move(str(tmp_path), str(state_file)) except Exception: if os.path.exists(tmp_path): os.unlink(tmp_path) raise def cmd_add_review(args: argparse.Namespace) -> int: """ 添加审查报告记录 用法: --add-review "章节范围" "报告路径" """ state_file = args.project_root / ".noma" / "novel_data" / "state.json" state = load_state(state_file) chapter_range = args.chapter_range report_path = args.report_path # 初始化 review_checkpoints state.setdefault("review_checkpoints", []) # 添加新记录 new_record = { "chapter_range": chapter_range, "report_path": report_path, "timestamp": datetime.now().isoformat(), "status": "completed" } state["review_checkpoints"].append(new_record) # 保存 save_state(state_file, state) print(f"✅ Added review checkpoint: {chapter_range} -> {report_path}") return 0 def cmd_update_entity(args: argparse.Namespace) -> int: """ 更新实体属性 用法: --update-entity <类型> """ state_file = args.project_root / ".noma" / "novel_data" / "state.json" state = load_state(state_file) entity_type = args.entity_type # 角色/地点/物品/势力 entity_id = args.entity_id updates = json.loads(args.data) # 确保 entities_v3 存在 state.setdefault("entities_v3", {}) state["entities_v3"].setdefault(entity_type, {}) # 获取或创建实体 if entity_id not in state["entities_v3"][entity_type]: state["entities_v3"][entity_type][entity_id] = { "id": entity_id, "name": entity_id, "type": entity_type, "tier": "次要" } # 更新属性 entity = state["entities_v3"][entity_type][entity_id] entity.update(updates) # 保存 save_state(state_file, state) print(f"✅ Updated entity: {entity_type}/{entity_id}") print(f" Fields updated: {list(updates.keys())}") return 0 def cmd_set_chapter_meta(args: argparse.Namespace) -> int: """ 设置章节元数据 用法: --set-chapter-meta <章节号> """ state_file = args.project_root / ".noma" / "novel_data" / "state.json" state = load_state(state_file) chapter_num = int(args.chapter) meta = json.loads(args.data) # 确保 chapter_meta 存在 state.setdefault("chapter_meta", {}) # 转换为字符串键 chapter_key = str(chapter_num) # 合并现有数据 if chapter_key not in state["chapter_meta"]: state["chapter_meta"][chapter_key] = {} state["chapter_meta"][chapter_key].update(meta) # 保存 save_state(state_file, state) print(f"✅ Set chapter meta for chapter {chapter_num}") print(f" Keys: {list(meta.keys())}") return 0 def cmd_set_progress(args: argparse.Namespace) -> int: """ 设置项目进度 用法: --set-progress --current-chapter """ state_file = args.project_root / ".noma" / "novel_data" / "state.json" state = load_state(state_file) # 更新 project 信息 state.setdefault("project", {}) state["project"]["current_chapter"] = args.current_chapter state["project"]["last_updated"] = datetime.now().isoformat() # 保存 save_state(state_file, state) print(f"✅ Updated progress: current_chapter = {args.current_chapter}") return 0 def main() -> None: parser = argparse.ArgumentParser(description="Update state.json") parser.add_argument("--project-root", type=str, required=True, help="项目根目录") subparsers = parser.add_subparsers(dest="command", help="命令") # add-review 命令 p_review = subparsers.add_parser("add-review", help="添加审查报告记录") p_review.add_argument("chapter_range", help="章节范围 (如 '1-10')") p_review.add_argument("report_path", help="报告文件路径") p_review.set_defaults(func=cmd_add_review) # update-entity 命令 p_entity = subparsers.add_parser("update-entity", help="更新实体属性") p_entity.add_argument("entity_type", help="实体类型 (角色/地点/物品/势力)") p_entity.add_argument("entity_id", help="实体 ID") p_entity.add_argument("data", help="JSON 格式的属性数据") p_entity.set_defaults(func=cmd_update_entity) # set-chapter-meta 命令 p_meta = subparsers.add_parser("set-chapter-meta", help="设置章节元数据") p_meta.add_argument("chapter", help="章节号") p_meta.add_argument("data", help="JSON 格式的元数据") p_meta.set_defaults(func=cmd_set_chapter_meta) # set-progress 命令 p_progress = subparsers.add_parser("set-progress", help="设置项目进度") p_progress.add_argument("--current-chapter", type=int, required=True, help="当前章节号") p_progress.set_defaults(func=cmd_set_progress) # 解析参数 args = parser.parse_args() if not args.command: parser.print_help() sys.exit(1) # 规范化路径 args.project_root = normalize_windows_path(args.project_root).resolve() # 执行命令 enable_windows_utf8_stdio(skip_in_pytest=True) sys.exit(args.func(args)) if __name__ == "__main__": main()