[English](./README.en.md) | 中文

# @easbot/note

> Note Knowledge Base - 文档知识库 + KG 节点图，支持混合搜索（FTS + 向量 + rerank）和 LLM 驱动的智能摄取

## 简介

`@easbot/note` 是 EASBot 生态的文档知识库模块（v0.4），提供非结构化文档（markdown / text / html / ast）的摄取、混合搜索、KG 节点关系推理与多模态 LLM 能力。

**v0.4 架构变化**：不再持有 v0.3 的 `NoteKnowledge` 主类 / `IngestionPipeline` / `SearchEngine` / `GraphQueryInterface` / `DatabaseManager` 这 5 个核心实体。所有能力下沉到 v0.4 service 层（`services/*.ts`）+ 9 op 注册表（`NOTE_OPERATIONS`）；外部调用通过 `executeNoteOp` 三端统一派发（CLI / MCP / agent tool 共用）。

## 特性

- **混合搜索**：FTS5 + 向量相似度 + 可选 LLM rerank（mode = conservative / balanced / tokenmax）
- **KG 集成**：文档摄取自动抽取 entities + relations，节点 / 边图查询（BFS + 子图）
- **多格式摄取**：markdown / text / html / ast（PDF / DOCX 占位 stage 2）
- **状态监控**：db stats / ingest queue / KG size 全方位
- **三端统一**：
  - 独立 CLI `easbot-note <command>`
  - Agent CLI `easbot note <command>`
  - MCP stdio server（暴露 9 个 `note.<op>` 工具）
  - Agent tool 9 op（`note(operation='search' | 'ingest' | ...)`）

## 安装

```bash
pnpm add @easbot/note
```

## v0.4 API

### 导出总览

```typescript
// 9 op Operations 注册表（真源；CI guard 校验）
import { NOTE_OPERATIONS, findOperation, executeNoteOp } from '@easbot/note';

// CLI handler（@easbot/agent 复用 + 独立 CLI easbot-note）
import { handleNoteCli, renderBanner, COMMANDS_HELP } from '@easbot/note';

// MCP stdio server
import { NoteStdioServer, formatFailClosed, toMcpText, TOOL_NAMES } from '@easbot/note';

// Format renderer（service result → markdown）
import { renderOperationMarkdown } from '@easbot/note';

// Database facade（决策 0036）
import { NoteDatabaseManager, getNoteDbPath } from '@easbot/note';

// 工具
import { FileScanner, chunkText } from '@easbot/note';
import { Llm } from '@easbot/note';
```

> **v0.4 不再导出**：`NoteKnowledge` / `createNoteKnowledge` / `IngestionPipeline` / `SearchEngine` / `GraphQueryInterface` / `DatabaseManager`（v0.3 实体全部随重构删除）。所有能力通过 9 op dispatch 走 service 层。

### 9 op（v0.4 stage 3-4 落地）

| op | scope | localOnly | 一句话 |
|---|---|---|---|
| `note.search` | read | no | 混合搜索（FTS + 向量 + rerank） |
| `note.ingest` | write | no | 摄入文档（md/text/html/ast） |
| `note.extract` | read | no | 抽取 chunk/document 的 KG 节点 + 关系 |
| `note.remove` | admin | **yes** | 删除文档 + 级联 KG |
| `note.sync` | admin | **yes** | 增量同步（async worker pool） |
| `note.graph_query` | read | no | KG 子图（nodes / edges / neighbors / path / explain） |
| `note.status` | read | no | 状态（db stats / ingest queue） |
| `note.doctor` | read | no | 健康检查（backend / FTS / parser） |
| `note.init` | admin | **yes** | 初始化 workspace |

**localOnly op**（remove / sync / init）由 `executeNoteOp` 集中 trust gate 拦截（ADR 0057），remote MCP caller 抛 `TRUST_DENIED`。

### 快速开始（程序化 API）

```typescript
import { executeNoteOp } from '@easbot/note';

// 9 op dispatch（推荐入口；scope / localOnly 自动校验）
const result = await executeNoteOp('note.search', {
  query: 'authentication flow',
  mode: 'balanced',
  maxResults: 10,
}, {
  workspaceDir: process.cwd(),
  rootDir: process.cwd(),
  dbPath: '.easbot/note.db',
  locale: 'zh-CN',
  remote: false, // trusted local
});
```

完整 service ctx 字段见 `services/types.ts` 的 `NoteServiceContext`。

### MCP stdio server

```bash
# 启动 stdio MCP server（暴露 9 个 note tool）
easbot-note mcp

# 或从 agent CLI
easbot note mcp
```

