Files
bilin/docs/superpowers/plans/2026-07-06-bilin-p5-workflow-knowledge.md
T

819 lines
25 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 笔临 P5 作家工作流 + 知识库 MVP 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.6.0:驾驶舱 + 存稿/更新计划 + 章间衔接(可选 AI)+ 知识库 MVP(手动 CRUD / 审核 / 伏笔追踪)。
**Architecture:** schema v6 新增 `knowledge_entries` / `book_preferences` / `chapter_bridge_dismiss``KnowledgeService` 负责 CRUD 与遗忘判定;`CockpitService` 只读聚合字数与存稿;`ChapterBridgeService` 提取相邻章片段 + 可选 `AiClientService`;渲染层 `CockpitModal` / `ChapterBridgeModal` / 重写 `KnowledgePanel`。测试 **禁止 Mock** AI 集成/E2E 打真实 LM Studio。
**Tech Stack:** Electron 36、electron-vite、React 18、TypeScript、node:sqlite、Zustand、Jotai、i18next、Vitest、Playwright
**Spec:** `docs/superpowers/specs/2026-07-06-bilin-p5-workflow-knowledge-design.md`
**LM Studio 默认:** `baseUrl=http://127.0.0.1:1234``model=gemma-4-e4b-it`
---
## File Map
| 文件 | 职责 |
|------|------|
| `src/main/db/schema-v6.sql` | v6 DDL |
| `src/main/db/migrate.ts` | v5→v6 |
| `src/main/db/repositories/knowledge.repo.ts` | knowledge_entries CRUD |
| `src/main/db/repositories/book-prefs.repo.ts` | book_preferences KV |
| `src/main/db/repositories/bridge-dismiss.repo.ts` | chapter_bridge_dismiss |
| `src/main/services/knowledge.service.ts` | 审核、遗忘判定、bookmark 同步 |
| `src/main/services/cockpit.service.ts` | 驾驶舱聚合 |
| `src/main/services/chapter-bridge.service.ts` | 衔接文本 + AI |
| `src/main/db/repositories/chapter.repo.ts` | publish_status 读写 |
| `src/main/ipc/handlers/knowledge.handler.ts` | knowledge IPC |
| `src/main/ipc/handlers/cockpit.handler.ts` | cockpit IPC |
| `src/main/ipc/handlers/bridge.handler.ts` | bridge IPC |
| `src/main/ipc/register.ts` | 注册 handler |
| `src/shared/types.ts` | KnowledgeEntry / CockpitSummary 等 |
| `src/shared/ipc-channels.ts` | 新通道 |
| `src/preload/index.ts` | knowledge / cockpit / bridge API |
| `src/shared/electron-api.d.ts` | 类型 |
| `src/renderer/stores/useKnowledgeStore.ts` | 知识库状态 |
| `src/renderer/stores/useCockpitStore.ts` | 驾驶舱状态 |
| `src/renderer/components/knowledge/KnowledgePanel.tsx` | 重写 |
| `src/renderer/components/knowledge/KnowledgeEditorDialog.tsx` | 编辑对话框 |
| `src/renderer/components/cockpit/CockpitModal.tsx` | 驾驶舱 UI |
| `src/renderer/components/bridge/ChapterBridgeModal.tsx` | 衔接 UI |
| `src/renderer/components/layout/TopBar.tsx` | 驾驶舱按钮 |
| `src/renderer/components/layout/EditorLayout.tsx` | 发布状态、bridge 触发、statusbar |
| `src/renderer/components/settings/SettingsPage.tsx` | updateSchedule |
| `src/main/services/global-settings.ts` | 默认值扩展 |
| `public/locales/zh-CN/translation.json` | 文案 |
| `public/locales/en/translation.json` | 文案 |
| `tests/main/migrate-v6.test.ts` | UT-MIG-06 |
| `tests/main/knowledge.service.test.ts` | UT-KNOW-* |
| `tests/main/cockpit.service.test.ts` | UT-COCK-01 |
| `tests/main/chapter-bridge.test.ts` | UT-BRIDGE-01 / IT-BRIDGE-01 |
| `e2e/cockpit-knowledge.spec.ts` | E2E-COCK-* / KNOW-* / BRIDGE-* / STOCK-* |
---
## Task 1: Schema v6 + 类型 + IPC 通道
**Files:**
- Create: `src/main/db/schema-v6.sql`
- Modify: `src/main/db/migrate.ts`
- Modify: `src/shared/types.ts`
- Modify: `src/shared/ipc-channels.ts`
- Create: `tests/main/migrate-v6.test.ts`
- Modify: `tests/main/migrate-v5.test.ts`(期望 version **6**
- [ ] **Step 1: 写迁移失败测试**
`tests/main/migrate-v6.test.ts`
```typescript
import { describe, it, expect, afterEach } from 'vitest'
import { DatabaseSync } from 'node:sqlite'
import { migrate } from '../../src/main/db/migrate'
import type { SqliteDb } from '../../src/main/db/types'
describe('migrate v6', () => {
let db: SqliteDb
afterEach(() => db?.close())
it('UT-MIG-06: applies v6 on fresh database', () => {
db = new DatabaseSync(':memory:')
migrate(db)
const version = db.prepare('SELECT MAX(version) AS v FROM schema_version').get() as { v: number }
expect(version.v).toBe(6)
const tables = db
.prepare("SELECT name FROM sqlite_master WHERE type='table'")
.all() as { name: string }[]
expect(tables.some((t) => t.name === 'knowledge_entries')).toBe(true)
expect(tables.some((t) => t.name === 'book_preferences')).toBe(true)
expect(tables.some((t) => t.name === 'chapter_bridge_dismiss')).toBe(true)
})
})
```
- [ ] **Step 2: 运行确认 FAIL**
```bash
npm run test -- tests/main/migrate-v6.test.ts
```
- [ ] **Step 3: 实现 schema v6**
`src/main/db/schema-v6.sql`(与 spec §3.1 一致)。
`src/main/db/migrate.ts`
```typescript
import schemaV6 from './schema-v6.sql?raw'
const CURRENT_VERSION = 6
// ...
if (current < 6) {
db.exec(schemaV6)
db.prepare('INSERT INTO schema_version (version, description) VALUES (?, ?)').run(6, 'P5 knowledge/cockpit schema')
}
```
`src/shared/types.ts` 新增:
```typescript
export type KnowledgeType = 'character' | 'foreshadow' | 'location' | 'relationship' | 'fact'
export type KnowledgeStatus = 'pending' | 'approved' | 'rejected'
export type ForeshadowStatus = 'buried' | 'partial' | 'resolved'
export type UpdateSchedule = 'daily' | 'alternate' | 'weekly' | 'none'
export interface KnowledgeEntry {
id: string
type: KnowledgeType
title: string
content: string
importance: number
status: KnowledgeStatus
foreshadowStatus?: ForeshadowStatus
sourceChapterId?: string
lastMentionChapterId?: string
linkedBookmarkId?: string
createdAt: string
updatedAt: string
}
export interface CreateKnowledgeInput {
type: KnowledgeType
title: string
content?: string
importance?: number
status?: KnowledgeStatus
foreshadowStatus?: ForeshadowStatus
sourceChapterId?: string
lastMentionChapterId?: string
linkedBookmarkId?: string
}
export interface CockpitSummary {
totalWords: number
volumeWords: number
stockReadyCount: number
stockThreshold: number
foreshadowBuried: number
foreshadowResolved: number
foreshadowForgotten: number
}
export interface ChapterBridgeResult {
previousChapterId: string | null
previousTail: string
currentHead: string
aiSuggestion?: string
}
```
扩展 `GlobalSettings`
```typescript
updateSchedule: UpdateSchedule // default 'none'
stockBufferThreshold: number // default 3
```
扩展 `Chapter`
```typescript
publishStatus?: PublishStatus
```
`src/shared/ipc-channels.ts` 追加 spec §4.1 全部通道。
- [ ] **Step 4: 更新 migrate-v5 期望 version 6;运行 PASS**
```bash
npm run test -- tests/main/migrate-v5.test.ts tests/main/migrate-v6.test.ts
```
- [ ] **Step 5: Commit**
```bash
git add src/main/db/schema-v6.sql src/main/db/migrate.ts src/shared/types.ts src/shared/ipc-channels.ts tests/main/migrate-v6.test.ts tests/main/migrate-v5.test.ts
git commit -m "feat: add schema v6 for knowledge and cockpit workflow"
```
---
## Task 2: Repository 层
**Files:**
- Create: `src/main/db/repositories/knowledge.repo.ts`
- Create: `src/main/db/repositories/book-prefs.repo.ts`
- Create: `src/main/db/repositories/bridge-dismiss.repo.ts`
- Modify: `src/main/db/repositories/chapter.repo.ts`
- Create: `tests/main/knowledge.repo.test.ts`
- [ ] **Step 1: UT-KNOW-01 失败测试**
```typescript
it('UT-KNOW-01: CRUD and approve updates status', () => {
const db = new DatabaseSync(':memory:')
migrate(db)
const repo = new KnowledgeRepository(db)
const entry = repo.create({ type: 'foreshadow', title: '测灵石异常', foreshadowStatus: 'buried' })
expect(entry.status).toBe('pending')
const approved = repo.update(entry.id, { status: 'approved' })
expect(approved.status).toBe('approved')
db.close()
})
```
- [ ] **Step 2: 实现 KnowledgeRepository**
```typescript
export class KnowledgeRepository {
create(input: CreateKnowledgeInput): KnowledgeEntry { /* INSERT */ }
get(id: string): KnowledgeEntry | null
list(filter?: { status?: KnowledgeStatus; type?: KnowledgeType }): KnowledgeEntry[]
update(id: string, patch: Partial<...>): KnowledgeEntry
delete(id: string): void
countByStatus(status: KnowledgeStatus): number
}
```
`mapRow` 蛇形 ↔ 驼峰映射。
- [ ] **Step 3: BookPrefsRepository + BridgeDismissRepository**
```typescript
// book-prefs.repo.ts
get(key: string): string | null
set(key: string, value: string): void
// bridge-dismiss.repo.ts
isDismissed(chapterId: string): boolean
dismiss(chapterId: string): void
```
- [ ] **Step 4: ChapterRepository 扩展 publish_status**
`mapRow` 增加 `publishStatus``update` patch 支持 `publishStatus`;新增:
```typescript
setPublishStatus(id: string, publishStatus: PublishStatus): Chapter
countByPublishStatus(status: PublishStatus): number
getPreviousChapter(chapterId: string): Chapter | null // 按 sort_order 找上一章
```
- [ ] **Step 5: 运行 PASS + Commit**
```bash
npm run test -- tests/main/knowledge.repo.test.ts
git commit -m "feat: add knowledge and bridge repositories with publish status"
```
---
## Task 3: KnowledgeService
**Files:**
- Create: `src/main/services/knowledge.service.ts`
- Create: `tests/main/knowledge.service.test.ts`
- [ ] **Step 1: UT-KNOW-02 遗忘判定测试**
```typescript
it('UT-KNOW-02: forgotten when sort_order gap >= 20', () => {
// 创建 25 章,在第 1 章埋 foreshadow approved buried
// latestOrder - mentionOrder >= 20 → forgotten count 1
})
```
- [ ] **Step 2: UT-KNOW-03 bookmark 同步测试**
```typescript
it('UT-KNOW-03: createFromBookmark creates pending entry', () => {
const bm = bookmarkRepo.create(chapterId, 0, '测灵石发热', 'foreshadow')
const entry = service.createFromBookmark(bm.id)
expect(entry.status).toBe('pending')
expect(entry.linkedBookmarkId).toBe(bm.id)
})
```
- [ ] **Step 3: 实现 KnowledgeService**
```typescript
const FORGOTTEN_GAP = 20
export class KnowledgeService {
list(filter?: { status?: KnowledgeStatus; type?: KnowledgeType; forgottenOnly?: boolean })
create(input: CreateKnowledgeInput): KnowledgeEntry
update(id: string, patch: Partial<CreateKnowledgeInput>): KnowledgeEntry
delete(id: string): void
approve(id: string): KnowledgeEntry
reject(id: string): KnowledgeEntry
batchApprove(ids: string[]): number
createFromBookmark(bookmarkId: string): KnowledgeEntry
getForeshadowStats(): { buried: number; resolved: number; forgotten: number }
countPending(): number
private isForgotten(entry: KnowledgeEntry): boolean {
if (entry.type !== 'foreshadow' || entry.status !== 'approved' || entry.foreshadowStatus !== 'buried') return false
const mentionId = entry.lastMentionChapterId ?? entry.sourceChapterId
if (!mentionId) return false
const mentionOrder = chapterRepo.get(mentionId)?.sortOrder ?? 0
const latestOrder = Math.max(...chapterRepo.list().map((c) => c.sortOrder), 0)
return latestOrder - mentionOrder >= FORGOTTEN_GAP
}
}
```
- [ ] **Step 4: IT-KNOW-04 批量采纳**
```typescript
it('IT-KNOW-04: batchApprove clears pending', () => {
// create 3 pending → batchApprove → countPending === 0
})
```
- [ ] **Step 5: 运行 PASS + Commit**
```bash
npm run test -- tests/main/knowledge.service.test.ts
git commit -m "feat: add KnowledgeService with foreshadow forgotten logic"
```
---
## Task 4: CockpitService + GlobalSettings 扩展
**Files:**
- Create: `src/main/services/cockpit.service.ts`
- Modify: `src/main/services/global-settings.ts`
- Create: `tests/main/cockpit.service.test.ts`
- [ ] **Step 1: UT-COCK-01 测试**
```typescript
it('UT-COCK-01: getSummary counts words and ready stock', () => {
volRepo.create('卷一', 0)
chapterRepo.create(volId, 'A', 0, '<p>hello world</p>')
chapterRepo.setPublishStatus(chId, 'ready')
const summary = service.getSummary(activeVolumeId)
expect(summary.totalWords).toBeGreaterThan(0)
expect(summary.stockReadyCount).toBe(1)
})
```
- [ ] **Step 2: 实现 CockpitService**
```typescript
export class CockpitService {
constructor(
private db: SqliteDb,
private settings: GlobalSettingsService
) {}
getSummary(activeVolumeId?: string): CockpitSummary {
const chapters = new ChapterRepository(this.db).list()
const totalWords = chapters.reduce((s, c) => s + stripHtml(c.content).length, 0)
const volumeWords = activeVolumeId
? chapters.filter((c) => c.volumeId === activeVolumeId).reduce(...)
: totalWords
const stockReadyCount = new ChapterRepository(this.db).countByPublishStatus('ready')
const stats = new KnowledgeService(this.db).getForeshadowStats()
const { stockBufferThreshold } = this.settings.get()
return { totalWords, volumeWords, stockReadyCount, stockThreshold: stockBufferThreshold, ...stats }
}
shouldShowOnOpen(): boolean {
return new BookPrefsRepository(this.db).get('cockpitSeen') !== 'true'
}
markSeen(): void {
new BookPrefsRepository(this.db).set('cockpitSeen', 'true')
}
}
```
- [ ] **Step 3: global-settings defaults**
```typescript
updateSchedule: 'none',
stockBufferThreshold: 3,
```
`get()` merge 时补默认值。
- [ ] **Step 4: 运行 PASS + Commit**
```bash
npm run test -- tests/main/cockpit.service.test.ts
git commit -m "feat: add CockpitService and extend global settings"
```
---
## Task 5: ChapterBridgeService
**Files:**
- Create: `src/main/services/chapter-bridge.service.ts`
- Create: `tests/main/chapter-bridge.test.ts`
- [ ] **Step 1: UT-BRIDGE-01 文本提取测试**
```typescript
it('UT-BRIDGE-01: extracts tail and head', () => {
const prev = chapterRepo.create(volId, '上一章', 0, '<p>' + '甲'.repeat(400) + '</p>')
const cur = chapterRepo.create(volId, '本章', 1, '<p>开头内容</p>')
const result = service.getBridgeSync(cur.id) // 无 AI 同步方法
expect(result.previousTail.length).toBeLessThanOrEqual(300)
expect(result.previousTail.length).toBeGreaterThan(50)
expect(result.currentHead).toContain('开头')
})
```
- [ ] **Step 2: 实现 ChapterBridgeService**
```typescript
export class ChapterBridgeService {
shouldPrompt(chapterId: string): boolean {
if (dismissRepo.isDismissed(chapterId)) return false
const ch = chapterRepo.get(chapterId)
if (!ch || stripHtml(ch.content).length >= 200) return false
return chapterRepo.getPreviousChapter(chapterId) !== null
}
extract(chapterId: string): ChapterBridgeResult {
const prev = chapterRepo.getPreviousChapter(chapterId)
const cur = chapterRepo.get(chapterId)!
const prevText = prev ? stripHtml(prev.content) : ''
const curText = stripHtml(cur.content)
return {
previousChapterId: prev?.id ?? null,
previousTail: prevText.slice(-300),
currentHead: curText.slice(0, 200)
}
}
async getBridge(chapterId: string, withAi: boolean): Promise<ChapterBridgeResult> {
const base = this.extract(chapterId)
if (!withAi || !base.previousTail) return base
try {
const suggestion = await this.aiClient.chat([
{ role: 'user', content: `你是连载小说编辑。上一章末尾:「${base.previousTail}」。本章开头:「${base.currentHead || '(尚未撰写)'}」。用 2-3 句话指出衔接是否顺畅,并给出一个开头方向建议。不要续写正文。` }
])
return { ...base, aiSuggestion: suggestion.trim() }
} catch {
return base
}
}
dismiss(chapterId: string): void {
dismissRepo.dismiss(chapterId)
}
}
```
- [ ] **Step 3: IT-BRIDGE-01240s LM Studio**
```typescript
it('IT-BRIDGE-01: getBridge returns aiSuggestion from LM Studio', async () => {
// 两章有内容 → getBridge(id, true) → aiSuggestion.length > 10
}, 240_000)
```
- [ ] **Step 4: 运行 PASS + Commit**
```bash
npm run test -- tests/main/chapter-bridge.test.ts
git commit -m "feat: add ChapterBridgeService with optional AI suggestion"
```
---
## Task 6: IPC + Preload
**Files:**
- Create: `src/main/ipc/handlers/knowledge.handler.ts`
- Create: `src/main/ipc/handlers/cockpit.handler.ts`
- Create: `src/main/ipc/handlers/bridge.handler.ts`
- Modify: `src/main/ipc/handlers/chapter.handler.ts`
- Modify: `src/main/ipc/register.ts`
- Modify: `src/preload/index.ts`
- Modify: `src/shared/electron-api.d.ts`
- [ ] **Step 1: 实现 handlers**
参照 `auto.handler.ts` 模式,`wrap(() => ...)``BookRegistryService.getDb(bookId)`
`knowledge.handler.ts``list` / `create` / `update` / `delete` / `approve` / `reject` / `batchApprove` / `createFromBookmark` / `stats`
`cockpit.handler.ts``getSummary(bookId, volumeId?)` / `markSeen` / `shouldShowOnOpen`
`bridge.handler.ts``get(bookId, chapterId, withAi?)` / `dismiss` / `shouldPrompt`
`chapter.handler.ts` 新增:
```typescript
IPC.CHAPTER_SET_PUBLISH_STATUS chapterRepo.setPublishStatus(chapterId, status)
```
- [ ] **Step 2: register.ts**
```typescript
registerKnowledgeHandlers(books, settings)
registerCockpitHandlers(books, settings)
registerBridgeHandlers(books, settings, aiClient)
```
- [ ] **Step 3: preload 命名空间**
```typescript
knowledge: { list, create, update, delete, approve, reject, batchApprove, createFromBookmark, stats },
cockpit: { getSummary, markSeen, shouldShowOnOpen },
bridge: { get, dismiss, shouldPrompt },
chapter: { /* 现有 */ setPublishStatus },
```
- [ ] **Step 4: build 验证**
```bash
npm run build
```
- [ ] **Step 5: Commit**
```bash
git commit -m "feat: add knowledge cockpit bridge IPC and preload"
```
---
## Task 7: KnowledgePanel UI
**Files:**
- Create: `src/renderer/stores/useKnowledgeStore.ts`
- Create: `src/renderer/components/knowledge/KnowledgeEditorDialog.tsx`
- Modify: `src/renderer/components/knowledge/KnowledgePanel.tsx`
- Modify: `src/renderer/styles/layout.css`
- [ ] **Step 1: useKnowledgeStore**
```typescript
interface KnowledgeState {
entries: KnowledgeEntry[]
stats: { buried: number; resolved: number; forgotten: number }
tab: 'pending' | 'all' | 'foreshadow'
load: (bookId: string) => Promise<void>
setTab: (tab: ...) => void
create / update / delete / approve / batchApprove
openForgottenFilter: () => void // tab=foreshadow + forgottenOnly
}
```
- [ ] **Step 2: KnowledgeEditorDialog**
Radix Dialog 或现有 `.modal-overlay` 模式;字段:type select、title、content textarea、importance 1-5、foreshadowStatustype=foreshadow 时)、sourceChapter select、「直接采纳」checkbox。
- [ ] **Step 3: 重写 KnowledgePanel**
布局见 spec §6.4;保留 `naming-check-open`Tab 切换过滤列表;`data-testid` 与 spec 一致。
- [ ] **Step 4: CSS `.knowledge-panel` / `.kb-item` 对齐 ui_pc.html**
- [ ] **Step 5: Commit**
```bash
git commit -m "feat: implement KnowledgePanel with review workflow UI"
```
---
## Task 8: CockpitModal + TopBar + 首次打开
**Files:**
- Create: `src/renderer/stores/useCockpitStore.ts`
- Create: `src/renderer/components/cockpit/CockpitModal.tsx`
- Modify: `src/renderer/components/layout/TopBar.tsx`
- Modify: `src/renderer/stores/useBookStore.ts`openBook 后检查 cockpit
- Modify: `src/renderer/App.tsx``EditorLayout.tsx`(挂载 CockpitModal
- [ ] **Step 1: useCockpitStore**
```typescript
loadSummary(bookId, volumeId?)
open / close
markSeen(bookId)
shouldShowOnOpen(bookId): Promise<boolean>
```
- [ ] **Step 2: CockpitModal**
加载 summary 展示卡片;按钮:
- `cockpit-resume-edit` → close + 恢复 lastChapterId
- `cockpit-bridge-check` → close + 打开 ChapterBridgeModal
- `cockpit-forgotten-foreshadow` → close + `useKnowledgeStore.openForgottenFilter()` + 切 rightPanel knowledge
- [ ] **Step 3: TopBar**
替换 `comingSoon``data-testid="topbar-cockpit"``useCockpitStore.open()`
- [ ] **Step 4: 首次打开书籍**
`useBookStore.openBook` 成功后:
```typescript
if (await ipcCall(() => window.electronAPI.cockpit.shouldShowOnOpen(bookId))) {
await ipcCall(() => window.electronAPI.cockpit.markSeen(bookId))
useCockpitStore.getState().open()
}
```
- [ ] **Step 5: Commit**
```bash
git commit -m "feat: add CockpitModal and first-open behavior"
```
---
## Task 9: ChapterBridgeModal + EditorLayout 集成
**Files:**
- Create: `src/renderer/components/bridge/ChapterBridgeModal.tsx`
- Modify: `src/renderer/components/layout/EditorLayout.tsx`
- Modify: `src/renderer/stores/useAppStore.ts`bridgeOpen 状态,可选)
- [ ] **Step 1: ChapterBridgeModal**
打开时 `bridge.get(bookId, chapterId, true)`;展示 tail/head/aiSuggestioncheckbox dismiss + `bridge-start-writing` 关闭。
- [ ] **Step 2: EditorLayout 自动触发**
`switchTarget` 到 chapter 后:
```typescript
const should = await ipcCall(() => window.electronAPI.bridge.shouldPrompt(chapterId))
if (should) setTimeout(() => setBridgeOpen(true), 500)
```
- [ ] **Step 3: 驾驶舱手动触发**
CockpitModal `cockpit-bridge-check` 设置 `bridgeChapterId` 为当前章并打开 modal。
- [ ] **Step 4: Commit**
```bash
git commit -m "feat: add ChapterBridgeModal with auto prompt on new chapter"
```
---
## Task 10: 发布状态 + 存稿提醒 + 设置
**Files:**
- Modify: `src/renderer/components/layout/EditorLayout.tsx`
- Modify: `src/renderer/stores/useBookStore.ts`
- Modify: `src/renderer/components/settings/SettingsPage.tsx`
- [ ] **Step 1: 章节 publish 循环按钮**
`chapter-item` 内增加小图标按钮 `data-testid={`chapter-publish-${ch.id}`}`,点击循环 `draft → ready → published`,调用 `chapter.setPublishStatus`,刷新 chapters。
- [ ] **Step 2: StatusBar 存稿警告**
EditorLayout `#editor-statusbar` 内:
```typescript
const summary = await cockpit.getSummary(bookId, activeVolumeId)
if (settings.updateSchedule !== 'none' && summary.stockReadyCount < summary.stockThreshold) {
// 渲染 data-testid="status-stock-warning"
}
```
书籍打开时 load summarypublish 变更后 refresh。
- [ ] **Step 3: SettingsPage**
新增「更新计划」区块:`updateSchedule` select、`stockBufferThreshold` number input (1-20)。
- [ ] **Step 4: Commit**
```bash
git commit -m "feat: add publish status UI stock warning and update schedule settings"
```
---
## Task 11: 地标同步 + i18n
**Files:**
- Modify: `src/renderer/components/editor/TipTapEditor.tsx` 或 landmark 插入流程
- Modify: `public/locales/zh-CN/translation.json`
- Modify: `public/locales/en/translation.json`
- [ ] **Step 1: 插入 foreshadow 地标时 checkbox**
在 insert landmark 对话框(或插入后 toast 行动)增加「同步到知识库」;勾选则 `knowledge.createFromBookmark(bookmarkId)`
若当前无独立 insert UI,在 `LandmarkModal` 或 TipTap landmark 命令回调中加 post-create hook。
- [ ] **Step 2: i18n 键**
`cockpit.*``bridge.*``knowledge.*``publish.status.draft/ready/published``settings.updateSchedule.*``stock.warning`
- [ ] **Step 3: Commit**
```bash
git commit -m "feat: add foreshadow landmark sync and P5 i18n strings"
```
---
## Task 12: E2E 测试
**Files:**
- Create: `e2e/cockpit-knowledge.spec.ts`
- [ ] **Step 1: 无 AI 门槛测试**
```typescript
test('E2E-COCK-01: cockpit modal shows stats', async () => { /* openBookWithContent → topbar-cockpit → cockpit-total-words visible */ })
test('E2E-COCK-02: first open shows cockpit once', async () => { /* 开书两次 */ })
test('E2E-PUB-01: publish ready increases stock count', async () => { /* chapter-publish click → cockpit-stock-count */ })
test('E2E-KNOW-01: create approve knowledge entry', async () => { /* knowledge-new → pending tab → approve */ })
test('E2E-BRIDGE-01: new chapter triggers bridge modal', async () => { /* 新章 → bridge-previous-tail visible */ })
test('E2E-STOCK-01: low stock warning in statusbar', async () => { /* set threshold + schedule in settings, 0 ready chapters */ })
```
- [ ] **Step 2: LM Studio 测试(300s**
```typescript
test('E2E-BRIDGE-02: bridge shows AI suggestion', async () => {
test.setTimeout(300_000)
// 两章有内容 → 手动打开 bridge → bridge-ai-suggestion 非空
})
test('E2E-KNOW-02: foreshadow landmark sync', async () => { /* 插入 foreshadow + sync */ })
```
- [ ] **Step 3: 运行 E2E**
```bash
npm run test:e2e -- e2e/cockpit-knowledge.spec.ts
```
- [ ] **Step 4: Commit**
```bash
git commit -m "test: add P5 cockpit knowledge and bridge E2E specs"
```
---
## Task 13: v0.6.0 交付
**Files:**
- Modify: `package.json`
- Modify: `README.md`
- Modify: `src/renderer/components/settings/SettingsPage.tsx`(版本号展示)
- Modify: `docs/superpowers/plans/2026-07-06-bilin-p5-workflow-knowledge.md`(勾选完成项)
- [ ] **Step 1: 版本号 → `0.6.0`**
- [ ] **Step 2: README 功能概览追加 P5**
```markdown
- 驾驶舱:全书/本卷字数、存稿缓冲、伏笔摘要、快速入口(P5)
- 知识库 MVP:手动条目、审核流、伏笔追踪、地标同步(P5)
- 章间衔接:上下章片段 + 可选 AI 建议(P5)
- 更新计划:发布状态、存稿阈值提醒(P5)
```
- [ ] **Step 3: 全量测试**
```bash
npm run build
npm run test
npm run test:e2e
```
- [ ] **Step 4: Commit**
```bash
git commit -m "feat: ship v0.6.0 with cockpit knowledge and chapter bridge"
```
---
## Spec Coverage Checklist
| Spec 要求 | Task |
|-----------|------|
| schema v6 三表 | Task 1 |
| Knowledge CRUD / 审核 / 遗忘 | Task 23, 7 |
| Cockpit 聚合 + 首次打开 | Task 4, 8 |
| ChapterBridge + AI | Task 5, 9 |
| publish_status UI | Task 10 |
| updateSchedule / 存稿提醒 | Task 4, 10 |
| 地标 foreshadow 同步 | Task 11 |
| IPC / preload | Task 6 |
| i18n | Task 11 |
| 全部测试 ID | Task 15, 12 |
| v0.6.0 DoD | Task 13 |