diff --git a/.claude/skills/kb/README.md b/.claude/skills/kb/README.md new file mode 100644 index 0000000..6ab7051 --- /dev/null +++ b/.claude/skills/kb/README.md @@ -0,0 +1,160 @@ +# /kb — LLM 知识库管理工具 + +基于 Karpathy 的 LLM Knowledge Base 模式:raw/ 存原始资料,LLM 编译成 wiki/,索引替代 RAG。 + +## 快速开始 + +### 1. 初始化知识库 + +``` +/kb init +``` + +在当前目录创建知识库目录结构: +- `raw/` — 原始资料(只读) +- `wiki/concepts/` — 核心概念 +- `wiki/sources/` — 来源摘要 +- `wiki/comparisons/` — 对比分析 +- `output/analysis/` — 分析报告 +- `output/slides/` — 幻灯片 +- `index/` — 索引文件 + +### 2. 导入文件 + +将 PDF、Excel、图片、Word 文档放入 `raw/` 目录,然后: + +``` +/kb ingest +``` + +自动提取文本并登记到索引。 + +### 3. 编译为 Wiki + +``` +/kb compile +``` + +LLM 读取原料,生成结构化 wiki 文章。 + +### 4. 查询知识库 + +``` +/kb query "你的问题" +``` + +生成结构化报告,包含分析、结论和回填建议。 + +### 5. 回填有价值的结果 + +``` +/kb file +``` + +将查询报告中有价值的内容并入 wiki。 + +### 6. 健康检查 + +``` +/kb lint +``` + +六项检查:断链、孤岛、溯源、一致性、覆盖度、空白发现。 + +### 7. 查看状态 + +``` +/kb status +``` + +仪表盘展示整体健康度和统计信息。 + +--- + +## 子命令速查 + +| 命令 | 功能 | 触发词 | +|------|------|--------| +| `kb init [目录]` | 初始化知识库 | "初始化"、"创建知识库" | +| `kb ingest` | 预处理 raw/ 文件 | "导入"、"处理新文件" | +| `kb compile [文件]` | 编译为 wiki | "编译"、"更新 wiki" | +| `kb query "<问题>"` | 查询知识库 | "查知识库"、"问知识库" | +| `kb file [报告]` | 回填到 wiki | "回填"、"归档" | +| `kb lint` | 健康检查 | "检查"、"lint" | +| `kb status` | 状态仪表盘 | "状态"、"看看知识库" | + +--- + +## 支持的文件格式 + +| 格式 | 后缀 | 说明 | +|------|------|------| +| PDF | .pdf | 提取文本和图片 | +| Excel | .xlsx, .xls, .csv | 提取表格内容 | +| 图片 | .png, .jpg, .jpeg | OCR 文字识别 | +| Word | .docx | 提取段落和表格 | + +--- + +## 工作流程 + +``` +投喂原料 LLM 编译 查询使用 + │ │ │ + ▼ ▼ ▼ + raw/ ──────► wiki/ ──────► 查询分析 ──────► 回填 + │ │ │ + 原始文件 结构化文章 知识增长 +``` + +--- + +## 目录结构 + +``` +{知识库根目录}/ +├── raw/ # 原始资料(只读) +│ └── .extracted/ # 提取的文本(自动生成) +├── wiki/ +│ ├── concepts/ # 核心概念 +│ ├── sources/ # 来源摘要 +│ └── comparisons/ # 对比分析 +├── output/ +│ ├── analysis/ # 查询报告 +│ └── slides/ # 幻灯片 +├── index/ +│ ├── MASTER-INDEX.md # 全局索引 +│ ├── TOPIC-MAP.md # 主题分组 +│ ├── RAW-REGISTRY.md # 原始文件登记 +│ ├── LINT-REPORT.md # 健康检查报告 +│ └── ONTOLOGY.md # 本体定义 +└── scripts/ + ├── ingest.py # 预处理脚本 + └── extractors/ # 文件提取器 +``` + +--- + +## Python 依赖 + +首次使用需要安装依赖: + +```bash +pip install -r .claude/skills/kb/scripts/requirements.txt +``` + +依赖列表: +- PyMuPDF — PDF 提取 +- openpyxl — Excel 读取 +- pandas — 数据处理 +- pytesseract — 图片 OCR +- python-docx — Word 读取 +- Pillow — 图片处理 + +--- + +## SessionStart Hook(可选) + +配置后,每次打开 Claude Code 会自动检测 `raw/` 中的新文件并提醒处理。 + +初始化时选择"是"即可启用。 diff --git a/.claude/skills/kb/SKILL.md b/.claude/skills/kb/SKILL.md new file mode 100644 index 0000000..40dfca1 --- /dev/null +++ b/.claude/skills/kb/SKILL.md @@ -0,0 +1,327 @@ +--- +name: kb +description: | + LLM 驱动的知识库管理工具箱。当用户说"kb"、"知识库"、"查知识库"、"初始化知识库"、"导入文件"、"编译"、"回填"等时触发。 + 支持对 vault 或外部目录建立知识库:预处理文件、编译 wiki、查询分析、健康检查。 + 基于 Karpathy 的 LLM Knowledge Base 模式:raw/ 存原始资料,LLM 编译成 wiki/,索引替代 RAG。 +user-invocable: true +--- + +# /kb — LLM 知识库管理 + +统一入口,包含 7 个子命令。 + +## 子命令速查 + +| 命令 | 功能 | 触发词 | +|------|------|--------| +| `kb init [目录]` | 初始化知识库 | "初始化"、"创建知识库" | +| `kb ingest` | 预处理 raw/ 文件 | "导入"、"处理新文件" | +| `kb compile [文件]` | 编译为 wiki | "编译"、"更新 wiki" | +| `kb query "<问题>"` | 查询知识库 | "查知识库"、"问知识库" | +| `kb file [报告]` | 回填到 wiki | "回填"、"归档" | +| `kb lint` | 健康检查 | "检查"、"lint" | +| `kb status` | 状态仪表盘 | "状态"、"看看知识库" | + +--- + +## kb init [目标目录] + +初始化知识库目录结构、索引和本体定义。 + +**参数**:可选目标目录,默认当前目录(vault)或指定外部目录。 + +### 执行步骤 + +1. **检查现有知识库**:查找 `{target}/index/MASTER-INDEX.md`,如果存在则警告并等待确认 + +2. **创建目录结构**: + ``` + {target}/raw/ — 原始资料(只读) + {target}/wiki/concepts/ — 核心概念 + {target}/wiki/sources/ — 来源摘要 + {target}/wiki/comparisons/ — 对比分析 + {target}/output/analysis/ — 分析报告 + {target}/output/slides/ — 幻灯片 + {target}/index/ — 索引文件 + {target}/scripts/ — 预处理脚本 + ``` + +3. **复制模板文件**:从本 Skill 的 `templates/` 目录复制到 `{target}/index/`: + - ONTOLOGY.md — 实体类型和关系定义 + - MASTER-INDEX.md — 全局索引 + - TOPIC-MAP.md — 主题分组 + - RAW-REGISTRY.md — 原始文件登记 + +4. **复制脚本**:从本 Skill 的 `scripts/` 目录复制到 `{target}/scripts/` + +5. **检查 Python 依赖**: + ```bash + pip show pymupdf openpyxl pandas pytesseract python-docx Pillow 2>&1 + ``` + 报告缺失的包,询问是否安装 + +6. **配置 SessionStart Hook(可选)**:询问是否配置,检测 raw/ 新文件时提醒 + +7. **输出初始化摘要** + +--- + +## kb ingest + +预处理 raw/ 中的新文件并登记到索引。 + +**前置条件**:知识库已初始化(存在 index/RAW-REGISTRY.md) + +### 支持格式 +- PDF (.pdf) +- Excel (.xlsx, .xls, .csv) +- 图片 (.png, .jpg, .jpeg) — OCR 提取 +- Word (.docx) + +### 执行步骤 + +1. **定位知识库**:向上查找 `index/RAW-REGISTRY.md` + +2. **运行预处理脚本**: + ```bash + python3 {skill_dir}/scripts/ingest.py {kb_root} + ``` + 脚本自动:扫描新文件 → 按类型提取文本 → 输出摘要 + +3. **登记到 RAW-REGISTRY.md**:为每个新文件添加条目: + - 文件路径、类型、摘要(一句话) + - 状态:`pending`(待编译) + +4. **输出摘要**:报告导入数量,提示下一步 `/kb-compile` + +--- + +## kb compile [文件] + +将 raw/ 中已导入但未编译的文件编译为 wiki 文章。 + +**参数**:可选指定文件,默认处理所有 `status=pending` 的条目 + +### 核心原则 +- Wiki 文章由 LLM 生成,遵循 ONTOLOGY.md 定义 +- 每篇文章必须有完整 YAML frontmatter +- 使用 `[[双链]]` 建立关联 +- 编译是增量的 + +### 执行步骤 + +1. **检查待编译条目**:读 `index/RAW-REGISTRY.md`,找 `status=pending` 的条目 + - 如果没有,告知用户并结束 + +2. **加载上下文**:读 ONTOLOGY.md、MASTER-INDEX.md、TOPIC-MAP.md + +3. **逐个编译**: + - 读取源文件或 `raw/.extracted/` 下的提取文本 + - 判断操作:新建 / 更新已有 / 综合分析 + - 按模板生成 wiki 文章 + - 更新 frontmatter(type, id, compiled_from, related, last_compiled) + - 用 `[[双链]]` 链接相关文章 + +4. **更新索引**: + - MASTER-INDEX.md 添加/更新条目 + - TOPIC-MAP.md 归入主题 + - RAW-REGISTRY.md 状态改为 `done`,填编译产物路径 + +5. **输出编译摘要** + +--- + +## kb query "<问题>" + +对知识库提问,生成结构化报告。 + +**参数**:必填,用户的问题 + +### 执行步骤 + +1. **定位知识库**:查找 `index/MASTER-INDEX.md` + +2. **检索相关文章**: + - 读 MASTER-INDEX.md 定位相关文件 + - 按需读 TOPIC-MAP.md 精确定位 + - 读取所有相关 wiki 文章内容 + +3. **研究分析**: + - 基于 wiki 内容深入分析问题 + - 交叉对比多篇文章 + - 结论必须基于实际内容,标注来源 + +4. **生成报告**:保存到 `output/analysis/YYYY-MM-DD-{topic-slug}.md`: + ```markdown + # {报告标题} + + - **Date**: YYYY-MM-DD + - **Query**: {用户问题} + - **Sources**: {引用的 wiki 文章} + + --- + + ## 分析 + {详细分析,引用具体文章用 [[双链]]} + + ## 结论 + {核心发现} + + ## 回填建议 + - [ ] {具体建议} + ``` + +5. **输出结果**:展示摘要,提示可运行 `/kb file` 回填 + +--- + +## kb file [报告路径] + +将查询输出回填到 wiki 知识库。 + +**参数**:可选指定 output/ 下的报告文件,默认扫描 `output/analysis/` + +### 执行步骤 + +1. **定位知识库和待回填内容** + +2. **展示回填建议**:列出所有建议,编号说明 + +3. **用户确认**:逐条 Y/N 或批量操作 + +4. **执行回填**: + - **更新已有文章**:将新内容有机融入 + - **新建文章**:按 ONTOLOGY.md 模板创建 + +5. **更新索引**:MASTER-INDEX.md 和 TOPIC-MAP.md + +6. **输出摘要** + +--- + +## kb lint + +对知识库进行六项健康检查。 + +### 检查项目 + +| 检查 | 说明 | +|------|------| +| 断链 | `[[链接]]` 指向不存在的文件 | +| 孤岛 | 没有被任何文章链接的文章 | +| 溯源 | frontmatter compiled_from 指向已删除的文件 | +| 一致性 | 同一概念在不同文章中的矛盾描述 | +| 覆盖度 | 未编译文件比例 | +| 空白发现 | 被提及但没有独立文章的概念 | + +### 执行步骤 + +1. **定位知识库** + +2. **执行六项检查** + +3. **输出 Lint 报告**(按严重程度排序) + +4. **提供修复选项**:可自动修复的问题询问是否执行 + +5. **保存报告到 `index/LINT-REPORT.md`** + +--- + +## kb status + +展示知识库整体状态仪表盘。 + +### 执行步骤 + +1. **定位知识库** + +2. **收集统计数据**: + - raw/ 文件数 + - wiki/ 文章数和字数 + - 编译率 + - 待回填报告数 + - 上次 lint 结果 + +3. **展示仪表盘**: + ``` + 知识库状态 + ═══════════════════════════════════ + 原始文件: N 个 + Wiki 文章: M 篇 (共 ~X 字) + 编译率: XX% + 待回填: Y 份报告 + 上次 Lint: 日期 — 问题摘要 + ═══════════════════════════════════ + + 最近编译的文章: + - wiki/concepts/xxx.md (日期) + + 待处理: + - N 个文件待编译 → /kb compile + - M 份报告待回填 → /kb file + ``` + +4. **建议下一步操作** + +--- + +## 目录结构约定 + +``` +{知识库根目录}/ +├── raw/ # 原始资料(只读) +│ └── .extracted/ # 提取的文本(自动生成) +├── wiki/ +│ ├── concepts/ # 核心概念 +│ ├── sources/ # 来源摘要 +│ └── comparisons/ # 对比分析 +├── output/ +│ ├── analysis/ # 查询报告 +│ └── slides/ # 幻灯片 +├── index/ +│ ├── MASTER-INDEX.md # 全局索引 +│ ├── TOPIC-MAP.md # 主题分组 +│ ├── RAW-REGISTRY.md # 原始文件登记 +│ ├── LINT-REPORT.md # 健康检查报告 +│ └── ONTOLOGY.md # 本体定义 +└── scripts/ + ├── ingest.py # 预处理脚本 + ├── requirements.txt # Python 依赖 + └── extractors/ # 各类文件提取器 +``` + +## 实体类型(ONTOLOGY.md) + +| 类型 | 目录 | 命名规则 | +|------|------|----------| +| concept | wiki/concepts/ | {slug}.md | +| source | wiki/sources/ | {slug}.md | +| comparison | wiki/comparisons/ | {a}-vs-{b}.md | + +## Wiki 文章 Frontmatter 模板 + +```yaml +--- +type: concept +id: {slug} +aliases: [] +compiled_from: + - raw/{source_file} +related: + - "[[other-article]]" +last_compiled: YYYY-MM-DD +--- +``` + +--- + +## 故障排除 + +| 问题 | 解决方案 | +|------|----------| +| 找不到知识库 | 先运行 `/kb init` 初始化 | +| 脚本报错 | 运行 `pip install -r scripts/requirements.txt` | +| 编译率低 | 运行 `/kb ingest` 导入新文件,然后 `/kb compile` | +| 断链太多 | 运行 `/kb lint` 查看详情,手动修复或删除断链 | diff --git a/.claude/skills/kb/index.html b/.claude/skills/kb/index.html new file mode 100644 index 0000000..7fc9926 --- /dev/null +++ b/.claude/skills/kb/index.html @@ -0,0 +1,381 @@ + + + + + + /kb — LLM 知识库管理工具 + + + +
+

