78f046890d
Co-authored-by: Cursor <cursoragent@cursor.com>
819 lines
25 KiB
Markdown
819 lines
25 KiB
Markdown
# 笔临 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-01(240s 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、foreshadowStatus(type=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/aiSuggestion;checkbox 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 summary;publish 变更后 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 2–3, 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 1–5, 12 |
|
||
| v0.6.0 DoD | Task 13 |
|