MCP tool 名：`note.search` / `note.ingest` / `note.extract` / `note.remove` / `note.sync` / `note.graph_query` / `note.status` / `note.doctor` / `note.init`

### 独立 CLI（`easbot-note`）

```bash
# lifecycle
easbot-note init [dir] [--force] [--skip-auto-sync]
easbot-note status [--dir <path>] [--json]
easbot-note doctor [--dir <path>] [--json]

# content
easbot-note search <query>... [--mode <conservative|balanced|tokenmax>] [--file <p>] [--kind <document|chunk|node>] [--max <n>] [--include-graph] [--rerank]
easbot-note ingest <path>... [--dir <path>] [--no-embed]
easbot-note extract <chunkId|documentId|path> [--dir <path>]
easbot-note remove <id|path> [--confirm] [--force]
easbot-note sync [--dir <path>] [--async] [--quiet]

# graph
easbot-note graph <nodeId> [--kind <nodes|edges|neighbors>] [--depth <n>] [--direction <incoming|outgoing|both>]

# external
easbot-note config <get|set> [key] [value]
easbot-note mcp
```

## Status / Doctor 统一输出格式（决策 0073 / CLI 规范 v1.4）

`note status` / `note doctor` 与 `codebase` / `memory` 三包统一走 `@easbot/terminal` 的 `formatKnowledgeStatus` / `formatKnowledgeDoctor` 渲染，视觉与字段结构完全一致。

### status 输出示例

```bash
$ easbot note status
─ note status ──────────────────────────────────────────────────────
  Root directory    E:/work/my-project
  Config            ✓ E:/work/my-project/.easbot/note.json
  DB                ✓ E:/work/my-project/.easbot/db/note.db (8388608 bytes)
  Index state       ready
  Schema version    1
  Extraction version 1
  Backend           better-sqlite3
  Last sync         2026-08-31T14:23:11.000Z
  Vector enabled    ✓
  Embedding dims    1536
  LLM:
    Initialized     ✓
    Provider        openai
    Capabilities:
      Embedding     ✓ text-embedding-3-small
      Graph LLM     ✓ gpt-4o-mini
      Rerank LLM    ✓ cohere-rerank-v3.5
  Counts:
    documents       42
    chunks          156
    nodes           389
    edges           1024
  Healthy           ✓
```

### doctor 输出示例

```bash
$ easbot note doctor
─ note doctor ──────────────────────────────────────────────────────
  Healthy           ✓
  Duration          145 ms
  Backend availability:
    - better-sqlite3                ✓
    - node:sqlite                   ✗
    - @tursodatabase/database       ✗
  DB stats:
    page_count                      512
    page_size                       4096
    freelist_count                  0
  Checks            12 total (0 error, 1 warn, 1 info, 10 ok)
    [ok]    ✓ database: database file exists and accessible (12 ms)
    [ok]    ✓ fts: FTS5 available (8 ms)
    [ok]    ✓ vector: vector index usable (23 ms)
    [ok]    ✓ schema: schema version matches (2 ms)
    [ok]    ✓ lock: no lock contention (1 ms)
    [ok]    ✓ disk_space: sufficient (156 MB free) (3 ms)
    [ok]    ✓ orphan: no orphan records (18 ms)
    [info]  ℹ llm: LLM reachable (ping 42 ms) (42 ms)
    [ok]    ✓ chunks_fts_sync: FTS and chunks in sync (15 ms)
    [ok]    ✓ kg_node_types_consistency: node types match schema (21 ms)
    [warn]  ⚠ embedding_cache_unused: 12 unused embedding cache entries (5 ms)
    [ok]    ✓ embedding_dims_consistency: meta=1536 probe=1536 config=1536 (35 ms)
```

### 关键字段说明

| 字段 | 说明 |
|------|------|
| `LLM.initialized` | LLM 是否已初始化（配置有效且可达） |
| `LLM.capabilities` | 三大能力：`embedding` / `graphLlm` / `rerankLlm`，各显示模型名或 ✗；note 独有 `rerankLlm` |
| `Vector enabled` | 向量索引是否启用 |
| `Embedding dims` | **实测** embedding 维度（非仅读 config），status 末尾探测并回填 meta 表 |
| `embedding_dims_consistency` | doctor 第 9 项检查：对比 meta / probe / config 三方维度是否一致 |
| `rerankAvailable` | note 独有：rerank 是否可用（LLM capabilities 中体现） |

### JSON 输出（`--json`）

