docs: add P6 AI knowledge context implementation plan for v0.7.0

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-07 14:24:19 +08:00
parent b5ae04af18
commit 2afbb83c43
@@ -0,0 +1,927 @@
# 笔临 P6 AI 知识上下文整合 Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 交付 v0.7.0:已审核知识库条目半自动推荐 + 用户勾选确认 + 注入对话/交互/自动/向导四种 AI 模式;修复写作流 `contextJson` 仅存标题摘要的问题。
**Architecture:** 新增 `KnowledgeRetrievalService``approved` 知识条目综合打分;扩展 `AiContextBinding.knowledgeIds``AiContextBuilderService` 合并知识块;`FlowContextPayload` 统一写作流上下文存储;`KnowledgeSuggestList` + `ContextEditorModal` 知识 Tab 供四种模式共用。测试 **禁止 Mock** AI 集成/E2E 打真实 LM Studio。
**Tech Stack:** Electron 36、electron-vite、React 18、TypeScript、node:sqlite、Zustand、i18next、Vitest、Playwright
**Spec:** `docs/superpowers/specs/2026-07-07-bilin-p6-ai-context-design.md`
**LM Studio 默认:** `baseUrl=http://127.0.0.1:1234``model=gemma-4-e4b-it`
---
## File Map
| 文件 | 职责 |
|------|------|
| `src/shared/types.ts` | `KnowledgeSuggestOptions``ScoredKnowledge``FlowContextPayload`;扩展 `AiContextBinding` |
| `src/shared/ipc-channels.ts` | `KNOWLEDGE_SUGGEST_CONTEXT` |
| `src/main/services/knowledge-retrieval.service.ts` | 打分 + TopN 推荐 |
| `src/main/services/ai-context-builder.service.ts` | knowledge 块 + 预算裁剪 |
| `src/main/services/flow-context.util.ts` | `buildFlowContextPayload()` 共享 |
| `src/main/services/interactive-writing.service.ts` | `readContextText``systemPrompt` |
| `src/main/db/repositories/ai-session.repo.ts` | `parseContext``knowledgeIds` |
| `src/main/ipc/handlers/knowledge.handler.ts` | `suggestContext` |
| `src/main/ipc/handlers/interactive.handler.ts` | `FlowContextPayload` |
| `src/main/ipc/handlers/auto.handler.ts` | `readContextText` 修复 |
| `src/main/ipc/handlers/wizard.handler.ts` | 同上 + preview 知识列表 |
| `src/main/ipc/handlers/ai.handler.ts` | binding 默认 `knowledgeIds: []` |
| `src/main/services/global-settings.ts` | `knowledgeAutoSuggest` / `knowledgeSuggestTopN` 默认值 |
| `src/preload/index.ts` | `knowledge.suggestContext` |
| `src/shared/electron-api.d.ts` | 类型 |
| `src/renderer/components/ai/KnowledgeSuggestList.tsx` | 共享勾选 UI |
| `src/renderer/components/ai/ContextEditorModal.tsx` | 知识 Tab + 推荐区 |
| `src/renderer/components/ai/InteractivePanel.tsx` | context_confirm 知识区 |
| `src/renderer/components/ai/AutoPanel.tsx` | goal 后知识确认 |
| `src/renderer/components/ai/WizardPanel.tsx` | wizard_context 知识区 |
| `src/renderer/lib/ai-context-summary.ts` | 摘要含知识计数 |
| `src/renderer/stores/useInteractiveStore.ts` | binding 含 knowledgeIds |
| `public/locales/zh-CN/translation.json` | P6 文案 |
| `public/locales/en/translation.json` | P6 文案 |
| `tests/main/knowledge-retrieval.test.ts` | UT-CTX-01/04 |
| `tests/main/ai-context-builder.test.ts` | UT-CTX-02/03(扩展) |
| `tests/main/flow-context.test.ts` | FlowContextPayload 单元 |
| `tests/main/ai-context-integration.test.ts` | IT-CTX-01/02/03 |
| `e2e/ai-context.spec.ts` | E2E-CTX-* |
---
## Task 1: 类型 + IPC 通道 + 默认 binding
**Files:**
- Modify: `src/shared/types.ts`
- Modify: `src/shared/ipc-channels.ts`
- Modify: `src/main/db/repositories/ai-session.repo.ts`
- [ ] **Step 1: 扩展 types**
`src/shared/types.ts` 追加:
```typescript
export interface KnowledgeSuggestOptions {
currentChapterId?: string
goalText?: string
topN?: number
}
export interface ScoredKnowledge {
entry: KnowledgeEntry
score: number
reasons: string[]
}
export interface FlowContextPayload {
binding: AiContextBinding
systemPrompt: string
contextSummary: string
}
```
扩展 `AiContextBinding`
```typescript
export interface AiContextBinding {
chapterIds: string[]
outlineIds: string[]
settingIds: string[]
inspirationIds: string[]
knowledgeIds: string[]
}
```
扩展 `ContextPreviewKind``| 'knowledge'`
扩展 `ContextPreviewItem``knowledgeType?: KnowledgeType`
扩展 `GlobalSettings`(可选,本 Task 仅类型,Task 2 实现默认值):
```typescript
knowledgeAutoSuggest: boolean
knowledgeSuggestTopN: number
```
- [ ] **Step 2: IPC 通道**
`src/shared/ipc-channels.ts`
```typescript
KNOWLEDGE_SUGGEST_CONTEXT: 'knowledge:suggestContext',
```
- [ ] **Step 3: ai-session.repo 默认 knowledgeIds**
```typescript
const EMPTY_CONTEXT: AiContextBinding = {
chapterIds: [],
outlineIds: [],
settingIds: [],
inspirationIds: [],
knowledgeIds: []
}
function parseContext(json: string): AiContextBinding {
try {
const parsed = JSON.parse(json) as Partial<AiContextBinding>
return {
chapterIds: parsed.chapterIds ?? [],
outlineIds: parsed.outlineIds ?? [],
settingIds: parsed.settingIds ?? [],
inspirationIds: parsed.inspirationIds ?? [],
knowledgeIds: parsed.knowledgeIds ?? []
}
} catch {
return { ...EMPTY_CONTEXT }
}
}
```
- [ ] **Step 4: 全局修补 EMPTY_BINDING**
搜索 `inspirationIds: []` 且无 `knowledgeIds``AiContextBinding` 字面量(`ContextEditorModal.tsx``useInteractiveStore.ts` 等),全部补上 `knowledgeIds: []`
- [ ] **Step 5: build 验证**
```bash
npm run build
```
- [ ] **Step 6: Commit**
```bash
git add src/shared/types.ts src/shared/ipc-channels.ts src/main/db/repositories/ai-session.repo.ts
git commit -m "feat: extend AiContextBinding with knowledgeIds for P6"
```
---
## Task 2: KnowledgeRetrievalService
**Files:**
- Create: `src/main/services/knowledge-retrieval.service.ts`
- Create: `tests/main/knowledge-retrieval.test.ts`
- [ ] **Step 1: UT-CTX-01 / UT-CTX-04 失败测试**
`tests/main/knowledge-retrieval.test.ts`
```typescript
import { describe, it, expect, afterEach } from 'vitest'
import { DatabaseSync } from 'node:sqlite'
import { migrate } from '../../src/main/db/migrate'
import { VolumeRepository } from '../../src/main/db/repositories/volume.repo'
import { ChapterRepository } from '../../src/main/db/repositories/chapter.repo'
import { KnowledgeRepository } from '../../src/main/db/repositories/knowledge.repo'
import { KnowledgeRetrievalService } from '../../src/main/services/knowledge-retrieval.service'
import type { SqliteDb } from '../../src/main/db/types'
describe('KnowledgeRetrievalService', () => {
let db: SqliteDb
afterEach(() => db?.close())
it('UT-CTX-01: ranks by importance + chapter proximity + goal match', () => {
db = new DatabaseSync(':memory:')
migrate(db)
const volId = new VolumeRepository(db).create('卷一', 0).id
const chapterRepo = new ChapterRepository(db)
const cur = chapterRepo.create(volId, '当前章', 5, '<p>x</p>')
const near = chapterRepo.create(volId, '近章', 4, '<p>x</p>')
chapterRepo.create(volId, '远章', 0, '<p>x</p>')
const repo = new KnowledgeRepository(db)
const low = repo.create({
type: 'fact',
title: '无关事实',
content: '普通内容',
importance: 2,
status: 'approved'
})
const high = repo.create({
type: 'foreshadow',
title: '测灵石异常',
content: '测灵石在入门考核中发热',
importance: 5,
status: 'approved',
foreshadowStatus: 'buried',
sourceChapterId: near.id
})
const service = new KnowledgeRetrievalService(db)
const result = service.suggest({
currentChapterId: cur.id,
goalText: '测灵石入门考核',
topN: 5
})
expect(result[0].entry.id).toBe(high.id)
expect(result[0].score).toBeGreaterThan(
result.find((r) => r.entry.id === low.id)!.score
)
expect(result[0].reasons.length).toBeGreaterThan(0)
})
it('UT-CTX-04: resolved foreshadow scores lower than buried', () => {
db = new DatabaseSync(':memory:')
migrate(db)
const volId = new VolumeRepository(db).create('卷一', 0).id
const chId = new ChapterRepository(db).create(volId, '章', 0).id
const repo = new KnowledgeRepository(db)
const buried = repo.update(
repo.create({
type: 'foreshadow',
title: '埋设伏笔',
importance: 3,
status: 'approved',
foreshadowStatus: 'buried',
sourceChapterId: chId
}).id,
{ status: 'approved' }
)
const resolved = repo.create({
type: 'foreshadow',
title: '已回收伏笔',
importance: 3,
status: 'approved',
foreshadowStatus: 'resolved',
sourceChapterId: chId
})
const service = new KnowledgeRetrievalService(db)
const scores = service.suggest({ currentChapterId: chId, topN: 10 })
const buriedScore = scores.find((s) => s.entry.id === buried.id)!.score
const resolvedScore = scores.find((s) => s.entry.id === resolved.id)!.score
expect(buriedScore).toBeGreaterThan(resolvedScore)
})
})
```
- [ ] **Step 2: 运行确认 FAIL**
```bash
npm run test -- tests/main/knowledge-retrieval.test.ts
```
- [ ] **Step 3: 实现 KnowledgeRetrievalService**
`src/main/services/knowledge-retrieval.service.ts` 核心:
```typescript
import type { KnowledgeEntry, KnowledgeSuggestOptions, ScoredKnowledge } from '../../shared/types'
import type { SqliteDb } from '../db/types'
import { ChapterRepository } from '../db/repositories/chapter.repo'
import { KnowledgeRepository } from '../db/repositories/knowledge.repo'
const STOP_WORDS = new Set(['的', '了', '在', '是', '与', '和', 'the', 'a', 'an'])
function tokenize(text: string): string[] {
return text
.toLowerCase()
.split(/[\s,,。!?、;;:]+/)
.map((t) => t.trim())
.filter((t) => t.length >= 2 && !STOP_WORDS.has(t))
}
function overlapRatio(a: string, b: string): number {
const ta = new Set(tokenize(a))
const tb = tokenize(b)
if (ta.size === 0 || tb.length === 0) return 0
let hit = 0
for (const t of tb) if (ta.has(t)) hit++
return hit / tb.length
}
function chapterProximityBonus(
entry: KnowledgeEntry,
currentSort: number | null,
chapterSort: Map<string, number>
): number {
if (currentSort == null) return 0
const ids = [entry.sourceChapterId, entry.lastMentionChapterId].filter(Boolean) as string[]
if (ids.length === 0) return 0
let best = Infinity
for (const id of ids) {
const order = chapterSort.get(id)
if (order != null) best = Math.min(best, Math.abs(currentSort - order))
}
if (best === Infinity) return 0
if (best === 0) return 30
if (best <= 3) return 20
if (best <= 10) return 10
return 0
}
export class KnowledgeRetrievalService {
constructor(private db: SqliteDb) {}
suggest(opts: KnowledgeSuggestOptions = {}): ScoredKnowledge[] {
const topN = opts.topN ?? 8
const entries = new KnowledgeRepository(this.db).list({ status: 'approved' })
if (entries.length === 0) return []
const chapters = new ChapterRepository(this.db).list()
const sortMap = new Map(chapters.map((c) => [c.id, c.sortOrder]))
const currentSort = opts.currentChapterId
? (sortMap.get(opts.currentChapterId) ?? null)
: null
const goalText = opts.goalText ?? ''
const scored: ScoredKnowledge[] = entries.map((entry) => {
let score = entry.importance * 20
const reasons: string[] = []
const prox = chapterProximityBonus(entry, currentSort, sortMap)
score += prox
if (prox > 0) reasons.push('knowledge.reason.chapterRelevant')
if (entry.type === 'foreshadow') {
const fs = entry.foreshadowStatus ?? 'buried'
if (fs === 'buried') {
score += 25
reasons.push('knowledge.reason.foreshadowBuried')
} else if (fs === 'partial') score += 15
}
if (goalText) {
const text = `${entry.title} ${entry.content}`
const ratio = overlapRatio(goalText, text)
score += Math.round(ratio * 30)
if (ratio > 0.2) reasons.push('knowledge.reason.goalMatch')
}
if (entry.importance >= 4) reasons.push('knowledge.reason.highImportance')
return { entry, score, reasons: [...new Set(reasons)] }
})
return scored.sort((a, b) => b.score - a.score).slice(0, topN)
}
}
```
- [ ] **Step 4: 运行 PASS**
```bash
npm run test -- tests/main/knowledge-retrieval.test.ts
```
- [ ] **Step 5: Commit**
```bash
git add src/main/services/knowledge-retrieval.service.ts tests/main/knowledge-retrieval.test.ts
git commit -m "feat: add KnowledgeRetrievalService with scoring for context suggest"
```
---
## Task 3: 扩展 AiContextBuilder + flow-context 工具
**Files:**
- Modify: `src/main/services/ai-context-builder.service.ts`
- Create: `src/main/services/flow-context.util.ts`
- Modify: `tests/main/ai-context-builder.test.ts`
- Create: `tests/main/flow-context.test.ts`
- [ ] **Step 1: UT-CTX-02 / UT-CTX-03 测试**
`tests/main/ai-context-builder.test.ts` 追加:
```typescript
import { KnowledgeRepository } from '../../src/main/db/repositories/knowledge.repo'
it('UT-CTX-02: build includes knowledge preview items', () => {
const know = new KnowledgeRepository(db)
const entry = know.create({
type: 'foreshadow',
title: '测灵石',
content: '石头发热',
status: 'approved'
})
const result = aiContextBuilder.build(
{ chapterIds: [], outlineIds: [], settingIds: [], inspirationIds: [], knowledgeIds: [entry.id] },
registry,
bookId,
'作者'
)
expect(result.preview.some((p) => p.kind === 'knowledge')).toBe(true)
expect(result.systemPrompt).toContain('【知识·伏笔】')
expect(result.systemPrompt).toContain('测灵石')
})
it('UT-CTX-03: caps knowledge section at 4000 chars', () => {
const know = new KnowledgeRepository(db)
const ids: string[] = []
for (let i = 0; i < 20; i++) {
const e = know.create({
type: 'fact',
title: `条目${i}`,
content: '长'.repeat(400),
importance: 5,
status: 'approved'
})
ids.push(e.id)
}
const result = aiContextBuilder.build(
{ chapterIds: [], outlineIds: [], settingIds: [], inspirationIds: [], knowledgeIds: ids },
registry,
bookId,
'作者'
)
const knowledgeChars = result.preview
.filter((p) => p.kind === 'knowledge')
.reduce((s, p) => s + p.charCount, 0)
expect(knowledgeChars).toBeLessThanOrEqual(4000)
})
```
`tests/main/flow-context.test.ts`
```typescript
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
import { mkdtempSync, rmSync } from 'fs'
import { join } from 'path'
import { tmpdir } from 'os'
import { BookRegistryService } from '../../src/main/services/book-registry'
import { buildFlowContextPayload } from '../../src/main/services/flow-context.util'
import { KnowledgeRepository } from '../../src/main/db/repositories/knowledge.repo'
describe('flow-context.util', () => {
let userDir: string
let registry: BookRegistryService
let bookId: string
beforeEach(() => {
userDir = mkdtempSync(join(tmpdir(), 'bilin-flow-ctx-'))
registry = new BookRegistryService(userDir)
bookId = registry.create({ name: '书', category: '玄幻' }).id
})
afterEach(() => rmSync(userDir, { recursive: true, force: true }))
it('buildFlowContextPayload stores systemPrompt and summary', () => {
const db = registry.getDb(bookId)
const entry = new KnowledgeRepository(db).create({
type: 'fact',
title: '测试知识',
content: '内容',
status: 'approved'
})
const payload = buildFlowContextPayload(
{
chapterIds: [],
outlineIds: [],
settingIds: [],
inspirationIds: [],
knowledgeIds: [entry.id]
},
registry,
bookId,
'作者'
)
expect(payload.systemPrompt).toContain('测试知识')
expect(payload.contextSummary).toContain('1')
db.close()
})
})
```
- [ ] **Step 2: 运行确认 FAIL**
```bash
npm run test -- tests/main/ai-context-builder.test.ts tests/main/flow-context.test.ts
```
- [ ] **Step 3: 扩展 ai-context-builder**
要点:
- `KNOWLEDGE_CHAR_LIMIT = 500``KNOWLEDGE_TOTAL_LIMIT = 4000`
- `knowledgeTypeLabel()` 映射五种类型
-`candidates` 循环后追加 knowledge 块,带 `sortKey: entry.importance * 1000`(高 important 先保留)
- 知识区单独裁剪至 4000 后再参与总 16KB 裁剪
- 所有现有 `build()` 调用 binding 需含 `knowledgeIds`(测试已补)
`src/main/services/flow-context.util.ts`
```typescript
import type { AiContextBinding, FlowContextPayload } from '../../shared/types'
import type { BookRegistryService } from './book-registry'
import { aiContextBuilder } from './ai-context-builder.service'
export function buildFlowContextPayload(
binding: AiContextBinding,
registry: BookRegistryService,
bookId: string,
penName: string
): FlowContextPayload {
const built = aiContextBuilder.build(binding, registry, bookId, penName)
const counts = { chapter: 0, outline: 0, setting: 0, inspiration: 0, knowledge: 0 }
for (const p of built.preview) {
if (p.kind in counts) counts[p.kind as keyof typeof counts]++
}
const contextSummary = `${counts.chapter}章·${counts.outline}纲·${counts.setting}设·${counts.inspiration}感·${counts.knowledge}`
return { binding, systemPrompt: built.systemPrompt, contextSummary }
}
```
- [ ] **Step 4: 运行 PASS**
```bash
npm run test -- tests/main/ai-context-builder.test.ts tests/main/flow-context.test.ts
```
- [ ] **Step 5: Commit**
```bash
git add src/main/services/ai-context-builder.service.ts src/main/services/flow-context.util.ts tests/main/
git commit -m "feat: extend AiContextBuilder with knowledge blocks and flow payload helper"
```
---
## Task 4: IPC suggestContext + preload
**Files:**
- Modify: `src/main/ipc/handlers/knowledge.handler.ts`
- Modify: `src/preload/index.ts`
- Modify: `src/shared/electron-api.d.ts`
- Modify: `src/main/services/global-settings.ts`
- Modify: `src/renderer/stores/useSettingsStore.ts`
- [ ] **Step 1: knowledge.handler 追加**
```typescript
ipcMain.handle(
IPC.KNOWLEDGE_SUGGEST_CONTEXT,
(
_e,
{ bookId, opts }: { bookId: string; opts?: KnowledgeSuggestOptions }
) => wrap(() => {
const settings = /* inject via handler param or read topN from settings */
new KnowledgeRetrievalService(registry.getDb(bookId)).suggest(opts)
})
)
```
`registerKnowledgeHandlers` 签名改为接收 `GlobalSettingsService``topN` 默认 `settings.get().knowledgeSuggestTopN ?? 8`
- [ ] **Step 2: global-settings 默认值**
```typescript
knowledgeAutoSuggest: true,
knowledgeSuggestTopN: 8,
```
`get()` merge 补默认。
- [ ] **Step 3: preload + electron-api.d.ts**
```typescript
suggestContext: (
bookId: string,
opts?: KnowledgeSuggestOptions
): Promise<IpcResult<ScoredKnowledge[]>>
```
- [ ] **Step 4: build**
```bash
npm run build
```
- [ ] **Step 5: Commit**
```bash
git commit -m "feat: add knowledge suggestContext IPC and global settings defaults"
```
---
## Task 5: 写作流 systemPrompt 修复
**Files:**
- Modify: `src/main/services/interactive-writing.service.ts`
- Modify: `src/main/ipc/handlers/interactive.handler.ts`
- Modify: `src/main/ipc/handlers/auto.handler.ts`
- Modify: `src/main/ipc/handlers/wizard.handler.ts`
- Modify: `src/main/services/wizard-writing.service.ts``buildPreview` 追加知识列表)
- [ ] **Step 1: interactive confirmContext 存 FlowContextPayload**
`interactive.handler.ts` `INTERACTIVE_CONFIRM_CONTEXT`
```typescript
const payload = buildFlowContextPayload(binding, registry, bookId, settings.get().penName)
const flow = service.confirmContext(flowId, payload)
```
`InteractiveWritingService.confirmContext` 改为:
```typescript
confirmContext(flowId: string, payload: FlowContextPayload): InteractiveFlow {
this.repo.updateStep(flowId, 'plot_suggest', {
contextJson: JSON.stringify(payload)
})
return this.repo.get(flowId)!
}
private readContextText(flowId: string): string {
try {
const parsed = JSON.parse(this.repo.getContextJson(flowId)) as FlowContextPayload
return parsed.systemPrompt || '(无上下文)'
} catch {
return '(无上下文)'
}
}
enrichFlow(flow: InteractiveFlow): InteractiveFlow {
try {
const parsed = JSON.parse(this.repo.getContextJson(flow.id)) as FlowContextPayload
return { ...flow, contextSummary: parsed.contextSummary }
} catch {
return flow
}
}
```
- [ ] **Step 2: auto.handler / wizard.handler readContextText**
替换本地 `readContextText` 为读 `systemPrompt`
```typescript
function readContextText(registry, bookId, flowId): string {
const row = registry.getDb(bookId)
.prepare('SELECT context_json FROM interactive_flows WHERE id = ?')
.get(flowId) as { context_json: string } | undefined
try {
const parsed = JSON.parse(row?.context_json ?? '{}') as FlowContextPayload
return parsed.systemPrompt ?? '(无上下文)'
} catch {
return '(无上下文)'
}
}
```
- [ ] **Step 3: wizard confirmContext 同 interactive**
`wizard.handler.ts` `WIZARD_CONFIRM_CONTEXT` 使用 `buildFlowContextPayload`
- [ ] **Step 4: buildPreview 追加知识标题**
`WizardWritingService.buildPreview` 返回前,从 `payload.binding.knowledgeIds` 查标题,append `\n将注入知识:A、B、C`
- [ ] **Step 5: auto planScenes 前自动 confirm**
`AutoPanel``planScenes` 调用前,若 flow 无 `systemPrompt`,用 session binding + 默认推荐 knowledgeIds 调 `interactive.confirmContext` 等价逻辑(可新增 `auto:confirmContext` IPC 或复用 `buildFlowContextPayload` + `setContextSummary` 扩展为存完整 payload)。
推荐:新增 `auto.handler` `AUTO_CONFIRM_CONTEXT` 与 interactive 对称。
- [ ] **Step 6: Commit**
```bash
git commit -m "fix: store full systemPrompt in writing flow contextJson for P6"
```
---
## Task 6: KnowledgeSuggestList + ContextEditorModal
**Files:**
- Create: `src/renderer/components/ai/KnowledgeSuggestList.tsx`
- Modify: `src/renderer/components/ai/ContextEditorModal.tsx`
- Modify: `src/renderer/lib/ai-context-summary.ts`
- [ ] **Step 1: KnowledgeSuggestList**
Props
```typescript
interface KnowledgeSuggestListProps {
items: ScoredKnowledge[]
selectedIds: string[]
onToggle: (id: string) => void
}
```
渲染 checkbox + title + `t(\`knowledge.type.${entry.type}\`)` + reason badges `t(reason)`。
- [ ] **Step 2: ContextEditorModal 扩展**
新增 props`goalText?: string`
打开时:
```typescript
const chapterId = useBookStore.getState().selectedChapterId ?? undefined
const suggested = await ipcCall(() =>
window.electronAPI.knowledge.suggestContext(bookId, { currentChapterId: chapterId, goalText })
)
// 默认 merge suggested ids into binding.knowledgeIds
```
新增 Tab `knowledge`;顶部推荐区 + `context-refresh-suggest`;下方 approved 全列表(`knowledge.list(bookId, { status: 'approved' })`)可勾选。
- [ ] **Step 3: formatContextSummary 含 knowledge**
```typescript
const counts = { chapter: 0, outline: 0, setting: 0, inspiration: 0, knowledge: 0 }
// ...
return t('ai.contextSummary', { ...counts })
```
更新 `ai.contextSummary` i18n 为 `{{chapters}}章·{{outlines}}纲·{{settings}}设·{{inspirations}}感·{{knowledge}}知`。
- [ ] **Step 4: Commit**
```bash
git commit -m "feat: add KnowledgeSuggestList and knowledge tab in context editor"
```
---
## Task 7: 交互 / 自动 / 向导 Panel 集成
**Files:**
- Modify: `src/renderer/components/ai/InteractivePanel.tsx`
- Modify: `src/renderer/components/ai/AutoPanel.tsx`
- Modify: `src/renderer/components/ai/WizardPanel.tsx`
- Modify: `src/renderer/stores/useInteractiveStore.ts`
- Modify: `src/renderer/stores/useAutoStore.ts`confirmContext
- Modify: `src/renderer/components/ai/AiPanel.tsx`(传 goalText 空)
- [ ] **Step 1: InteractivePanel context_confirm**
- mount 时 `suggestContext` → state `suggested`
- 内联 `<KnowledgeSuggestList>``data-testid="interactive-knowledge-suggest"`
- 勾选同步到 session `context.knowledgeIds``ai.sessionUpdate`
- `confirmContextAndStart` 传含 `knowledgeIds` 的 binding
- [ ] **Step 2: AutoPanel**
- `goal_setup` 区域下方折叠「知识上下文」`data-testid="auto-knowledge-suggest"`
- `goalText` = `chapterGoal` + 选中大纲 description
- `planScenes` 前调用 `auto.confirmContext`Task 5 新增 IPC
- [ ] **Step 3: WizardPanel wizard_context**
- 同 Interactive 内联推荐列表 `data-testid="wizard-knowledge-suggest"`
- `handleContextClose` 传 `goalText` = goal + styleNote
- [ ] **Step 4: Commit**
```bash
git commit -m "feat: integrate knowledge suggest into interactive auto wizard panels"
```
---
## Task 8: i18n
**Files:**
- Modify: `public/locales/zh-CN/translation.json`
- Modify: `public/locales/en/translation.json`
- [ ] **Step 1: 追加 spec §9 全部键 + `ai.contextSummary` 更新**
- [ ] **Step 2: Commit**
```bash
git commit -m "feat: add P6 AI context i18n strings"
```
---
## Task 9: 集成测试
**Files:**
- Create: `tests/main/ai-context-integration.test.ts`
- [ ] **Step 1: IT-CTX-01**
```typescript
it('IT-CTX-01: suggestContext returns entries when approved exist', () => {
// setup approved entry → suggest → length >= 1
})
```
- [ ] **Step 2: IT-CTX-02**
通过 `aiContextBuilder.build` 含 knowledgeIds 断言 `systemPrompt` 含 `【知识·`(无需 Electron IPC)。
- [ ] **Step 3: IT-CTX-03240s**
```typescript
it('IT-CTX-03: interactive confirm then suggestPlots uses knowledge context', async () => {
// setup book + knowledge + interactive flow
// confirmContext with knowledgeIds
// suggestPlots returns 3 options
}, 240_000)
```
- [ ] **Step 4: 运行**
```bash
npm run test -- tests/main/ai-context-integration.test.ts
```
- [ ] **Step 5: Commit**
```bash
git commit -m "test: add P6 AI context integration tests"
```
---
## Task 10: E2E
**Files:**
- Create: `e2e/ai-context.spec.ts`
- [ ] **Step 1: E2E-CTX-01**
```typescript
test('E2E-CTX-01: context editor knowledge tab shows suggestions', async () => {
// create book → add approved knowledge via panel → open AI context editor → context-tab-knowledge visible
})
```
- [ ] **Step 2: E2E-CTX-02**
```typescript
test('E2E-CTX-02: interactive context_confirm shows knowledge suggest', async () => {
// open interactive → interactive-knowledge-suggest visible
})
```
- [ ] **Step 3: E2E-CTX-03300s**
```typescript
test('E2E-CTX-03: chat with knowledge context gets AI reply', async () => {
test.setTimeout(300_000)
// add knowledge → select in context → send chat → non-empty reply
})
```
- [ ] **Step 4: 运行**
```bash
npm run test:e2e -- e2e/ai-context.spec.ts
```
- [ ] **Step 5: Commit**
```bash
git commit -m "test: add P6 AI context E2E specs"
```
---
## Task 11: v0.7.0 交付
**Files:**
- Modify: `package.json`
- Modify: `README.md`
- Modify: `src/renderer/components/settings/SettingsPage.tsx`about 版本号)
- Modify: `docs/superpowers/plans/2026-07-07-bilin-p6-ai-context.md`(勾选完成项)
- [ ] **Step 1: 版本号 `0.7.0`**
- [ ] **Step 2: README 追加**
```markdown
- 知识库 AI 上下文:半自动推荐 + 勾选注入四种写作模式(P6)
```
- [ ] **Step 3: 全量测试**
```bash
npm run build
npm run test
npm run test:e2e
```
- [ ] **Step 4: Commit**
```bash
git commit -m "feat: ship v0.7.0 with AI knowledge context integration"
```
---
## Spec Coverage Checklist
| Spec 要求 | Task |
|-----------|------|
| KnowledgeRetrievalService 打分 | Task 2 |
| AiContextBinding.knowledgeIds | Task 1 |
| AiContextBuilder 知识块 | Task 3 |
| FlowContextPayload / systemPrompt 修复 | Task 3, 5 |
| knowledge:suggestContext IPC | Task 4 |
| ContextEditorModal 知识 Tab | Task 6 |
| 对话静默注入 | Task 5AI_CHAT 已用 build+ Task 6 |
| 交互 context_confirm | Task 7 |
| 自动写作知识确认 | Task 5, 7 |
| 向导 wizard_context | Task 7 |
| global-settings 默认值 | Task 4 |
| UT-CTX-01~04 | Task 2, 3 |
| IT-CTX-01~03 | Task 9 |
| E2E-CTX-01~03 | Task 10 |
| v0.7.0 DoD | Task 11 |