Files
bilin/docs/superpowers/plans/2026-07-07-bilin-p21-p3.md
bing 8ce35a1e8e feat: ship v0.3.0 with P2.1 editor polish and P3 AI collaboration
Add chapter reorder, search replace, inspiration capture, and AI chat with context editing, settings, offline handling, and naming checks. Fix dev black screen by keeping process.env reads in the main process only.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-06 16:33:56 +08:00

24 KiB
Raw Permalink Blame History

笔临 P2.1 + P3 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.3.0:P2 遗留(章节拖拽、搜索替换、灵感模态+语音、@跳转、引用钉住)+ P3 严格(LM Studio 对话、上下文注入、快捷命令、离线降级、命名检查、AI 快照)。

Architecture: 延续 electron-vite 单体;主进程 schema v3 + AiClientServiceOpenAI 兼容 fetch 至 LM Studio+ 流式 IPC 事件;渲染进程 AiPanel 替换占位;测试 禁止 Mock,集成/E2E 必须打真实 http://127.0.0.1:1234

Tech Stack: Electron 36、electron-vite、React 18、TypeScript、TipTap、node:sqlite、Zustand、Jotai、i18next、Vitest、Playwright

Spec: docs/superpowers/specs/2026-07-07-bilin-p21-p3-design.md

LM Studio 默认: baseUrl=http://127.0.0.1:1234model=gemma-4-e4b-it(可用 BILIN_AI_BASE_URL / BILIN_AI_MODEL 覆盖)


File Map

文件 职责
src/main/db/schema-v3.sql ai_sessions / ai_messages
src/main/db/migrate.ts v2→v3 迁移
src/main/db/repositories/chapter.repo.ts move()
src/main/db/repositories/ai-session.repo.ts 会话/消息 CRUD
src/main/services/ai-client.service.ts LM Studio HTTP + SSE 流
src/main/services/ai-context-builder.service.ts 上下文拼接
src/main/services/network-monitor.service.ts online/offline 推送
src/main/ipc/handlers/ai.handler.ts AI IPC + 流式
src/main/ipc/handlers/chapter.handler.ts chapter:move
src/shared/types.ts AiConfig / AiSession / NamingConflict
src/shared/ipc-channels.ts P2.1/P3 IPC
src/preload/index.ts AI API + 事件订阅
src/renderer/stores/useAiStore.ts 会话/流式/disabled
src/renderer/components/ai/AiPanel.tsx 右栏 AI 主 UI
src/renderer/components/ai/ContextEditorModal.tsx 上下文勾选
src/renderer/components/ai/NamingCheckModal.tsx 命名检查
src/renderer/components/inspiration/InspirationModal.tsx 灵感捕获
src/renderer/components/settings/AiSettingsPage.tsx AI 配置 Tab
src/renderer/components/search/SearchModal.tsx 替换区
src/renderer/components/layout/EditorLayout.tsx 章节 drag
e2e/p21-features.spec.ts P2.1 E2E
e2e/ai-chat.spec.ts P3 对话 E2E

Task 1: 章节拖拽(P2.1a

Files:

  • Modify: src/main/db/repositories/chapter.repo.ts

  • Modify: src/main/ipc/handlers/chapter.handler.ts

  • Modify: src/shared/ipc-channels.ts

  • Modify: src/preload/index.ts, src/shared/electron-api.d.ts

  • Modify: src/renderer/components/layout/EditorLayout.tsx

  • Modify: src/renderer/styles/layout.css

  • Create: tests/main/chapter-move.test.ts

  • Create: e2e/p21-features.spec.tsDRAG 用例)

  • Step 1: 写 chapter.move 失败测试

tests/main/chapter-move.test.ts

import { describe, it, expect, beforeEach, afterEach } from 'vitest'
import { DatabaseSync } from 'node:sqlite'
import { migrate } from '../../src/main/db/migrate'
import { ChapterRepository } from '../../src/main/db/repositories/chapter.repo'
import { VolumeRepository } from '../../src/main/db/repositories/volume.repo'
import type { SqliteDb } from '../../src/main/db/types'

