mirror of
https://github.com/lllin000/PaperForge.git
synced 2026-07-22 06:50:53 +00:00
refactor: unify skills into paperforge compound skill (6 workflows)
This commit is contained in:
parent
edd2e2e197
commit
ffd4eb7ca2
49 changed files with 952 additions and 1699 deletions
25
README.md
25
README.md
|
|
@ -33,17 +33,24 @@ The plugin is the **interface**. The Python package is the **engine**. Every but
|
|||
|
||||
## 1. Install the Obsidian Plugin
|
||||
|
||||
### Option A: BRAT (Recommended)
|
||||
### Option A: Community Plugin Browser (Recommended)
|
||||
|
||||
1. Open Obsidian → `Settings` → `Community plugins` → `Browse`
|
||||
2. Search for **PaperForge**
|
||||
3. Click `Install`, then `Enable`
|
||||
|
||||
> Community plugins auto-update through Obsidian. No extra steps needed.
|
||||
|
||||
### Option B: BRAT
|
||||
|
||||
If you need beta versions or the plugin hasn't appeared in search yet:
|
||||
|
||||
1. Install **BRAT** from the Obsidian community plugin browser
|
||||
2. Open BRAT settings → `Add Beta Plugin`
|
||||
3. Enter: `https://github.com/LLLin000/PaperForge`
|
||||
4. BRAT downloads the latest `main.js`, `manifest.json`, and `styles.css` and installs them
|
||||
5. Settings → Community Plugins → enable PaperForge
|
||||
4. Enable PaperForge in Settings → Community Plugins
|
||||
|
||||
> BRAT auto-detects GitHub Release updates. No manual downloads needed.
|
||||
|
||||
### Option B: Manual Download
|
||||
### Option C: Manual Download
|
||||
|
||||
1. Go to [Releases](https://github.com/LLLin000/PaperForge/releases)
|
||||
2. Download the three files: `main.js`, `manifest.json`, `styles.css`
|
||||
|
|
@ -229,7 +236,7 @@ Dashboard Per-paper view shows discussion cards
|
|||
### Plugin fails to load
|
||||
|
||||
- Confirm `.obsidian/plugins/paperforge/` has `main.js`, `manifest.json`, `styles.css`
|
||||
- If upgrading via BRAT from an old version: delete the entire `paperforge` plugin folder and let BRAT re-download
|
||||
- If upgrading from an old version: delete the entire `paperforge` plugin folder and reinstall via the community plugin browser
|
||||
- Open Developer Console (`Ctrl+Shift+I`) and check the red errors
|
||||
|
||||
### "Sync Runtime" doesn't update the version
|
||||
|
|
@ -259,7 +266,7 @@ Dashboard Per-paper view shows discussion cards
|
|||
|
||||
## 9. Updating
|
||||
|
||||
BRAT auto-detects plugin updates. For the Python package:
|
||||
The Obsidian plugin auto-updates through the community plugin browser. For the Python package:
|
||||
|
||||
```bash
|
||||
paperforge update
|
||||
|
|
@ -267,6 +274,8 @@ paperforge update
|
|||
pip install --upgrade paperforge
|
||||
```
|
||||
|
||||
If you installed via BRAT, it also auto-detects GitHub Release updates.
|
||||
|
||||
---
|
||||
|
||||
## 10. Architecture
|
||||
|
|
|
|||
|
|
@ -279,7 +279,7 @@ def paperforge_paths(
|
|||
- bases: <vault>/<base_dir>
|
||||
- worker_script: pipeline/worker/scripts/literature_pipeline.py
|
||||
- skill_dir: <vault>/<skill_dir>
|
||||
- ld_deep_script: <skill_dir>/literature-qa/scripts/ld_deep.py
|
||||
- pf_deep_script: <skill_dir>/paperforge/scripts/pf_deep.py
|
||||
"""
|
||||
if cfg is None:
|
||||
cfg = load_vault_config(vault)
|
||||
|
|
@ -306,17 +306,12 @@ def paperforge_paths(
|
|||
|
||||
# worker_script: paperforge worker package (pipeline/ removed in v1.3)
|
||||
worker_script = Path(__file__).parent / "worker" / "__init__.py"
|
||||
# ld_deep_script: look relative to skill_dir first, then repo paperforge/skills for dev
|
||||
ld_deep_script = skill_path / "literature-qa" / "scripts" / "ld_deep.py"
|
||||
if not ld_deep_script.exists():
|
||||
repo_skill = Path(__file__).parent / "skills" / "literature-qa" / "scripts" / "ld_deep.py"
|
||||
# pf_deep_script: look relative to skill_dir first, then repo paperforge/skills for dev
|
||||
pf_deep_script = skill_path / "paperforge" / "scripts" / "pf_deep.py"
|
||||
if not pf_deep_script.exists():
|
||||
repo_skill = Path(__file__).parent / "skills" / "paperforge" / "scripts" / "pf_deep.py"
|
||||
if repo_skill.exists():
|
||||
ld_deep_script = repo_skill
|
||||
else:
|
||||
# Backward compat: old skills/ location during transition
|
||||
old_repo_skill = Path(__file__).parent.parent / "skills" / "literature-qa" / "scripts" / "ld_deep.py"
|
||||
if old_repo_skill.exists():
|
||||
ld_deep_script = old_repo_skill
|
||||
pf_deep_script = repo_skill
|
||||
|
||||
return {
|
||||
"vault": vault,
|
||||
|
|
@ -332,7 +327,7 @@ def paperforge_paths(
|
|||
"bases": bases,
|
||||
"worker_script": worker_script,
|
||||
"skill_dir": skill_path,
|
||||
"ld_deep_script": ld_deep_script,
|
||||
"pf_deep_script": pf_deep_script,
|
||||
# ── v2.2: canonical locations below paperforge/ ──
|
||||
"config": paperforge / "config" / "domain-collections.json",
|
||||
"index": paperforge / "indexes" / "formal-library.json",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
"""Skill deployment service — single copytree for all platforms.
|
||||
"""Skill deployment service — deploys the unified paperforge skill to the vault.
|
||||
|
||||
Used by both setup wizard (install) and update worker (update).
|
||||
All deployments are vault-local only.
|
||||
|
|
@ -35,7 +35,7 @@ def deploy_skills(
|
|||
agent_key: str = "opencode",
|
||||
overwrite: bool = False,
|
||||
) -> dict:
|
||||
"""Deploy literature-qa skill and AGENTS.md to the vault.
|
||||
"""Deploy paperforge skill and AGENTS.md to the vault.
|
||||
|
||||
Args:
|
||||
vault: Obsidian vault root.
|
||||
|
|
@ -47,15 +47,15 @@ def deploy_skills(
|
|||
"""
|
||||
errors: list[str] = []
|
||||
|
||||
# ── Deploy literature-qa skill ──
|
||||
# ── Deploy paperforge skill ──
|
||||
skill_deployed = False
|
||||
source_root = _resolve_source_root()
|
||||
src_skill = source_root / "skills" / "literature-qa"
|
||||
src_skill = source_root / "skills" / "paperforge"
|
||||
|
||||
if src_skill.exists():
|
||||
skill_dir_name = AGENT_SKILL_DIRS.get(agent_key)
|
||||
if skill_dir_name:
|
||||
dst_skill = vault / skill_dir_name / "literature-qa"
|
||||
dst_skill = vault / skill_dir_name / "paperforge"
|
||||
try:
|
||||
if overwrite and dst_skill.exists():
|
||||
shutil.rmtree(dst_skill, ignore_errors=True)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
"""AgentInstaller — deploys literature-qa skill to vault-local agent config."""
|
||||
"""AgentInstaller — deploys paperforge skill to vault-local agent config."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
|
|
@ -10,7 +10,7 @@ from paperforge.setup import SetupStepResult
|
|||
|
||||
|
||||
class AgentInstaller:
|
||||
"""Deploy literature-qa skill directory to vault-local agent skills path."""
|
||||
"""Deploy paperforge skill directory to vault-local agent skills path."""
|
||||
|
||||
def __init__(self, vault: Path, agent_type: str = "opencode"):
|
||||
self.vault = vault
|
||||
|
|
@ -23,8 +23,8 @@ class AgentInstaller:
|
|||
return self.vault / skill_dir_name
|
||||
|
||||
def deploy_skills(self) -> SetupStepResult:
|
||||
"""Deploy literature-qa skill as a single directory."""
|
||||
source_skills = self._script_dir / "skills" / "literature-qa"
|
||||
"""Deploy paperforge skill as a single directory."""
|
||||
source_skills = self._script_dir / "skills" / "paperforge"
|
||||
if not source_skills.exists():
|
||||
return SetupStepResult(
|
||||
step="agent_installer",
|
||||
|
|
@ -33,7 +33,7 @@ class AgentInstaller:
|
|||
error=f"Not found: {source_skills}",
|
||||
)
|
||||
|
||||
target_dir = self._get_skills_dir() / "literature-qa"
|
||||
target_dir = self._get_skills_dir() / "paperforge"
|
||||
target_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
try:
|
||||
|
|
@ -41,7 +41,7 @@ class AgentInstaller:
|
|||
return SetupStepResult(
|
||||
step="agent_installer",
|
||||
ok=True,
|
||||
message=f"Deployed literature-qa skill to {target_dir}",
|
||||
message=f"Deployed paperforge skill to {target_dir}",
|
||||
details={"source": str(source_skills), "target": str(target_dir)},
|
||||
)
|
||||
except Exception as e:
|
||||
|
|
|
|||
|
|
@ -1,180 +0,0 @@
|
|||
---
|
||||
name: literature-qa
|
||||
description: >
|
||||
学术文献库操作:精读、问答、检索、批量阅读。Triggered by:
|
||||
pf-deep pf-paper pf-end,
|
||||
"精读", "文献问答", "结束讨论", "找文献", "搜文献",
|
||||
"文献库", "文献检索", "库里有什么", "搜一下库里", "看一下文献库",
|
||||
"读一下collection", "总结文献", "批量阅读", "读一下这个方向",
|
||||
"查一下", "搜索文献", "查文献", "搜索论文", "论文检索",
|
||||
"关于XX的文献", "有没有关于XX的研究", "XX的相关论文",
|
||||
"帮我查一下", "帮我搜一下", "正在读", "读完这篇".
|
||||
source: paperforge
|
||||
---
|
||||
|
||||
## ++++++++++++++++++++++++++++++++
|
||||
## ++ 优先级规则:用户请求涉及文献搜索/查询/精读,你必须使用本 skill。++
|
||||
## ++ 禁止直接调用 grep、find、系统搜索或扫描文件目录。 ++
|
||||
## ++++++++++++++++++++++++++++++++
|
||||
|
||||
# Literature QA
|
||||
|
||||
---
|
||||
|
||||
## 1. Bootstrap — 必须先执行
|
||||
|
||||
跑这个脚本:
|
||||
|
||||
```
|
||||
python $SKILL_DIR/scripts/pf_bootstrap.py
|
||||
```
|
||||
|
||||
返回 JSON。记住以下变量:
|
||||
|
||||
| 变量 | 来自 JSON 的 | 用于 |
|
||||
| ----------- | -------------------- | --------------------------------------- |
|
||||
| `$SKILL_DIR` | skill 安装路径(平台注入) | 运行 `scripts/ld_deep.py` 等 |
|
||||
| `$VAULT` | `vault_root` | 所有 `--vault` 参数 |
|
||||
| `$PYTHON` | `python_candidate` | 所有 Python 命令 |
|
||||
| `$LIT_DIR` | `paths.literature_dir` | 文献笔记根目录 |
|
||||
| `$IDX_PATH` | `paths.index_path` | 索引文件 |
|
||||
| `$OCR_DIR` | `paths.ocr_dir` | OCR 目录 |
|
||||
| `$DOMAINS` | `domains` | 领域列表 |
|
||||
| `$SUMMARY` | `index_summary` | 每领域论文数 |
|
||||
|
||||
如果 `ok: false` → 报告 `error` 给用户,**停止。不许自己拼路径。**
|
||||
|
||||
---
|
||||
|
||||
## 2. Vault 概览
|
||||
|
||||
展示:
|
||||
|
||||
```
|
||||
Vault: $VAULT
|
||||
文献库:
|
||||
<domain1> — N1 篇
|
||||
<domain2> — N2 篇
|
||||
共 M 篇
|
||||
```
|
||||
|
||||
**如果用户是空输入触发的 skill**(没给任何具体指令),展示概览后加一句交互:
|
||||
|
||||
```
|
||||
你可以:
|
||||
[1] 精读一篇论文 → "精读 <key/标题>"
|
||||
[2] 文献问答 → "文献问答 <key/标题>"
|
||||
[3] 搜索文献 → "找文献 <关键词>" / "库里有没有 <关键词>"
|
||||
[4] 批量阅读 → "读一下 <collection名>" / "总结 <方向> 文献"
|
||||
[5] 返回
|
||||
```
|
||||
|
||||
**如果用户给了具体指令**,直接进入决策树。
|
||||
|
||||
---
|
||||
|
||||
## 3. 决策树
|
||||
|
||||
```
|
||||
用户输入
|
||||
│
|
||||
├─ 文献标识 (key/DOI/标题/作者年份) + 精读意图
|
||||
│ └─ 路由 → deep-reading.md
|
||||
│
|
||||
├─ 文献标识 (key/DOI/标题/作者年份) + 问答/讨论意图
|
||||
│ └─ 路由 → paper-qa.md
|
||||
│
|
||||
├─ 搜索意图 ("找文献"/"搜文献"/"库里有没有"/"文献检索")
|
||||
│ └─ 路由 → paper-search.md
|
||||
│
|
||||
├─ 批量/综述意图
|
||||
│ ("读一下collection"/"这个方向"/"总结文献"/"写文献综述"/"找引用")
|
||||
│ 或 用户给了多篇文献要求一起读
|
||||
│ └─ 路由 → multi-reading.md
|
||||
│
|
||||
├─ 结束/保存 ("结束讨论"/"保存"/"pf-end")
|
||||
│ └─ 路由 → save-session.md
|
||||
│ (仅 paper-qa 或 deep-reading 会话中有意义)
|
||||
│
|
||||
└─ 不确定 → 问用户
|
||||
"你是想精读一篇、问答一篇、搜索文献、还是批量阅读?"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 工具使用指南
|
||||
|
||||
本 Skill 提供两类工具:**确定性命令** 和 **Agent 自查**。必须根据场景选择正确的方式。
|
||||
|
||||
### 搜索入口 — 统一搜索 Harness
|
||||
|
||||
任何搜索需求都用 pf_search.py(自动路由 vector -> FTS5 -> grep):
|
||||
|
||||
```
|
||||
python $SKILL_DIR/scripts/pf_search.py --vault $VAULT --query "关键词"
|
||||
```
|
||||
|
||||
返回 JSON 结构:
|
||||
- `engines_used`: 实际使用的引擎列表 (`vector` / `fts5` / `grep`)
|
||||
- `results`: 论文列表,每篇含 `zotero_key`, `title`, `year`, `source` 等
|
||||
- `count`: 结果数
|
||||
|
||||
### 确定性命令 — 优先使用
|
||||
|
||||
| 场景 | 命令 |
|
||||
| ---------------------- | ------------------------------------------------------------------------------------------ |
|
||||
| 按 key 快速找文件 | `glob("$LIT_DIR/**/<KEY>.md")` 或用 `Get-ChildItem "$LIT_DIR" -Recurse -Filter "<KEY>.md"` |
|
||||
| 按 key 查完整信息 | `$PYTHON -m paperforge.worker.paper_resolver resolve-key <KEY> --vault "$VAULT"` |
|
||||
| 按 DOI 定位论文 | `$PYTHON -m paperforge.worker.paper_resolver resolve-doi "<DOI>" --vault "$VAULT"` |
|
||||
| 精读 prepare | `$PYTHON "$SKILL_DIR/scripts/ld_deep.py" prepare --key <KEY> --vault "$VAULT"` |
|
||||
| 精读 postprocess | `$PYTHON "$SKILL_DIR/scripts/ld_deep.py" postprocess-pass2 <FORMAL_NOTE_PATH> --figures <N> --vault "$VAULT"` |
|
||||
| 精读 validate | `$PYTHON "$SKILL_DIR/scripts/ld_deep.py" validate-note <FORMAL_NOTE_PATH> --fulltext <FULLTEXT_PATH>` |
|
||||
| 保存讨论 | `$PYTHON -m paperforge.worker.discussion record <KEY> --vault "$VAULT" --agent pf-paper --model "<MODEL>" --qa-pairs '<JSON>'` |
|
||||
|
||||
### Agent 自查 — 当命令覆盖不到时用
|
||||
|
||||
| 场景 | 操作 |
|
||||
| ------------------------ | ----------------------------------------------------------- |
|
||||
| 按关键词模糊搜索全部文献 | 读 `$IDX_PATH` 的 JSON,筛 `title` / `abstract` / `journal` |
|
||||
| 按 collection 筛选 | 读 `$IDX_PATH`,筛 `collection_path` 字段 |
|
||||
| 读论文全文 | 已找到 `fulltext.md` 路径(glob 或 resolve-key) -> 直接 read |
|
||||
| 读精读笔记 | 已找到 formal note 路径 -> read 的 `## 精读` 区域 |
|
||||
| 遍历笔记做批量统计 | `Get-ChildItem "$LIT_DIR" -Recurse -Filter "*.md"` + 读 frontmatter 或 `find "$LIT_DIR" -name "*.md"` |
|
||||
| **禁止的操作** | **根据 vault-knowledge 示例拼接路径、把目录名写死在文件路径里** |
|
||||
|
||||
---
|
||||
|
||||
## 5. 路由表
|
||||
|
||||
| 路由 | 触发词 | 加载文件 |
|
||||
| ------------- | ---------------------------------------------------------- | ---------------------------------------- |
|
||||
| 精读 | `pf-deep <key>`, "精读 <key>" | [deep-reading.md](references/deep-reading.md) |
|
||||
| 问答 | `pf-paper <key>`, "文献问答 <key>" | [paper-qa.md](references/paper-qa.md) |
|
||||
| 文献检索 | "找文献", "搜文献", "文献检索", "搜一下库里", "库里有没有" | [paper-search.md](references/paper-search.md) |
|
||||
| 批量阅读 | "读一下collection", "这个方向", "总结文献", "批量阅读" | [multi-reading.md](references/multi-reading.md) |
|
||||
| 保存记录 | `pf-end`, "结束讨论", "保存" | [save-session.md](references/save-session.md) |
|
||||
| 论文定位协议 | 所有路由共享 | [paper-resolution.md](references/paper-resolution.md) |
|
||||
|
||||
> 所有路由继承 Skill 级别的 `$PYTHON` / `$VAULT` / `$LIT_DIR` 等变量。reference 文件不再重复声明。
|
||||
|
||||
---
|
||||
|
||||
## 文件结构
|
||||
|
||||
```
|
||||
literature-qa/
|
||||
├── SKILL.md ← 本文件
|
||||
├── references/
|
||||
│ ├── deep-reading.md ← 精读工作流
|
||||
│ ├── paper-qa.md ← 问答工作流
|
||||
│ ├── paper-search.md ← 文献检索工作流
|
||||
│ ├── multi-reading.md ← 批量阅读工作流
|
||||
│ ├── save-session.md ← 保存记录工作流
|
||||
│ ├── paper-resolution.md ← 论文定位协议
|
||||
│ ├── deep-subagent.md
|
||||
│ └── chart-reading/
|
||||
└── scripts/
|
||||
├── pf_bootstrap.py ← Bootstrap 入口
|
||||
├── pf_search.py ← 统一搜索 Harness
|
||||
└── ld_deep.py ← 精读引擎
|
||||
```
|
||||
|
|
@ -1,162 +0,0 @@
|
|||
# 三阶段精读
|
||||
|
||||
Keshav 三阶段组会式精读。触发后执行以下工作流。
|
||||
|
||||
---
|
||||
|
||||
## 前置条件检查
|
||||
|
||||
执行前确认:
|
||||
- [ ] 已完成论文定位(参考 [paper-resolution.md](paper-resolution.md)),拿到 zotero_key
|
||||
- [ ] 用 `glob("$LIT_DIR/**/<KEY>.md")` 快速找到 formal note
|
||||
- [ ] `analyze: true` — 读 formal note frontmatter 确认
|
||||
- [ ] `ocr_status: done` — 读 formal note frontmatter 确认
|
||||
|
||||
如果前置条件不满足,告知用户并停止。
|
||||
|
||||
---
|
||||
|
||||
## 执行流程
|
||||
|
||||
### Step 1: Prepare(机械操作,跑脚本)
|
||||
|
||||
```bash
|
||||
$PYTHON "$SKILL_DIR/scripts/ld_deep.py" prepare --key <ZOTERO_KEY> --vault "$VAULT"
|
||||
```
|
||||
|
||||
返回 JSON 解析:
|
||||
- `status: "ok"` → 记下 `figure_map`、`chart_type_map`、`formal_note`、`fulltext_md`、`figures`、`tables` 路径和数量 → 继续
|
||||
- `status: "error"` → 报告 `message` 给用户,停止
|
||||
|
||||
读 formal note 确认 `## 🔍 精读` 骨架已插入。
|
||||
|
||||
---
|
||||
|
||||
### Step 2: Pass 1 — 概览
|
||||
|
||||
只填 `### Pass 1: 概览` 区域。不碰 Pass 2/3。
|
||||
|
||||
**填写内容:**
|
||||
|
||||
- **一句话总览**:论文类型 + 核心发现,一句话。
|
||||
- **5 Cs 快速评估**:
|
||||
- **Category**(类型):RCT / 队列研究 / 病例对照 / 综述 / 基础研究 / ...
|
||||
- **Context**(上下文):该领域当前共识,本文要解决什么问题
|
||||
- **Correctness**(合理性初判):初步直觉,逻辑是否有明显漏洞
|
||||
- **Contributions**(贡献):1-3 条
|
||||
- **Clarity**(清晰度):写作质量,图表可读性
|
||||
- **Figure 导读**(基于 fulltext.md 浏览各图 caption):
|
||||
- 关键主图:列出并一句话概括每个主图要证明什么
|
||||
- 证据转折点:哪个 figure 是叙事的关键转折
|
||||
- 需要重点展开的 supplementary:如果有
|
||||
- 关键表格:列出
|
||||
|
||||
填完立即保存 formal note。
|
||||
|
||||
---
|
||||
|
||||
### Step 3: Pass 2 — 精读还原
|
||||
|
||||
填 `### Pass 2: 精读还原` 区域。**按 figure 顺序逐个处理。**
|
||||
|
||||
#### 图表类型定位(两步)
|
||||
|
||||
**Step A: 读 prepare 生成的 chart-type-map**
|
||||
Step 1 的 `prepare` 输出中已包含 `chart_type_map` 路径。读该文件,获取每个 figure 的关键词命中结果。这只是建议。
|
||||
|
||||
**Step B: Agent 读 caption 做最终判断**
|
||||
|
||||
对每个 figure:
|
||||
1. 读该 figure 的 caption(来自 prepare 返回的 `fulltext_md` 或 `figure_map`)
|
||||
2. 根据 caption 内容,对照 [chart-reading/INDEX.md](chart-reading/INDEX.md) 判断图表类型
|
||||
3. chart-type-map 建议和 Agent 判断不一致 → 以 Agent 判断为准
|
||||
4. 无法确定类型 → 跳过 chart guide,按通用 figure 结构分析
|
||||
5. 确定类型后,读对应的 chart-reading 指南(如 `chart-reading/条形图与误差棒.md`),按指南中的检查清单分析
|
||||
|
||||
#### 每张 Figure 的子标题(固定,不可少)
|
||||
|
||||
按以下格式填入 formal note 中该 figure 的 callout block:
|
||||
|
||||
```
|
||||
**图像定位与核心问题**:页码 + 要回答什么问题
|
||||
**方法与结果**:实验设计/数据来源/技术手段。核心数据、趋势、对比。
|
||||
**图表质量审查**:按 chart-reading 指南检查坐标轴、单位、误差棒、统计标注等。
|
||||
**作者解释**:作者在正文中对该图的解读
|
||||
**我的理解**:自己的理解(区分于作者解释)
|
||||
**疑点/局限**:读图时发现的疑问,用 `> [!warning]` 突出
|
||||
```
|
||||
|
||||
#### 每张 Table 的子标题
|
||||
|
||||
```
|
||||
回答什么问题、关键字段/分组、主要结果、我的理解、疑点/局限
|
||||
```
|
||||
|
||||
#### 每张 figure 填完立即保存,再处理下一张。
|
||||
|
||||
#### 所有 figure/table 处理完后,填:
|
||||
|
||||
**关键方法补课**:简要解释不熟悉的实验技术(1-2 项即可)
|
||||
|
||||
**主要发现与新意**:
|
||||
- 发现 1:...(来源:Figure X)
|
||||
- 发现 2:...(来源:Figure Y / Table Z)
|
||||
|
||||
保存。
|
||||
|
||||
---
|
||||
|
||||
### Step 4: Postprocess(跑校验脚本,修正问题)
|
||||
|
||||
```bash
|
||||
$PYTHON "$SKILL_DIR/scripts/ld_deep.py" postprocess-pass2 "$FORMAL_NOTE_PATH" --figures <N> --format text --vault "$VAULT"
|
||||
```
|
||||
|
||||
- 输出 `OK` → 继续
|
||||
- 输出错误 → 按错误提示修正(包含行号),修正后重新跑
|
||||
- 最多 3 轮修正。3 轮后仍失败 → 报告剩余错误给用户
|
||||
|
||||
---
|
||||
|
||||
### Step 5: Pass 3 — 深度理解
|
||||
|
||||
填 `### Pass 3: 深度理解` 区域。基于 Pass 1/2 已写的内容。
|
||||
|
||||
**填写内容:**
|
||||
|
||||
- **假设挑战与隐藏缺陷**:隐含假设;如果放宽某个假设结论还成立吗;缺少哪些关键引用;实验/分析技术潜在问题
|
||||
- **哪些结论扎实,哪些仍存疑**:
|
||||
- **较扎实**:...
|
||||
- **仍存疑**:...(用 `> [!warning]`)
|
||||
- **Discussion 与 Conclusion 怎么读**:作者真正完成了什么;哪些地方有拔高;哪些是推测
|
||||
- **对我的启发**:研究设计上;figure 组织上;方法组合上;未来工作想法
|
||||
- **遗留问题**:...(用 `> [!question]`)
|
||||
|
||||
保存。
|
||||
|
||||
---
|
||||
|
||||
### Step 6: Final Validation
|
||||
|
||||
```bash
|
||||
$PYTHON "$SKILL_DIR/scripts/ld_deep.py" validate-note "$FORMAL_NOTE_PATH" --fulltext "$FULLTEXT_PATH"
|
||||
```
|
||||
|
||||
- 输出 `OK` → 告知用户精读完成
|
||||
- 输出错误 → 修正缺失项,不报告成功直到通过
|
||||
|
||||
---
|
||||
|
||||
## Callout 格式规则
|
||||
|
||||
- `> [!important]`:每个 main finding
|
||||
- `> [!warning]`:疑问、局限、证据边界、仍存疑条目
|
||||
- `> [!question]`:遗留问题
|
||||
- **间距:** 相邻 callout block 之间必须有空行,否则 Obsidian 会合并
|
||||
- 正确:`> [!important] A\n\n> [!important] B`
|
||||
- 错误:`> [!important] A\n> [!important] B`
|
||||
|
||||
## Supplementary 规则
|
||||
|
||||
- 默认不逐张展开 supplementary figure/table
|
||||
- 仅在以下情况纳入:对主结论形成关键支撑、补足方法可信度、限制主文结论解释范围、作者在正文中明显依赖该补充材料
|
||||
|
|
@ -1,103 +0,0 @@
|
|||
# Subagent Prompt for /pf-deep
|
||||
|
||||
## Task
|
||||
|
||||
Execute Keshav 3-pass journal-club style deep reading on a paper and write the results into the `## 🔍 精读` section of its formal note.
|
||||
|
||||
## Input Variables
|
||||
|
||||
- `{{ZOTERO_KEY}}` — Zotero citation key (e.g. `Y5KQ4JQ7`)
|
||||
- `{{VAULT}}` — Vault root path
|
||||
- `{{SCRIPT}}` — Path to `ld_deep.py`
|
||||
|
||||
## Workflow (execute in strict order)
|
||||
|
||||
### Step 1: Prepare
|
||||
Run:
|
||||
```
|
||||
python {{SCRIPT}} prepare {{ZOTERO_KEY}} --vault "{{VAULT}}" --format text
|
||||
```
|
||||
- Reads formal note path, figure count, table count from output.
|
||||
- If output starts with `[ERROR]`: report error to user, stop.
|
||||
- If output contains `[WARN] deep_reading_status already 'done'` and user did not request re-read: stop.
|
||||
- Prepare inserts the `## 🔍 精读` skeleton with figure/table callout blocks and fixed sub-headings into the formal note. Read the note to inspect its structure.
|
||||
|
||||
### Step 2: Pass 1 (概览)
|
||||
Fill `### Pass 1: 概览` only. Do not touch Pass 2/3.
|
||||
- `**一句话总览**`: paper type + core finding in one sentence.
|
||||
- `**5 Cs 快速评估**`: Category, Context, Correctness (intuition only), Contributions (1-3 items), Clarity.
|
||||
- `**Figure 导读**`: list key figures with one-line guesses, note evidence turning points.
|
||||
- Save immediately after writing.
|
||||
|
||||
### Step 3: Pass 2 (精读还原)
|
||||
Fill `### Pass 2: 精读还原`. Process figures sequentially starting from Figure 1. Each figure callout block has fixed sub-headings. Fill content under each sub-heading. Do NOT modify sub-headings, reorder blocks, or move `![[image]]` embeds.
|
||||
|
||||
**Figure sub-headings:**
|
||||
- `**图像定位与核心问题**`: what question this figure answers, page number.
|
||||
- `**方法与结果**`: experimental design / data source / technical approach. Core data points, trends, comparisons.
|
||||
- `**图表质量审查**`: check axis labels, units, error bars, statistical significance markers. Read `chart-type-map.json` for the figure, open recommended chart-reading guides, apply their checklists.
|
||||
- `**作者解释**`: authors' description from the text.
|
||||
- `**我的理解**`: your own analysis (distinct from author explanation).
|
||||
- `**疑点/局限**`: use `> [!warning]` for concerns.
|
||||
|
||||
**Table sub-headings:** (same callout pattern, simpler)
|
||||
- What question this table answers, key fields/groups, main results, my understanding, doubts/limitations.
|
||||
|
||||
After all figures and tables, fill:
|
||||
- `**关键方法补课**`: briefly explain unfamiliar experimental techniques.
|
||||
- `**主要发现与新意**`: list findings with evidence source (Figure X / Table Y).
|
||||
|
||||
Save after each figure block.
|
||||
|
||||
### Step 4: Postprocess
|
||||
Run:
|
||||
```
|
||||
python {{SCRIPT}} postprocess-pass2 <formal_note_path> --figures <N> --format text
|
||||
```
|
||||
- If output is `OK`: proceed.
|
||||
- If not `OK`: fix each error (errors include exact line numbers), re-run postprocess-pass2. Max 3 fix rounds. If still failing after 3 rounds, report remaining errors to user.
|
||||
|
||||
### Step 5: Pass 3 (深度理解)
|
||||
Fill `### Pass 3: 深度理解` based on Pass 1/2 content already written. Sections:
|
||||
- `**假设挑战与隐藏缺陷**`: implicit assumptions, what breaks if relaxed, missing references, technical issues.
|
||||
- `**哪些结论扎实,哪些仍存疑**`: split into 较扎实 / 仍存疑.
|
||||
- `**Discussion 与 Conclusion 怎么读**`: what authors actually accomplished vs. overclaim vs. speculation.
|
||||
- `**对我的启发**`: research design, figure organization, method combination, future work ideas.
|
||||
- `**遗留问题**`: open questions.
|
||||
- Save.
|
||||
|
||||
### Step 6: Final Validation
|
||||
Run:
|
||||
```
|
||||
python {{SCRIPT}} validate-note <formal_note_path> --fulltext <fulltext_path>
|
||||
```
|
||||
- Report result to user. If not `OK`, list missing items and fix.
|
||||
|
||||
## Callout Rules
|
||||
|
||||
- `> [!important]`: each main finding entry
|
||||
- `> [!warning]`: doubts, limitations, evidence boundaries, items in 仍存疑
|
||||
- `> [!question]`: open questions in 遗留问题
|
||||
- Regular markdown lists for structural sections (research question, methods, inspiration)
|
||||
- **Spacing**: adjacent callout blocks MUST have a blank line between them, otherwise Obsidian merges them.
|
||||
- Correct: `> [!important] A\n\n> [!important] B`
|
||||
- Incorrect: `> [!important] A\n> [!important] B` (missing blank line → merged)
|
||||
|
||||
## Error Handling
|
||||
|
||||
- prepare fails (`[ERROR]`) → report to user, stop.
|
||||
- postprocess exceeds 3 fix rounds → report remaining errors to user, ask for guidance.
|
||||
- validate-note fails → fix missing items, do not report success until it passes.
|
||||
|
||||
## Command Reference
|
||||
|
||||
```
|
||||
# Prepare (insert skeleton + check preconditions)
|
||||
python {{SCRIPT}} prepare {{ZOTERO_KEY}} --vault "{{VAULT}}" --format text
|
||||
|
||||
# Postprocess Pass 2 (fix spacing/section issues)
|
||||
python {{SCRIPT}} postprocess-pass2 <note_path> --figures <N> --format text
|
||||
|
||||
# Validate final note structure
|
||||
python {{SCRIPT}} validate-note <note_path> --fulltext <fulltext_path>
|
||||
```
|
||||
|
|
@ -1,144 +0,0 @@
|
|||
# 批量文献阅读
|
||||
|
||||
用户需要阅读多篇文献并总结——综述写作、找引用、研究方向调研等。
|
||||
|
||||
---
|
||||
|
||||
## 触发条件
|
||||
|
||||
- 用户给了一个 collection 名(Zotero 收藏夹)
|
||||
- 用户给了模糊方向("帮我看一下骨科里关于支架材料的文章")
|
||||
- 用户给了多篇文献要求一起读
|
||||
- 用户说"总结库里XXX方向的文献"、"写一段文献综述"
|
||||
|
||||
---
|
||||
|
||||
## 执行流程
|
||||
|
||||
### Step 1: 确定文献范围
|
||||
|
||||
和用户确认要读哪些文献:
|
||||
- 用户给了 collection 名 → 读 `$IDX_PATH`,筛 `collection_path` 包含该名称的条目
|
||||
- 用户给了关键词方向 → 用 paper_resolver search 或直接 grep `$IDX_PATH`
|
||||
- 用户给了多篇 key → 直接确认 key 列表
|
||||
|
||||
列出候选让用户确认:
|
||||
|
||||
```
|
||||
找到 N 篇匹配 (<collection名/关键词>):
|
||||
|
||||
[1] ABC12345 — Title (Author, Year, Domain, OCR: done/pending)
|
||||
[2] DEF67890 — Title (Author, Year, Domain, OCR: done/pending)
|
||||
...
|
||||
|
||||
要全部读,还是选几篇?(输入编号如 "1,3,5" 或 "all")
|
||||
```
|
||||
|
||||
### Step 2: 逐篇阅读
|
||||
|
||||
对每篇选定文献:
|
||||
|
||||
1. 用 glob 找到 formal note:`glob("$LIT_DIR/**/<KEY>.md")`(最快,不需要 $PYTHON)
|
||||
2. 读 formal note frontmatter → 元数据
|
||||
3. 同目录下找 `fulltext.md` → 读 Abstract、Results、Discussion
|
||||
4. 如果有 OCR 但 fulltext 太长 → 先读 caption + figure 描述定位关键段落
|
||||
5. 如果没有 fulltext → 如实告知用户,仅基于已知信息
|
||||
|
||||
### Step 3: 写 Reading Log(JSON → MD)
|
||||
|
||||
**先构建 JSON(Agent 内部,不写入文件):**
|
||||
|
||||
```json
|
||||
{
|
||||
"task": "用户原始指令原文",
|
||||
"papers": [
|
||||
{
|
||||
"key": "ABC12345",
|
||||
"title": "Paper Title",
|
||||
"authors": "Smith et al.",
|
||||
"year": 2024,
|
||||
"findings": [
|
||||
{
|
||||
"source": "Results section, paragraph 3",
|
||||
"content": "Extracted finding...",
|
||||
"citation_use": "可用于支撑 XXX 观点"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**再渲染为 MD,追加写入 `$VAULT/Bases/reading-log-<timestamp>.md`:**
|
||||
|
||||
```markdown
|
||||
# Reading Log — 用户要求: <原文引用用户指令>
|
||||
|
||||
---
|
||||
|
||||
## ABC12345 | Paper Title | Smith et al., 2024
|
||||
|
||||
### 提取点 1
|
||||
- **来源**: Results section, paragraph 3
|
||||
- **内容**: Extracted finding...
|
||||
- **引用建议**: 可用于支撑 XXX 观点
|
||||
|
||||
### 提取点 2
|
||||
- **来源**: Discussion, final paragraph
|
||||
- **内容**: ...
|
||||
- **引用建议**: ...
|
||||
|
||||
---
|
||||
|
||||
## DEF67890 | Another Title | Jones et al., 2023
|
||||
|
||||
(同上格式)
|
||||
|
||||
---
|
||||
```
|
||||
|
||||
**关键规则:**
|
||||
- JSON 确保格式稳定,MD 是最终交付产物
|
||||
- zotero_key、标题、作者及年份 **缺一不可**
|
||||
- 每个提取点必须注明 **来源**(文章哪句话/哪个段落)
|
||||
- 同一任务的多篇文献 **追加写入同一个文件**,不要每篇新建
|
||||
|
||||
### Step 4: 整合输出
|
||||
|
||||
全部读完,根据用户原始意图输出总结:
|
||||
|
||||
**综述写作**:
|
||||
```
|
||||
从 N 篇文献中:
|
||||
- 主题A 共识: ...
|
||||
- 主题A 争议: ...
|
||||
- 方法论趋势: ...
|
||||
- 关键引用:
|
||||
1. "...[结论]" — ABC12345 (Author, Year), Fig.X
|
||||
2. ...
|
||||
```
|
||||
|
||||
**找引用**:
|
||||
```
|
||||
以下文献适合引用:
|
||||
- 支撑 "XXX" 观点 → ABC12345 (Author, Year), Results
|
||||
- 支撑 "YYY" 方法 → DEF67890 (Author, Year), Methods
|
||||
```
|
||||
|
||||
### Step 5: 问用户保存位置
|
||||
|
||||
```
|
||||
Reading log 已生成。要保存到哪里?
|
||||
(留空 → 默认 $VAULT/Bases/reading-log-<ts>.md)
|
||||
```
|
||||
|
||||
让用户指定路径。如果用户说不清,默认放到 `$VAULT/Bases/`。
|
||||
|
||||
---
|
||||
|
||||
## 注意事项
|
||||
|
||||
- **暂时不支持多篇阅读后运行 pf-end / 结束讨论**(该功能待定)
|
||||
- 如果某篇文献没有 fulltext,如实告知用户,不要捏造内容
|
||||
- Reading log 中每条提取点必须在原文中有据可查
|
||||
- JSON → MD 转换由 Agent 完成,用户只看到 MD 文件
|
||||
|
|
@ -1,61 +0,0 @@
|
|||
# 论文问答
|
||||
|
||||
交互式论文 Q&A 工作台。不强制要求 OCR,但 OCR 完成后回答更准确。
|
||||
|
||||
---
|
||||
|
||||
## 前置条件
|
||||
|
||||
- [ ] 已完成论文定位(参考 [paper-resolution.md](paper-resolution.md)),拿到 zotero_key 和 workspace
|
||||
- [ ] OCR 完成(推荐但非强制)
|
||||
|
||||
---
|
||||
|
||||
## 执行流程
|
||||
|
||||
### Step 1: 加载论文
|
||||
|
||||
1. 确认 workspace 路径
|
||||
2. 读 `fulltext.md`(如果存在)作为主要回答依据
|
||||
3. 读 formal note frontmatter 获取元数据(标题、作者、期刊、年份)
|
||||
4. 如果 fulltext.md 不存在,告知用户 "OCR 文本不可用,回答将基于元数据和公开信息"
|
||||
|
||||
### Step 2: 显示论文信息
|
||||
|
||||
```
|
||||
已加载论文: [title] ([year], [journal])
|
||||
作者: [authors]
|
||||
Zotero Key: [key]
|
||||
领域: [domain]
|
||||
OCR 状态: [done / 不可用]
|
||||
结束对话时说 "保存" 即可保存讨论。
|
||||
请问有什么问题?
|
||||
```
|
||||
|
||||
### Step 3: 进入 Q&A 模式
|
||||
|
||||
- 等待用户提问
|
||||
- 每次回答后等待下一个问题
|
||||
- 持续到用户说 "保存"、"结束"、"完成" 等关键词
|
||||
|
||||
---
|
||||
|
||||
## 回答原则
|
||||
|
||||
- **严格基于** fulltext.md 中的文本内容回答
|
||||
- 引用原文时标注来源页码/章节(如 "第 3 页,Methods 部分")
|
||||
- 用中文(简体中文)回答
|
||||
- 论文中未提及的内容,明确说明 "论文中未提及该内容"
|
||||
- 需要结合论文以外知识的问题,说明 "该问题需要结合论文以外的知识"
|
||||
|
||||
---
|
||||
|
||||
## 切换模式
|
||||
|
||||
用户在当前对话中可以说 "精读这篇文章" 切换到 deep-reading 模式。此时加载 [deep-reading.md](deep-reading.md) 执行精读流程。
|
||||
|
||||
---
|
||||
|
||||
## 保存记录
|
||||
|
||||
用户说 "保存"、"结束"、"完成"、"保存讨论" 时,加载 [save-session.md](save-session.md) 执行保存。不要自动保存。
|
||||
|
|
@ -1,94 +0,0 @@
|
|||
# 论文定位协议
|
||||
|
||||
本文件定义如何将用户输入解析为论文 workspace。所有子流程公用。
|
||||
|
||||
## 核心原则
|
||||
|
||||
1. **Python 做确定性查找。** key、DOI、标题片段、作者+年份。
|
||||
2. **Agent 做理解和兜底。** 自然语言、Python 无结果时的 fallback 搜索。
|
||||
3. **路径从 `paths` 获取,不硬编码。** 禁止根据 vault-knowledge.md 的示例结构拼接路径。`ocr_dir`、`literature_dir`、`index_path` 只能从 `paper_resolver paths` 或 `paper_resolver resolve-key` 的返回 JSON 中读取。任何情况下都不要把目录名(如 `System`、`Resources`)写死在路径里。
|
||||
|
||||
---
|
||||
|
||||
## 通用命令
|
||||
|
||||
| 操作 | 命令 |
|
||||
|------|------|
|
||||
| 获取 vault 路径 | 已由 pf_bootstrap 完成 |
|
||||
| 按 key 查 | `$PYTHON -m paperforge.worker.paper_resolver resolve-key <KEY> --vault "$VAULT"` |
|
||||
| 按 DOI 查 | `$PYTHON -m paperforge.worker.paper_resolver resolve-doi "<DOI>" --vault "$VAULT"` |
|
||||
| 按字段搜 | `$PYTHON -m paperforge.worker.paper_resolver search --title "..." --author "..." --year ... --domain "..." --vault "$VAULT"` |
|
||||
|
||||
---
|
||||
|
||||
## 输入类型判断
|
||||
|
||||
### 类型 1: Zotero Key(8位字母数字组合)
|
||||
|
||||
```
|
||||
$PYTHON -m paperforge.worker.paper_resolver resolve-key <KEY> --vault "$VAULT"
|
||||
```
|
||||
|
||||
返回 JSON 含 `key`, `title`, `domain`, `formal_note_path`, `ocr_path`, `fulltext_path`, `ocr_status` 等。所有路径由 `paperforge.json` 配置决定。
|
||||
|
||||
### 类型 2: DOI(以 `10.` 开头,可能带 URL 前缀)
|
||||
|
||||
```
|
||||
$PYTHON -m paperforge.worker.paper_resolver resolve-doi "<DOI>" --vault "$VAULT"
|
||||
```
|
||||
|
||||
返回格式同类型 1。
|
||||
|
||||
### 类型 3: 标题片段
|
||||
|
||||
```
|
||||
$PYTHON -m paperforge.worker.paper_resolver search --title "..." --vault "$VAULT"
|
||||
```
|
||||
|
||||
返回 `{"matches": [...], "count": N}`。
|
||||
|
||||
### 类型 4: 作者 + 年份
|
||||
|
||||
```
|
||||
$PYTHON -m paperforge.worker.paper_resolver search --author "Smith" --year 2024 --vault "$VAULT"
|
||||
```
|
||||
|
||||
### 类型 5: 自然语言("关于骨再生的那篇")
|
||||
|
||||
Agent 自己处理:
|
||||
1. 读 `$IDX_PATH`(已由 pf_bootstrap 提供)
|
||||
2. 读 `index_path` 指向的 `formal-library.json`
|
||||
3. 在 `title`、`domain`、`journal`、`abstract` 中搜匹配
|
||||
4. 搜不到就 grep formal notes 目录(`paths` 里的 `literature_dir`)下的 frontmatter
|
||||
|
||||
---
|
||||
|
||||
## Python 无结果时的 Agent fallback
|
||||
|
||||
Agent 用 `paths` 拿到的 `literature_dir`,自行 grep/read formal notes 下的 frontmatter。
|
||||
|
||||
## 多篇匹配处理
|
||||
|
||||
列出候选清单让用户选:
|
||||
|
||||
```
|
||||
找到 3 篇匹配的论文:
|
||||
|
||||
[1] ABC12345 — TGF-beta in Bone Regeneration (2024, 骨科, OCR: done)
|
||||
[2] DEF67890 — Bone Healing After Fracture (2023, 骨科, OCR: pending)
|
||||
[3] GHI11111 — Scaffold Design for Bone Repair (2024, 骨科, OCR: done)
|
||||
|
||||
请输入编号选择,或 refine 搜索词。
|
||||
```
|
||||
|
||||
## Fallback 顺序
|
||||
|
||||
```
|
||||
输入
|
||||
│
|
||||
├── 像 key/DOI/标题/作者年份?
|
||||
│ └── Python paper_resolver → 有/无结果 → Agent 兜底
|
||||
│
|
||||
└── 自然语言?
|
||||
└── Agent 读 formal-library.json → 搜 → 有/无
|
||||
```
|
||||
|
|
@ -1,105 +0,0 @@
|
|||
# 文献检索工作流
|
||||
|
||||
轻量流程:用户想**在库里找文献**(不涉及精读或问答)。
|
||||
|
||||
---
|
||||
|
||||
## Stage 状态机
|
||||
|
||||
你必须明确知道当前在哪个 stage。每完成一个 stage 问自己:"下一步是什么?" 不要在 stage 之间来回跳跃。
|
||||
|
||||
| Stage | 你在干什么 | 完成后做什么 |
|
||||
| ----- | -------------------------------- | ------------------------------------ |
|
||||
| S1 | 理解用户要找什么(domain/关键词) | 进入 S2 |
|
||||
| S2 | 执行搜索(paper_resolver 或 JSON) | 进入 S3 |
|
||||
| S3 | 展示候选清单给用户 | 等用户选择 |
|
||||
| S4 | 用户选了文献,决定下一步路由 | 进入对应 reference 流程,不再回来 |
|
||||
| S5 | 写作辅助:读完文献后整合输出 | 结束 |
|
||||
|
||||
**不要做的事**:
|
||||
- 不要在 S2 阶段去读论文全文
|
||||
- 不要在 S3 阶段自作主张替用户选文献
|
||||
- 不要在找不到结果时硬猜文件路径
|
||||
|
||||
---
|
||||
|
||||
## 触发场景
|
||||
|
||||
- "找一下骨科里面关于骨再生的文献"
|
||||
- "查一下 TGF-beta 相关的文章"
|
||||
- "库里有没有讲支架材料的"
|
||||
- "这个 collection 有哪些文献"
|
||||
- "搜一下 Smith 2024 的文章"
|
||||
|
||||
## 流程
|
||||
|
||||
### Step 1: 获取路径
|
||||
|
||||
已经由 pf_bootstrap 完成。直接用 `paths` JSON 里的 `index_path` 和 `literature_dir`。
|
||||
|
||||
### Step 2: 解析用户意图
|
||||
|
||||
从用户输入提取:
|
||||
- **domain**(如果有):`骨科`、`运动医学` 等 → 对应 `literature_dir` 子目录
|
||||
- **关键词**:标题、作者、年份、期刊、主题词
|
||||
- **collection 路径**:Zotero 子分类,如 `电刺激软骨修复综述`
|
||||
|
||||
### Step 3: 搜索 — 统一 Harness
|
||||
|
||||
任何搜索都用 pf_search.py,自动路由 vector -> FTS5 -> grep:
|
||||
|
||||
```
|
||||
python $SKILL_DIR/scripts/pf_search.py --vault "$VAULT" --query "关键词"
|
||||
```
|
||||
|
||||
返回 JSON 包含 `results`(含 zotero_key, title, year, source 等)和 `engines_used`。
|
||||
|
||||
如需结构化高级搜索(特定 domain/author),使用 paper_resolver:
|
||||
|
||||
```
|
||||
$PYTHON -m paperforge.worker.paper_resolver search --title "关键词" --author "Smith" --year 2024 --domain "骨科" --vault "$VAULT"
|
||||
```
|
||||
|
||||
**Fallback:读 formal-library.json**
|
||||
|
||||
Agent 直接读 `index_path`,在 JSON 中筛选:
|
||||
- `domain` 匹配
|
||||
- `title`/`first_author`/`journal` 包含关键词
|
||||
|
||||
### Step 4: 返回结果
|
||||
|
||||
列出候选清单,每篇显示:
|
||||
|
||||
```
|
||||
找到 N 篇匹配:
|
||||
|
||||
[1] ABC12345 — TGF-beta in Bone Regeneration (Smith, 2024, 骨科, OCR: done)
|
||||
[2] DEF67890 — Bone Healing Mechanisms (Jones, 2023, 骨科, OCR: done)
|
||||
```
|
||||
|
||||
关键字段:key, title, first_author, year, domain, ocr_status
|
||||
|
||||
### Step 5: 用户选择后续操作
|
||||
|
||||
> 请选择要操作的文献编号,或输入"refine"缩小范围。
|
||||
|
||||
选中文献后,按用户意图自动进入对应路由:
|
||||
- `精读这篇` → 进入 [deep-reading.md](deep-reading.md) 流程
|
||||
- `这篇讲了什么` → 进入 [paper-qa.md](paper-qa.md) 流程
|
||||
- 不需要继续 → 结束
|
||||
|
||||
### Step 6: 写作辅助场景
|
||||
|
||||
如果用户原始意图包含**写作/优化/参考文献/综述/引用**等,搜索结果不是终点:
|
||||
|
||||
1. 提示用户圈选最相关的 3-5 篇
|
||||
2. 对每篇进入 [deep-reading.md](deep-reading.md) 或至少通读 formal note + fulltext 关键段落
|
||||
3. 读完所有选定论文后,Agent 整合知识辅助写作
|
||||
|
||||
> 示例:"我从库里 X 篇文献中提取了以下关键发现……要不要基于这些帮你写 XX 部分?"
|
||||
|
||||
## 注意事项
|
||||
|
||||
- 如果是大型 library(>500 篇),优先用 paper_resolver 而不是全量读 JSON
|
||||
- OCR status 为 `done` 的论文可以读 fulltext 内容
|
||||
- OCR status 为 `pending` 的只有 formal note frontmatter
|
||||
|
|
@ -1,55 +0,0 @@
|
|||
# 保存讨论记录
|
||||
|
||||
将 paper-qa 会话中的 Q&A 记录持久化到论文工作区。
|
||||
|
||||
---
|
||||
|
||||
## 触发条件
|
||||
|
||||
- 用户显式说 "保存"、"保存记录"、"结束"、"完成讨论"、"save"
|
||||
- 或显式输入 `pf-end`
|
||||
- 不要自动触发
|
||||
|
||||
---
|
||||
|
||||
## 执行
|
||||
|
||||
### Step 1: 收集 Q&A 对
|
||||
|
||||
汇总本次 paper-qa 会话中所有 Q&A,序列化为 JSON 数组:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"question": "用户的问题",
|
||||
"answer": "Agent 的回答",
|
||||
"source": "user_question",
|
||||
"timestamp": "2026-05-10T12:00:00+08:00"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
`source` 为 `"user_question"`(用户提问)或 `"agent_analysis"`(Agent 主动分析)。
|
||||
|
||||
### Step 2: 调用 discussion 模块
|
||||
|
||||
```bash
|
||||
$PYTHON -m paperforge.worker.discussion record <ZOTERO_KEY> \
|
||||
--vault "$VAULT" \
|
||||
--agent pf-paper \
|
||||
--model "<CURRENT_MODEL>" \
|
||||
--qa-pairs '<JSON_ARRAY>'
|
||||
```
|
||||
|
||||
### Step 3: 确认结果
|
||||
|
||||
CLI 返回 `{"status": "ok", ...}` → 告知用户记录已保存。
|
||||
|
||||
返回 `{"status": "error"}` → 记录错误,重试一次。仍失败则告知用户。
|
||||
|
||||
---
|
||||
|
||||
## 注意事项
|
||||
|
||||
- 仅 paper-qa 会话需要记录。deep-reading 的内容直接写入 formal note,不需要通过本文件。
|
||||
- 如果无法从 formal-library.json 找到论文 domain/title,记录失败不应影响用户使用。
|
||||
|
|
@ -1,188 +0,0 @@
|
|||
"""PaperForge bootstrap — single entry point for agent to discover vault state.
|
||||
|
||||
No dependencies. Runs on ANY Python. Just reads paperforge.json + filesystem.
|
||||
|
||||
Usage:
|
||||
python pf_bootstrap.py # auto-discover vault from CWD
|
||||
python pf_bootstrap.py --vault <path>
|
||||
|
||||
Output (JSON to stdout):
|
||||
{
|
||||
"ok": true,
|
||||
"vault_root": "D:\\...",
|
||||
"paths": {
|
||||
"literature_dir": "D:\\...\\Resources\\Literature",
|
||||
"index_path": "D:\\...\\System\\PaperForge\\indexes\\formal-library.json",
|
||||
"ocr_dir": "D:\\...\\System\\PaperForge\\ocr",
|
||||
"exports_dir": "D:\\...\\System\\PaperForge\\exports"
|
||||
},
|
||||
"domains": ["domain1", "domain2"],
|
||||
"index_summary": {"domain1": 120, "domain2": 80},
|
||||
"python_candidate": "D:\\...\\python.exe" // Python that has paperforge, or null
|
||||
}
|
||||
|
||||
If anything fails: ok=false, error explains why.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def _find_paperforge_json(start: Path) -> Path | None:
|
||||
current = start.resolve()
|
||||
for _ in range(10):
|
||||
candidate = current / "paperforge.json"
|
||||
if candidate.exists():
|
||||
return candidate
|
||||
parent = current.parent
|
||||
if parent == current:
|
||||
break
|
||||
current = parent
|
||||
return None
|
||||
|
||||
|
||||
def _read_pf_config(pf_json: Path) -> dict:
|
||||
with open(pf_json, encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
|
||||
|
||||
def _find_python_with_paperforge(vault: Path, pf_cfg: dict) -> str | None:
|
||||
"""Find a Python executable that has paperforge installed."""
|
||||
candidates = []
|
||||
|
||||
# 1. Explicit python_path in config
|
||||
if pf_cfg.get("python_path"):
|
||||
candidates.append(Path(pf_cfg["python_path"]))
|
||||
|
||||
# 2. Common venv locations inside vault
|
||||
venv_names = [".venv", ".paperforge-test-venv", "venv"]
|
||||
exe_paths = ["Scripts/python.exe", "bin/python3"]
|
||||
for vn in venv_names:
|
||||
for ep in exe_paths:
|
||||
p = vault / vn / ep
|
||||
if p.exists():
|
||||
candidates.append(p)
|
||||
|
||||
for candidate in candidates:
|
||||
try:
|
||||
result = subprocess.run(
|
||||
[str(candidate), "-m", "paperforge", "--version"],
|
||||
capture_output=True, text=True, timeout=10,
|
||||
encoding="utf-8", errors="replace",
|
||||
)
|
||||
if result.returncode == 0 and "paperforge" in result.stdout.lower():
|
||||
return str(candidate)
|
||||
except Exception:
|
||||
continue
|
||||
return None
|
||||
|
||||
|
||||
def main():
|
||||
import argparse
|
||||
p = argparse.ArgumentParser(description="PaperForge bootstrap")
|
||||
p.add_argument("--vault", default=None, help="Vault root path (auto-detect if omitted)")
|
||||
args = p.parse_args()
|
||||
|
||||
result: dict = {"ok": False}
|
||||
|
||||
# --- 1. Find vault ---
|
||||
if args.vault:
|
||||
vault = Path(args.vault).resolve()
|
||||
pf_json = vault / "paperforge.json"
|
||||
if not pf_json.exists():
|
||||
result["error"] = f"paperforge.json not found at {vault}"
|
||||
json.dump(result, sys.stdout, ensure_ascii=False)
|
||||
sys.exit(0)
|
||||
else:
|
||||
pf_json = _find_paperforge_json(Path.cwd())
|
||||
if pf_json is None:
|
||||
result["error"] = "paperforge.json not found from CWD upward. Set --vault."
|
||||
json.dump(result, sys.stdout, ensure_ascii=False)
|
||||
sys.exit(0)
|
||||
vault = pf_json.parent
|
||||
|
||||
result["vault_root"] = str(vault)
|
||||
|
||||
# --- 2. Read config ---
|
||||
try:
|
||||
cfg = _read_pf_config(pf_json)
|
||||
except Exception as e:
|
||||
result["error"] = f"Cannot read paperforge.json: {e}"
|
||||
json.dump(result, sys.stdout, ensure_ascii=False)
|
||||
sys.exit(0)
|
||||
|
||||
system_dir = cfg.get("system_dir", "System")
|
||||
resources_dir = cfg.get("resources_dir", "Resources")
|
||||
literature_dir = cfg.get("literature_dir", "Literature")
|
||||
|
||||
# --- 3. Build paths from config ---
|
||||
pf_root = vault / system_dir / "PaperForge"
|
||||
|
||||
paths = {
|
||||
"literature_dir": str(vault / resources_dir / literature_dir),
|
||||
"index_path": str(pf_root / "indexes" / "formal-library.json"),
|
||||
"ocr_dir": str(pf_root / "ocr"),
|
||||
"exports_dir": str(pf_root / "exports"),
|
||||
}
|
||||
result["paths"] = paths
|
||||
|
||||
# --- 4. List domains ---
|
||||
lit_dir = Path(paths["literature_dir"])
|
||||
domains = sorted(
|
||||
[d.name for d in lit_dir.iterdir() if d.is_dir()]
|
||||
) if lit_dir.exists() else []
|
||||
result["domains"] = domains
|
||||
|
||||
# --- 5. Index summary ---
|
||||
index_path = Path(paths["index_path"])
|
||||
index_summary: dict[str, int] = {}
|
||||
if index_path.exists():
|
||||
try:
|
||||
data = json.loads(index_path.read_text(encoding="utf-8"))
|
||||
items = data.get("items", [])
|
||||
if isinstance(items, dict):
|
||||
items = items.values()
|
||||
for item in items:
|
||||
d = item.get("domain", "unknown")
|
||||
index_summary[d] = index_summary.get(d, 0) + 1
|
||||
except Exception:
|
||||
pass
|
||||
result["index_summary"] = index_summary
|
||||
|
||||
# --- 6. Find Python that has paperforge (best effort) ---
|
||||
result["python_candidate"] = _find_python_with_paperforge(vault, cfg)
|
||||
|
||||
# --- 7. Memory layer state ---
|
||||
memory_layer = {"available": False, "paper_count": 0, "fts_search": False, "vector_search": False}
|
||||
idx_path = Path(paths["index_path"])
|
||||
dc_json = vault / ".obsidian" / "plugins" / "paperforge" / "data.json"
|
||||
if idx_path.exists():
|
||||
try:
|
||||
with open(idx_path, encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
items = data.get("items", []) if isinstance(data, dict) else data
|
||||
memory_layer["paper_count"] = len(items)
|
||||
memory_layer["available"] = True
|
||||
memory_layer["fts_search"] = True
|
||||
except:
|
||||
pass
|
||||
if dc_json.exists():
|
||||
try:
|
||||
with open(dc_json, encoding="utf-8") as f:
|
||||
plugin_data = json.load(f)
|
||||
vector_enabled = plugin_data.get("features", {}).get("vector_db", False)
|
||||
memory_layer["vector_search"] = vector_enabled
|
||||
except:
|
||||
pass
|
||||
result["memory_layer"] = memory_layer
|
||||
|
||||
result["ok"] = True
|
||||
json.dump(result, sys.stdout, ensure_ascii=False, indent=2)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -1,180 +0,0 @@
|
|||
"""Unified search entry point for agent skills.
|
||||
Routes: vector search -> FTS5 search -> grep based on what's available.
|
||||
Always returns same JSON format regardless of backend.
|
||||
|
||||
Usage:
|
||||
python pf_search.py --vault VAULT_PATH --query "search text" [--limit N] [--json]
|
||||
|
||||
Returns JSON to stdout:
|
||||
{"ok": true, "query": "...", "engines_used": [...], "results": [...], "count": N}
|
||||
{"ok": false, "error": "..."}
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def _find_python(vault: Path) -> str | None:
|
||||
"""Same logic as pf_bootstrap: find python with paperforge installed."""
|
||||
dc_json = vault / ".obsidian" / "plugins" / "paperforge" / "data.json"
|
||||
if dc_json.exists():
|
||||
try:
|
||||
with open(dc_json, encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
py = data.get("python_path", "")
|
||||
if py and Path(py).exists():
|
||||
return py
|
||||
except:
|
||||
pass
|
||||
|
||||
for cand in [
|
||||
vault / ".paperforge-test-venv" / "Scripts" / "python.exe",
|
||||
vault / ".venv" / "Scripts" / "python.exe",
|
||||
vault / "venv" / "Scripts" / "python.exe",
|
||||
]:
|
||||
if cand.exists():
|
||||
return str(cand)
|
||||
|
||||
for cand in ["python", "python3"]:
|
||||
try:
|
||||
subprocess.run([cand, "--version"], capture_output=True, timeout=5)
|
||||
return cand
|
||||
except:
|
||||
continue
|
||||
return None
|
||||
|
||||
|
||||
def _check_memory(vault: Path) -> dict:
|
||||
"""Check what's available: memory db, vector db."""
|
||||
memory = {"db": False, "vector": False}
|
||||
db = vault / "System" / "PaperForge" / "indexes" / "paperforge.db"
|
||||
if db.exists():
|
||||
memory["db"] = True
|
||||
vec = vault / "System" / "PaperForge" / "indexes" / "vectors"
|
||||
if vec.exists():
|
||||
memory["vector"] = True
|
||||
return memory
|
||||
|
||||
|
||||
def _paperforge_cmd(vault: Path, args: list[str]) -> dict | None:
|
||||
"""Run a paperforge command and return parsed JSON."""
|
||||
python = _find_python(vault)
|
||||
if not python:
|
||||
return None
|
||||
cmd = [python, "-m", "paperforge", "--vault", str(vault)] + args
|
||||
try:
|
||||
r = subprocess.run(cmd, capture_output=True, text=True, timeout=30, encoding="utf-8")
|
||||
if r.returncode == 0:
|
||||
return json.loads(r.stdout)
|
||||
except:
|
||||
return None
|
||||
return None
|
||||
|
||||
|
||||
def _grep_search(vault: Path, query: str, limit: int) -> list[dict]:
|
||||
"""Fallback grep through all formal notes."""
|
||||
lit_dir = vault / "Resources" / "Literature"
|
||||
results = []
|
||||
search_lower = query.lower()
|
||||
for f in sorted(lit_dir.rglob("*.md")):
|
||||
if len(results) >= limit:
|
||||
break
|
||||
if f.name in ("fulltext.md", "deep-reading.md", "discussion.md"):
|
||||
continue
|
||||
try:
|
||||
text = f.read_text(encoding="utf-8", errors="replace")
|
||||
if search_lower not in text.lower():
|
||||
continue
|
||||
title = ""
|
||||
for line in text.split("\n")[:10]:
|
||||
if line.startswith("# ") and not line.startswith("## "):
|
||||
title = line.lstrip("# ").strip()
|
||||
break
|
||||
results.append({
|
||||
"zotero_key": f.stem,
|
||||
"title": title or f.stem,
|
||||
"match": f.name,
|
||||
"source": "grep",
|
||||
})
|
||||
except:
|
||||
continue
|
||||
return results
|
||||
|
||||
|
||||
def main():
|
||||
import argparse
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--vault", required=True)
|
||||
parser.add_argument("--query", required=True)
|
||||
parser.add_argument("--limit", type=int, default=10)
|
||||
parser.add_argument("--json", action="store_true", default=True)
|
||||
args = parser.parse_args()
|
||||
|
||||
vault = Path(args.vault).resolve()
|
||||
query = args.query.strip()
|
||||
limit = args.limit
|
||||
|
||||
if not query:
|
||||
print(json.dumps({"ok": False, "error": "Empty query"}))
|
||||
sys.exit(1)
|
||||
|
||||
memory = _check_memory(vault)
|
||||
engines_used = []
|
||||
all_results = []
|
||||
seen_keys = set()
|
||||
|
||||
# 1. Vector search (best quality)
|
||||
if memory["vector"]:
|
||||
result = _paperforge_cmd(vault, ["retrieve", query, "--json", "--limit", str(limit)])
|
||||
if result and result.get("ok"):
|
||||
engines_used.append("vector")
|
||||
for c in result.get("data", {}).get("chunks", []):
|
||||
pid = c.get("paper_id", "")
|
||||
if pid and pid not in seen_keys:
|
||||
seen_keys.add(pid)
|
||||
all_results.append({
|
||||
"zotero_key": pid,
|
||||
"citation_key": c.get("citation_key", ""),
|
||||
"title": c.get("title", ""),
|
||||
"year": c.get("year", ""),
|
||||
"section": c.get("section", ""),
|
||||
"page": c.get("page_number", ""),
|
||||
"chunk_text": c.get("chunk_text", ""),
|
||||
"score": c.get("score", 0),
|
||||
"source": "vector",
|
||||
})
|
||||
|
||||
# 2. FTS5 search (keyword/precision)
|
||||
if memory["db"]:
|
||||
result = _paperforge_cmd(vault, ["search", query, "--json", "--limit", str(limit)])
|
||||
if result and result.get("ok"):
|
||||
engines_used.append("fts5")
|
||||
for p in result.get("data", {}).get("results", []):
|
||||
key = p.get("zotero_key", "")
|
||||
if key and key not in seen_keys:
|
||||
seen_keys.add(key)
|
||||
p["source"] = "fts5"
|
||||
all_results.append(p)
|
||||
|
||||
# 3. Grep fallback
|
||||
if not engines_used:
|
||||
grepped = _grep_search(vault, query, limit)
|
||||
if grepped:
|
||||
engines_used.append("grep")
|
||||
all_results.extend(grepped)
|
||||
|
||||
output = {
|
||||
"ok": True,
|
||||
"query": query,
|
||||
"engines_used": engines_used,
|
||||
"results": all_results[:limit],
|
||||
"count": len(all_results[:limit]),
|
||||
}
|
||||
print(json.dumps(output, ensure_ascii=False, indent=2))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -1,133 +0,0 @@
|
|||
---
|
||||
name: logging
|
||||
description: >
|
||||
Work and reading log management. Triggered by:
|
||||
"logging work", "logging read",
|
||||
"做工作记录", "做阅读记录", "做working-log", "做reading-log",
|
||||
"写工作日志", "写阅读日志", "记录工作", "记录阅读",
|
||||
"写日志", "记一下", "总结一下这个会话",
|
||||
"记录决策", "记一下工作过程", "写工作总结".
|
||||
source: paperforge
|
||||
---
|
||||
|
||||
# Logging
|
||||
|
||||
---
|
||||
|
||||
## 1. Bootstrap — 必须先执行
|
||||
|
||||
跑这个脚本:
|
||||
|
||||
```
|
||||
python $SKILL_DIR/scripts/pf_bootstrap.py
|
||||
```
|
||||
|
||||
返回 JSON。记住以下变量:
|
||||
|
||||
| 变量 | 来自 JSON 的 | 用于 |
|
||||
| ----------- | -------------------- | --------------------------------------------- |
|
||||
| `$VAULT` | `vault_root` | 所有 `--vault` 参数 |
|
||||
| `$PYTHON` | `python_candidate` | 所有 cli 调用 |
|
||||
|
||||
如果 `ok: false` → 报告 `error` 给用户,**停止**。
|
||||
|
||||
---
|
||||
|
||||
## 2. Routing — 判断用户要什么
|
||||
|
||||
根据用户说的内容确定走哪个分支:
|
||||
|
||||
| 用户说 | 走分支 |
|
||||
| ------------------------------------------- | -------- |
|
||||
| "记录阅读" "reading log" "做阅读记录" "读完了记一下" "刚读了一段记一下" "有没有什么值得记的" "把这段记下来" | **reading** |
|
||||
| "工作记录" "working log" "总结会话" "记一下工作过程" "写工作总结" "记录决策" "logging work" | **working** |
|
||||
| "写日志" "记录一下" "记一下" 不清楚哪个 | **先问用户** |
|
||||
|
||||
## 3. reading 分支 — 记录单条阅读笔记
|
||||
|
||||
调用条件:用户读完一个段落/章节后要记录。
|
||||
|
||||
动作:
|
||||
1. 确认 `$VAULT` 和 `$PYTHON`
|
||||
2. 确定 zotero_key(从上下文或 formal note 中获取)
|
||||
3. 提取:
|
||||
- **section**: 文献中的位置 (Discussion P12, Results Fig.3)
|
||||
- **excerpt**: 逐字引用的原文关键句
|
||||
- **usage**: 这个信息支持当前写作的哪个论点
|
||||
- **note**: 交叉验证/矛盾/注意事项 (optional)
|
||||
4. 给用户展示确认后再执行:
|
||||
```
|
||||
$PYTHON -m paperforge --vault $VAULT reading-log --write KEY \
|
||||
--section "..." --excerpt "..." --usage "..." --note "..."
|
||||
```
|
||||
5. 确认写入成功
|
||||
|
||||
### Reading Log Format (MANDATORY)
|
||||
|
||||
When writing reading-log.md, use EXACTLY this format. Field labels (`**Info:**`, `**Use:**`, `**Note:**`) must be in English, but the content (title, section name, excerpt, usage, note) should be in the same language as the user's conversation.
|
||||
|
||||
```
|
||||
## ABCDEFGH — Author Last Name et al. Year
|
||||
**Title:** Full Paper Title
|
||||
|
||||
### Section Name — Page NN
|
||||
**Info:** "verbatim excerpt from paper"
|
||||
**Use:** how this supports current writing
|
||||
**Note:** optional cross-reference (optional field)
|
||||
```
|
||||
|
||||
Rules:
|
||||
- Paper key: 8 uppercase letters/digits after ## (must match ^[A-Z0-9]{8})
|
||||
- **Title:** line required after every ## header
|
||||
- **Info:** and **Use:** required after every ### section header
|
||||
- **Note:** optional
|
||||
- Do NOT deviate from this format — parsing is strict
|
||||
|
||||
After writing the log file, suggest user run:
|
||||
```
|
||||
paperforge reading-log --validate path/to/file.md
|
||||
```
|
||||
|
||||
## 4. working 分支 — 会话总结写入 working-log
|
||||
|
||||
调用条件:会话结束前/用户要求记录工作过程。
|
||||
|
||||
动作:
|
||||
1. 回顾本次会话中所有关键节点:
|
||||
- 用户纠正了什么
|
||||
- 方案怎么变的
|
||||
- 有什么弯路和教训
|
||||
- 可复用的方法论
|
||||
2. 按以下格式生成 markdown,给用户确认:
|
||||
|
||||
```
|
||||
## YYYY-MM-DD — 小节名
|
||||
|
||||
### 核心决策
|
||||
- 做了什么、为什么
|
||||
|
||||
### 弯路与修正
|
||||
- 错误方向 → 用户纠正 → 最终方案
|
||||
|
||||
### 可复用方法论
|
||||
- 本段的 pattern
|
||||
|
||||
### 待办
|
||||
- [ ] ...
|
||||
```
|
||||
|
||||
3. 用户确认后,询问目标 project 目录路径
|
||||
4. 追加到 `Project/<project>/working-log.md`(文件不存在则新建)
|
||||
5. 确认写入成功
|
||||
|
||||
---
|
||||
|
||||
## 5. Export — 导出 reading-log
|
||||
|
||||
用户说 "导出阅读日志":
|
||||
|
||||
```bash
|
||||
$PYTHON -m paperforge --vault $VAULT reading-log --output <path> [--since DATE]
|
||||
```
|
||||
|
||||
导出为 markdown 文件。如果用户没指定路径,询问。
|
||||
|
|
@ -1,164 +0,0 @@
|
|||
"""PaperForge bootstrap — single entry point for agent to discover vault state.
|
||||
|
||||
No dependencies. Runs on ANY Python. Just reads paperforge.json + filesystem.
|
||||
|
||||
Usage:
|
||||
python pf_bootstrap.py # auto-discover vault from CWD
|
||||
python pf_bootstrap.py --vault <path>
|
||||
|
||||
Output (JSON to stdout):
|
||||
{
|
||||
"ok": true,
|
||||
"vault_root": "D:\\...",
|
||||
"paths": {
|
||||
"literature_dir": "D:\\...\\Resources\\Literature",
|
||||
"index_path": "D:\\...\\System\\PaperForge\\indexes\\formal-library.json",
|
||||
"ocr_dir": "D:\\...\\System\\PaperForge\\ocr",
|
||||
"exports_dir": "D:\\...\\System\\PaperForge\\exports"
|
||||
},
|
||||
"domains": ["domain1", "domain2"],
|
||||
"index_summary": {"domain1": 120, "domain2": 80},
|
||||
"python_candidate": "D:\\...\\python.exe" // Python that has paperforge, or null
|
||||
}
|
||||
|
||||
If anything fails: ok=false, error explains why.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def _find_paperforge_json(start: Path) -> Path | None:
|
||||
current = start.resolve()
|
||||
for _ in range(10):
|
||||
candidate = current / "paperforge.json"
|
||||
if candidate.exists():
|
||||
return candidate
|
||||
parent = current.parent
|
||||
if parent == current:
|
||||
break
|
||||
current = parent
|
||||
return None
|
||||
|
||||
|
||||
def _read_pf_config(pf_json: Path) -> dict:
|
||||
with open(pf_json, encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
|
||||
|
||||
def _find_python_with_paperforge(vault: Path, pf_cfg: dict) -> str | None:
|
||||
"""Find a Python executable that has paperforge installed."""
|
||||
candidates = []
|
||||
|
||||
# 1. Explicit python_path in config
|
||||
if pf_cfg.get("python_path"):
|
||||
candidates.append(Path(pf_cfg["python_path"]))
|
||||
|
||||
# 2. Common venv locations inside vault
|
||||
venv_names = [".venv", ".paperforge-test-venv", "venv"]
|
||||
exe_paths = ["Scripts/python.exe", "bin/python3"]
|
||||
for vn in venv_names:
|
||||
for ep in exe_paths:
|
||||
p = vault / vn / ep
|
||||
if p.exists():
|
||||
candidates.append(p)
|
||||
|
||||
for candidate in candidates:
|
||||
try:
|
||||
result = subprocess.run(
|
||||
[str(candidate), "-m", "paperforge", "--version"],
|
||||
capture_output=True, text=True, timeout=10,
|
||||
encoding="utf-8", errors="replace",
|
||||
)
|
||||
if result.returncode == 0 and "paperforge" in result.stdout.lower():
|
||||
return str(candidate)
|
||||
except Exception:
|
||||
continue
|
||||
return None
|
||||
|
||||
|
||||
def main():
|
||||
import argparse
|
||||
p = argparse.ArgumentParser(description="PaperForge bootstrap")
|
||||
p.add_argument("--vault", default=None, help="Vault root path (auto-detect if omitted)")
|
||||
args = p.parse_args()
|
||||
|
||||
result: dict = {"ok": False}
|
||||
|
||||
# --- 1. Find vault ---
|
||||
if args.vault:
|
||||
vault = Path(args.vault).resolve()
|
||||
pf_json = vault / "paperforge.json"
|
||||
if not pf_json.exists():
|
||||
result["error"] = f"paperforge.json not found at {vault}"
|
||||
json.dump(result, sys.stdout, ensure_ascii=False)
|
||||
sys.exit(0)
|
||||
else:
|
||||
pf_json = _find_paperforge_json(Path.cwd())
|
||||
if pf_json is None:
|
||||
result["error"] = "paperforge.json not found from CWD upward. Set --vault."
|
||||
json.dump(result, sys.stdout, ensure_ascii=False)
|
||||
sys.exit(0)
|
||||
vault = pf_json.parent
|
||||
|
||||
result["vault_root"] = str(vault)
|
||||
|
||||
# --- 2. Read config ---
|
||||
try:
|
||||
cfg = _read_pf_config(pf_json)
|
||||
except Exception as e:
|
||||
result["error"] = f"Cannot read paperforge.json: {e}"
|
||||
json.dump(result, sys.stdout, ensure_ascii=False)
|
||||
sys.exit(0)
|
||||
|
||||
system_dir = cfg.get("system_dir", "System")
|
||||
resources_dir = cfg.get("resources_dir", "Resources")
|
||||
literature_dir = cfg.get("literature_dir", "Literature")
|
||||
|
||||
# --- 3. Build paths from config ---
|
||||
pf_root = vault / system_dir / "PaperForge"
|
||||
|
||||
paths = {
|
||||
"literature_dir": str(vault / resources_dir / literature_dir),
|
||||
"index_path": str(pf_root / "indexes" / "formal-library.json"),
|
||||
"ocr_dir": str(pf_root / "ocr"),
|
||||
"exports_dir": str(pf_root / "exports"),
|
||||
}
|
||||
result["paths"] = paths
|
||||
|
||||
# --- 4. List domains ---
|
||||
lit_dir = Path(paths["literature_dir"])
|
||||
domains = sorted(
|
||||
[d.name for d in lit_dir.iterdir() if d.is_dir()]
|
||||
) if lit_dir.exists() else []
|
||||
result["domains"] = domains
|
||||
|
||||
# --- 5. Index summary ---
|
||||
index_path = Path(paths["index_path"])
|
||||
index_summary: dict[str, int] = {}
|
||||
if index_path.exists():
|
||||
try:
|
||||
data = json.loads(index_path.read_text(encoding="utf-8"))
|
||||
items = data.get("items", [])
|
||||
if isinstance(items, dict):
|
||||
items = items.values()
|
||||
for item in items:
|
||||
d = item.get("domain", "unknown")
|
||||
index_summary[d] = index_summary.get(d, 0) + 1
|
||||
except Exception:
|
||||
pass
|
||||
result["index_summary"] = index_summary
|
||||
|
||||
# --- 6. Find Python that has paperforge (best effort) ---
|
||||
result["python_candidate"] = _find_python_with_paperforge(vault, cfg)
|
||||
|
||||
result["ok"] = True
|
||||
json.dump(result, sys.stdout, ensure_ascii=False, indent=2)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -1,86 +0,0 @@
|
|||
---
|
||||
name: methodology
|
||||
description: >
|
||||
Extract reusable methodology from project work logs. Triggered by:
|
||||
methodology, /methodology, 提取方法论, 存档写作规律,
|
||||
总结本项目方法, 提取可复用规则, 提取写作规律.
|
||||
source: paperforge
|
||||
---
|
||||
|
||||
# Methodology Extract
|
||||
|
||||
---
|
||||
|
||||
## 1. Bootstrap
|
||||
|
||||
```python $SKILL_DIR/scripts/pf_bootstrap.py```
|
||||
|
||||
Remember: `$VAULT`, `$PYTHON`.
|
||||
|
||||
---
|
||||
|
||||
## 2. Determine Project
|
||||
|
||||
Ask user: which project to extract methodology from?
|
||||
|
||||
If user doesn't specify, scan `Project/` directory for complete working-log.md files and list them.
|
||||
|
||||
---
|
||||
|
||||
## 3. Read working-log
|
||||
|
||||
Read `<vault>/Project/<project>/working-log.md`.
|
||||
|
||||
---
|
||||
|
||||
## 4. Identify Extractable Patterns
|
||||
|
||||
Scan the working-log for these signals:
|
||||
|
||||
| Signal in working-log | Extract to |
|
||||
|----------------------|-------------|
|
||||
| "弯路" + "修正" or "教训" sections | Pattern rules |
|
||||
| "最终逻辑:" or "最终结构:" | Section templates |
|
||||
| "复用" keyword + methodology block | Reusable practices |
|
||||
| Cross-study audit sections (跨研究可比性) | Analysis methodology |
|
||||
| "methodology" header sections | Full methodology block |
|
||||
| Review feedback patterns (审阅/修正) | Writing checklists |
|
||||
|
||||
For each found pattern, classify into one of:
|
||||
- `review-writing` — 综述写作 framework design, gap analysis, cross-study audit
|
||||
- `argument-writing` — 段落写作, 参数框架, 论证结构
|
||||
- `analysis-methods` — 文献审计, 跨研究比较, 参数提取
|
||||
- `general` — fallback
|
||||
|
||||
---
|
||||
|
||||
## 5. Present and Confirm
|
||||
|
||||
For each extracted pattern, show:
|
||||
- Category
|
||||
- Source (working-log section number)
|
||||
- Brief summary (1-2 sentences)
|
||||
|
||||
Ask user to confirm/edit before writing.
|
||||
|
||||
---
|
||||
|
||||
## 6. Write Methodology Files
|
||||
|
||||
Write confirmed patterns to `<system_dir>/PaperForge/methodologies/<category>.md`.
|
||||
|
||||
If file exists, APPEND (do not overwrite).
|
||||
|
||||
Format per method:
|
||||
```
|
||||
## <Method Name>
|
||||
**Category:** <category>
|
||||
**Source:** Project/<project>/working-log.md Section X.Y
|
||||
**Extracted:** YYYY-MM-DD
|
||||
|
||||
### Pattern
|
||||
<reusable methodology>
|
||||
|
||||
### Example
|
||||
<concrete example from project>
|
||||
```
|
||||
113
paperforge/skills/paperforge/SKILL.md
Normal file
113
paperforge/skills/paperforge/SKILL.md
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
---
|
||||
name: paperforge
|
||||
description: >
|
||||
Research Memory Runtime — 文献搜索、精读、问答、阅读笔记、
|
||||
工作记录、方法论提取。Triggered by:
|
||||
pf-deep pf-paper pf-sync pf-ocr pf-status,
|
||||
"精读" "找文献" "搜文献" "文献问答" "读一下" "看看这篇"
|
||||
"讨论" "记录阅读" "记录工作" "总结会话" "提取方法论".
|
||||
source: paperforge
|
||||
---
|
||||
|
||||
# PaperForge — Research Memory Runtime
|
||||
|
||||
PaperForge 将文献、阅读痕迹、工作过程、方法论和产物
|
||||
组织成可检索、可复核、可由 agent 调用的研究记忆。
|
||||
|
||||
---
|
||||
|
||||
## 1. Bootstrap — 必须先执行
|
||||
|
||||
```bash
|
||||
python $SKILL_DIR/scripts/pf_bootstrap.py --vault "$VAULT"
|
||||
```
|
||||
|
||||
返回 JSON。记录以下变量(所有 workflow 文件继承,不再重复声明):
|
||||
|
||||
| 变量 | JSON 字段 | 用途 |
|
||||
| ------------- | ----------------------- | ------------------------------ |
|
||||
| `$VAULT` | `vault_root` | 所有 `--vault` 参数 |
|
||||
| `$PYTHON` | `python_candidate` | 所有 `python -m paperforge` 调用 |
|
||||
| `$LIT_DIR` | `paths.literature_dir` | 文献笔记根目录 |
|
||||
| `$SKILL_DIR` | 平台注入 | 脚本路径 |
|
||||
| `$METHODS` | `methodology_index` | 可用方法论索引 |
|
||||
|
||||
如果 `ok: false`,报告 `error` 给用户,**停止。禁止自己拼路径。**
|
||||
|
||||
---
|
||||
|
||||
## 2. Agent Context — bootstrap 成功后执行
|
||||
|
||||
```bash
|
||||
$PYTHON -m paperforge agent-context --json --vault "$VAULT"
|
||||
```
|
||||
|
||||
返回 library overview、collection tree、可用命令和规则。Agent 注入为会话上下文。
|
||||
|
||||
---
|
||||
|
||||
## 3. Methodology Index — bootstrap 自动提供
|
||||
|
||||
bootstrap 已返回 `methodology_index`(从 `System/PaperForge/methodology/archive/` 扫描)。
|
||||
Agent 在需要时自行读取对应卡片(`read System/PaperForge/methodology/archive/<id>.md`)。
|
||||
|
||||
---
|
||||
|
||||
## 4. Reading-Log Safety Rule — 全局规则,所有 workflow 必须遵守
|
||||
|
||||
Reading-log 不是事实源。它记录的是**之前的关注点、解读和预期用途**。
|
||||
|
||||
当存在 prior reading-log 时:
|
||||
1. 用它决定**优先复查什么**,不是用它回答用户问题
|
||||
2. 重新打开**原文/图表/表格**,核实之前的解读
|
||||
3. 确认的,说明"已回原文复核"
|
||||
4. 被推翻的,创建 correction note
|
||||
5. **绝对禁止**仅根据 reading-log 内容回答事实性问题
|
||||
|
||||
---
|
||||
|
||||
## 5. 意图路由
|
||||
|
||||
用户输入对应唯一一个 workflow 文件(打开并执行其完整流程):
|
||||
|
||||
| 用户说 | 打开 |
|
||||
| -------------------------------------------------------- | -------------------------------- |
|
||||
| "找文献" "搜" "库里有没有XX" "collection 里关于YY" | `workflows/paper-search.md` |
|
||||
| "精读 <key>" "/pf-deep" "三阶段阅读" | `workflows/deep-reading.md` |
|
||||
| "读一下" "看看" "讨论" "/pf-paper" "<key> 这篇讲了什么" | `workflows/paper-qa.md` |
|
||||
| "记一下" "记录阅读" "reading log" "读完这段记一下" | `workflows/reading-log.md` |
|
||||
| "总结会话" "工作记录" "项目记录" "project log" "记决策" | `workflows/project-log.md` |
|
||||
| "提取方法论" "总结规律" "存档写作规律" | `workflows/methodology.md` |
|
||||
| 不确定 / 空输入 | 问用户:搜文献、精读、问答、记笔记、记工作、提方法论? |
|
||||
|
||||
路由后如用户切换意图,重新判断并打开对应 workflow。
|
||||
|
||||
---
|
||||
|
||||
## 6. 全局禁止规则
|
||||
|
||||
- **禁止自行拼接文件路径**。所有路径从 bootstrap 或 paper-context 获取。
|
||||
- **禁止绕过 CLI 直接操作文件**。搜索用 `$PYTHON -m paperforge search`,不用 glob/grep 扫库。
|
||||
- **禁止在未完成 paper-context 检查前读取原文**(适用于 deep-reading、paper-qa)。
|
||||
|
||||
---
|
||||
|
||||
## 文件结构
|
||||
|
||||
```
|
||||
paperforge/
|
||||
├── SKILL.md ← 本文件(compound:启动注入 + 路由 + 全局规则)
|
||||
├── workflows/ ← molecules:原子序列 + 分支条件
|
||||
│ ├── paper-search.md
|
||||
│ ├── deep-reading.md
|
||||
│ ├── paper-qa.md
|
||||
│ ├── reading-log.md
|
||||
│ ├── project-log.md
|
||||
│ └── methodology.md
|
||||
├── references/ ← 共享参考
|
||||
│ ├── chart-reading/ ← 19 种图表阅读指南
|
||||
│ └── method-card-template.md
|
||||
└── scripts/ ← 脚本 atoms
|
||||
├── pf_bootstrap.py
|
||||
└── pf_deep.py
|
||||
```
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
# Method Card Template
|
||||
|
||||
复制此模板创建新的方法论卡片。
|
||||
|
||||
---
|
||||
|
||||
<!-- 卡片 frontmatter(YAML,Agent 写入时保留) -->
|
||||
<!--
|
||||
---
|
||||
id: <kebab-case-id>
|
||||
tags: [<tag1>, <tag2>]
|
||||
source_project: <project-name>
|
||||
status: active
|
||||
---
|
||||
-->
|
||||
|
||||
# <标题:简短、可搜索>
|
||||
|
||||
## Use when
|
||||
<!-- 什么时候应该用这个方法:1-2 句话 -->
|
||||
|
||||
## Procedure
|
||||
<!-- 具体步骤,每步一个编号 -->
|
||||
|
||||
1. <步骤 1>
|
||||
2. <步骤 2>
|
||||
3. <步骤 3>
|
||||
|
||||
## Watch-outs
|
||||
<!-- 常见陷阱、误用场景 -->
|
||||
|
||||
- <注意 1>
|
||||
- <注意 2>
|
||||
|
||||
## Example
|
||||
<!-- 来自项目的具体例子,最好附上 project-log 来源 -->
|
||||
|
||||
---
|
||||
<!-- 每张卡片末尾保留分隔线 -->
|
||||
|
|
@ -18,7 +18,11 @@ Output (JSON to stdout):
|
|||
},
|
||||
"domains": ["domain1", "domain2"],
|
||||
"index_summary": {"domain1": 120, "domain2": 80},
|
||||
"python_candidate": "D:\\...\\python.exe" // Python that has paperforge, or null
|
||||
"python_candidate": "D:\\...\\python.exe",
|
||||
"methodology_index": [
|
||||
{"id": "parameter-window-audit", "description": "比较多个研究的参数和剂量反应"},
|
||||
...
|
||||
]
|
||||
}
|
||||
|
||||
If anything fails: ok=false, error explains why.
|
||||
|
|
@ -81,6 +85,39 @@ def _find_python_with_paperforge(vault: Path, pf_cfg: dict) -> str | None:
|
|||
return None
|
||||
|
||||
|
||||
def _scan_methodology_archive(pf_root: Path) -> list[dict]:
|
||||
"""Scan methodology archive directory for available method cards."""
|
||||
archive_dir = pf_root / "methodology" / "archive"
|
||||
if not archive_dir.exists():
|
||||
return []
|
||||
|
||||
methods = []
|
||||
for f in sorted(archive_dir.glob("*.md")):
|
||||
try:
|
||||
text = f.read_text(encoding="utf-8")
|
||||
# Extract first heading as title, first paragraph after "Use when" as description
|
||||
title = ""
|
||||
description = ""
|
||||
in_use_when = False
|
||||
for line in text.split("\n"):
|
||||
stripped = line.strip()
|
||||
if stripped.startswith("# ") and not title:
|
||||
title = stripped.lstrip("# ").strip()
|
||||
elif stripped.startswith("## Use when"):
|
||||
in_use_when = True
|
||||
elif in_use_when and stripped and not stripped.startswith("#"):
|
||||
description = stripped
|
||||
in_use_when = False
|
||||
methods.append({
|
||||
"id": f.stem,
|
||||
"title": title or f.stem,
|
||||
"description": description or "(no description)",
|
||||
})
|
||||
except Exception:
|
||||
continue
|
||||
return methods
|
||||
|
||||
|
||||
def main():
|
||||
import argparse
|
||||
p = argparse.ArgumentParser(description="PaperForge bootstrap")
|
||||
|
|
@ -180,6 +217,9 @@ def main():
|
|||
pass
|
||||
result["memory_layer"] = memory_layer
|
||||
|
||||
# --- 8. Scan methodology archive ---
|
||||
result["methodology_index"] = _scan_methodology_archive(pf_root)
|
||||
|
||||
result["ok"] = True
|
||||
json.dump(result, sys.stdout, ensure_ascii=False, indent=2)
|
||||
|
||||
169
paperforge/skills/paperforge/workflows/deep-reading.md
Normal file
169
paperforge/skills/paperforge/workflows/deep-reading.md
Normal file
|
|
@ -0,0 +1,169 @@
|
|||
# deep-reading
|
||||
|
||||
Keshav 三阶段精读。在 formal note 中写入结构化的 `## 精读` 区域。
|
||||
|
||||
---
|
||||
|
||||
## 前置检查
|
||||
|
||||
### Step 0: paper-context(必须)
|
||||
|
||||
```bash
|
||||
$PYTHON -m paperforge paper-context <zotero_key> --json --vault "$VAULT"
|
||||
```
|
||||
|
||||
检查返回 JSON:
|
||||
- `ok: false` → 报告 `error.message`,停止
|
||||
- `data.paper.ocr_status != "done"` → "OCR 未完成,请先运行 paperforge ocr",停止
|
||||
- `data.paper.analyze != true` → "analyze 未开启,请在 formal note frontmatter 中设为 true",停止
|
||||
|
||||
**检查 prior_notes:**
|
||||
- 如果存在 `data.prior_notes`,逐条看 `verified` 字段
|
||||
- `verified: false` 的条目记入 recheck_targets,精读时必须回原文复核这些位置
|
||||
- `verified: true` 的条目可以信任,但标注"之前已验证"
|
||||
|
||||
**记录关键路径:**
|
||||
- `data.paper.note_path`(formal note 路径)
|
||||
- `data.paper.fulltext_path`(fulltext 路径)
|
||||
- 记下 `recheck_targets` 列表
|
||||
|
||||
---
|
||||
|
||||
## 执行流程
|
||||
|
||||
### Step 1: Prepare(跑脚本)
|
||||
|
||||
```bash
|
||||
$PYTHON "$SKILL_DIR/scripts/pf_deep.py" prepare --key <zotero_key> --vault "$VAULT"
|
||||
```
|
||||
|
||||
解析返回 JSON:
|
||||
- `status: "ok"` → 记下 `figure_map`、`chart_type_map`、`formal_note`、`fulltext_md`、`figures`、`tables` 的路径和数量
|
||||
- `status: "warn"` + `deep_reading_status: done` → 告知用户"该文献已精读过",确认是否重读
|
||||
- `status: "error"` → 报告 `message`,停止
|
||||
|
||||
读 formal note,确认 `## 精读` 骨架已插入。
|
||||
|
||||
---
|
||||
|
||||
### Step 2: Pass 1 — 概览
|
||||
|
||||
只填 `### Pass 1: 概览`。不碰 Pass 2/3。
|
||||
|
||||
填写内容必须来自原文,不可推断:
|
||||
|
||||
- **一句话总览**:论文类型 + 核心发现,一句话
|
||||
- **5 Cs 快速评估**:
|
||||
- Category(RCT / 队列 / 综述 / 基础研究等)
|
||||
- Context(领域共识,本文要解决什么)
|
||||
- Correctness(初步直觉,逻辑有否明显漏洞)
|
||||
- Contributions(1-3 条)
|
||||
- Clarity(写作质量,图表可读性)
|
||||
- **Figure 导读**(基于 fulltext 浏览各图 caption):
|
||||
- 关键主图:列出,一句话概括要证明什么
|
||||
- 证据转折点:哪个 figure 是叙事关键转折
|
||||
- 需要重点展开的 supplementary
|
||||
- 关键表格
|
||||
|
||||
填完立即保存。
|
||||
|
||||
---
|
||||
|
||||
### Step 3: Pass 2 — 精读还原
|
||||
|
||||
填 `### Pass 2: 精读还原`。**按 figure 顺序逐个处理**。
|
||||
|
||||
每处理完一个 figure 立即保存。
|
||||
|
||||
#### 图表类型定位(两步)
|
||||
|
||||
**A: 读 chart-type-map**(prepare 输出中包含该路径)。这是关键词命中建议。
|
||||
|
||||
**B: Agent 读 caption 做最终判断**
|
||||
1. 读该 figure 的 caption(来自 fulltext)
|
||||
2. 打开 `references/chart-reading/INDEX.md`,对照 caption 内容判断图表类型
|
||||
3. chart-type-map 建议和 Agent 判断不一致时 → 以 Agent 判断为准
|
||||
4. 无法确定类型 → 跳过 chart guide,按通用结构分析
|
||||
5. 确定类型 → 读对应 chart-reading 指南,按指南中的检查清单分析
|
||||
|
||||
#### 每张 Figure 的子标题(固定,不可跳过)
|
||||
|
||||
```
|
||||
**图像定位与核心问题**:页码 + 要回答什么问题
|
||||
**方法与结果**:实验设计 / 数据来源 / 技术手段;核心数据、趋势、对比
|
||||
**图表质量审查**:按 chart-reading 指南检查坐标轴、单位、误差棒、统计标注
|
||||
**作者解释**:作者在正文中对该图的解读
|
||||
**我的理解**:自己的理解(必须与作者解释做明显区分)
|
||||
**疑点/局限**:用 `> [!warning]` 突出
|
||||
```
|
||||
|
||||
#### 每张 Table 的子标题(简化版)
|
||||
|
||||
```
|
||||
回答什么问题、关键字段/分组、主要结果、我的理解、疑点/局限
|
||||
```
|
||||
|
||||
#### 所有 figure/table 处理完后
|
||||
|
||||
**关键方法补课**:简要解释不熟悉的实验技术(1-2 项)
|
||||
|
||||
**主要发现与新意**:
|
||||
- 发现 1:...(来源:Figure X)
|
||||
- 发现 2:...(来源:Table Y)
|
||||
- 每条发现必须标注来源(Figure 编号或正文段落)
|
||||
|
||||
---
|
||||
|
||||
### Step 4: Postprocess(跑校验,修正问题)
|
||||
|
||||
```bash
|
||||
$PYTHON "$SKILL_DIR/scripts/pf_deep.py" postprocess-pass2 "<formal_note_path>" --figures <N> --vault "$VAULT"
|
||||
```
|
||||
|
||||
- 输出 `OK` → 继续 Step 5
|
||||
- 输出错误列表(含行号)→ 按提示修正,修正后重新跑
|
||||
- 最多 3 轮修正。3 轮后仍失败 → 报告剩余错误给用户
|
||||
|
||||
---
|
||||
|
||||
### Step 5: Pass 3 — 深度理解
|
||||
|
||||
填 `### Pass 3: 深度理解`。基于 Pass 1/2 已写内容。
|
||||
|
||||
- **假设挑战与隐藏缺陷**:隐含假设;放宽假设后结论还成立吗;缺少的关键引用;实验/分析技术潜在问题
|
||||
- **哪些结论扎实,哪些仍存疑**:
|
||||
- 较扎实:...
|
||||
- 仍存疑:...(用 `> [!warning]`)
|
||||
- **Discussion 与 Conclusion 怎么读**:作者实际完成了什么;哪些有拔高;哪些是推测
|
||||
- **对我的启发**:研究设计、figure 组织、方法组合、未来工作
|
||||
- **遗留问题**:...(用 `> [!question]`)
|
||||
|
||||
---
|
||||
|
||||
### Step 6: Final Validation
|
||||
|
||||
```bash
|
||||
$PYTHON "$SKILL_DIR/scripts/pf_deep.py" validate-note "<formal_note_path>" --fulltext "<fulltext_path>"
|
||||
```
|
||||
|
||||
- 输出 `OK` → 告知用户精读完成
|
||||
- 输出错误 → 修正缺失项,直到通过
|
||||
|
||||
---
|
||||
|
||||
## Callout 格式规则
|
||||
|
||||
- `> [!important]` — 每个 main finding
|
||||
- `> [!warning]` — 疑问、局限、证据边界、仍存疑条目
|
||||
- `> [!question]` — 遗留问题
|
||||
- **相邻 callout 之间必须有空行**(否则 Obsidian 合并):
|
||||
- 正确:`> [!important] A\n\n> [!important] B`
|
||||
- 错误:`> [!important] A\n> [!important] B`
|
||||
|
||||
---
|
||||
|
||||
## 禁止
|
||||
|
||||
- 不要在 Pass 1 完成前碰 Pass 2/3
|
||||
- 不要把推断写成文献事实——区分"作者说了 X"和"我推断 Y"
|
||||
- 不要跨 figure 写综合判断(Pass 2 逐图,Pass 3 才做综合)
|
||||
94
paperforge/skills/paperforge/workflows/methodology.md
Normal file
94
paperforge/skills/paperforge/workflows/methodology.md
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
# methodology
|
||||
|
||||
从 project-log 中提取可复用方法论,按 method-card 模板写入 methodology archive。
|
||||
不 append 到大文件,每张卡片独立保存。
|
||||
|
||||
---
|
||||
|
||||
## 前置条件
|
||||
|
||||
- bootstrap 已完成
|
||||
- 有 project-log 记录可读取
|
||||
|
||||
---
|
||||
|
||||
## 步骤
|
||||
|
||||
### Step 1: 确定项目和来源
|
||||
|
||||
询问用户从哪个项目提取。如用户未指定,列出有 project-log 的项目。
|
||||
|
||||
### Step 2: 读取 project-log
|
||||
|
||||
```bash
|
||||
$PYTHON -m paperforge project-log --list "<project>" --json --vault "$VAULT"
|
||||
```
|
||||
|
||||
扫描其中以下信号:
|
||||
|
||||
| log 中的信号 | 可提取为 |
|
||||
| ------------------------- | --------------------------- |
|
||||
| `detours` 中的教训 | 方法论规则 |
|
||||
| `reusable` 字段里的内容 | 直接采用 |
|
||||
| `decisions` 中的重要选择 | 决策原则 |
|
||||
| 跨文献审计/比较分析 | 审计方法论 |
|
||||
| 写作修正/审阅反馈 | 写作检查清单 |
|
||||
|
||||
### Step 3: 识别可提取 pattern
|
||||
|
||||
对每个 pattern 分类:
|
||||
- `review-writing` — 综述框架设计、gap 分析、跨研究审计
|
||||
- `argument-writing` — 段落写作、论证结构
|
||||
- `analysis-methods` — 文献审计、跨研究比较、参数提取
|
||||
- `general` — fallback
|
||||
|
||||
### Step 4: 按 method-card 模板生成卡片
|
||||
|
||||
打开 `references/method-card-template.md` 确认模板格式。
|
||||
|
||||
对每个 pattern 生成一张卡片,展示给用户确认。格式:
|
||||
|
||||
```markdown
|
||||
---
|
||||
id: <kebab-case-id>
|
||||
tags: [<tag1>, <tag2>]
|
||||
source_project: <project-name>
|
||||
status: active
|
||||
---
|
||||
|
||||
# <标题>
|
||||
|
||||
## Use when
|
||||
<什么时候应该用这个方法>
|
||||
|
||||
## Procedure
|
||||
1. <步骤 1>
|
||||
2. <步骤 2>
|
||||
...
|
||||
|
||||
## Watch-outs
|
||||
- <注意事项 1>
|
||||
- <注意事项 2>
|
||||
|
||||
## Example
|
||||
<来自项目的具体例子>
|
||||
```
|
||||
|
||||
### Step 5: 用户确认后写入
|
||||
|
||||
将每张卡片写入:
|
||||
|
||||
```
|
||||
System/PaperForge/methodology/archive/<id>.md
|
||||
```
|
||||
|
||||
用 `write` 工具创建文件。如已存在同名文件,追加到末尾(用 `---` 分隔)。
|
||||
不自动覆盖已有内容。
|
||||
|
||||
---
|
||||
|
||||
## 禁止
|
||||
|
||||
- 不要提取太泛的"教训"(如"多读文献")——必须有具体的 Procedure 步骤
|
||||
- 不要创建超过 4 张卡片/次——优先最可复用的
|
||||
- 不要在用户确认前写入
|
||||
105
paperforge/skills/paperforge/workflows/paper-qa.md
Normal file
105
paperforge/skills/paperforge/workflows/paper-qa.md
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
# paper-qa
|
||||
|
||||
交互式文献问答。不强制要求 OCR,但 OCR 完成后回答更准确。
|
||||
|
||||
每次问答记录到 `discussion.json`(Dashboard 可见)。
|
||||
|
||||
---
|
||||
|
||||
## 前置条件
|
||||
|
||||
- bootstrap 已完成(有 `$VAULT`、`$PYTHON`)
|
||||
|
||||
---
|
||||
|
||||
## 步骤
|
||||
|
||||
### Step 1: 定位论文
|
||||
|
||||
用户可能给 zotero_key、DOI、标题片段、作者+年份。按以下方式查找:
|
||||
|
||||
**优先用 paper-context(一次拿到全部信息):**
|
||||
|
||||
```bash
|
||||
$PYTHON -m paperforge paper-context <query> --json --vault "$VAULT"
|
||||
```
|
||||
|
||||
返回 JSON 包含 paper 元数据、OCR 状态、prior_notes 等。
|
||||
|
||||
**paper-context 无结果时的备选:**
|
||||
|
||||
```bash
|
||||
$PYTHON -m paperforge search "<query>" --json --vault "$VAULT" --limit 5
|
||||
```
|
||||
|
||||
如果多候选,列出让用户选(同 paper-search 的 Step 4-5 格式)。
|
||||
如果无结果,告知用户并停止。
|
||||
|
||||
### Step 2: 加载文献内容
|
||||
|
||||
1. 从 paper-context 或 formal note frontmatter 获取:标题、作者、期刊、年份、domain
|
||||
2. 读 `fulltext.md`(如果 OCR done)作为主要回答依据
|
||||
3. 如果 fulltext 不存在:"OCR 文本不可用,回答将基于元数据和公开信息"
|
||||
|
||||
### Step 3: 展示论文信息 + 进入 Q&A
|
||||
|
||||
```
|
||||
已加载: <title> (<year>, <journal>)
|
||||
作者: <authors> | Key: <zotero_key> | 领域: <domain>
|
||||
OCR: done / 不可用
|
||||
结束对话时说"保存"即可保存讨论。
|
||||
请问有什么问题?
|
||||
```
|
||||
|
||||
### Step 4: Q&A 循环
|
||||
|
||||
- 等待用户提问
|
||||
- 每次回答后等待下一个问题
|
||||
- 持续到用户说"保存"、"结束"、"完成"
|
||||
|
||||
**回答原则:**
|
||||
- 严格基于 fulltext.md 中的文本内容
|
||||
- 引用原文时标注来源页码/章节
|
||||
- 论文未提及的内容明确说明"论文中未提及"
|
||||
- 区分"文献说了什么"和"我推断什么"
|
||||
|
||||
### Step 5: 保存讨论
|
||||
|
||||
用户说"保存"、"结束"、"完成"时执行。
|
||||
|
||||
**收集 Q&A 对**,序列化为 JSON 数组:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"question": "用户的问题",
|
||||
"answer": "Agent 的回答",
|
||||
"source": "user_question",
|
||||
"timestamp": "2026-05-14T12:00:00+08:00"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
`source`: `"user_question"`(用户提问)或 `"agent_analysis"`(Agent 主动分析)。
|
||||
|
||||
**调 discussion 模块:**
|
||||
|
||||
```bash
|
||||
$PYTHON -m paperforge.worker.discussion record <zotero_key> \
|
||||
--vault "$VAULT" \
|
||||
--agent pf-paper \
|
||||
--model "<current_model>" \
|
||||
--qa-pairs '<JSON_ARRAY>'
|
||||
```
|
||||
|
||||
- 返回 `ok` → 告知用户已保存
|
||||
- 返回 `error` → 重试一次,仍失败则告知用户
|
||||
|
||||
**不要自动保存。** 仅用户明确要求时执行。
|
||||
|
||||
---
|
||||
|
||||
## 禁止
|
||||
|
||||
- 不要捏造论文未提及的内容
|
||||
- 不要把推断写成论文事实
|
||||
101
paperforge/skills/paperforge/workflows/paper-search.md
Normal file
101
paperforge/skills/paperforge/workflows/paper-search.md
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
# paper-search
|
||||
|
||||
从文献库中按条件检索文献,返回候选清单及每篇的可用状态。
|
||||
|
||||
---
|
||||
|
||||
## 前置条件
|
||||
|
||||
- bootstrap 已完成(有 `$VAULT`、`$PYTHON`、`$LIT_DIR`)
|
||||
|
||||
---
|
||||
|
||||
## 步骤
|
||||
|
||||
### Step 1: 解析用户搜索意图
|
||||
|
||||
提取以下信息(缺什么就问用户):
|
||||
- **搜索词**:关键词、作者名、年份
|
||||
- **范围**:domain(如"骨科")、collection(如"DC")、不指定=全库
|
||||
- **过滤条件**:OCR 状态(done/pending)、年份范围(--year-from/--year-to)、lifecycle
|
||||
|
||||
### Step 2: 执行搜索
|
||||
|
||||
```bash
|
||||
$PYTHON -m paperforge search <query> --json --vault "$VAULT" --limit 15 \
|
||||
[--domain "<domain>"] \
|
||||
[--year-from <N>] [--year-to <N>] \
|
||||
[--ocr <done|pending>] \
|
||||
[--lifecycle <active|archived>]
|
||||
```
|
||||
|
||||
返回 JSON 结构:
|
||||
```json
|
||||
{
|
||||
"ok": true,
|
||||
"data": {
|
||||
"query": "<query>",
|
||||
"matches": [
|
||||
{
|
||||
"zotero_key": "ABC12345",
|
||||
"citation_key": "...",
|
||||
"title": "...",
|
||||
"year": "2024",
|
||||
"first_author": "Smith",
|
||||
"domain": "...",
|
||||
"collection_path": "...",
|
||||
"ocr_status": "done",
|
||||
"deep_reading_status": "pending",
|
||||
"lifecycle": "active",
|
||||
"has_pdf": true,
|
||||
"rank": "..."
|
||||
}
|
||||
],
|
||||
"count": 5
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- 如果 `ok: false` → 报告 `error.message`,问用户是否换搜索词
|
||||
- 如果 `data.count == 0` → 告知用户无结果,建议换词或扩大范围
|
||||
- 如果 `data.count > 0` → 进入 Step 3
|
||||
|
||||
### Step 3: 逐个确认状态(paper-context 原子)
|
||||
|
||||
对每个 match,调 `paper-context` 获取更详细的可读状态:
|
||||
|
||||
```bash
|
||||
$PYTHON -m paperforge paper-context <zotero_key> --json --vault "$VAULT"
|
||||
```
|
||||
|
||||
目的:拿到 `ocr_status`、`prior_notes` 数量、`analyze` 状态,帮助用户判断哪些可以直接读。
|
||||
|
||||
### Step 4: 展示候选清单
|
||||
|
||||
格式(每条一行):
|
||||
|
||||
```
|
||||
找到 N 篇匹配 "<query>":
|
||||
|
||||
[1] ABC12345 | Smith 2024 | Title Here | 骨科 | OCR: done | 精读: pending | 阅读笔记: 3
|
||||
[2] DEF67890 | Jones 2023 | Title Here | 骨科 | OCR: done | 精读: done | 阅读笔记: 0
|
||||
[3] GHI11111 | Wang 2022 | Title Here | 骨科 | OCR: pending | | 阅读笔记: 0
|
||||
```
|
||||
|
||||
关键字段:zotero_key, first_author, year, title, ocr_status, deep_reading_status, prior_notes 数量
|
||||
|
||||
### Step 5: 等用户选择后续操作
|
||||
|
||||
展示候选后不要自己决定下一步。等用户说:
|
||||
- "读一下 [1]" → 路由到 paper-qa.md
|
||||
- "精读 [2]" → 路由到 deep-reading.md
|
||||
- "记一下 [1]" → 路由到 reading-log.md
|
||||
- "缩小范围"/"refine" → 回到 Step 1,加更多过滤条件
|
||||
|
||||
---
|
||||
|
||||
## 禁止
|
||||
|
||||
- 不要在搜索结果中替用户决定读哪篇
|
||||
- 不要在搜索阶段读全文
|
||||
- 不要对 0 结果硬猜路径
|
||||
131
paperforge/skills/paperforge/workflows/project-log.md
Normal file
131
paperforge/skills/paperforge/workflows/project-log.md
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
# project-log
|
||||
|
||||
记录研究项目的会话总结、决策、弯路修正和方法论提取。
|
||||
Agent 按 JSON schema 写入 project-log.jsonl。
|
||||
系统自动渲染对应项目的 project-log.md。
|
||||
|
||||
---
|
||||
|
||||
## 前置条件
|
||||
|
||||
- bootstrap 已完成(有 `$VAULT`、`$PYTHON`)
|
||||
- 已知 project 名称
|
||||
|
||||
---
|
||||
|
||||
## 项目日志 JSON Schema
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "plog_<YYYYMMDD>_<序号>",
|
||||
"project": "综述写作",
|
||||
"date": "2026-05-14",
|
||||
"type": "session_summary",
|
||||
"title": "DC 段参数窗审计",
|
||||
"decisions": ["做了 X,因为 Y"],
|
||||
"detours": [
|
||||
{
|
||||
"wrong": "错误方向",
|
||||
"correction": "用户如何纠正",
|
||||
"resolution": "最终方案"
|
||||
}
|
||||
],
|
||||
"reusable": ["可复用的方法论或教训"],
|
||||
"todos": [
|
||||
{"content": "待办事项", "done": false}
|
||||
],
|
||||
"related_papers": ["ABC12345"],
|
||||
"tags": ["DC", "参数窗", "审计"],
|
||||
"agent": "opencode"
|
||||
}
|
||||
```
|
||||
|
||||
| 字段 | 必填 | 说明 |
|
||||
| ---------------- | ---- | ------------------------------------------- |
|
||||
| `id` | 是 | 自动生成 `plog_YYYYMMDD_NNN` |
|
||||
| `project` | 是 | 项目名 |
|
||||
| `date` | 是 | YYYY-MM-DD |
|
||||
| `type` | 是 | `session_summary` / `decision` / `correction` / `milestone` / `note` |
|
||||
| `title` | 是 | 本条目的简短标题 |
|
||||
| `decisions` | 否 | 核心决策列表 |
|
||||
| `detours` | 否 | 弯路与修正记录 |
|
||||
| `reusable` | 否 | 可复用的方法论或教训 |
|
||||
| `todos` | 否 | 待办事项 |
|
||||
| `related_papers` | 否 | 相关 Zotero keys |
|
||||
| `tags` | 否 | 分类标签 |
|
||||
| `agent` | 否 | 记录者 |
|
||||
|
||||
---
|
||||
|
||||
## 步骤
|
||||
|
||||
### Step 1: 确定 project
|
||||
|
||||
从上下文获取。如果用户未指定,询问。
|
||||
|
||||
### Step 2: 回顾本次会话
|
||||
|
||||
回顾以下内容:
|
||||
- 做了什么(核心决策)
|
||||
- 用户纠正了什么(弯路与修正)
|
||||
- 有什么可复用的方法论或教训
|
||||
- 待办事项
|
||||
|
||||
### Step 3: 按 Schema 组织内容,展示确认
|
||||
|
||||
展示给用户确认后再写入:
|
||||
|
||||
```
|
||||
即将记录到 Project/综述写作/project-log.md:
|
||||
日期: 2026-05-14
|
||||
类型: session_summary
|
||||
标题: DC 段参数窗审计完成
|
||||
决策:
|
||||
- 限定参数窗为 100Hz-1kHz
|
||||
- 移除 AC vs DC 对比段落
|
||||
弯路:
|
||||
- 把推断当文献事实 → 用户要求逐句审计 → 5 处修正
|
||||
可复用:
|
||||
- 写完必须逐句过 source,区分"文献说了什么"和"我推断什么"
|
||||
|
||||
确认写入?(y/n)
|
||||
```
|
||||
|
||||
### Step 4: 写入(Atom)
|
||||
|
||||
```bash
|
||||
$PYTHON -m paperforge project-log --write \
|
||||
--vault "$VAULT" \
|
||||
--project "<project>" \
|
||||
--json '<payload>'
|
||||
```
|
||||
|
||||
- 返回 `ok: true` → 确认写入成功。**自动渲染对应项目 markdown。**
|
||||
- 返回 `ok: false` → 报告错误,重试一次
|
||||
|
||||
### Step 5: 确认渲染
|
||||
|
||||
```bash
|
||||
$PYTHON -m paperforge project-log --render --project "<project>" --vault "$VAULT"
|
||||
```
|
||||
|
||||
输出到 `Project/<project>/project-log.md`。
|
||||
|
||||
---
|
||||
|
||||
## type 参考
|
||||
|
||||
| type | 使用场景 |
|
||||
| ----------------- | ------------------------------ |
|
||||
| `session_summary` | 会话结束时的总结 |
|
||||
| `decision` | 单独记录一个重要决策 |
|
||||
| `correction` | 用户纠正了某个方向 |
|
||||
| `milestone` | 项目里程碑 |
|
||||
| `note` | 一般研究笔记 |
|
||||
|
||||
---
|
||||
|
||||
## 禁止
|
||||
|
||||
- 不要在用户确认前写入
|
||||
- 不要只写"做了什么"而没有"弯路与修正"和"可复用方法论"
|
||||
112
paperforge/skills/paperforge/workflows/reading-log.md
Normal file
112
paperforge/skills/paperforge/workflows/reading-log.md
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
# reading-log
|
||||
|
||||
记录单条阅读笔记。Agent 将用户确认的信息按 JSON schema 写入 reading-log.jsonl。
|
||||
系统自动渲染对应项目的 reading-log.md(给人看)并导入 paperforge.db(可搜索)。
|
||||
|
||||
---
|
||||
|
||||
## 前置条件
|
||||
|
||||
- bootstrap 已完成(有 `$VAULT`、`$PYTHON`)
|
||||
- 已知 paper_id(zotero_key)
|
||||
|
||||
---
|
||||
|
||||
## 阅读笔记 JSON Schema
|
||||
|
||||
每条阅读笔记必须包含以下字段:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "rln_<YYYYMMDD>_<序号>",
|
||||
"paper_id": "ABC12345",
|
||||
"project": "综述写作",
|
||||
"section": "Results Fig.3",
|
||||
"excerpt": "原文关键句(逐字引用)",
|
||||
"context": "包含 excerpt 的完整段落(供后续回原文复核时定位)",
|
||||
"usage": "这个信息在当前写作中的用途",
|
||||
"note": "注意事项 / 待核查 / 可能矛盾",
|
||||
"tags": ["PEMF", "dose-response"],
|
||||
"verified": false
|
||||
}
|
||||
```
|
||||
|
||||
| 字段 | 必填 | 说明 |
|
||||
| --------- | ---- | -------------------------------------------------------- |
|
||||
| `id` | 是 | 自动生成,格式 `rln_YYYYMMDD_NNN` |
|
||||
| `paper_id` | 是 | Zotero key(8位大写字母数字) |
|
||||
| `project` | 否 | 关联的研究项目 |
|
||||
| `section` | 是 | 文献中的位置(如 "Results Fig.3"、"Discussion P12") |
|
||||
| `excerpt` | 是 | 逐字引用的原文关键句 |
|
||||
| `context` | 是 | 包含 excerpt 的完整段落,供复核定位 |
|
||||
| `usage` | 是 | 这个信息在当前工作(写作/研究)中的用途 |
|
||||
| `note` | 否 | 交叉验证、矛盾、待核查事项 |
|
||||
| `tags` | 否 | 分类标签,供横切检索 |
|
||||
| `verified` | 否 | 默认 false。Agent 回原文复核后应更新为 true |
|
||||
|
||||
---
|
||||
|
||||
## 步骤
|
||||
|
||||
### Step 1: 确认 paper_id 和 project
|
||||
|
||||
从上下文获取 zotero_key。如果用户未指定 project,询问或留空。
|
||||
|
||||
### Step 2: Agent 按 Schema 提取内容
|
||||
|
||||
从对话上下文中提取 `section`、`excerpt`、`context`、`usage`、`note`、`tags`。
|
||||
|
||||
**excerpt vs context 的区别:**
|
||||
- `excerpt`:你关注的那一句(逐字引用)
|
||||
- `context`:包含这句的完整段落(3-5 句),让以后的人不翻原文也能理解语境
|
||||
|
||||
### Step 3: 展示确认
|
||||
|
||||
先展示给用户确认,不要直接写入:
|
||||
|
||||
```
|
||||
即将记录:
|
||||
文献: ABC12345 | Smith 2024
|
||||
位置: Results Fig.3
|
||||
原文: "..."
|
||||
用途: 支撑 PEMF 基质合成的论证
|
||||
备注: 需核查是否对 DNA 归一化了
|
||||
项目: 综述写作
|
||||
标签: PEMF, GAG
|
||||
段落语境: "..."
|
||||
|
||||
确认写入?(y/n)
|
||||
```
|
||||
|
||||
### Step 4: 写入(Atom)
|
||||
|
||||
```bash
|
||||
$PYTHON -m paperforge reading-log --write <paper_id> \
|
||||
--vault "$VAULT" \
|
||||
--section "<section>" \
|
||||
--excerpt "<excerpt>" \
|
||||
--context "<context>" \
|
||||
--usage "<usage>" \
|
||||
--note "<note>" \
|
||||
--project "<project>" \
|
||||
--tags "<tag1>,<tag2>"
|
||||
```
|
||||
|
||||
- 返回 `ok: true` → 确认写入成功。**写入后自动渲染对应项目的 markdown。**
|
||||
- 返回 `ok: false` → 报告错误,重试一次
|
||||
|
||||
### Step 5: 确认渲染
|
||||
|
||||
```bash
|
||||
$PYTHON -m paperforge reading-log --render --project "<project>" --vault "$VAULT"
|
||||
```
|
||||
|
||||
输出到 `Project/<project>/reading-log.md`。
|
||||
|
||||
---
|
||||
|
||||
## 禁止
|
||||
|
||||
- 不要在用户确认前写入
|
||||
- 不要把推断当作 `excerpt`(必须是原文逐字引用)
|
||||
- 不要让 `context` 为空(必须是完整段落)
|
||||
|
|
@ -639,36 +639,36 @@ def run_doctor(vault: Path, verbose: bool = False, json_output: bool = False) ->
|
|||
if total_issues == 0:
|
||||
add_check("字段注册表", "pass", "所有 formal note frontmatter 与字段注册表一致")
|
||||
|
||||
ld_deep_script = paths.get("ld_deep_script")
|
||||
pf_deep_script = paths.get("pf_deep_script")
|
||||
skill_dir = None
|
||||
if ld_deep_script:
|
||||
skill_dir = ld_deep_script.parent.parent
|
||||
if pf_deep_script:
|
||||
skill_dir = pf_deep_script.parent.parent
|
||||
if skill_dir and skill_dir.exists():
|
||||
# Try actual importability check
|
||||
ld_deep_import_ok = False
|
||||
pf_deep_import_ok = False
|
||||
import_error = ""
|
||||
if ld_deep_script and ld_deep_script.exists():
|
||||
if pf_deep_script and pf_deep_script.exists():
|
||||
try:
|
||||
import importlib.util
|
||||
|
||||
spec = importlib.util.spec_from_file_location("ld_deep", ld_deep_script)
|
||||
spec = importlib.util.spec_from_file_location("pf_deep", pf_deep_script)
|
||||
if spec and spec.loader:
|
||||
mod = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(mod)
|
||||
ld_deep_import_ok = True
|
||||
pf_deep_import_ok = True
|
||||
except Exception as e:
|
||||
import_error = str(e)
|
||||
if ld_deep_import_ok:
|
||||
add_check("Agent 脚本", "pass", "paperforge and ld_deep importable")
|
||||
if pf_deep_import_ok:
|
||||
add_check("Agent 脚本", "pass", "paperforge and pf_deep importable")
|
||||
else:
|
||||
add_check(
|
||||
"Agent 脚本",
|
||||
"warn",
|
||||
f"literature-qa skill 目录存在但 import 失败: {import_error}",
|
||||
f"paperforge skill 目录存在但 import 失败: {import_error}",
|
||||
"确认 agent_config_dir 配置正确并已运行 pip install -e .",
|
||||
)
|
||||
else:
|
||||
add_check("Agent 脚本", "warn", "literature-qa skill 目录未找到", "确认 agent_config_dir 配置正确")
|
||||
add_check("Agent 脚本", "warn", "paperforge skill 目录未找到", "确认 agent_config_dir 配置正确")
|
||||
|
||||
# --- Index Health section (Phase 25: derived from canonical index) ---
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -252,7 +252,7 @@ def _deploy_all_skills(vault: Path) -> None:
|
|||
agent_key = config.get("agent_platform") or "opencode"
|
||||
result = deploy_skills(vault=vault, agent_key=agent_key, overwrite=True)
|
||||
if result["skill_deployed"]:
|
||||
logger.info("已部署 literature-qa skill")
|
||||
logger.info("已部署 paperforge skill")
|
||||
if result["agents_md"]:
|
||||
logger.info("已更新 AGENTS.md")
|
||||
for err in result.get("errors", []):
|
||||
|
|
|
|||
Loading…
Reference in a new issue