Stats
Actions
Tags
From quasi
Use when the user wants to process a book from title, author, ISBN, or source file into chapter analyses and a book overview.
How this skill is triggered — by the user, by Claude, or both
Slash command
/quasi:process-bookThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
下载、切分、逐章分析并综合用户提供的书。
下载、切分、逐章分析并综合用户提供的书。
从用户请求中提取可用于搜索和定位源文件的线索:
title 或自然语言 queryauthor:可选,但强烈优先使用year_hint:可选isbn:可选source_path 或 slug_hint:可选,用于命中已有 sources/ 文件topic:可选,传给 analyse/synthesisbook_slug 是 Step 0 产物,由 search/download/source filename 确定;
canonical 格式为 {author-surname}-{short-title}-{year}。processing/chapters/{book_slug}/manifest.json 的消费、
vault/books/{book_slug}/ 的完成判断、以及 localise cache 写入触发。sources/{book_slug}.{epub,pdf} 存在表示 acquisition 已完成。vault/books/{book_slug}/00-overview.md 存在表示整本书生成完成。download-agent 只负责 acquisition 判断和 accept;year ambiguity 是 human gate。extract-agent 负责提取、验证、修复,并产出统一 chapter manifest
(slot/title/filename/word_count)。analyse-agent 每章一个 background worker,不得合并多章。synthesis-agent 只写 00-overview.md;audit escalated 由本 skill 路由回生成阶段。{output_dir}/ch*.md 数量来判断完成主进程 (dispatcher)
├─ Step 0: local duplicate/resume recall + 必要时 search/download
├─ Step 1: extract-agent (sonnet, 前台) → 提取+验证+修复
├─ Step 2: 主进程读 manifest.json → 筛选章节
├─ Step 3: analyse-agent ×N (opus, 后台并行) → Glob 轮询
├─ Step 4: synthesis-agent(mode=book) (opus, 前台)
├─ Step 5: audit-agent (sonnet, 前台) → 校验
└─ Step 6: search-agent + quasi-helpers localise → 中译本 metadata 回填
# 0. 从用户请求提取搜索线索。book_slug 不是输入,而是 Step 0 的结果。
request = parse_user_request() # title/query, author?, year_hint?, isbn?, source_path?, slug_hint?, topic?
# Step 0: LOCAL RECALL + METADATA
# 先查本地成果/中间态:overview/source/chapter manifest/chapter outputs。exact miss 后用
# author surname + year/isbn + 非虚词 title keywords 在 vault/books、processing、sources 做 rg fuzzy recall。
# rg 只召回候选;inspect 后 high-confidence 才复用/续跑,多候选只列证据,不要盲目跳过。
local = find_local_book_state(request)
if local.high_confidence:
book_slug = local.slug
source_file = local.source_file
book_meta = local.metadata
if local.overview_path:
report(f"已有书籍页面,无需重复处理: {local.overview_path}"); return
chapters_dir = f"processing/chapters/{book_slug}/" # manifest/ch*.md 决定后续跳过点
else:
if local.candidates:
report_candidate_list(local.candidates, note="rg fuzzy recall only; do not blindly skip")
# 本地没有 high-confidence completed/source/partial state 时,才用 search-agent 补 canonical metadata/slug。
search = Agent("quasi:search-agent", foreground=True, prompt=f"""\
task: find canonical metadata for this book
context:
kind: book
title: {request.title or request.query}
author: {request.author or ""}
year_hint: {request.year_hint or ""}
isbn: {request.isbn or ""}
constraints:
count: 1
""")
book_meta = search.picked
book_slug = book_meta["slug"]
source_file = Glob(f"sources/{book_slug}.epub") or Glob(f"sources/{book_slug}.pdf")
chapters_dir = f"processing/chapters/{book_slug}/"
if not source_file and not exists(f"{chapters_dir}/manifest.json"):
# 只有没有 local source/extracted state 时才 download。
# download-agent 内部完成 fetch → inspect → accept,并返回 year_evidence。
# 主进程只根据 per_item[0].status 分支。
result = Agent("quasi:download-agent", foreground=True, prompt=f"""\
kind: book
items:
- slug: {book_slug}
expected_author: {book_meta.get('authors', [''])[0] if book_meta.get('authors') else request.author or ''}
expected_title: {book_meta.get('title') or request.title or request.query}
identifiers:
isbn: {book_meta.get('isbn') or request.isbn or ''}
output_dir: sources/
""")
item = result.per_item[0]
if item.status == "ok":
source_file = item.path # agent 已 accept temp → sources/{slug}.{ext}
elif item.status in ("year_mismatch", "year_ambiguous"):
# 把 year_evidence 整块原样递给用户(含 tmp_path),让用户拍板:
# 1) 改 slug 中的 year 重跑(slug 重命名 → 触发 download-agent 重新 accept)
# 2) 接受 recommended_year,手动 mv tmp_path → 正式名 + 重跑(跳过 Step 0)
report(f"""\
YEAR_TRIAGE for {book_slug}: verdict={item.year_evidence.verdict}
- slug_year: {item.year_evidence.slug_year}
- source_years: {item.year_evidence.source_years}
- pdf_signals: {item.year_evidence.pdf_signals}
- recommended_year: {item.year_evidence.recommended_year}
- reason: {item.year_evidence.recommendation_reason}
- tmp_file: {item.tmp_path}
Action: 改 slug 的 year 重跑,或手动 mv {item.tmp_path} 到正确路径后重跑。
""")
return
else: # download_failed
report(f"download-agent failed to acquire {book_slug}: {item.get('verdict_note', 'no details')}")
return
chapters_dir = f"processing/chapters/{book_slug}/"
# 1. EXTRACT(一次调用完成提取+验证+修复)
if not exists(f"{chapters_dir}/manifest.json"):
result = Agent("quasi:extract-agent", foreground=True,
prompt=f"source_file: {source_file}, chapters_dir: {chapters_dir}")
if result.status == "failed":
report("需人工检查"); return
# 2. 读取章节清单(全部章节,不筛选)
manifest = Read(f"{chapters_dir}/manifest.json")
selected = manifest.chapters # 每项含 slot, title, filename, word_count
output_dir = f"vault/books/{book_slug}"
# 3. 并行分析
# slot 格式:"01".."99" 真章节 / "00a".."00z" 前言 / "99a".."99z" 后记 / "{N}b".."{N}z" 章间插曲
# 根据 slot 推导人类可读的 chapter_label 传给 analyse-agent:
# slot 纯数字 N → chapter_label = f"第{int(slot)}章"
# slot 以 "00" 开头 → chapter_label = "前言"(或根据 title:Foreword/Preface/Introduction)
# slot 以 "99" 开头 → chapter_label = "后记"(或根据 title:Afterword/Epilogue/Appendix)
# slot 形如 "{N}{x}" → chapter_label = f"第{N}章(附)"
for ch in selected:
if not exists(f"{output_dir}/ch{ch.slot}-{ch.slug}.md"):
Agent("quasi:analyse-agent", background=True,
prompt=f"""\
type: A
book_slug: {book_slug}
book_title: {book_meta.title}
slot: {ch.slot}
chapter_label: {ch.chapter_label}
chapter_title: {ch.title}
year: {book_meta.year}
chapter_authors: {ch.authors or book_meta.authors}
input: {chapters_dir}/{ch.filename}
output: {output_dir}/ch{ch.slot}-{ch.slug}.md
topic: {request.topic or ''}
""")
while Glob(f"{output_dir}/ch*.md").count < len(selected):
sleep(30)
# 4. 概览(走大一统 synthesis-agent, mode=book)
if not exists(f"{output_dir}/00-overview.md"):
Agent("quasi:synthesis-agent", foreground=True,
prompt=f"mode: book\noutput_dir: {output_dir}\nbook_title: ...\ntopic: ...")
# Step 5: AUDIT
# 对整本书目录(overview + 所有章节)运行 audit-agent。
audit = Agent("quasi:audit-agent", foreground=True,
prompt=f"path: {output_dir}")
# audit-agent 只做本地最小修复。若返回 escalated,说明对应内容需要由本 workflow
# 的生成阶段重做,不要让 audit-agent 补写内容。
if audit.audit_result.escalated:
for item in audit.audit_result.escalated:
path = item.path
if path.endswith("/00-overview.md"):
Agent("quasi:synthesis-agent", foreground=True,
prompt=f"mode: book\noutput_dir: {output_dir}\nbook_title: ...\ntopic: ...\n"
f"overwrite: true\nreason: audit escalated {item.kind}: {item.reason}")
elif basename(path).startswith("ch"):
ch = find_manifest_chapter_for_output(manifest, path)
Agent("quasi:analyse-agent", foreground=True,
prompt=f"""\
type: A
book_slug: {book_slug}
book_title: {book_meta.title}
slot: {ch.slot}
chapter_label: {ch.chapter_label}
chapter_title: {ch.title}
year: {book_meta.year}
chapter_authors: {ch.authors or book_meta.authors}
input: {chapters_dir}/{ch.filename}
output: {path}
topic: {request.topic or ''}
overwrite: true
reason: audit escalated {item.kind}: {item.reason}
""")
else:
report(f"audit escalated unknown book path: {path}")
audit = Agent("quasi:audit-agent", foreground=True,
prompt=f"path: {output_dir}")
if audit.audit_result.escalated:
report("audit still has escalated items after one regeneration pass; hand off to user")
return
# Step 6: LOCALISE
# 只回填中译本 / 中文版本 metadata。search-agent 返回核验过的
# localisations.zh.candidates,顶层用 helper 写入 .quasi/localise/cndouban.json。
# helper 按原书 ISBN 幂等:已查过的 ISBN 不重复跑。
localise_scan = Bash(f"quasi-helpers localise scan --path {output_dir} --json")
if localise_scan.pending > 0:
search = Agent("quasi:search-agent", foreground=True,
prompt=f"kind: book\ncontext: read {output_dir}/00-overview.md and search metadata/localisations")
candidates_file = write_temp_json(search.localisations.zh.candidates)
Bash("quasi-helpers localise write "
f"--book-path {output_dir}/00-overview.md "
f"--candidates-file {candidates_file}")
# Step 7: OPEN IN MARPLE (best-effort UX)
# The book page is the overview. This must never fail the workflow; on failure,
# print the manual command and continue.
final_page = f"{output_dir}/00-overview.md"
Bash(f"/opt/homebrew/bin/marple-cli open '{final_page}' || marple-cli open '{final_page}' || echo 'Marple open skipped; run: marple-cli open {final_page}'")
print(f"Done: {len(selected)} chapters, overview generated, audited, localised")
| 阶段 | 检查 | 跳过条件 |
|---|---|---|
| Step 0 | local recall: 00-overview.md / sources/{slug}.{epub,pdf} / processing/chapters/{slug}/manifest.json / ch{slot}-*.md; exact miss 后 rg fuzzy recall | 在 search/download 前确认是否已完成或可续跑;高置信才跳过/复用,多候选只列证据 |
| Step 1 | {chapters_dir}/manifest.json | 存在则跳过 extract-agent |
| Step 3 | ch{slot}-*.md | 存在则跳过该章 |
| Step 4 | 00-overview.md | 存在则跳过 |
| Step 5 | 无 —— 幂等,可重复跑 | 上次 audit clean 时几乎无成本 |
| Step 6 | .quasi/localise/cndouban.json#by_isbn[isbn] | 已存在 entry(status found/none)则 helper scan 跳过 |
sources/{book-slug}.epub|.pdf ← canonical slug 对应的源文件
processing/chapters/{book-slug}/ ← 规范 slug: {author}-{title}-{year}
├── manifest.json
└── *.txt
vault/books/{book-slug}/ ← 含原 monographs 与 handbooks,统一归位
├── 00-overview.md
└── ch{slot}-{title}.md ← slot 见 manifest.json("01".."99"/"00a"/"99a"/...)
npx claudepluginhub giraphant/quasi --plugin quasiCreates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.