describe('ChapterRepository.move', () => {
  let db: SqliteDb
  let chapters: ChapterRepository
  let volId: string

  beforeEach(() => {
    db = new DatabaseSync(':memory:')
    migrate(db)
    const vols = new VolumeRepository(db)
    volId = vols.create('卷一', 0).id
    chapters = new ChapterRepository(db)
    chapters.create(volId, 'A', 0)
    chapters.create(volId, 'B', 1)
    chapters.create(volId, 'C', 2)
  })

  afterEach(() => db.close())

  it('reorders within volume', () => {
    const list = chapters.listByVolume(volId)
    const idC = list.find((x) => x.title === 'C')!.id
    chapters.move(idC, volId, 0)
    chapters.reorderVolume(volId, [idC, list.find((x) => x.title === 'A')!.id, list.find((x) => x.title === 'B')!.id])
    const titles = chapters.listByVolume(volId).map((x) => x.title)
    expect(titles).toEqual(['C', 'A', 'B'])
  })
})
  • Step 2: 运行确认 FAIL
npm run test -- tests/main/chapter-move.test.ts

Expected: FAIL chapters.move is not a function

  • Step 3: 实现 ChapterRepository.move
move(chapterId: string, volumeId: string, sortOrder: number): Chapter {
  this.db
    .prepare(
      `UPDATE chapters SET volume_id = ?, sort_order = ?, updated_at = datetime('now') WHERE id = ?`
    )
    .run(volumeId, sortOrder, chapterId)
  return this.get(chapterId)!
}

reorderVolume(volumeId: string, orderedIds: string[]): void {
  const stmt = this.db.prepare(
    `UPDATE chapters SET sort_order = ? WHERE id = ? AND volume_id = ?`
  )
  orderedIds.forEach((id, idx) => stmt.run(idx, id, volumeId))
}
  • Step 4: IPC CHAPTER_MOVE

ipc-channels.ts 追加 CHAPTER_MOVE: 'chapter:move'

chapter.handler.ts

ipcMain.handle(
  IPC.CHAPTER_MOVE,
  (_e, { bookId, chapterId, volumeId, sortOrder }: { bookId: string; chapterId: string; volumeId: string; sortOrder: number }) =>
    wrap(() => registry.getChapterRepo(bookId).move(chapterId, volumeId, sortOrder))
)

preload / electron-api.d.ts 暴露 chapter.move(bookId, chapterId, volumeId, sortOrder)

  • Step 5: EditorLayout 拖拽 UI

章节 .chapter-item 增加 draggableonDragStartchapterIdonDragOver preventDefaultonDrop 计算新 sortOrder 调 IPC。

同卷:根据 drop 目标 index 调用 reorderVolume(新增 IPC chapter:reorder 或在 move 后批量 reorder)。

跨卷:drop 到另一卷 header → move(chapterId, targetVolId, targetLen)

  • Step 6: E2E E2E-P21-DRAG-01

e2e/p21-features.spec.ts:两章 → drag 交换顺序 → 重启 → 顺序保持。

  • Step 7: 验证
npm run test -- tests/main/chapter-move.test.ts
npm run build
npm run test:e2e -- e2e/p21-features.spec.ts
  • Step 8: Commit
git commit -am "feat: add chapter drag reorder within and across volumes"

Task 2: 搜索替换 UIP2.1b

Files:

  • Modify: src/renderer/components/search/SearchModal.tsx

  • Modify: src/renderer/styles/layout.css

  • Modify: public/locales/zh-CN/translation.json, public/locales/en/translation.json

  • Extend: e2e/p21-features.spec.ts

  • Step 1: SearchModal 替换折叠区

search-results 下方增加:

<details className="search-replace-panel" data-testid="search-replace-panel">
  <summary>{t('search.replace')}</summary>
  <input data-testid="search-replace-input" value={replacement} onChange={...} />
  <button data-testid="search-replace-preview" onClick={() => void previewReplace()}>
    {t('search.replacePreview')}
  </button>
  <button data-testid="search-replace-execute" onClick={() => void executeReplace()}>
    {t('search.replaceExecute')}
  </button>
</details>

previewReplace

const result = await ipcCall(() =>
  window.electronAPI.search.replace(bookId, query, replacement, true, { regex, caseSensitive })
)
setReplacePreviews(result.previews)

executeReplacedryRun: falserefreshChapters() + toast。

  • Step 2: i18n 键

search.replace, search.replacePreview, search.replaceExecute, search.replaceConfirm, search.replaceDone

  • Step 3: E2E E2E-P21-REP-01

写入章节唯一词 → 搜索 → 预览 → 执行 → editor 含新词。

  • Step 4: 验证 + Commit
