Co-authored-by: Cursor <cursoragent@cursor.com>
24 KiB
笔临 P5.2 写作日志 + 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.8.0:writing_logs 日更追踪(保存差分 + 成章补计)、驾驶舱热力图/连更、状态栏日更进度,以及 AI 章节知识自动抽取 + 合并审核增强,与 P6 注入形成上下游闭环。
Architecture: 全局 userData/writing_logs.sqlite 存 (date, bookId) 日字数;每书 schema v7 增加 chapter_writing_snapshots 与 knowledge_entries 抽取元数据列;ChapterWritingTracker 挂 CHAPTER_UPDATE / finishChapter;KnowledgeExtractionService 异步调用 AiClientService 落库 pending。测试 禁止 Mock AI 集成/E2E。
Tech Stack: Electron 36、electron-vite、React 18、TypeScript、node:sqlite、Zustand、i18next、Vitest、Playwright
Spec: docs/superpowers/specs/2026-07-07-bilin-p52-writing-logs-extraction-design.md
LM Studio 默认: baseUrl=http://127.0.0.1:1234,model=gemma-4-e4b-it
File Map
| 文件 | 职责 |
|---|---|
src/shared/types.ts |
WritingStats、ExtractedKnowledgeItem、KnowledgeExtractionResult;扩展 KnowledgeEntry、CockpitSummary、GlobalSettings |
src/shared/ipc-channels.ts |
WRITING_GET_STATS、KNOWLEDGE_EXTRACT_CHAPTER、KNOWLEDGE_APPROVE_MERGE、KNOWLEDGE_BATCH_APPROVE_HIGH_CONFIDENCE |
src/main/db/schema-v7.sql |
chapter_writing_snapshots + knowledge ALTER |
src/main/db/migrate.ts |
v7 迁移 |
src/main/db/repositories/writing-log.repo.ts |
全局 writing_logs |
src/main/db/repositories/chapter-writing-snapshot.repo.ts |
每书 snapshot |
src/main/db/repositories/knowledge.repo.ts |
抽取列读写、createExtracted、batchApproveHighConfidence |
src/main/services/writing-log.service.ts |
日更、连更、热力图 |
src/main/services/chapter-writing-tracker.service.ts |
recordDelta / supplementOnFinish |
src/main/services/knowledge-extraction-prompt.ts |
prompt + parseExtractionJson |
src/main/services/knowledge-extraction.service.ts |
extractForChapter |
src/main/services/knowledge.service.ts |
approveMerge、clearMergeTarget |
src/main/services/cockpit.service.ts |
附加 writingStats |
src/main/services/global-settings.ts |
新设置默认值 |
src/main/ipc/handlers/writing.handler.ts |
writing:getStats |
src/main/ipc/handlers/knowledge.handler.ts |
抽取/合并 IPC |
src/main/ipc/handlers/chapter.handler.ts |
挂钩 tracker |
src/main/ipc/handlers/interactive.handler.ts |
finish 后 supplement + 异步抽取 |
src/main/ipc/handlers/auto.handler.ts |
同上 |
src/main/ipc/register.ts |
注入 WritingLogService |
src/preload/index.ts + electron-api.d.ts |
新 API |
src/renderer/components/cockpit/WritingHeatmap.tsx |
Canvas 热力图 |
src/renderer/components/cockpit/CockpitModal.tsx |
今日进度/连更/热力图 |
src/renderer/components/layout/EditorLayout.tsx |
状态栏日更进度 |
src/renderer/components/knowledge/KnowledgePanel.tsx |
批次/合并/批量采纳 |
src/renderer/components/knowledge/KnowledgeEditorDialog.tsx |
合并预填模式 |
src/renderer/components/settings/SettingsPage.tsx |
抽取设置 |
src/renderer/components/layout/ChapterList.tsx 或编辑器菜单 |
抽取本章入口 |
public/locales/*/translation.json |
P5.2 文案 |
tests/main/writing-log.test.ts |
UT-LOG-* / IT-LOG-01 |
tests/main/knowledge-extraction.test.ts |
UT-EXT-* / IT-EXT-* |
e2e/writing-logs.spec.ts |
E2E-LOG-* |
e2e/knowledge-extraction.spec.ts |
E2E-EXT-* |
Task 1: 类型 + IPC 通道 + GlobalSettings
Files:
-
Modify:
src/shared/types.ts -
Modify:
src/shared/ipc-channels.ts -
Modify:
src/main/services/global-settings.ts -
Modify:
src/renderer/stores/useSettingsStore.ts -
Step 1: 扩展 types
src/shared/types.ts 追加:
export interface WritingDayLog {
date: string
bookId: string
wordCount: number
}
export interface WritingStats {
todayWords: number
dailyGoal: number
goalProgress: number
streakDays: number
heatmap: WritingDayLog[]
}
export interface ExtractedKnowledgeItem {
type: KnowledgeType
title: string
content: string
importance?: number
confidence: number
foreshadowStatus?: ForeshadowStatus
suggestedMergeId?: string
proposedPatch?: Partial<CreateKnowledgeInput>
}
export interface KnowledgeExtractionResult {
batchId: string
chapterId: string
items: ExtractedKnowledgeItem[]
}
扩展 KnowledgeEntry:
extractionConfidence?: number
mergeTargetId?: string
extractionBatchId?: string
扩展 CockpitSummary:
writingStats?: WritingStats
扩展 GlobalSettings:
knowledgeAutoExtract?: boolean
knowledgeExtractConfidenceThreshold?: number
- Step 2: IPC 通道
WRITING_GET_STATS: 'writing:getStats',
KNOWLEDGE_EXTRACT_CHAPTER: 'knowledge:extractChapter',
KNOWLEDGE_APPROVE_MERGE: 'knowledge:approveMerge',
KNOWLEDGE_BATCH_APPROVE_HIGH_CONFIDENCE: 'knowledge:batchApproveHighConfidence',
- Step 3: global-settings 默认值
knowledgeAutoExtract: true,
knowledgeExtractConfidenceThreshold: 0.8,
get() merge 补默认。
-
Step 4: useSettingsStore 同步默认
-
Step 5: build
npm run build
Task 2: schema v7 + KnowledgeRepository 扩展
Files:
-
Create:
src/main/db/schema-v7.sql -
Modify:
src/main/db/migrate.ts -
Modify:
src/main/db/repositories/knowledge.repo.ts -
Create:
tests/main/migrate-v7.test.ts -
Step 1: schema-v7.sql
CREATE TABLE IF NOT EXISTS chapter_writing_snapshots (
chapter_id TEXT PRIMARY KEY,
logged_word_count INTEGER NOT NULL DEFAULT 0,
finish_supplemented INTEGER NOT NULL DEFAULT 0,
FOREIGN KEY (chapter_id) REFERENCES chapters(id) ON DELETE CASCADE
);
- Step 2: migrate.ts
import schemaV7 from './schema-v7.sql?raw'
const CURRENT_VERSION = 7
// in migrate(), after v6:
if (current < 7) {
db.exec(schemaV7)
tryAlter(db, 'ALTER TABLE knowledge_entries ADD COLUMN extraction_confidence REAL')
tryAlter(db, 'ALTER TABLE knowledge_entries ADD COLUMN merge_target_id TEXT')
tryAlter(db, 'ALTER TABLE knowledge_entries ADD COLUMN extraction_batch_id TEXT')
db.prepare('INSERT INTO schema_version (version, description) VALUES (?, ?)').run(7, 'P5.2 writing logs extraction')
}
- Step 3: knowledge.repo mapRow + createExtracted
更新 mapRow 读取三列;新增:
createExtracted(input: CreateKnowledgeInput & {
extractionConfidence?: number
mergeTargetId?: string
extractionBatchId?: string
}): KnowledgeEntry {
const id = randomUUID()
this.db.prepare(
`INSERT INTO knowledge_entries (
id, type, title, content, importance, status, foreshadow_status,
source_chapter_id, last_mention_chapter_id, linked_bookmark_id,
extraction_confidence, merge_target_id, extraction_batch_id
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
).run(
id, input.type, input.title, input.content ?? '', input.importance ?? 3,
input.status ?? 'pending', input.foreshadowStatus ?? null,
input.sourceChapterId ?? null, input.lastMentionChapterId ?? null, input.linkedBookmarkId ?? null,
input.extractionConfidence ?? null, input.mergeTargetId ?? null, input.extractionBatchId ?? null
)
return this.get(id)!
}
batchApproveHighConfidence(threshold: number): number {
const rows = this.db.prepare(
`SELECT id FROM knowledge_entries
WHERE status = 'pending' AND merge_target_id IS NULL
AND extraction_confidence IS NOT NULL AND extraction_confidence >= ?`
).all(threshold) as { id: string }[]
let count = 0
for (const { id } of rows) {
this.update(id, { status: 'approved' })
count++
}
return count
}
clearMergeTarget(id: string): KnowledgeEntry {
this.db.prepare('UPDATE knowledge_entries SET merge_target_id = NULL, updated_at = datetime(\'now\') WHERE id = ?').run(id)
return this.get(id)!
}
- Step 4: migrate-v7 测试
it('UT-MIG-07: v6 database migrates to v7 with snapshot table and knowledge columns', () => {
const db = new DatabaseSync(':memory:')
migrate(db) // fresh → v7
const tables = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='chapter_writing_snapshots'").get()
expect(tables).toBeTruthy()
db.close()
})
- Step 5: 运行
npm run test -- tests/main/migrate-v7.test.ts
Task 3: WritingLogService(全局)
Files:
-
Create:
src/main/db/repositories/writing-log.repo.ts -
Create:
src/main/services/writing-log.service.ts -
Create:
tests/main/writing-log.test.ts -
Step 1: WritingLogRepository
全局 sqlite 路径 join(userDataDir, 'writing_logs.sqlite');migrate() 建表;方法:
addToday(bookId: string, delta: number, date?: string): number
getToday(bookId: string, date?: string): number
listRange(bookId: string, fromDate: string, toDate: string): WritingDayLog[]
addToday:INSERT ... ON CONFLICT DO UPDATE SET word_count = MAX(0, word_count + excluded.word_count)。
日期工具:
export function localDateString(d = new Date()): string {
return d.toLocaleDateString('sv-SE') // YYYY-MM-DD
}
- Step 2: WritingLogService
export class WritingLogService {
constructor(private repo: WritingLogRepository, private settings: GlobalSettingsService) {}
addToday(bookId: string, delta: number): number {
return this.repo.addToday(bookId, delta)
}
getStats(bookId: string): WritingStats {
const dailyGoal = this.settings.get().dailyWordGoal ?? 0
const today = localDateString()
const todayWords = this.repo.getToday(bookId, today)
const goalProgress = dailyGoal > 0 ? todayWords / dailyGoal : 0
const heatmap = this.buildHeatmap(bookId, 365)
const streakDays = dailyGoal > 0 ? this.calcStreak(bookId, dailyGoal) : 0
return { todayWords, dailyGoal, goalProgress, streakDays, heatmap }
}
private buildHeatmap(bookId: string, days: number): WritingDayLog[] { /* 填充 0 */ }
private calcStreak(bookId: string, goal: number): number { /* 从昨日向前 */ }
}
- Step 3: UT-LOG-01~04
it('UT-LOG-01: addToday accumulates +200', () => { /* ... */ })
it('UT-LOG-02: negative delta clamps at 0', () => { /* ... */ })
it('UT-LOG-04: streak counts consecutive goal-met days', () => { /* seed 3 days */ })
- Step 4: 运行
npm run test -- tests/main/writing-log.test.ts
Task 4: ChapterWritingTracker + Snapshot Repo
Files:
-
Create:
src/main/db/repositories/chapter-writing-snapshot.repo.ts -
Create:
src/main/services/chapter-writing-tracker.service.ts -
Modify:
tests/main/writing-log.test.ts -
Step 1: ChapterWritingSnapshotRepository
get(chapterId: string): { loggedWordCount: number; finishSupplemented: boolean } | null
upsert(chapterId: string, loggedWordCount: number, finishSupplemented?: boolean): void
- Step 2: ChapterWritingTracker
export class ChapterWritingTracker {
constructor(
private bookDb: SqliteDb,
private bookId: string,
private writingLogs: WritingLogService
) {}
recordDelta(chapterId: string, newWordCount: number): void {
const snap = this.snapRepo.get(chapterId)
const logged = snap?.loggedWordCount ?? 0
const delta = newWordCount - logged
if (delta !== 0) this.writingLogs.addToday(this.bookId, delta)
this.snapRepo.upsert(chapterId, newWordCount, snap?.finishSupplemented ?? false)
}
supplementOnFinish(chapterId: string, wordCount: number): void {
const snap = this.snapRepo.get(chapterId)
const logged = snap?.loggedWordCount ?? 0
const gap = wordCount - logged
if (gap > 0) this.writingLogs.addToday(this.bookId, gap)
this.snapRepo.upsert(chapterId, wordCount, true)
}
}
-
Step 3: UT-LOG-03 supplementOnFinish
-
Step 4: 运行测试
npm run test -- tests/main/writing-log.test.ts
Task 5: 挂钩 CHAPTER_UPDATE + finishChapter
Files:
-
Modify:
src/main/ipc/handlers/chapter.handler.ts -
Modify:
src/main/ipc/handlers/interactive.handler.ts -
Modify:
src/main/ipc/handlers/auto.handler.ts -
Modify:
src/main/ipc/register.ts -
Modify:
tests/main/writing-log.test.ts(IT-LOG-01) -
Step 1: register.ts 创建 WritingLogService 单例
const writingLogs = new WritingLogService(new WritingLogRepository(userData), settings)
registerChapterHandlers(books, writingLogs)
registerInteractiveHandlers(registry, settings, aiClient, books, writingLogs, ...)
工厂函数:
function trackerFor(registry: BookRegistryService, bookId: string, writingLogs: WritingLogService) {
return new ChapterWritingTracker(registry.getDb(bookId), bookId, writingLogs)
}
- Step 2: chapter.handler CHAPTER_UPDATE 后
wrap(() => {
const chapter = registry.getChapterRepo(bookId).update(...)
ftsSync.upsert(...)
trackerFor(registry, bookId, writingLogs).recordDelta(chapterId, chapter.wordCount)
registry.updateMeta(bookId, { lastChapterId: chapterId })
return chapter
})
- Step 3: interactive/auto finishChapter 后
finishChapter 返回 { chapterId } 后:
const ch = registry.getChapterRepo(bookId).get(chapterId)!
trackerFor(registry, bookId, writingLogs).supplementOnFinish(chapterId, ch.wordCount)
if (settings.get().knowledgeAutoExtract !== false) {
setImmediate(() => void extractKnowledge(registry, bookId, chapterId, aiClient, settings))
}
return result
抽取辅助放在 src/main/services/knowledge-extraction-runner.ts(避免 handler 膨胀)。
- Step 4: IT-LOG-01
两次 CHAPTER_UPDATE 模拟,断言 writing_logs 累计。
- Step 5: 运行
npm run test -- tests/main/writing-log.test.ts
Task 6: Cockpit 扩展 + writing IPC + preload
Files:
-
Modify:
src/main/services/cockpit.service.ts -
Create:
src/main/ipc/handlers/writing.handler.ts -
Modify:
src/main/ipc/register.ts -
Modify:
src/preload/index.ts -
Modify:
src/shared/electron-api.d.ts -
Step 1: CockpitService 注入 WritingLogService
getSummary(activeVolumeId?: string, bookId?: string): CockpitSummary {
const base = { /* existing */ }
const writingStats = bookId ? this.writingLogs.getStats(bookId) : undefined
return { ...base, writingStats }
}
更新 cockpit.handler.ts 传 bookId。
- Step 2: writing.handler
ipcMain.handle(IPC.WRITING_GET_STATS, (_e, { bookId }: { bookId: string }) =>
wrap(() => writingLogs.getStats(bookId))
)
- Step 3: preload
writing: {
getStats: (bookId: string): Promise<IpcResult<WritingStats>> =>
ipcRenderer.invoke(IPC.WRITING_GET_STATS, { bookId })
},
- Step 4: build
npm run build
Task 7: KnowledgeExtraction prompt + parse + service
Files:
-
Create:
src/main/services/knowledge-extraction-prompt.ts -
Create:
src/main/services/knowledge-extraction.service.ts -
Create:
tests/main/knowledge-extraction.test.ts -
Step 1: knowledge-extraction-prompt.ts
export function buildExtractionPrompt(
chapterTitle: string,
plainText: string,
approvedTitles: Array<{ id: string; title: string }>
): string { /* 中文指令 + JSON schema 说明 */ }
export function parseExtractionJson(raw: string): ExtractedKnowledgeItem[] {
const cleaned = raw.replace(/```json\n?|\n?```/g, '').trim()
const arr = JSON.parse(cleaned) as ExtractedKnowledgeItem[]
if (!Array.isArray(arr)) throw new Error('expected array')
return arr.map((item) => ({
type: item.type,
title: String(item.title).trim(),
content: String(item.content ?? '').trim(),
importance: item.importance ?? 3,
confidence: Number(item.confidence) || 0.5,
foreshadowStatus: item.foreshadowStatus,
suggestedMergeId: item.suggestedMergeId ?? undefined,
proposedPatch: item.proposedPatch
})).filter((x) => x.title.length > 0)
}
- Step 2: KnowledgeExtractionService
export class KnowledgeExtractionService {
async extractForChapter(chapterId: string): Promise<KnowledgeExtractionResult> {
const chapter = this.chapterRepo.get(chapterId)
if (!chapter) throw new Error('Chapter not found')
const plain = stripHtml(chapter.content).slice(0, 12000)
if (!plain.trim()) throw new Error('empty chapter')
const approved = this.knowledgeRepo.list({ status: 'approved' }).slice(0, 30)
const prompt = buildExtractionPrompt(chapter.title, plain, approved.map((e) => ({ id: e.id, title: e.title })))
let response = await this.aiClient.chat([{ role: 'user', content: prompt }])
let items: ExtractedKnowledgeItem[]
try {
items = parseExtractionJson(response)
} catch {
response = await this.aiClient.chat([
{ role: 'user', content: prompt },
{ role: 'user', content: '仅返回 JSON 数组,不要 markdown 或其它说明。' }
])
items = parseExtractionJson(response)
}
const batchId = randomUUID()
const saved: ExtractedKnowledgeItem[] = []
for (const item of items) {
const mergeTargetId = item.suggestedMergeId && this.knowledgeRepo.get(item.suggestedMergeId) ? item.suggestedMergeId : undefined
this.knowledgeRepo.createExtracted({
type: item.type,
title: item.title,
content: item.content,
importance: item.importance,
status: 'pending',
foreshadowStatus: item.type === 'foreshadow' ? (item.foreshadowStatus ?? 'buried') : undefined,
sourceChapterId: chapterId,
extractionConfidence: item.confidence,
mergeTargetId,
extractionBatchId: batchId
})
saved.push({ ...item, suggestedMergeId: mergeTargetId })
}
return { batchId, chapterId, items: saved }
}
}
- Step 3: UT-EXT-01 / UT-EXT-02
对 parseExtractionJson 测合法 JSON 与 suggestedMergeId 字段。
- Step 4: 运行
npm run test -- tests/main/knowledge-extraction.test.ts -t "UT-EXT"
Task 8: Knowledge 合并 IPC + KnowledgeService
Files:
-
Modify:
src/main/services/knowledge.service.ts -
Modify:
src/main/ipc/handlers/knowledge.handler.ts -
Modify:
src/preload/index.ts -
Modify:
tests/main/knowledge-extraction.test.ts -
Step 1: KnowledgeService.approveMerge
approveMerge(pendingId: string): KnowledgeEntry {
const pending = this.repo.get(pendingId)
if (!pending?.mergeTargetId) throw new Error('not a merge suggestion')
const target = this.repo.get(pending.mergeTargetId)
if (!target || target.status !== 'approved') throw new Error('merge target missing')
const patch: Partial<CreateKnowledgeInput> = {
content: pending.content,
importance: pending.importance,
lastMentionChapterId: pending.lastMentionChapterId ?? pending.sourceChapterId
}
const updated = this.repo.update(target.id, patch)
this.repo.update(pendingId, { status: 'rejected' })
return updated
}
saveMergeAsNew(pendingId: string): KnowledgeEntry {
return this.repo.clearMergeTarget(pendingId)
}
- Step 2: knowledge.handler IPC
ipcMain.handle(IPC.KNOWLEDGE_EXTRACT_CHAPTER, (_e, { bookId, chapterId }) =>
wrap(() => new KnowledgeExtractionService(registry.getDb(bookId), aiClient).extractForChapter(chapterId))
)
ipcMain.handle(IPC.KNOWLEDGE_APPROVE_MERGE, (_e, { bookId, pendingId }) =>
wrap(() => new KnowledgeService(registry.getDb(bookId)).approveMerge(pendingId))
)
ipcMain.handle(IPC.KNOWLEDGE_BATCH_APPROVE_HIGH_CONFIDENCE, (_e, { bookId, threshold }) =>
wrap(() => {
const t = threshold ?? settings.get().knowledgeExtractConfidenceThreshold ?? 0.8
return new KnowledgeRepository(registry.getDb(bookId)).batchApproveHighConfidence(t)
})
)
-
Step 3: IT-EXT-01 / IT-EXT-02(240s)
-
Step 4: IT-EXT-03 — 抽取 → approve →
KnowledgeRetrievalService.suggest含新条目
Task 9: 驾驶舱 UI + 状态栏日更进度
Files:
-
Create:
src/renderer/components/cockpit/WritingHeatmap.tsx -
Modify:
src/renderer/components/cockpit/CockpitModal.tsx -
Modify:
src/renderer/components/layout/EditorLayout.tsx -
Modify:
src/renderer/styles/layout.css -
Step 1: WritingHeatmap 组件
Props:heatmap: WritingDayLog[];Canvas 13×7 格(52 周近似 365 天);四档颜色;data-testid="cockpit-heatmap"。
- Step 2: CockpitModal 扩展
在 cockpit-grid 下方:
{summary.writingStats && summary.writingStats.dailyGoal > 0 && (
<div data-testid="cockpit-today-progress">...</div>
)}
{summary.writingStats && summary.writingStats.streakDays > 0 && (
<div data-testid="cockpit-streak">{t('cockpit.streak', { days: summary.writingStats.streakDays })}</div>
)}
<WritingHeatmap heatmap={summary.writingStats?.heatmap ?? []} />
- Step 3: EditorLayout 状态栏
const writingStats = useWritingStatsStore() // 或 cockpit summary 缓存
{dailyGoal > 0 && (
<span className={`status-daily-goal ${progress >= 1 ? 'goal-met' : progress >= 0.8 ? 'goal-near' : ''}`} data-testid="status-daily-goal">
{t('status.dailyGoal', { current: todayWords, goal: dailyGoal })}
</span>
)}
章节保存后 writing.getStats 刷新(监听 save 或轮询 useBookStore wordCount)。
- Step 4: build 验证
npm run build
Task 10: 知识库 UI + 设置 + 抽取入口
Files:
-
Modify:
src/renderer/components/knowledge/KnowledgePanel.tsx -
Modify:
src/renderer/components/knowledge/KnowledgeEditorDialog.tsx -
Modify:
src/renderer/stores/useKnowledgeStore.ts -
Modify:
src/renderer/components/settings/SettingsPage.tsx -
Modify:
src/renderer/components/layout/ChapterList.tsx(或EditorLayout章节菜单) -
Modify:
public/locales/zh-CN/translation.json -
Modify:
public/locales/en/translation.json -
Step 1: KnowledgePanel 待审核增强
-
卡片显示
extractionConfidence百分比徽章 -
mergeTargetId时显示t('knowledge.mergeSuggest')+data-testid="knowledge-merge-review-{id}" -
工具栏按钮
knowledge-batch-high-confidence -
工具栏「从当前章抽取」
knowledge-extract-current(需selectedChapterId) -
Step 2: KnowledgeEditorDialog 合并模式
props:mergeTarget?: KnowledgeEntry、initialPatch?: Partial<CreateKnowledgeInput>
底部按钮:「确认更新」→ approveMerge;「作为新条目」→ saveMergeAsNew 后常规编辑。
- Step 3: SettingsPage
settings-knowledge-auto-extract 开关;settings-extract-threshold 数字输入。
- Step 4: 章节抽取入口
章节列表项或编辑器右键:data-testid="chapter-extract-knowledge" → knowledge.extractChapter。
- Step 5: i18n — spec §9 全部键
Task 11: E2E + v0.8.0 交付
Files:
-
Create:
e2e/writing-logs.spec.ts -
Create:
e2e/knowledge-extraction.spec.ts -
Modify:
package.json -
Modify:
README.md -
Modify:
src/renderer/components/settings/SettingsPage.tsx(about 版本) -
Modify:
docs/superpowers/plans/2026-07-07-bilin-p52-writing-logs-extraction.md(勾选完成项) -
Step 1: E2E-LOG-01 / E2E-LOG-02
写 100 字保存 → 打开驾驶舱 → cockpit-today-progress 更新;cockpit-heatmap 可见。
-
Step 2: E2E-EXT-01 / E2E-EXT-02 / E2E-EXT-03(300s timeout on AI tests)
-
Step 3: 全量测试
npm run build
npm run test
npm run test:e2e -- e2e/writing-logs.spec.ts e2e/knowledge-extraction.spec.ts
- Step 4: 版本 0.8.0
package.json、README.md 追加:
- 写作日志与热力图、AI 章节知识自动抽取(P5.2)
- Step 5: Commit
git commit -m "feat: ship v0.8.0 with writing logs and AI knowledge extraction"
Spec Coverage Checklist
| Spec 要求 | Task |
|---|---|
| writing_logs 全局表 | Task 3 |
| chapter_writing_snapshots v7 | Task 2, 4 |
| 保存差分 + 成章补计 | Task 4, 5 |
| 连更 + 热力图 | Task 3, 9 |
| 状态栏日更进度 | Task 9 |
| knowledgeAutoExtract 设置 | Task 1, 5, 10 |
| AI 抽取五类 + JSON | Task 7 |
| 合并建议 approveMerge | Task 8, 10 |
| 批量高置信度 | Task 2, 8, 10 |
| P6 suggestContext 联动 | Task 8 IT-EXT-03 |
| UT-LOG / UT-EXT / IT / E2E | Task 3–8, 11 |
| v0.8.0 DoD | Task 11 |