From 2afbb83c43f0f3be169df6c655440dfe90ec8651 Mon Sep 17 00:00:00 2001 From: kun1h Date: Tue, 7 Jul 2026 14:24:19 +0800 Subject: [PATCH] docs: add P6 AI knowledge context implementation plan for v0.7.0 Co-authored-by: Cursor --- .../plans/2026-07-07-bilin-p6-ai-context.md | 927 ++++++++++++++++++ 1 file changed, 927 insertions(+) create mode 100644 docs/superpowers/plans/2026-07-07-bilin-p6-ai-context.md diff --git a/docs/superpowers/plans/2026-07-07-bilin-p6-ai-context.md b/docs/superpowers/plans/2026-07-07-bilin-p6-ai-context.md new file mode 100644 index 0000000..51bbb5f --- /dev/null +++ b/docs/superpowers/plans/2026-07-07-bilin-p6-ai-context.md @@ -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 + 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, '

x

') + const near = chapterRepo.create(volId, '近章', 4, '

x

') + chapterRepo.create(volId, '远章', 0, '

x

') + 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 +): 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> +``` + +- [ ] **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` +- 内联 ``,`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-03(240s)** + +```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-03(300s)** + +```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 5(AI_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 |