/kb — LLM 知识库管理工具

+

基于 Karpathy 的 LLM Knowledge Base 模式:raw/ 存原始资料,LLM 编译成 wiki/,索引替代 RAG。

+ +

🚀 快速开始

+ +

1. 初始化知识库

+
/kb init
+

在当前目录创建知识库目录结构:

+ +
+
+├── raw/                    # 原始资料(只读)
+├── wiki/
+│   ├── concepts/          # 核心概念
+│   ├── sources/           # 来源摘要
+│   └── comparisons/       # 对比分析
+├── output/
+│   ├── analysis/          # 分析报告
+│   └── slides/           # 幻灯片
+└── index/                # 索引文件
+      
+
+ +

2. 导入文件

+

将 PDF、Excel、图片、Word 文档放入 raw/ 目录,然后:

+
/kb ingest
+

自动提取文本并登记到索引。

+ +

3. 编译为 Wiki

+
/kb compile
+

LLM 读取原料,生成结构化 wiki 文章。

+ +

4. 查询知识库

+
/kb query "你的问题"
+

生成结构化报告,包含分析、结论和回填建议。

+ +

5. 回填有价值的结果

+
/kb file
+

将查询报告中有价值的内容并入 wiki。

+ +

6. 健康检查

+
/kb lint
+

