# 笔临 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 |