nonvioce 6 months ago
parent
commit
54ffbc2930
2 changed files with 584 additions and 0 deletions
  1. 28 0
      README.md
  2. 556 0
      inkos-write-next-atomic.sh

+ 28 - 0
README.md

@@ -106,6 +106,34 @@
 
 ---
 
+## InkOS 写作辅助
+
+配合 [inkos](https://github.com/Narcooo/inkos)(AI 长篇小说写作 CLI)使用,用原子命令组装完整的章节写入管线。
+
+### Shell
+
+| 文件 | 说明 |
+|------|------|
+| `inkos-write-next-atomic.sh` | **InkOS 原子写下一章**。用原子命令(`plan` → `compose` → `draft` → `audit` → `revise` → `polish`)替代直接调用 `inkos write next`,支持交互式检查点确认草稿、审计不通过自动修订(最多 3 轮)、多种修订模式(spot-fix / rewrite / rework / anti-detect)。依赖:`inkos`、`jq`、`less` |
+
+用法:
+```bash
+# 自动检测作品,交互式输入创作引导
+./inkos-write-next-atomic.sh
+
+# 指定作品 ID 和创作引导
+./inkos-write-next-atomic.sh my-novel "主角在雨夜中独自前行,回忆往事"
+
+# 查看帮助
+./inkos-write-next-atomic.sh --help
+```
+
+环境变量:
+- `INKOS_LLM_MODEL` — LLM 模型(默认 `deepseek-v3.2-exp`)
+- `INKOS_LLM_BASE_URL` — API 端点(默认 `https://platform.shuyanai.com/v1`)
+
+---
+
 ## 其他
 
 | 文件 | 说明 |

+ 556 - 0
inkos-write-next-atomic.sh

@@ -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 ""