六项检查:断链、孤岛、溯源、一致性、覆盖度、空白发现。

+ +

7. 查看状态

+
/kb status
+

仪表盘展示整体健康度和统计信息。

+ +

📋 子命令速查

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
命令功能触发词
kb init [目录]初始化知识库初始化、创建知识库
kb ingest预处理 raw/ 文件导入、处理新文件
kb compile [文件]编译为 wiki编译、更新 wiki
kb query "<问题>"查询知识库查知识库、问知识库
kb file [报告]回填到 wiki回填、归档
kb lint健康检查检查、lint
kb status状态仪表盘状态、看看知识库
+ +

📦 支持的文件格式

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
格式后缀说明
PDF.pdf提取文本和图片
Excel.xlsx, .xls, .csv提取表格内容
图片.png, .jpg, .jpegOCR 文字识别
Word.docx提取段落和表格
+ +

🔄 工作流程

+ +
+
投喂原料
raw/
+ +
LLM 编译
wiki/
+ +
查询使用
/kb query
+ +
知识增长
/kb file
+
+ +

📁 完整目录结构

+ +
+{知识库根目录}/
+├── raw/                    # 原始资料(只读)
+│   └── .extracted/        # 提取的文本(自动生成)
+├── wiki/
+│   ├── concepts/          # 核心概念
+│   ├── sources/           # 来源摘要
+│   └── comparisons/       # 对比分析
+├── output/
+│   ├── analysis/          # 查询报告
+│   └── slides/           # 幻灯片
+├── index/
+│   ├── MASTER-INDEX.md   # 全局索引
+│   ├── TOPIC-MAP.md      # 主题分组
+│   ├── RAW-REGISTRY.md   # 原始文件登记
+│   ├── LINT-REPORT.md    # 健康检查报告
+│   └── ONTOLOGY.md       # 本体定义
+└── scripts/
+    ├── ingest.py          # 预处理脚本
+    └── extractors/        # 文件提取器
+    
+ +