npm run test:e2e -- e2e/p21-features.spec.ts
git commit -am "feat: add global search replace preview and execute UI"

Task 3: 灵感模态 + @跳转 + 引用钉住(P2.1c)

Files:

  • Create: src/renderer/components/inspiration/InspirationModal.tsx

  • Modify: src/renderer/App.tsx

  • Modify: src/renderer/components/editor/TipTapEditor.tsx

  • Modify: src/renderer/extensions/mention.ts

  • Modify: src/renderer/stores/useReferenceStore.ts

  • Modify: src/renderer/components/reference/ReferencePanel.tsx

  • Modify: src/renderer/styles/layout.css

  • Extend: e2e/p21-features.spec.ts

  • Step 1: InspirationModal

对齐 design/ui_pc.html #inspirationModaltitle、content、tags;保存调 inspiration.createdata-testid="inspiration-modal"

语音按钮:

const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition
if (SpeechRecognition) {
  const rec = new SpeechRecognition()
  rec.lang = 'zh-CN'
  rec.onresult = (e) => setContent((c) => c + e.results[0][0].transcript)
  rec.start()
}

不可用时 disabled + title={t('inspiration.voiceUnavailable')}

  • Step 2: App.tsx 改 captureInspiration

captureInspiration 快捷键 → useAppStore.setInspirationModalOpen(true),不再直接 create。

  • Step 3: Mention 点击跳转

TipTapEditor editorProps

handleClick: (view, pos, event) => {
  const el = (event.target as HTMLElement).closest('[data-setting-mention]')
  if (!el) return false
  const id = el.getAttribute('data-id')
  if (id) void useEditStore.getState().switchTarget({ kind: 'setting', id })
  return true
}

确保 applyMention 插入 HTML 含 data-id

  • Step 4: 引用钉住

useReferenceStorepinned: boolean, setPinned

ReferencePanelpinned 时关闭按钮改为取消钉住;setOpen(false)pinned 则忽略。

  • Step 5: E2E E2E-P21-INSP-01

Control+Shift+I → 填内容 → 保存 → inspiration-list 可见。

  • Step 6: Commit
git commit -am "feat: add inspiration modal, mention navigation, reference pin"

Task 4: Schema v3 + 共享类型 + AI 配置(P3.0)

Files:

  • Create: src/main/db/schema-v3.sql

  • Modify: src/main/db/migrate.ts

  • Create: tests/main/migrate-v3.test.ts

  • Modify: src/shared/types.ts

  • Modify: src/shared/ipc-channels.ts

  • Modify: src/main/services/global-settings.ts

  • Step 1: schema-v3.sql

内容同 spec §3.1 ai_sessions / ai_messages / index。

  • Step 2: migrate.ts

CURRENT_VERSION = 3current < 3db.exec(schemaV3) + insert version 3。

  • Step 3: migrate-v3.test.ts

v2 库 migrate 后 SELECT name FROM sqlite_masterai_sessions

  • Step 4: types.ts 扩展
export interface AiConfig {
  backend: AiBackend
  baseUrl: string
  model: string
  apiKey?: string
  timeoutMs: number
  maxTokens: number
}

export const DEFAULT_AI_CONFIG: AiConfig = {
  backend: 'lmstudio',
  baseUrl: process.env.BILIN_AI_BASE_URL ?? 'http://127.0.0.1:1234',
  model: process.env.BILIN_AI_MODEL ?? 'gemma-4-e4b-it',
  timeoutMs: 60_000,
  maxTokens: 2048
}

GlobalSettings 追加 aiConfig, namingIgnoreWords: string[]

  • Step 5: global-settings defaults()

合并 aiConfig: DEFAULT_AI_CONFIG, namingIgnoreWords: []

  • Step 6: 验证 + Commit
npm run test -- tests/main/migrate-v3.test.ts
git commit -am "feat: add schema v3 and AI config types"

Task 5: AiClientServiceP3.0

