feat: initial commit
This commit is contained in:
@@ -0,0 +1,253 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Update State - 状态更新脚本
|
||||
|
||||
用途:
|
||||
- 更新 state.json 中的各种状态信息
|
||||
- 支持多种更新操作:添加审查报告、更新实体、修改进度等
|
||||
- 提供 CLI 接口供 Skills 调用
|
||||
|
||||
典型用法:
|
||||
python update_state.py --project-root <root> --add-review "1-10" "审查报告/第 1-10 章审查报告.md"
|
||||
python update_state.py --project-root <root> --update-entity 角色 萧炎 '{"境界": "斗皇"}'
|
||||
python update_state.py --project-root <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 <类型> <ID> <JSON 数据>
|
||||
"""
|
||||
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 <章节号> <JSON 数据>
|
||||
"""
|
||||
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 <N>
|
||||
"""
|
||||
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()
|
||||
Reference in New Issue
Block a user