```bash
$ easbot note status --json
{
  "ok": true,
  "data": {
    "rootDir": "E:/work/my-project",
    "configExists": true,
    "dbExists": true,
    "indexState": "ready",
    "schemaVersion": 1,
    "llm": {
      "initialized": true,
      "capabilities": { "embedding": true, "graphLlm": true, "rerankLlm": true },
      "embeddingModel": "text-embedding-3-small",
      "graphModel": "gpt-4o-mini",
      "rerankModel": "cohere-rerank-v3.5",
      "providerId": "openai"
    },
    "counts": { "documents": 42, "chunks": 156, "nodes": 389, "edges": 1024 },
    "healthy": true
  },
  "meta": { "scope": "all" }
}
```

兼容 [CLI 输出规范 v1.4](file:///e:/work/apps/eas/easbot/docs/spec/cli-output-spec.md) / [决策 0040](file:///e:/work/apps/eas/easbot/docs/decisions/0040-cli-output-unified-json.md)。

### Agent CLI（`easbot note`)

```bash
# 复用同一组 cmd
easbot note search "user authentication" --mode tokenmax
easbot note ingest ./docs/spec.md
easbot note extract --path docs/spec.md
easbot note status --json
easbot note mcp
```

### Agent tool 9 op

```typescript
// LLM 调用（zod discriminated union）
note(operation='search', query='auth flow', mode='tokenmax', maxResults=10)
note(operation='ingest', path='./spec.md')
note(operation='extract', chunkId=42, documentId=1, path='./spec.md')
note(operation='remove', id='doc-123', confirm=true)
note(operation='sync', async=true, quiet=true)
note(operation='graph_query', nodeId='42', kind='neighbors', depth=2, direction='both')
note(operation='status')
note(operation='doctor', repair=true)
note(operation='init', dir='.', force=false, skipAutoSync=false)
```

## 数据结构

| 类型 | 字段 | 用途 |
|---|---|---|
| `Document` | id / path / title / metadata | 文档元数据 |
| `Chunk` | id / documentId / content / startLine / endLine / nodeIds | 文档切片 |
| `Node` | id / name / type / properties | KG 节点 |
| `Edge` | id / source / target / relation / properties | KG 边 |
| `SearchHit` | chunkId / documentId / documentPath / score / snippet / source / nodes? | 搜索结果 |
| `IngestResult` | documentId / path / chunksCreated / vectorsCreated / durationMs / warnings | 摄入结果 |
| `ExtractResult` | entities / relations / documentId? / chunkId? / durationMs | 抽取结果 |
| `SyncResult` | filesAdded / filesUpdated / filesDeleted / filesSkipped / durationsMs / errors | 同步结果 |
| `StatusResult` | indexState / schemaVersion / documentsCount / chunksCount / nodesCount / edgesCount / ftsAvailable | 状态 |
| `DoctorResult` | healthy / checks[] / totalChecks | 健康检查 |

## 错误码（ADR 0057 LLM-friendly）

| code | 含义 |
|---|---|
| `NOT_FOUND` | 资源未找到 |
| `AMBIGUOUS` | 名称歧义 |
| `INTERNAL` | 内部错误 |
| `NOT_INDEXED` | 未索引 |
| `OPTIONAL_DEP_MISSING` | 可选依赖未装 |
| `PATH_TRAVERSAL` | 路径越界 |
| `TRUST_DENIED` | 不受信 caller |
| `INGEST_FAILED` | 摄入失败 |
| `UNSUPPORTED_FORMAT` | 不支持的文件格式 |
| `CONTENT_REQUIRED` | 缺必填参数 |

remote MCP caller 仅返 code（防 stack / SQL / 路径泄漏）；trusted local 返 code + message。

## 开发

```bash
# 安装依赖
pnpm install

# 构建（dev / prod 两种）
pnpm --filter @easbot/note dev
pnpm --filter @easbot/note build

# 测试（Vitest 4）
pnpm --filter @easbot/note test:run
npx vitest run packages/note/src/mcp/__tests__/stdio-server.test.ts

# 类型检查 + Biome
pnpm --filter @easbot/note type-check
pnpm --filter @easbot/note lint
pnpm --filter @easbot/note format

# CI guards
bash ./scripts/check-no-process-cwd.sh note
bash ./scripts/check-localOnly-assert.sh note
bash ./scripts/check-service-handler-coverage.sh note
```

## 许可证

MIT

2026-09-04 00:33:47 [force re-ingest test]
(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') + ' [force re-ingest verify v0.7]'
(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') + ' [force re-ingest v2]'
(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')
(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')
(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')