Files:

  • Create: src/main/services/ai-client.service.ts

  • Create: tests/main/ai-client.test.ts

  • Step 1: 写真实 LM Studio 集成测试(禁止 skip

import { describe, it, expect } from 'vitest'
import { AiClientService } from '../../src/main/services/ai-client.service'
import { DEFAULT_AI_CONFIG } from '../../src/shared/types'

describe('AiClientService', () => {
  const client = new AiClientService(() => DEFAULT_AI_CONFIG)

  it('IT-AI-01: chat returns non-empty content from LM Studio', async () => {
    const content = await client.chat([
      { role: 'user', content: '回复一个字:好' }
    ])
    expect(content.trim().length).toBeGreaterThan(0)
  }, 90_000)
})
  • Step 2: 运行确认 FAIL
npm run test -- tests/main/ai-client.test.ts
  • Step 3: 实现 AiClientService
export interface ChatMessage {
  role: 'system' | 'user' | 'assistant'
  content: string
}

export class AiClientService {
  constructor(private getConfig: () => AiConfig) {}

  private endpoint(): string {
    const { baseUrl } = this.getConfig()
    return `${baseUrl.replace(/\/$/, '')}/v1/chat/completions`
  }

  async chat(messages: ChatMessage[], onChunk?: (delta: string) => void): Promise<string> {
    const config = this.getConfig()
    const controller = new AbortController()
    const timer = setTimeout(() => controller.abort(), config.timeoutMs)
    const res = await fetch(this.endpoint(), {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        ...(config.apiKey ? { Authorization: `Bearer ${config.apiKey}` } : {})
      },
      body: JSON.stringify({
        model: config.model,
        messages,
        max_tokens: config.maxTokens,
        stream: Boolean(onChunk)
      }),
      signal: controller.signal
    })
    clearTimeout(timer)
    if (!res.ok) throw new Error(`AI ${res.status}: ${await res.text()}`)
    if (!onChunk) {
      const json = await res.json()
      return json.choices[0].message.content as string
    }
    // SSE parse: data: {...}\n\n
    let full = ''
    const reader = res.body!.getReader()
    const decoder = new TextDecoder()
    let buf = ''
    while (true) {
      const { done, value } = await reader.read()
      if (done) break
      buf += decoder.decode(value, { stream: true })
      const lines = buf.split('\n')
      buf = lines.pop() ?? ''
      for (const line of lines) {
        if (!line.startsWith('data: ')) continue
        const payload = line.slice(6).trim()
        if (payload === '[DONE]') continue
        const parsed = JSON.parse(payload)
        const delta = parsed.choices?.[0]?.delta?.content ?? ''
        if (delta) {
          full += delta
          onChunk(delta)
        }
      }
    }
    return full
  }
}
  • Step 4: 验证 PASS(需 LM Studio 运行)
npm run test -- tests/main/ai-client.test.ts
  • Step 5: Commit
git commit -am "feat: add AiClientService with LM Studio OpenAI compatibility"

Task 6: AiSessionRepository + IPCP3.1

Files:

  • Create: src/main/db/repositories/ai-session.repo.ts

  • Create: tests/main/ai-session.repo.test.ts

  • Create: src/main/ipc/handlers/ai.handler.ts

  • Modify: src/main/ipc/register.ts

  • Modify: src/main/services/book-registry.ts

  • Modify: src/preload/index.ts, src/shared/electron-api.d.ts

  • Step 1: ai-session.repo 测试 + 实现

CRUDlist, create, update, delete, listMessages, addMessage

context_jsonAiContextBinding JSON 序列化。

  • Step 2: ai.handler.ts

注册 spec §4.1 全部 ai:session* / ai:messageList

ai:chat 骨架(暂同步,Task 7 接流式):

ipcMain.handle(IPC.AI_CHAT, async (event, { bookId, sessionId, content }) => {
  return wrap(async () => {
    const repo = registry.getAiSessionRepo(bookId)
    const settings = globalSettings.get()
    repo.addMessage(sessionId, 'user', content)
    const history = repo.listMessages(sessionId)
    const assistantId = randomUUID()
    const reply = await aiClient.chat(history.map(...))
    repo.addMessage(sessionId, 'assistant', reply, assistantId)
    return { messageId: assistantId }
  })
})
  • Step 3: book-registry getAiSessionRepo

  • Step 4: preload 暴露 window.electronAPI.ai.*

  • Step 5: 验证

npm run test -- tests/main/ai-session.repo.test.ts
  • Step 6: Commit
git commit -am "feat: add AI session repository and IPC handlers"

Task 7: 流式 IPC + NetworkMonitorP3.1

Files:

  • Create: src/main/services/network-monitor.service.ts

  • Modify: src/main/ipc/handlers/ai.handler.ts

  • Modify: src/main/index.ts

  • Modify: src/preload/index.ts

  • Step 1: 改造 ai:chat 为流式