🐍 Python 依赖

+ +

首次使用需要安装依赖:

+
pip install -r .claude/skills/kb/scripts/requirements.txt
+ +
+ 依赖列表: + +
+ +

⚙️ SessionStart Hook(可选)

+ +

配置后,每次打开 Claude Code 会自动检测 raw/ 中的新文件并提醒处理。

+

初始化时选择"是"即可启用。

+ + +
+ + diff --git a/.claude/skills/kb/scripts/extractors/__init__.py b/.claude/skills/kb/scripts/extractors/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/.claude/skills/kb/scripts/extractors/docx_extractor.py b/.claude/skills/kb/scripts/extractors/docx_extractor.py new file mode 100644 index 0000000..33648eb --- /dev/null +++ b/.claude/skills/kb/scripts/extractors/docx_extractor.py @@ -0,0 +1,28 @@ +"""Extract text from Word documents.""" +from docx import Document +import os + + +def extract(docx_path: str, output_dir: str) -> str: + """Extract all paragraphs and tables from docx.""" + basename = os.path.splitext(os.path.basename(docx_path))[0] + txt_path = os.path.join(output_dir, f"{basename}.txt") + + doc = Document(docx_path) + parts = [] + + for para in doc.paragraphs: + if para.text.strip(): + parts.append(para.text) + + for i, table in enumerate(doc.tables): + parts.append(f"\n--- Table {i+1} ---") + for row in table.rows: + cells = [cell.text.strip() for cell in row.cells] + parts.append(" | ".join(cells)) + + with open(txt_path, "w", encoding="utf-8") as f: + f.write("\n".join(parts)) + + print(f" Word: {len(doc.paragraphs)} paragraphs, {len(doc.tables)} tables extracted") + return txt_path diff --git a/.claude/skills/kb/scripts/extractors/excel_extractor.py b/.claude/skills/kb/scripts/extractors/excel_extractor.py new file mode 100644 index 0000000..1414d20 --- /dev/null +++ b/.claude/skills/kb/scripts/extractors/excel_extractor.py @@ -0,0 +1,34 @@ +"""Extract text summary from Excel files.""" +import pandas as pd +import os + + +def extract(excel_path: str, output_dir: str) -> str: + """Read all sheets, output text summary.""" + basename = os.path.splitext(os.path.basename(excel_path))[0] + txt_path = os.path.join(output_dir, f"{basename}.txt") + + ext = os.path.splitext(excel_path)[1].lower() + if ext == ".csv": + df = pd.read_csv(excel_path) + parts = [f"--- CSV ({len(df)} rows x {len(df.columns)} cols) ---"] + parts.append(f"Columns: {', '.join(df.columns.astype(str))}") + parts.append(df.head(50).to_string(index=False)) + if len(df) > 50: + parts.append(f"... ({len(df) - 50} more rows)") + else: + xls = pd.ExcelFile(excel_path) + parts = [] + for sheet in xls.sheet_names: + df = pd.read_excel(xls, sheet_name=sheet) + parts.append(f"--- Sheet: {sheet} ({len(df)} rows x {len(df.columns)} cols) ---") + parts.append(f"Columns: {', '.join(df.columns.astype(str))}") + parts.append(df.head(50).to_string(index=False)) + if len(df) > 50: + parts.append(f"... ({len(df) - 50} more rows)") + + with open(txt_path, "w", encoding="utf-8") as f: + f.write("\n\n".join(parts)) + + print(f" Excel: extracted to {basename}.txt") + return txt_path diff --git a/.claude/skills/kb/scripts/extractors/image_extractor.py b/.claude/skills/kb/scripts/extractors/image_extractor.py new file mode 100644 index 0000000..3eee591 --- /dev/null +++ b/.claude/skills/kb/scripts/extractors/image_extractor.py @@ -0,0 +1,20 @@ +"""OCR text from images using pytesseract.""" +import pytesseract +from PIL import Image +import os + + +def extract(image_path: str, output_dir: str) -> str: + """OCR image, return text file path.""" + basename = os.path.splitext(os.path.basename(image_path))[0] + txt_path = os.path.join(output_dir, f"{basename}.txt") + + img = Image.open(image_path) + text = pytesseract.image_to_string(img, lang="chi_sim+eng") + + with open(txt_path, "w", encoding="utf-8") as f: + f.write(text) + + chars = len(text.strip()) + print(f" Image OCR: {chars} characters extracted") + return txt_path diff --git a/.claude/skills/kb/scripts/extractors/pdf_extractor.py b/.claude/skills/kb/scripts/extractors/pdf_extractor.py new file mode 100644 index 0000000..be92a91 --- /dev/null +++ b/.claude/skills/kb/scripts/extractors/pdf_extractor.py @@ -0,0 +1,34 @@ +"""Extract text and images from PDF files using PyMuPDF.""" +import fitz # PyMuPDF +import os + + +def extract(pdf_path: str, output_dir: str) -> str: + """Extract text from PDF, save images, return text file path.""" + doc = fitz.open(pdf_path) + text_parts = [] + img_count = 0 + + for page_num, page in enumerate(doc): + text_parts.append(f"--- Page {page_num + 1} ---") + text_parts.append(page.get_text()) + + for img_idx, img in enumerate(page.get_images(full=True)): + xref = img[0] + pix = fitz.Pixmap(doc, xref) + if pix.n > 4: + pix = fitz.Pixmap(fitz.csRGB, pix) + img_path = os.path.join(output_dir, f"page{page_num+1}_img{img_idx+1}.png") + pix.save(img_path) + img_count += 1 + pix = None + + doc.close() + + basename = os.path.splitext(os.path.basename(pdf_path))[0] + txt_path = os.path.join(output_dir, f"{basename}.txt") + with open(txt_path, "w", encoding="utf-8") as f: + f.write("\n".join(text_parts)) + + print(f" PDF: {len(text_parts)//2} pages, {img_count} images extracted") + return txt_path diff --git a/.claude/skills/kb/scripts/ingest.py b/.claude/skills/kb/scripts/ingest.py new file mode 100644 index 0000000..a36284c --- /dev/null +++ b/.claude/skills/kb/scripts/ingest.py @@ -0,0 +1,102 @@ +#!/usr/bin/env python3 +"""Scan raw/ for new files, extract text, print summary for LLM to parse.""" +import importlib +import os +import sys + +# Add scripts dir to path so extractors can be imported +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + +EXTRACTORS = { + ".pdf": "extractors.pdf_extractor", + ".xlsx": "extractors.excel_extractor", + ".xls": "extractors.excel_extractor", + ".csv": "extractors.excel_extractor", + ".png": "extractors.image_extractor", + ".jpg": "extractors.image_extractor", + ".jpeg": "extractors.image_extractor", + ".docx": "extractors.docx_extractor", +} +SKIP_EXT = {".md", ".txt"} +SKIP_DIRS = {".extracted"} + + +def scan_raw(raw_dir, registry_path): + """Find files in raw/ not yet in RAW-REGISTRY.md.""" + registered = set() + if os.path.exists(registry_path): + with open(registry_path, "r", encoding="utf-8") as f: + for line in f: + if line.startswith("| raw/") or line.startswith("| ./raw/"): + path = line.split("|")[1].strip() + registered.add(path) + + new_files = [] + for root, dirs, files in os.walk(raw_dir): + dirs[:] = [d for d in dirs if d not in SKIP_DIRS] + for fname in sorted(files): + fpath = os.path.join(root, fname) + rel = os.path.relpath(fpath, os.path.dirname(raw_dir)) + if rel not in registered: + new_files.append(fpath) + return new_files + + +def process_file(fpath): + """Extract text from a single file. Returns (txt_path, file_type) or (None, file_type).""" + ext = os.path.splitext(fpath)[1].lower() + extracted_dir = os.path.join(os.path.dirname(fpath), ".extracted") + os.makedirs(extracted_dir, exist_ok=True) + + if ext in SKIP_EXT: + return None, ext + + mod_name = EXTRACTORS.get(ext) + if not mod_name: + print(f" SKIP (unsupported): {os.path.basename(fpath)}") + return None, ext + + try: + extractor = importlib.import_module(mod_name) + txt_path = extractor.extract(fpath, extracted_dir) + return txt_path, ext + except ImportError as e: + print(f" ERROR (missing dependency): {e}") + return None, ext + except Exception as e: + print(f" ERROR: {e}") + return None, ext + + +def main(): + kb_root = sys.argv[1] if len(sys.argv) > 1 else os.getcwd() + raw_dir = os.path.join(kb_root, "raw") + registry = os.path.join(kb_root, "index", "RAW-REGISTRY.md") + + if not os.path.isdir(raw_dir): + print(f"ERROR: {raw_dir} not found") + sys.exit(1) + + new_files = scan_raw(raw_dir, registry) + if not new_files: + print("No new files in raw/") + return + + print(f"Found {len(new_files)} new file(s) in raw/:\n") + results = [] + for fpath in sorted(new_files): + print(f"Processing: {os.path.basename(fpath)}") + txt_path, ext = process_file(fpath) + results.append((fpath, txt_path, ext)) + + # Output summary for LLM to parse and update RAW-REGISTRY.md + print(f"\n--- INGEST SUMMARY ---") + print(f"Processed: {len(results)} files") + for fpath, txt_path, ext in results: + rel = os.path.relpath(fpath, kb_root) + status = "extracted" if txt_path else "ready" + print(f" {rel} [{ext}] -> {status}") + + +if __name__ == "__main__": + main() diff --git a/.claude/skills/kb/scripts/requirements.txt b/.claude/skills/kb/scripts/requirements.txt new file mode 100644 index 0000000..7bbc86b --- /dev/null +++ b/.claude/skills/kb/scripts/requirements.txt @@ -0,0 +1,6 @@ +PyMuPDF>=1.24 +openpyxl>=3.1 +pandas>=2.0 +pytesseract>=0.3 +python-docx>=1.1 +Pillow>=10.0 diff --git a/.claude/skills/kb/templates/MASTER-INDEX.md b/.claude/skills/kb/templates/MASTER-INDEX.md new file mode 100644 index 0000000..3ab4e99 --- /dev/null +++ b/.claude/skills/kb/templates/MASTER-INDEX.md @@ -0,0 +1,4 @@ +# Master Index + +| 路径 | 类型 | 摘要 | +|------|------|------| diff --git a/.claude/skills/kb/templates/ONTOLOGY.md b/.claude/skills/kb/templates/ONTOLOGY.md new file mode 100644 index 0000000..d3a255b --- /dev/null +++ b/.claude/skills/kb/templates/ONTOLOGY.md @@ -0,0 +1,50 @@ +# Ontology + +## 实体类型 + +| 类型 | 目录 | 命名规则 | 说明 | +|------|------|---------|------| +| concept | wiki/concepts/ | {slug}.md | 核心概念 | +| source | wiki/sources/ | {slug}.md | 来源摘要 | +| comparison | wiki/comparisons/ | {a}-vs-{b}.md | 对比分析 | + +## 关系 + +- 用 `[[双链]]` 表达引用关系 +- frontmatter 的 `compiled_from` 表达溯源 +- frontmatter 的 `related` 表达关联 + +## Wiki 文章模板 + +每篇 wiki 文章使用以下结构: + +```yaml +--- +type: {entity_type} +id: {slug} +aliases: [] +compiled_from: + - raw/{source_file} +related: + - "[[other-article]]" +last_compiled: {date} +--- +``` + +### 正文结构 + +```markdown +# {标题} + +## 概述 +一段话定义... + +## 要点 +- ... + +## 关联 +- [[相关概念]] + +## 来源 +- 编译自 raw/xxx.pdf +``` diff --git a/.claude/skills/kb/templates/RAW-REGISTRY.md b/.claude/skills/kb/templates/RAW-REGISTRY.md new file mode 100644 index 0000000..936b460 --- /dev/null +++ b/.claude/skills/kb/templates/RAW-REGISTRY.md @@ -0,0 +1,4 @@ +# Raw Registry + +| 文件 | 类型 | 摘要 | 状态 | 编译产物 | +|------|------|------|------|---------| diff --git a/.claude/skills/kb/templates/TOPIC-MAP.md b/.claude/skills/kb/templates/TOPIC-MAP.md new file mode 100644 index 0000000..acafe1f --- /dev/null +++ b/.claude/skills/kb/templates/TOPIC-MAP.md @@ -0,0 +1,3 @@ +# Topic Map + + diff --git a/.claudian/sessions/conv-1776074446367-y9l6jom6z.meta.json b/.claudian/sessions/conv-1776074446367-y9l6jom6z.meta.json new file mode 100644 index 0000000..53967ce --- /dev/null +++ b/.claudian/sessions/conv-1776074446367-y9l6jom6z.meta.json @@ -0,0 +1,23 @@ +{ + "id": "conv-1776074446367-y9l6jom6z", + "providerId": "claude", + "title": "Start conversation", + "titleGenerationStatus": "success", + "createdAt": 1776074446367, + "updatedAt": 1776074479191, + "lastResponseAt": 1776074479191, + "sessionId": "bc8edeba-7a77-4523-8e79-95b84004035b", + "providerState": { + "providerSessionId": "bc8edeba-7a77-4523-8e79-95b84004035b" + }, + "usage": { + "model": "haiku", + "inputTokens": 163, + "cacheCreationInputTokens": 21877, + "cacheReadInputTokens": 0, + "contextWindow": 200000, + "contextTokens": 22040, + "percentage": 11, + "contextWindowIsAuthoritative": true + } +} \ No newline at end of file diff --git a/.obsidian/community-plugins.json b/.obsidian/community-plugins.json index d3f66fa..e5897c5 100644 --- a/.obsidian/community-plugins.json +++ b/.obsidian/community-plugins.json @@ -1,3 +1,4 @@ [ - "obsidian-git" + "obsidian-git", + "claudian" ] \ No newline at end of file diff --git a/.obsidian/plugins/claudian/data.json b/.obsidian/plugins/claudian/data.json new file mode 100644 index 0000000..fc829cb --- /dev/null +++ b/.obsidian/plugins/claudian/data.json @@ -0,0 +1,11 @@ +{ + "tabManagerState": { + "openTabs": [ + { + "tabId": "tab-1776074442549-1h1fbx3", + "conversationId": "conv-1776074446367-y9l6jom6z" + } + ], + "activeTabId": "tab-1776074442549-1h1fbx3" + } +} \ No newline at end of file diff --git a/测试.md b/Karpathy 让 AI 自己管知识库,告别 RAG 幻觉?我做了一个工具,把这套方法落地了.md similarity index 100% rename from 测试.md rename to Karpathy 让 AI 自己管知识库,告别 RAG 幻觉?我做了一个工具,把这套方法落地了.md diff --git a/ok.md b/ok.md deleted file mode 100644 index 8b13789..0000000 --- a/ok.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/未命名.canvas b/未命名.canvas deleted file mode 100644 index 9e26dfe..0000000 --- a/未命名.canvas +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file