|
|
@@ -0,0 +1,556 @@
|
|
|
+#!/usr/bin/env bash
|
|
|
+# =============================================================================
|
|
|
+# inkos-write-next-atomic.sh
|
|
|
+#
|
|
|
+# 用原子命令组装 inkos write next 的完整管线,绝不直接调用 inkos write next。
|
|
|
+#
|
|
|
+# 使用的原子命令:
|
|
|
+# inkos plan chapter – 预检:生成章节意图(无 LLM 调用)
|
|
|
+# inkos compose chapter – 预检:编译上下文/规则栈/追踪产物(无 LLM 调用)
|
|
|
+# inkos draft – 撰写章节草稿(LLM)
|
|
|
+# inkos audit – 连续性审计(LLM)
|
|
|
+# inkos revise – 修复问题 / 润色文笔(LLM)
|
|
|
+#
|
|
|
+# 管线:
|
|
|
+# plan → compose → draft → [audit → revise]×N → polish → 完成
|
|
|
+# =============================================================================
|
|
|
+
|
|
|
+set -euo pipefail
|
|
|
+
|
|
|
+# ---------------------------------------------------------------------------
|
|
|
+# LLM 环境变量(确保 inkos 使用正确的 API 端点和模型)
|
|
|
+# ---------------------------------------------------------------------------
|
|
|
+export INKOS_LLM_MODEL="${INKOS_LLM_MODEL:-deepseek-v3.2-exp}"
|
|
|
+export INKOS_LLM_BASE_URL="${INKOS_LLM_BASE_URL:-https://platform.shuyanai.com/v1}"
|
|
|
+
|
|
|
+# ---------------------------------------------------------------------------
|
|
|
+# 用法说明
|
|
|
+# ---------------------------------------------------------------------------
|
|
|
+usage() {
|
|
|
+ cat <<'EOF'
|
|
|
+用法:inkos-write-next-atomic.sh [book-id] [创作引导] [选项]
|
|
|
+
|
|
|
+ 用原子命令组装 inkos 写下一章的完整管线:
|
|
|
+ plan → [确认意图] → compose → draft → [确认草稿] → audit → revise×N → polish
|
|
|
+
|
|
|
+参数:
|
|
|
+ book-id 作品 ID(可选,仅一个作品时自动检测)
|
|
|
+ 创作引导 章节创作引导文本(可选,不提供则交互式输入)
|
|
|
+
|
|
|
+选项:
|
|
|
+ -h, --help 显示此帮助信息
|
|
|
+
|
|
|
+交互式检查点:
|
|
|
+ 第 1 步后 用 less 查看意图文件,可选择重新输入引导或继续
|
|
|
+ 第 3 步后 确认草稿,可选择输入提示词用 rewrite 重写
|
|
|
+
|
|
|
+环境变量:
|
|
|
+ INKOS_LLM_MODEL 模型(默认 deepseek-v4-flash)
|
|
|
+ INKOS_LLM_BASE_URL API 端点(默认 https://platform.shuyanai.com/v1)
|
|
|
+EOF
|
|
|
+ exit 0
|
|
|
+}
|
|
|
+
|
|
|
+# 检查 --help / -h
|
|
|
+for arg in "$@"; do
|
|
|
+ if [[ "$arg" == "--help" || "$arg" == "-h" ]]; then
|
|
|
+ usage
|
|
|
+ fi
|
|
|
+done
|
|
|
+
|
|
|
+# ---------------------------------------------------------------------------
|
|
|
+# 辅助函数
|
|
|
+# ---------------------------------------------------------------------------
|
|
|
+RED='\033[0;31m'
|
|
|
+GREEN='\033[0;32m'
|
|
|
+YELLOW='\033[0;33m'
|
|
|
+CYAN='\033[0;36m'
|
|
|
+BOLD='\033[1m'
|
|
|
+NC='\033[0m'
|
|
|
+
|
|
|
+banner() { echo -e "${CYAN}${BOLD}==>${NC} ${BOLD}$*${NC}"; }
|
|
|
+ok() { echo -e " ${GREEN}✓${NC} $*"; }
|
|
|
+warn() { echo -e " ${YELLOW}⚠${NC} $*"; }
|
|
|
+fail() { echo -e " ${RED}✗${NC} $*"; }
|
|
|
+stage() { echo -e "\n${CYAN}━━━ $* ━━━${NC}"; }
|
|
|
+info() { echo -e " $*"; }
|
|
|
+
|
|
|
+die() {
|
|
|
+ echo -e "${RED}${BOLD}致命错误:${NC} $*" >&2
|
|
|
+ exit 1
|
|
|
+}
|
|
|
+
|
|
|
+require_cmd() {
|
|
|
+ command -v "$1" >/dev/null 2>&1 || die "缺少命令:$1,请先安装。"
|
|
|
+}
|
|
|
+
|
|
|
+# 解析 JSON 字段(取首个匹配值)
|
|
|
+json_field() {
|
|
|
+ local json="$1" key="$2"
|
|
|
+ echo "$json" | jq -r ".${key} // empty" 2>/dev/null
|
|
|
+}
|
|
|
+
|
|
|
+# spinner:在后台任务运行时显示旋转动画和耗时
|
|
|
+spinner() {
|
|
|
+ local pid="$1" msg="$2" spin='⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏' i=0
|
|
|
+ local start="${3:-$SECONDS}"
|
|
|
+ while kill -0 "$pid" 2>/dev/null; do
|
|
|
+ local elapsed=$((SECONDS - start))
|
|
|
+ printf "\r ${spin:$i:1} %s (已耗时 %ds)..." "$msg" "$elapsed" >&2
|
|
|
+ i=$(( (i + 1) % ${#spin} ))
|
|
|
+ sleep 0.2
|
|
|
+ done
|
|
|
+ wait "$pid"
|
|
|
+}
|
|
|
+
|
|
|
+# run_with_spinner <描述> <命令> [参数...]
|
|
|
+# 在后台运行命令,显示 spinner,捕获 stdout 到临时文件,保持原始退出码
|
|
|
+run_with_spinner() {
|
|
|
+ local msg="$1" start_ts="$SECONDS"
|
|
|
+ shift
|
|
|
+ local tmpfile
|
|
|
+ tmpfile=$(mktemp)
|
|
|
+ # 后台运行命令,stdout → tmpfile,stderr → /dev/null
|
|
|
+ "$@" > "$tmpfile" 2>/dev/null &
|
|
|
+ local pid=$!
|
|
|
+ spinner "$pid" "$msg" "$start_ts"
|
|
|
+ local rc=$?
|
|
|
+ # 清掉 spinner 行,输出结果
|
|
|
+ local elapsed=$((SECONDS - start_ts))
|
|
|
+ printf "\r\033[K ${GREEN}✓${NC} %s (耗时 %ds)\n" "$msg" "$elapsed" >&2
|
|
|
+ cat "$tmpfile"
|
|
|
+ rm -f "$tmpfile"
|
|
|
+ return $rc
|
|
|
+}
|
|
|
+
|
|
|
+# ---------------------------------------------------------------------------
|
|
|
+# 自动检测项目中的作品 ID
|
|
|
+# ---------------------------------------------------------------------------
|
|
|
+detect_book_id() {
|
|
|
+ local books_dir
|
|
|
+ books_dir="$(findProjectRoot)/books"
|
|
|
+ if [[ ! -d "$books_dir" ]]; then
|
|
|
+ die "未找到 books/ 目录。请在 inkos 项目内运行此脚本。"
|
|
|
+ fi
|
|
|
+
|
|
|
+ local books
|
|
|
+ books=($(ls -1 "$books_dir" 2>/dev/null || true))
|
|
|
+ if [[ ${#books[@]} -eq 0 ]]; then
|
|
|
+ die "项目中还没有作品。请先用 inkos book create --title ... 创建。"
|
|
|
+ fi
|
|
|
+
|
|
|
+ if [[ ${#books[@]} -eq 1 ]]; then
|
|
|
+ echo "${books[0]}"
|
|
|
+ return
|
|
|
+ fi
|
|
|
+
|
|
|
+ # 多作品时让用户选择
|
|
|
+ echo "" >&2
|
|
|
+ info "检测到多个作品:" >&2
|
|
|
+ for i in "${!books[@]}"; do
|
|
|
+ info " [$((i+1))] ${books[$i]}" >&2
|
|
|
+ done
|
|
|
+ echo "" >&2
|
|
|
+ read -r -p "请选择作品 [1-${#books[@]}]:" choice || true
|
|
|
+ if [[ ! "$choice" =~ ^[0-9]+$ ]] || (( choice < 1 || choice > ${#books[@]} )); then
|
|
|
+ die "选择无效。"
|
|
|
+ fi
|
|
|
+ echo "${books[$((choice-1))]}"
|
|
|
+}
|
|
|
+
|
|
|
+findProjectRoot() {
|
|
|
+ local dir="$PWD"
|
|
|
+ while [[ "$dir" != "/" ]]; do
|
|
|
+ if [[ -f "$dir/inkos.json" ]]; then
|
|
|
+ echo "$dir"
|
|
|
+ return
|
|
|
+ fi
|
|
|
+ dir="$(dirname "$dir")"
|
|
|
+ done
|
|
|
+ die "当前目录及父目录均未找到 inkos.json。请在 inkos 项目内运行此脚本。"
|
|
|
+}
|
|
|
+
|
|
|
+# ---------------------------------------------------------------------------
|
|
|
+# 章节号提取
|
|
|
+# ---------------------------------------------------------------------------
|
|
|
+get_latest_chapter() {
|
|
|
+ local book_id="$1" root
|
|
|
+ root="$(findProjectRoot)"
|
|
|
+ local index="$root/books/$book_id/chapters/index.json"
|
|
|
+ if [[ ! -f "$index" ]]; then
|
|
|
+ echo "1"
|
|
|
+ return
|
|
|
+ fi
|
|
|
+ jq -r 'if length == 0 then 1 else (max_by(.number) | .number + 1) end' "$index" 2>/dev/null || echo "1"
|
|
|
+}
|
|
|
+
|
|
|
+get_last_written_chapter() {
|
|
|
+ local book_id="$1" root
|
|
|
+ root="$(findProjectRoot)"
|
|
|
+ local index="$root/books/$book_id/chapters/index.json"
|
|
|
+ if [[ ! -f "$index" ]]; then
|
|
|
+ echo "0"
|
|
|
+ return
|
|
|
+ fi
|
|
|
+ jq -r 'max_by(.number) | .number // 0' "$index" 2>/dev/null || echo "0"
|
|
|
+}
|
|
|
+
|
|
|
+# ---------------------------------------------------------------------------
|
|
|
+# 前置检查
|
|
|
+# ---------------------------------------------------------------------------
|
|
|
+require_cmd inkos
|
|
|
+require_cmd jq
|
|
|
+require_cmd less
|
|
|
+
|
|
|
+echo ""
|
|
|
+echo -e "${BOLD}${CYAN}╔══════════════════════════════════════╗${NC}"
|
|
|
+echo -e "${BOLD}${CYAN}║ InkOS 原子写下一章(原子管线) ║${NC}"
|
|
|
+echo -e "${BOLD}${CYAN}╚══════════════════════════════════════╝${NC}"
|
|
|
+echo ""
|
|
|
+
|
|
|
+ROOT="$(findProjectRoot)"
|
|
|
+BOOK_ID="${1:-$(detect_book_id)}"
|
|
|
+NEXT_CH="$(get_latest_chapter "$BOOK_ID")"
|
|
|
+LAST_CH="$(get_last_written_chapter "$BOOK_ID")"
|
|
|
+
|
|
|
+info "项目 :${ROOT}"
|
|
|
+info "作品 :${BOOK_ID}"
|
|
|
+info "下一章 :第 ${NEXT_CH} 章"
|
|
|
+if (( LAST_CH > 0 )); then
|
|
|
+ info "最新章 :第 ${LAST_CH} 章"
|
|
|
+fi
|
|
|
+
|
|
|
+# 可选章节引导
|
|
|
+CONTEXT="${2:-}"
|
|
|
+if [[ -z "$CONTEXT" ]]; then
|
|
|
+ echo ""
|
|
|
+ read -r -p "章节创作引导(可选,回车跳过):" CONTEXT || true
|
|
|
+fi
|
|
|
+
|
|
|
+# 用数组安全传参(处理空格等特殊字符)
|
|
|
+CONTEXT_ARGS=()
|
|
|
+if [[ -n "$CONTEXT" ]]; then
|
|
|
+ CONTEXT_ARGS=(--context "$CONTEXT")
|
|
|
+fi
|
|
|
+
|
|
|
+# ---------------------------------------------------------------------------
|
|
|
+# 第 1 步:plan chapter(循环,允许用户查看意图后重新输入引导)
|
|
|
+# ---------------------------------------------------------------------------
|
|
|
+while true; do
|
|
|
+ stage "第 1/5 步:规划章节意图"
|
|
|
+
|
|
|
+ PLAN_OUTPUT=""
|
|
|
+ PLAN_OUTPUT=$(run_with_spinner "正在规划章节意图" inkos plan chapter "$BOOK_ID" "${CONTEXT_ARGS[@]}" --json) || {
|
|
|
+ fail "章节意图规划失败"
|
|
|
+ echo "$PLAN_OUTPUT"
|
|
|
+ exit 1
|
|
|
+ }
|
|
|
+ if [[ -z "$(json_field "$PLAN_OUTPUT" chapterNumber)" ]]; then
|
|
|
+ fail "章节意图 JSON 解析失败(可能 stdout/stderr 混合)"
|
|
|
+ exit 1
|
|
|
+ fi
|
|
|
+ PLAN_GOAL="$(json_field "$PLAN_OUTPUT" goal)"
|
|
|
+ PLAN_CH="$(json_field "$PLAN_OUTPUT" chapterNumber)"
|
|
|
+ PLAN_INTENT_FILE="$(json_field "$PLAN_OUTPUT" intentPath)"
|
|
|
+
|
|
|
+ ok "已生成第 ${PLAN_CH} 章意图"
|
|
|
+ if [[ -n "$PLAN_GOAL" ]]; then
|
|
|
+ info "章节目标:${PLAN_GOAL}"
|
|
|
+ fi
|
|
|
+ if [[ -n "$PLAN_INTENT_FILE" ]]; then
|
|
|
+ info "意图文件:${PLAN_INTENT_FILE}"
|
|
|
+ fi
|
|
|
+
|
|
|
+ # ---- 检查点:查看意图文件 ----
|
|
|
+ echo ""
|
|
|
+ INTENT_FULL_PATH="$ROOT/books/$BOOK_ID/$PLAN_INTENT_FILE"
|
|
|
+ if [[ -n "$PLAN_INTENT_FILE" && -f "$INTENT_FULL_PATH" ]]; then
|
|
|
+ info "正在用 less 显示意图文件(按 q 退出,↑↓ 滚动,/ 搜索)……"
|
|
|
+ echo ""
|
|
|
+ less -R "$INTENT_FULL_PATH" || true
|
|
|
+ else
|
|
|
+ warn "意图文件不存在(${INTENT_FULL_PATH}),跳过预览。"
|
|
|
+ fi
|
|
|
+
|
|
|
+ echo ""
|
|
|
+ info "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
|
+ info " c / 回车 — 继续下一步(compile)"
|
|
|
+ info " r — 重新输入创作引导,重新 plan"
|
|
|
+ info " q — 退出"
|
|
|
+ info "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
|
+ read -r -p "请选择 [c/r/q](默认 c):" PLAN_CHOICE || true
|
|
|
+
|
|
|
+ case "${PLAN_CHOICE:-c}" in
|
|
|
+ c|C|"")
|
|
|
+ break
|
|
|
+ ;;
|
|
|
+ r|R)
|
|
|
+ echo ""
|
|
|
+ read -r -p "请输入新的创作引导:" CONTEXT || true
|
|
|
+ CONTEXT_ARGS=()
|
|
|
+ if [[ -n "$CONTEXT" ]]; then
|
|
|
+ CONTEXT_ARGS=(--context "$CONTEXT")
|
|
|
+ fi
|
|
|
+ ;;
|
|
|
+ q|Q)
|
|
|
+ info "用户退出。"
|
|
|
+ exit 0
|
|
|
+ ;;
|
|
|
+ *)
|
|
|
+ warn "无效输入,默认继续。"
|
|
|
+ break
|
|
|
+ ;;
|
|
|
+ esac
|
|
|
+done
|
|
|
+
|
|
|
+# ---------------------------------------------------------------------------
|
|
|
+# 第 2 步:compose chapter(无 LLM 调用 — 编译上下文/规则栈/追踪产物)
|
|
|
+# ---------------------------------------------------------------------------
|
|
|
+stage "第 2/5 步:编译章节上下文"
|
|
|
+
|
|
|
+COMPOSE_OUTPUT=""
|
|
|
+COMPOSE_OUTPUT=$(inkos compose chapter "$BOOK_ID" "${CONTEXT_ARGS[@]}" --json 2>/dev/null) || {
|
|
|
+ fail "章节上下文编译失败"
|
|
|
+ echo "$COMPOSE_OUTPUT"
|
|
|
+ exit 1
|
|
|
+}
|
|
|
+if [[ -z "$(json_field "$COMPOSE_OUTPUT" contextPath)" ]]; then
|
|
|
+ fail "章节上下文 JSON 解析失败(可能 stdout/stderr 混合)"
|
|
|
+ exit 1
|
|
|
+fi
|
|
|
+COMPOSE_CH="$(json_field "$COMPOSE_OUTPUT" chapterNumber)"
|
|
|
+COMPOSE_CTX="$(json_field "$COMPOSE_OUTPUT" contextPath)"
|
|
|
+COMPOSE_RULES="$(json_field "$COMPOSE_OUTPUT" ruleStackPath)"
|
|
|
+COMPOSE_TRACE="$(json_field "$COMPOSE_OUTPUT" tracePath)"
|
|
|
+
|
|
|
+ok "已编译第 ${COMPOSE_CH} 章上下文"
|
|
|
+info "上下文 :${COMPOSE_CTX}"
|
|
|
+info "规则栈 :${COMPOSE_RULES}"
|
|
|
+info "追踪文件 :${COMPOSE_TRACE}"
|
|
|
+
|
|
|
+# ---------------------------------------------------------------------------
|
|
|
+# 第 3 步:draft(LLM — 撰写章节草稿,然后循环确认 / rewrite 重写)
|
|
|
+# ---------------------------------------------------------------------------
|
|
|
+stage "第 3/5 步:撰写章节草稿"
|
|
|
+
|
|
|
+DRAFT_OUTPUT=""
|
|
|
+DRAFT_OUTPUT=$(run_with_spinner "正在生成草稿" inkos draft "$BOOK_ID" "${CONTEXT_ARGS[@]}" --json) || {
|
|
|
+ fail "草稿撰写失败"
|
|
|
+ echo "$DRAFT_OUTPUT"
|
|
|
+ exit 1
|
|
|
+}
|
|
|
+DRAFT_CH="$(json_field "$DRAFT_OUTPUT" chapterNumber)"
|
|
|
+DRAFT_TITLE="$(json_field "$DRAFT_OUTPUT" title)"
|
|
|
+DRAFT_WORDS="$(json_field "$DRAFT_OUTPUT" wordCount)"
|
|
|
+DRAFT_FILE="$(json_field "$DRAFT_OUTPUT" filePath)"
|
|
|
+
|
|
|
+if [[ -z "$DRAFT_CH" || -z "$DRAFT_FILE" ]]; then
|
|
|
+ fail "草稿 JSON 解析失败(缺少 chapterNumber 或 filePath)"
|
|
|
+ exit 1
|
|
|
+fi
|
|
|
+
|
|
|
+# ---- 确认循环:less 预览 → [c/r/q] → rewrite 后回到预览 ----
|
|
|
+while true; do
|
|
|
+ info "第 ${DRAFT_CH} 章:${DRAFT_TITLE}"
|
|
|
+ info "字数:${DRAFT_WORDS}"
|
|
|
+ info "文件:${DRAFT_FILE}"
|
|
|
+
|
|
|
+ # 检查字数警告
|
|
|
+ LEN_WARNINGS="$(json_field "$DRAFT_OUTPUT" lengthWarnings)"
|
|
|
+ if [[ -n "$LEN_WARNINGS" && "$LEN_WARNINGS" != "null" ]]; then
|
|
|
+ warn "字数警告:"
|
|
|
+ echo "$DRAFT_OUTPUT" | jq -r '.lengthWarnings[]? | " - \(.)"' 2>/dev/null || true
|
|
|
+ fi
|
|
|
+
|
|
|
+ echo ""
|
|
|
+ if [[ -n "$DRAFT_FILE" && -f "$DRAFT_FILE" ]]; then
|
|
|
+ info "正在用 less 显示草稿(按 q 退出,↑↓ 滚动,/ 搜索)……"
|
|
|
+ echo ""
|
|
|
+ less -R "$DRAFT_FILE" || true
|
|
|
+ else
|
|
|
+ warn "草稿文件不存在(${DRAFT_FILE}),跳过预览。"
|
|
|
+ fi
|
|
|
+ echo ""
|
|
|
+ info "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
|
+ info " c / 回车 — 继续下一步(审计)"
|
|
|
+ info " r — 输入提示词,用 rewrite 重写本章"
|
|
|
+ info " q — 退出"
|
|
|
+ info "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
|
+ read -r -p "请选择 [c/r/q](默认 c):" DRAFT_CHOICE || true
|
|
|
+
|
|
|
+ case "${DRAFT_CHOICE:-c}" in
|
|
|
+ c|C|"")
|
|
|
+ break
|
|
|
+ ;;
|
|
|
+ r|R)
|
|
|
+ echo ""
|
|
|
+ read -r -p "请输入重写提示词:" REWRITE_BRIEF || true
|
|
|
+ if [[ -z "$REWRITE_BRIEF" ]]; then
|
|
|
+ warn "未输入提示词,跳过重写。"
|
|
|
+ continue
|
|
|
+ fi
|
|
|
+ REWRITE_OUTPUT=""
|
|
|
+ REWRITE_OUTPUT=$(run_with_spinner "正在 rewrite 重写" inkos revise "$BOOK_ID" "$DRAFT_CH" --mode rewrite --brief "$REWRITE_BRIEF" --json) || {
|
|
|
+ warn "重写失败,将使用当前草稿继续。"
|
|
|
+ echo "$REWRITE_OUTPUT"
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ REWRITE_APPLIED="$(json_field "$REWRITE_OUTPUT" applied)"
|
|
|
+ REWRITE_WORDS="$(json_field "$REWRITE_OUTPUT" wordCount)"
|
|
|
+ if [[ "$REWRITE_APPLIED" == "true" ]]; then
|
|
|
+ ok "重写已应用 —— ${REWRITE_WORDS} 字"
|
|
|
+ DRAFT_TITLE="$(json_field "$REWRITE_OUTPUT" title)"
|
|
|
+ DRAFT_WORDS="$REWRITE_WORDS"
|
|
|
+ # 重写后回到预览,让用户再次确认
|
|
|
+ else
|
|
|
+ warn "重写未应用 —— $(json_field "$REWRITE_OUTPUT" skippedReason)"
|
|
|
+ continue
|
|
|
+ fi
|
|
|
+ ;;
|
|
|
+ q|Q)
|
|
|
+ info "用户退出。"
|
|
|
+ exit 0
|
|
|
+ ;;
|
|
|
+ *)
|
|
|
+ warn "无效输入,默认继续。"
|
|
|
+ break
|
|
|
+ ;;
|
|
|
+ esac
|
|
|
+done
|
|
|
+
|
|
|
+# ---------------------------------------------------------------------------
|
|
|
+# 第 4 步:审计 + 修订循环
|
|
|
+# ---------------------------------------------------------------------------
|
|
|
+MAX_REVISE_ROUNDS=3
|
|
|
+AUDIT_PASSED=false
|
|
|
+REVISED=false
|
|
|
+ROUND=0
|
|
|
+
|
|
|
+stage "第 4/5 步:审计 + 修订循环"
|
|
|
+
|
|
|
+while true; do
|
|
|
+ ROUND=$((ROUND + 1))
|
|
|
+
|
|
|
+ # ---- 审计 ----
|
|
|
+ AUDIT_OUTPUT=""
|
|
|
+ AUDIT_OUTPUT=$(run_with_spinner "正在审计(第 ${ROUND} 轮)" inkos audit "$BOOK_ID" "$DRAFT_CH" --json) || {
|
|
|
+ fail "审计失败"
|
|
|
+ echo "$AUDIT_OUTPUT"
|
|
|
+ exit 1
|
|
|
+ }
|
|
|
+ AUDIT_PASSED_FLAG="$(json_field "$AUDIT_OUTPUT" passed)"
|
|
|
+ AUDIT_SUMMARY="$(json_field "$AUDIT_OUTPUT" summary)"
|
|
|
+
|
|
|
+ if [[ "$AUDIT_PASSED_FLAG" == "true" ]]; then
|
|
|
+ ok "审计通过 —— ${AUDIT_SUMMARY}"
|
|
|
+ AUDIT_PASSED=true
|
|
|
+ break
|
|
|
+ fi
|
|
|
+
|
|
|
+ warn "审计未通过(第 ${ROUND} 轮)—— ${AUDIT_SUMMARY}"
|
|
|
+
|
|
|
+ # 打印问题列表
|
|
|
+ ISSUE_COUNT=$(echo "$AUDIT_OUTPUT" | jq '.issues | length' 2>/dev/null || echo "0")
|
|
|
+ if (( ISSUE_COUNT > 0 )); then
|
|
|
+ echo "$AUDIT_OUTPUT" | jq -r '.issues[] | " [\(.severity)] \(.category): \(.description)"' 2>/dev/null
|
|
|
+ fi
|
|
|
+
|
|
|
+ # 检查是否还有修订轮次
|
|
|
+ if (( ROUND >= MAX_REVISE_ROUNDS )); then
|
|
|
+ warn "已达最大修订轮次(${MAX_REVISE_ROUNDS})。章节将保留为草稿状态。"
|
|
|
+ break
|
|
|
+ fi
|
|
|
+
|
|
|
+ echo ""
|
|
|
+ read -r -p "是否现在修订?[Y/n]:" DO_REVISE || true
|
|
|
+ if [[ "$DO_REVISE" =~ ^[Nn] ]]; then
|
|
|
+ warn "跳过修订。章节将保留为草稿状态。"
|
|
|
+ break
|
|
|
+ fi
|
|
|
+
|
|
|
+ # 选择修订模式
|
|
|
+ echo ""
|
|
|
+ info "修订模式:"
|
|
|
+ info " 1) spot-fix – 定点修复,最小改动(默认)"
|
|
|
+ info " 2) rewrite – 全章改写,保留剧情走向"
|
|
|
+ info " 3) rework – 深层重构"
|
|
|
+ info " 4) anti-detect – 去 AI 痕迹"
|
|
|
+ read -r -p "请选择模式 [1-4](默认 1):" MODE_CHOICE || true
|
|
|
+
|
|
|
+ case "${MODE_CHOICE:-1}" in
|
|
|
+ 1) REVISE_MODE="spot-fix" ;;
|
|
|
+ 2) REVISE_MODE="rewrite" ;;
|
|
|
+ 3) REVISE_MODE="rework" ;;
|
|
|
+ 4) REVISE_MODE="anti-detect" ;;
|
|
|
+ *) REVISE_MODE="spot-fix" ;;
|
|
|
+ esac
|
|
|
+
|
|
|
+ # ---- 修订 ----
|
|
|
+ REVISE_OUTPUT=""
|
|
|
+ REVISE_OUTPUT=$(run_with_spinner "正在修订 [$REVISE_MODE]" inkos revise "$BOOK_ID" "$DRAFT_CH" --mode "$REVISE_MODE" --json) || {
|
|
|
+ fail "修订失败"
|
|
|
+ echo "$REVISE_OUTPUT"
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ REVISE_APPLIED="$(json_field "$REVISE_OUTPUT" applied)"
|
|
|
+ REVISE_WORDS="$(json_field "$REVISE_OUTPUT" wordCount)"
|
|
|
+ REVISE_STATUS="$(json_field "$REVISE_OUTPUT" status)"
|
|
|
+ REVISE_SKIPPED="$(json_field "$REVISE_OUTPUT" skippedReason)"
|
|
|
+
|
|
|
+ if [[ "$REVISE_APPLIED" == "true" ]]; then
|
|
|
+ ok "修订已应用 —— ${REVISE_WORDS} 字,状态:${REVISE_STATUS}"
|
|
|
+ REVISED=true
|
|
|
+ echo "$REVISE_OUTPUT" | jq -r '.fixedIssues[]? | " 已修复:\(.)"' 2>/dev/null
|
|
|
+ else
|
|
|
+ warn "修订未应用 —— ${REVISE_SKIPPED:-无需修改}"
|
|
|
+ fi
|
|
|
+done
|
|
|
+
|
|
|
+# ---------------------------------------------------------------------------
|
|
|
+# 第 5 步:润色(可选,仅审计通过后执行)
|
|
|
+# ---------------------------------------------------------------------------
|
|
|
+stage "第 5/5 步:文笔润色"
|
|
|
+
|
|
|
+if [[ "$AUDIT_PASSED" == "true" ]]; then
|
|
|
+ echo ""
|
|
|
+ read -r -p "是否进行文笔润色?[Y/n]:" DO_POLISH || true
|
|
|
+ if [[ ! "$DO_POLISH" =~ ^[Nn] ]]; then
|
|
|
+ POLISH_OUTPUT=""
|
|
|
+ POLISH_OUTPUT=$(run_with_spinner "正在润色" inkos revise "$BOOK_ID" "$DRAFT_CH" --mode polish --json) || {
|
|
|
+ warn "润色失败(非致命错误)"
|
|
|
+ echo "$POLISH_OUTPUT"
|
|
|
+ }
|
|
|
+ POLISH_APPLIED="$(json_field "$POLISH_OUTPUT" applied)"
|
|
|
+ POLISH_WORDS="$(json_field "$POLISH_OUTPUT" wordCount)"
|
|
|
+ if [[ "$POLISH_APPLIED" == "true" ]]; then
|
|
|
+ ok "润色已应用 —— ${POLISH_WORDS} 字"
|
|
|
+ else
|
|
|
+ info "润色跳过(无需修改)"
|
|
|
+ fi
|
|
|
+ else
|
|
|
+ info "跳过润色。"
|
|
|
+ fi
|
|
|
+else
|
|
|
+ warn "审计未通过,跳过润色。"
|
|
|
+fi
|
|
|
+
|
|
|
+# ---------------------------------------------------------------------------
|
|
|
+# 完成
|
|
|
+# ---------------------------------------------------------------------------
|
|
|
+echo ""
|
|
|
+echo -e "${GREEN}${BOLD}╔══════════════════════════════════════╗${NC}"
|
|
|
+echo -e "${GREEN}${BOLD}║ 写入下一章完成 ║${NC}"
|
|
|
+echo -e "${GREEN}${BOLD}╚══════════════════════════════════════╝${NC}"
|
|
|
+echo ""
|
|
|
+info "作品 :${BOOK_ID}"
|
|
|
+info "章节 :第 ${DRAFT_CH} 章 — ${DRAFT_TITLE}"
|
|
|
+info "字数 :${DRAFT_WORDS}"
|
|
|
+info "文件 :${DRAFT_FILE}"
|
|
|
+info "审计 :$( [[ "$AUDIT_PASSED" == "true" ]] && echo "${GREEN}通过${NC}" || echo "${YELLOW}未通过${NC}" )"
|
|
|
+info "已修订 :$( [[ "$REVISED" == "true" ]] && echo "${GREEN}是${NC}" || echo "否" )"
|
|
|
+echo ""
|
|
|
+banner "后续操作:"
|
|
|
+info " inkos review approve ${BOOK_ID} ${DRAFT_CH}"
|
|
|
+info " inkos export ${BOOK_ID}"
|
|
|
+info " inkos status"
|
|
|
+echo ""
|