ai:chat 内:

const win = BrowserWindow.fromWebContents(event.sender)
let full = ''
await aiClient.chat(messages, (delta) => {
  full += delta
  win?.webContents.send(IPC.AI_STREAM_CHUNK, { messageId: assistantId, delta, done: false })
})
win?.webContents.send(IPC.AI_STREAM_CHUNK, { messageId: assistantId, delta: '', done: true })
repo.addMessage(sessionId, 'assistant', full, assistantId)

ai:abort:维护 Map<messageId, AbortController>

  • Step 2: preload 订阅
onAiStreamChunk: (cb) => {
  const handler = (_e, payload) => cb(payload)
  ipcRenderer.on(IPC.AI_STREAM_CHUNK, handler)
  return () => ipcRenderer.removeListener(IPC.AI_STREAM_CHUNK, handler)
}
  • Step 3: NetworkMonitor

net.isOnline() + 每 30s 检测;变化时 webContents.send(IPC.AI_NETWORK_STATUS, { online })

  • Step 4: Commit
git commit -am "feat: add AI streaming IPC and network monitor"

Task 8: AiPanel UI + useAiStoreP3.1

Files:

  • Create: src/renderer/stores/useAiStore.ts

  • Create: src/renderer/components/ai/AiPanel.tsx

  • Modify: src/renderer/components/layout/RightPanel.tsx

  • Modify: src/renderer/styles/layout.css

  • Step 1: useAiStore

interface AiStore {
  sessions: AiSession[]
  activeSessionId: string | null
  messages: AiMessage[]
  streamingMessageId: string | null
  streamingBuffer: string
  disabled: boolean
  writingMode: AiWritingMode // default 'chat'
  loadSessions(bookId: string): Promise<void>
  sendMessage(content: string): Promise<void>
  appendStreamChunk(payload: StreamChunk): void
}

挂载时订阅 onAiStreamChunk / onAiNetworkStatus

  • Step 2: AiPanel 结构

对齐 ui_pc.htmlai-session-barai-mode-tabs(非 chat 调 showToast(comingSoon))、context-previewTask 9)、offline-bannerchat-messagesquick-cmdschat-input-area

data-testidai-panel, ai-session-select, ai-chat-input, ai-send, chat-message-assistant.

  • Step 3: RightPanel 替换 AI 占位

panel-content AI Tab 渲染 <AiPanel />

  • Step 4: E2E E2E-P3-CHAT-01

e2e/ai-chat.spec.ts:打开书 → 新建会话 → 输入「你好」→ Enter → chat-message-assistant 非空(timeout 90s)。

  • Step 5: 验证 + Commit
npm run test:e2e -- e2e/ai-chat.spec.ts
git commit -am "feat: add AiPanel with streaming chat UI"

Task 9: 上下文编辑器 + 快捷命令(P3.2)

Files:

  • Create: src/main/services/ai-context-builder.service.ts

  • Create: tests/main/ai-context-builder.test.ts

  • Create: src/renderer/components/ai/ContextEditorModal.tsx

  • Modify: src/main/ipc/handlers/ai.handler.ts

  • Modify: src/renderer/components/ai/AiPanel.tsx

  • Step 1: AiContextBuilder 测试

截断:超长 chapter content → 块 ≤ 2000 字;总量 ≤ 16KB。

  • Step 2: 实现 build(binding, bookId)

返回 { systemPrompt, preview: ContextPreviewItem[] }

  • Step 3: ContextEditorModal

四 Tab 勾选列表(chapters/outlines/settings/inspirations);保存 → ai:sessionUpdate patch context_json;「更新上下文」→ ai:buildContext

  • Step 4: 快捷命令

src/renderer/lib/ai-slash-commands.ts

export function resolveSlashCommand(cmd: string, t: TFunction): string | null {
  const map: Record<string, string> = {
    '/续写': t('ai.slash.continue'),
    '/扩写': t('ai.slash.expand'),
    // ...
  }
  return map[cmd.split(/\s/)[0]] ?? null
}

发送前:若匹配 slash,用模板替换实际 prompt。

  • Step 5: E2E E2E-P3-CTX-01 / E2E-P3-CMD-01

  • Step 6: Commit

git commit -am "feat: add AI context editor and slash commands"

Task 10: AI 设置页(P3.0 续)

