Add semi-automatic knowledge suggestions and user-confirmed injection across chat, interactive, auto, and wizard modes; fix writing flows to persist full systemPrompt in contextJson. Co-authored-by: Cursor <cursoragent@cursor.com>
28 KiB
笔临 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 (
- [x]) 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 追加:
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:
export interface AiContextBinding {
chapterIds: string[]
outlineIds: string[]
settingIds: string[]
inspirationIds: string[]
knowledgeIds: string[]
}
扩展 ContextPreviewKind:| 'knowledge'
扩展 ContextPreviewItem:knowledgeType?: KnowledgeType
扩展 GlobalSettings(可选,本 Task 仅类型,Task 2 实现默认值):
knowledgeAutoSuggest: boolean
knowledgeSuggestTopN: number
- Step 2: IPC 通道
src/shared/ipc-channels.ts:
KNOWLEDGE_SUGGEST_CONTEXT: 'knowledge:suggestContext',
- Step 3: ai-session.repo 默认 knowledgeIds
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 验证
npm run build
- Step 6: Commit
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:
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
npm run test -- tests/main/knowledge-retrieval.test.ts
- Step 3: 实现 KnowledgeRetrievalService
src/main/services/knowledge-retrieval.service.ts 核心:
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
npm run test -- tests/main/knowledge-retrieval.test.ts
- Step 5: Commit
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 追加:
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:
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
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 = 4000knowledgeTypeLabel()映射五种类型- 在
candidates循环后追加 knowledge 块,带sortKey: entry.importance * 1000(高 important 先保留) - 知识区单独裁剪至 4000 后再参与总 16KB 裁剪
- 所有现有
build()调用 binding 需含knowledgeIds(测试已补)
src/main/services/flow-context.util.ts:
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
npm run test -- tests/main/ai-context-builder.test.ts tests/main/flow-context.test.ts
- Step 5: Commit
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 追加
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 默认值
knowledgeAutoSuggest: true,
knowledgeSuggestTopN: 8,
get() merge 补默认。
- Step 3: preload + electron-api.d.ts
suggestContext: (
bookId: string,
opts?: KnowledgeSuggestOptions
): Promise<IpcResult<ScoredKnowledge[]>>
- Step 4: build
npm run build
- Step 5: Commit
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:
const payload = buildFlowContextPayload(binding, registry, bookId, settings.get().penName)
const flow = service.confirmContext(flowId, payload)
InteractiveWritingService.confirmContext 改为:
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:
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
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:
interface KnowledgeSuggestListProps {
items: ScoredKnowledge[]
selectedIds: string[]
onToggle: (id: string) => void
}
渲染 checkbox + title + t(\knowledge.type.${entry.type}`)+ reason badgest(reason)`。
- Step 2: ContextEditorModal 扩展
新增 props:goalText?: string
打开时:
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
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
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→ statesuggested -
内联
<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
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
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
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)
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: 运行
npm run test -- tests/main/ai-context-integration.test.ts
- Step 5: Commit
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
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
test('E2E-CTX-02: interactive context_confirm shows knowledge suggest', async () => {
// open interactive → interactive-knowledge-suggest visible
})
- Step 3: E2E-CTX-03(300s)
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: 运行
npm run test:e2e -- e2e/ai-context.spec.ts
- Step 5: Commit
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 追加
- 知识库 AI 上下文:半自动推荐 + 勾选注入四种写作模式(P6)
- Step 3: 全量测试
npm run build
npm run test
npm run test:e2e
- Step 4: Commit
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 |