Files:

  • Create: src/renderer/components/settings/AiSettingsPage.tsx

  • Modify: src/renderer/components/settings/SettingsPage.tsx

  • Modify: src/main/ipc/handlers/ai.handler.tsai:testConnection

  • Modify: public/locales/*/translation.json

  • Step 1: AiSettingsPage

字段:backend select、baseUrl、model、apiKey(非 lmstudio 显示)、timeout。

云端 backend 显示 §6.9 许可链接。

「测试连接」→ ai:testConnection → 最小 chat → toast。

  • Step 2: Settings 导航增加 AI Tab

  • Step 3: Commit

git commit -am "feat: add AI settings page with connection test"

Task 11: 离线降级 + 命名检查 + AI 快照(P3.3)

Files:

  • Create: src/renderer/components/ai/NamingCheckModal.tsx

  • Create: src/main/services/naming-check.service.ts

  • Create: tests/main/naming-parse.test.ts

  • Modify: src/renderer/components/layout/RightPanel.tsx(知识库 Tab 按钮)

  • Modify: src/renderer/lib/editor-commands.ts

  • Modify: src/renderer/components/ai/AiPanel.tsx

  • Step 1: 离线规则

useAiStoredisabled = aiConfig.backend !== 'lmstudio' && !networkOnline

AiPanel 显示 #offlineBanner;网络恢复 toast。

  • Step 2: naming-check.service

buildPrompt(settings, chaptersPlain) → 调 AiClientService.chatparseNamingJson(response)

解析器单测(不 mock HTTP):tests/main/naming-parse.test.ts

  • Step 3: NamingCheckModal

列表 + 忽略/替换;替换调 search.replace

  • Step 4: AI 快照

insertToEditor(text)

await ipcCall(() => window.electronAPI.snapshot.create(bookId, chapterId, 'ai', t('snapshot.ai'), currentHtml))
// then insert text at cursor
  • Step 5: E2E E2E-P3-NAME-01

种子:设定「林远」+ 正文「林悦」→ 命名检查 → 至少一行结果。

  • Step 6: Commit
git commit -am "feat: add offline fallback, naming check, and AI snapshots"

Task 12: i18n + CSS + 全量测试 + v0.3.0P3.4

Files:

  • Modify: public/locales/zh-CN/translation.json, public/locales/en/translation.json

  • Modify: src/renderer/styles/layout.css

  • Modify: package.json, README.md

  • Create: e2e/ai-chat.spec.ts(补全 CTX/CMD/OFF

  • Extend: e2e/p21-features.spec.ts

  • Step 1: i18n 全量键

ai.*, naming.*, inspiration.voice*, search.replace*, snapshot.ai

  • Step 2: layout.css

.ai-session-bar, .ai-mode-tabs, .chat-msg.user/.ai, .quick-cmd, .offline-banner, .naming-modal, .search-replace-panel

  • Step 3: 全量测试
npm run test
npm run build
npm run test:e2e

Expected: 全部 PASSLM Studio 必须在 127.0.0.1:1234 运行

  • Step 4: package.json version 0.3.0 + README

  • Step 5: Commit + tag

git commit -am "chore: prepare v0.3.0 release"
git tag v0.3.0

Plan Self-Review

Spec 章节 对应用 Task
§5.1 章节拖拽 Task 1
§5.2 搜索替换 Task 2
§5.3 灵感模态+语音 Task 3
§5.4 @跳转 Task 3
§5.5 引用钉住 Task 3
§3 Schema v3 Task 4
§2.2 AiClientService Task 5
§4 AI IPC Task 67
§6.1 AiPanel Task 8
§6.2 上下文+快捷命令 Task 9
§6.7 AI 设置 Task 10
§6.4 离线 Task 11
§6.5 命名检查 Task 11
§6.6 AI 快照 Task 11
§7 测试 Task 112
§10 DoD Task 12
检查项 结果
Placeholder 扫描 无 TBD;写作模式 Tab 在 Task 8 明确 comingSoon
测试 Mock 全文禁止 skipai-client.test 真实 HTTP
类型一致 AiContextBinding / context_json / IPC 命名一致

执行方式

Plan 已保存至 docs/superpowers/plans/2026-07-07-bilin-p21-p3.md

两种执行选项:

  1. Subagent-Driven(推荐) — 每个 Task 派发独立 subagent,任务间 review,迭代快
  2. Inline Execution — 在本会话按 Task 顺序直接实现,批次间设检查点

你选哪种?