
CLI 前端架构评审决策文档【免费下载链接】cannbot-skillsCANNBot 是面向 CANN 开发的用于提升开发效率的系列智能体本仓库为其提供可复用的 Skills 模块。项目地址: https://gitcode.com/cann/cannbot-skills评审日期: 2026-06-14基于: design.md v1.0 test-plan.md v1.0当前版本: v0.31 (src/lib/version.ts)问题逐条分析问题1: Commander.js 与 Ink 的 stdin 冲突 [P0]设计文档现状: design.md L810-812runTui()直接调用ink.render()没有任何 stdin 接管边界处理。Commander 解析 argv 期间 Node.js 进程 stdin 处于 cooked mode而 Ink 需要 raw mode 来捕获键盘事件。严重程度: P0 — 不处理会导致 TUI 模式下键盘事件完全失效或抛出 EAGAIN 错误。PoC 验证结果: Ink 版本确认为v7.0.6不是 v5React 19.2.7需要 ESMpackage.jsontype: module运行工具用 tsx。Ink v7 render() 返回实例方法有变化没有 lastFrame/frames/output新增 waitUntilRenderFlush。stdin 切换方案resume setRawMode保持不变。决策方案: Commander 完成 argv 解析后stdin 处于 paused 状态。需要在runTui()中显式处理 stdin 切换。Ink v7 的 render() 实例 API 如下// Ink v7 render() 返回实例方法PoC 验证 // - rerender: function — 重新渲染组件 // - unmount: function — 卸载组件 // - waitUntilExit: function — 等待退出 // - waitUntilRenderFlush: function — 新增等待渲染刷新完成 // - cleanup: function — 清理资源 // - clear: function — 清除终端输出 // 注意Ink v7 原生 render() 没有 lastFrame()、frames、output // 测试时需用 ink-testing-library 的 render() 才有 lastFrame()// src/cli/tui/App.tsx import { render } from ink; import React from react; export function runTui(config: CliConfig): Promisevoid { // Commander 解析完 argv 后 stdin 处于 paused 状态 // 需要先 resume再让 Ink 接管 raw mode if (process.stdin.isTTY) { process.stdin.resume(); process.stdin.setRawMode(true); } const { waitUntilExit } render(App config{config} /, { exitOnCtrlC: false, // 我们自己处理 CtrlC patchConsole: false, // 不 patch console.log避免和命令模式冲突 }); return waitUntilExit().then(() { // Ink 退出后恢复 stdin if (process.stdin.isTTY) { process.stdin.setRawMode(false); process.stdin.pause(); } }); }关键点process.stdin.resume()— Commander 解析 argv 后 stdin 处于 paused必须先 resumeprocess.stdin.setRawMode(true)— 让 Ink 能捕获键盘事件exitOnCtrlC: false— App 中 useInput 自己处理 q/CtrlC避免 Ink 和 App 双重处理退出时恢复 stdin — 防止进程退出后终端状态异常patchConsole: false— 命令模式和 TUI 模式可能交替使用 console不 patch 更安全Ink v7 需要 ESM — package.json 必须type: module是否修改文档: 是修改 design.md L810-812 的runTui()实现并标注 Ink v7 ESM 要求。问题2: Ink 第三方组件库成熟度 [P0]设计文档现状: design.md 推荐了 ink-table, ink-select, ink-text-input, ink-spinner, ink-box, ink-big-text 等第三方组件。这些库大多针对 Ink v3/v4 开发2-3 年未更新与 Ink v7 (React 19) 的兼容性存疑。PoC 验证 Ink 实际版本为 v7.0.6。严重程度: P0 — 如果第三方库不兼容TUI 核心交互选择、输入、表格将无法工作。PoC 验证结果: Ink 版本确认为v7.0.6不是 v5第三方库兼容性问题更严重。PoC 验证了 Ink v7 基础渲染正常表格布局、颜色输出、中文字符、borderStyle。评估结论:组件库Ink v7 兼容性替代方案ink-spinner不兼容 v7自写 3 行代码即可ink-text-input不兼容 v7自写30 行ink-select-input不兼容 v7自写 SelectInput 50 行ink-table不兼容 v7自写 DataTabledesign.md 已有设计ink-box不兼容 v7Ink 内置Box已够用ink-big-text不兼容 v7用 chalk.bold 替代不需要大字决策方案:全部自实现不依赖任何第三方 Ink 组件库。理由design.md 已经设计了 DataTable、TabBar、ConfirmDialog、AsciiBar、TreeView 等组件它们不依赖第三方库缺少的 Spinner 和 TextInput 极简自写成本低Spinner: 一个Text useState 轮换字符序列10 行代码TextInput: 一个Box useInput 捕获字符 useState 保存输入值30 行代码自写组件可以完全控制行为和样式适配 Ink v7 无风险避免依赖链问题Ink v7 的第三方库生态确实不成熟// src/cli/tui/components/Spinner.tsx — 自写示例 const SPINNER_FRAMES [⠋,⠙,⠹,⠸,⠼,⠴,⠦,⠧,⠇,⠏]; function Spinner({ label }: { label?: string }) { const [frame, setFrame] useState(0); useEffect(() { const timer setInterval(() setFrame(f (f 1) % SPINNER_FRAMES.length), 80); return () clearInterval(timer); }, []); return Text{SPINNER_FRAMES[frame]} {label ?? Loading...}/Text; } // src/cli/tui/components/TextInput.tsx — 自写示例 function TextInput({ value, onChange, placeholder, focus true }: TextInputProps) { useInput((input, key) { if (!focus) return; if (key.backspace || key.delete) onChange(value.slice(0, -1)); else if (key.return) { /* 提交 */ } else if (!key.ctrl !key.meta) onChange(value input); }, { isActive: focus }); return ( Box Text colorgray{placeholder ?? }/Text Text{value}/Text Text colorgray█/Text /Box ); }是否修改文档: 是删除 design.md 中对第三方组件库的引用新增 Spinner.tsx 和 TextInput.tsx 的自写设计。问题3: ink-testing-library 兼容性 [P0] —决策修正设计文档现状: test-plan.md 大量依赖ink-testing-library(v4) 做 TUI 组件渲染测试4.1-4.9 全部使用renderTuihelper。原评审认为该库不兼容 Ink v7决策删除依赖。严重程度: P0 — TUI 组件测试20% 测试比例的核心工具。PoC 验证结果:ink-testing-library完全兼容 Ink v7PoC 3 验证ink-testing-library 可以正常 importink-testing-library 的 render() 返回的实例有 lastFrame() 方法lastFrame() 能正确返回渲染后的文本内容关键发现: Ink v7 原生 render() 没有 lastFrame()PoC 2 验证但 ink-testing-library 的 render() 提供了 lastFrame()。两者 API 不同Ink v7 原生 render() 返回: ink-testing-library render() 返回: - rerender - rerender - unmount - unmount - waitUntilExit - waitUntilExit - waitUntilRenderFlush (新增) - lastFrame ← 关键差异 - cleanup - frames - clear - stdin (可模拟按键) ❌ 没有 lastFrame/frames/output ✅ 有 lastFrame stdin修正决策:保留 ink-testing-library 依赖采用双 render 策略测试代码: 使用ink-testing-library的 render() — 有 lastFrame() stdin 模拟生产代码: 使用 Ink 原生 render() — 有 waitUntilExit/waitUntilRenderFlush/clear// tests/helpers/render-tui.tsx — 使用 ink-testing-libraryPoC 验证兼容 import React from react; import { render } from ink-testing-library; export function renderTui(element: React.ReactElement) { const instance render(element); return { ...instance, // ink-testing-library 的 lastFrame() 返回 ANSI 含色码的输出 lastFrame: () instance.lastFrame(), // 获取纯文本去除 ANSI 色码 getPlainText: () instance.lastFrame()?.replace(/\x1b\[[0-9;]*m/g, ) ?? , // stdin 可模拟按键ink-testing-library 提供 pressKey: (key: string) instance.stdin.write(key), pressEnter: () instance.stdin.write(\r), pressEscape: () instance.stdin.write(\x1b), pressUp: () instance.stdin.write(\x1b[A), pressDown: () instance.stdin.write(\x1b[B), pressCtrlC: () instance.stdin.write(\x03), }; }// src/cli/tui/App.tsx — 生产代码使用 Ink 原生 render() import { render } from ink; export function runTui(config: CliConfig): Promisevoid { // ... stdin 处理 ... const { waitUntilExit } render(App config{config} /, { exitOnCtrlC: false }); return waitUntilExit(); }层2: handler 逻辑独立测试仍保留 — 对复杂 useInput 组件提取 handler 逻辑不依赖 stdin 模拟// tests/cli/components/ConfirmDialog.test.tsx — handler 逻辑测试 import { describe, it, expect, vi } from vitest; describe(ConfirmDialog logic, () { it(calls onConfirm when input is y, () { const onConfirm vi.fn(); const handler createInputHandler({ onConfirm, onCancel: vi.fn() }); handler(y, {}); expect(onConfirm).toHaveBeenCalled(); }); });是否修改文档: 是修改 test-plan.md 的 4.1 render-tui.tsx helper 改用 ink-testing-library保留所有组件测试4.2-4.9的现有方案。修改 design.md 中 Ink 版本标注为 v7。问题4: 类型定义重复 [P1]设计文档现状: design.md 在src/cli/types.ts定义了 20 个接口SessionListItem、TurnItem、BridgeItem 等而src/lib/shared/types.ts已有 SessionListItem字段不同shared 有 id/createdAt/firstQuery/turnCount/modelNameCLI 有 sessionId/startTime/endTime/totalTokens 等。严重程度: P1 — 字段名和结构不一致会导致维护困难但 CLI 类型来自 API response 格式和 shared 内部类型本就不同。决策方案: CLI 类型源自 API response JSON 结构和 shared 的内部数据库映射类型snake_case → camelCase 转换前确实不同。不应强行合并而是通过明确命名区分// src/cli/types.ts — CLI 视图类型来自 API response // 前缀 Api 命名与 shared 内部类型明确区分 // 不复用 shared 的 SessionListItem字段完全不同 // shared: { id, createdAt, firstQuery, turnCount, modelName } // CLI API: { sessionId, taskId, query, startTime, endTime, totalTokens, ... } // 保留 CLI 专用类型但增加注释说明来源 /** 来自 /api/observe/data response — 与 shared/SessionListItem 不同 */ export interface ApiSessionListItem { ... } /** 来自 /api/observe/session/turns response */ export interface ApiTurnItem { ... }对于确实重叠的部分如 TokenUsage 结构从 shared 导入// src/cli/types.ts import type { TokenUsage } from /lib/shared/types; // CLI 专用类型中复用共享的子结构 export interface ApiTurnItem { // ... CLI 专用字段 tokenUsage: TokenUsage; // 复用 shared 的 TokenUsage }是否修改文档: 是修改 design.md 的类型定义章节给 CLI 类型加Api前缀标注哪些复用 shared。问题5: InsightClient 每次渲染都重新实例化 [P1]设计文档现状: design.md L816 App.tsx 中const client new InsightClient(config.server, { timeout: config.timeout })直接在组件函数体内创建实例每次 re-render 都会创建新 client。严重程度: P1 — 导致 useApi deps[client]每次都是新引用缓存 key 变化缓存失效。决策方案: 使用useMemo或useRef保持 client 单例// src/cli/tui/App.tsx — 修复方案 function App({ config }: TuiAppProps) { // useMemo: config 不变时 client 不变 const client useMemo( () new InsightClient(config.server, { timeout: config.timeout }), [config.server, config.timeout] // 只依赖实际变化的字段 ); // ... 其他逻辑不变 }选择useMemo而非useRef的理由config 变化时应该重建 client比如用户修改了 server URLuseMemo 自动处理这种场景。是否修改文档: 是修改 design.md L816 的 App.tsx。问题6: useApi 缓存 key 性能问题 [P1]设计文档现状: design.md L1277const cacheKey JSON.stringify(deps)每次 render 都执行。deps 是数组每次 render 可能是新引用即使内容相同导致 JSON.stringify 白跑。严重程度: P1 — 每次 render 都做 JSON.stringify 是不必要的开销且如果 deps 是新引用但内容相同会产生相同 key这恰好是正确行为但 stringify 本身浪费 CPU。决策方案: 使用稳定化 deps 只在 deps 变化时计算 key// src/cli/hooks/useApi.ts — 优化方案 export function useApiT( fetcher: () PromiseT, deps: unknown[], ttl: number DEFAULT_TTL, ): { data: T | null; loading: boolean; error: Error | null; refresh: () void } { const [data, setData] useStateT | null(null); const [loading, setLoading] useState(true); const [error, setError] useStateError | null(null); // 只在 deps 实际变化时才重新计算 cacheKey const cacheKey useMemo(() JSON.stringify(deps), [deps]); // ... fetchData 用 useCallback [fetcher, cacheKey] 依赖 // 与原设计相同但 cacheKey 计算被 memo 住了 }更进一步如果 deps 中包含函数如() client.listSessions({ pageSize: 100 })每次 render 函数引用都不同会导致 cacheKey 不稳定。解决方案是将 fetcher 也稳定化// 调用方应该稳定化 fetcher const fetchSessions useCallback( () client.listSessions({ pageSize: 100 }), [client] // client 已通过问题5 的 useMemo 稳定化 ); const { data } useApi(fetchSessions, [client]);是否修改文档: 是修改 design.md useApi 实现和调用示例。问题7: 虚拟滚动 vs 分页 [P1]设计文档现状: design.md 的 DataTable 实现了虚拟滚动只渲染 visibleRows 行但 Ink 是全量重绘框架——每次状态变化都重绘整个输出虚拟滚动只减少了 React 组件数量但不减少终端渲染面积。1000 条数据时终端仍会卡顿。严重程度: P1 — 虚拟滚动在 Ink 环境下收益有限只减少 React reconcile 成本不减少终端 I/O 成本。决策方案: 改为分页加载 服务端分页// src/cli/tui/screens/SessionList.tsx — 分页方案 function SessionList({ client, onSelect }: SessionListProps) { const [page, setPage] useState(1); const PAGE_SIZE 20; const { data, loading } useApi( useCallback(() client.listSessions({ page, pageSize: PAGE_SIZE }), [client, page]), [client, page] ); useKeyboard({ onNavigateUp: table.selectUp, onNavigateDown: table.selectDown, custom: { n: () setPage(p p 1), // Next page p: () setPage(p Math.max(1, p - 1)), // Previous page }, }); // DataTable 不再需要虚拟滚动每页最多 20 行 return ( Box flexDirectioncolumn DataTable columns{SESSION_COLUMNS} data{data?.items ?? []} selectedIndex{table.state.selectedIndex} / Text colorgrayPage {page}/{Math.ceil((data?.total ?? 0) / PAGE_SIZE)} │ n: Next │ p: Prev │ Total: {data?.total ?? 0}/Text /Box ); }对于 Turn 列表等场景数据量可能很大同样采用分页// API 调用时传递 page 参数 client.getTurns(taskId, { page: 1, pageSize: 50 })如果后端 API 不支持 page 参数则在前端做分页切割一次性加载全部数据前端 slice 分页展示但限制单次加载上限如最多 500 条。是否修改文档: 是修改 design.md 的 DataTable 设计移除虚拟滚动修改 SessionList 和 TurnsTab 的数据加载方式改为分页。问题8: E2E TUI 测试在 CI 中不可靠 [P1]设计文档现状: test-plan.md 的 E2E 测试5.2-5.4模拟完整用户流程但实际上是 API 级别的 E2EInsightClient MockServer并不涉及真实 PTY/TUI 渲染。严重程度: P1 — 原设计的 E2E 实际上是 API 集成测试不涉及 PTY所以 CI 可靠性问题不存在于当前设计中。但文档声称是 E2E 测试名称误导。决策方案: 当前 test-plan.md 的 E2E 测试实际上是API 流程集成测试不需要 PTY。调整分类和命名将现有 E2E 测试5.2-5.4归类为API 流程集成测试移到 integration 目录TUI 交互测试降级为组件级交互测试mock useInput不做真实 PTY E2E如果未来需要真实 TUI E2E使用node-ptychild_process.spawn在本地手动执行不进 CI// tests/cli/integration/api-flow.test.ts — 原 E2E 重命名 describe(API flow: session list → detail → turns, () { // ... 原内容不变只是重命名和移目录 }); // 不再在 CI 中运行真实 TUI E2E // 本地手动测试脚本放在 scripts/test-tui-manual.sh是否修改文档: 是修改 test-plan.md 将 E2E 测试改为 API 流程集成测试删除 TUI E2E 章节或标注为手动测试。问题9: GitHub Actions CI 不适用 [P1]设计文档现状: test-plan.md L2830-2875 配置了.github/workflows/cli-test.yml使用 GitHub Actions。项目实际托管在 GitCode 上。严重程度: P1 — CI 配置完全无法使用。决策方案: GitCode 使用 GitLab-compatible CI.gitlab-ci.yml改为适配 GitCode 的 CI 配置# .gitlab-ci.yml (GitCode CI) stages: - test cli-test: stage: test image: node:22 only: changes: - src/cli/**/* - tests/cli/**/* script: - npm ci - npx prisma generate - npm run test:cli:unit - npm run test:cli:components - npm run test:cli:integration - npm run test:coverage artifacts: paths: - coverage/同时保留.github/workflows/cli-test.yml作为备用如果有人 fork 到 GitHub但主 CI 使用 GitCode 配置。是否修改文档: 是修改 test-plan.md 的 CI 配置改为 GitCode/GitLab-compatible 格式。问题10: 版本号硬编码 [P2]设计文档现状: design.md L442 Commander.version(0.18)和 StatusBarText v0.18/Text硬编码了版本号。当前实际版本是 v0.31。严重程度: P2 — 每次版本更新都要手动改两处容易遗漏。决策方案: 从/lib/version.ts导入版本号// src/cli/index.ts import { VERSION } from /lib/version; program.version(VERSION); // src/cli/tui/components/StatusBar.tsx import { VERSION_DISPLAY } from /lib/version; Text {VERSION_DISPLAY}/Text是否修改文档: 是修改 design.md 的两处硬编码。问题11: cbin 短别名未配置 [P2]设计文档现状: design.md L441.alias(cbin)在 Commander 中配置了别名但 package.json 的 bin 字段没有配置。严重程度: P2 — npm install -g 后只能用cannbot-insight命令无法用cbin。决策方案: 在 package.json 中配置双入口 bin{ bin: { cannbot-insight: ./src/cli/index.ts, cbin: ./src/cli/index.ts } }实际执行时需要编译为 JS 或使用 tsx 运行。建议在 bin 入口脚本中使用#!/usr/bin/env node 预编译路径或用tsx直接运行{ bin: { cannbot-insight: ./dist/cli/index.js, cbin: ./dist/cli/index.js } }是否修改文档: 是在 design.md 中新增 package.json bin 配置说明。问题12: 缺少认证机制 [P2]设计文档现状: design.md 的 ClientConfig 只有 baseUrl/timeout/retries/retryDelay没有 authToken。远程访问时需要认证。严重程度: P2 — 当前项目是本地工具localhost:21025远程访问场景非核心需求。但预留接口成本极低。决策方案: 在 ClientConfig 中预留 authToken但不实现复杂认证流程// src/cli/client.ts export interface ClientConfig { baseUrl: string; timeout: number; retries: number; retryDelay: number; authToken?: string; // 预留Bearer token } // request 方法中如果有 authToken自动添加 header private async requestT(...): PromiseT { const init: RequestInit { method, headers: { Content-Type: application/json, ...(this.config.authToken ? { Authorization: Bearer ${this.config.authToken} } : {}), }, signal: AbortSignal.timeout(this.config.timeout), }; // ... } // 配置来源环境变量 CANNBOT_TOKEN 或配置文件 export interface CliConfig { server: string; timeout: number; theme: dark | light | auto; keybindings: Recordstring, string; authToken?: string; // 预留 }是否修改文档: 是修改 design.md 的 ClientConfig 和 CliConfig。问题13: 缺少性能基准 [P2]设计文档现状: design.md 和 test-plan.md 都没有提到性能基准测试。Ink TUI 在大数据量下的渲染性能、API Client 的请求延迟等没有量化目标。严重程度: P2 — 性能基准对 CLI 工具很重要但可以在开发中逐步建立。决策方案: 新增简易性能基准测试不引入额外框架// tests/cli/bench/render-perf.test.ts describe(TUI render performance baseline, () { it(DataTable renders 20 rows within 100ms, () { const data createMockSessionList(20); const start performance.now(); const output renderSnapshot(DataTable columns{columns} data{data} selectedIndex{0} /); const elapsed performance.now() - start; expect(elapsed).toBeLessThan(100); }); it(API Client listSessions completes within 2s with mock server, async () { const start performance.now(); await client.listSessions({ pageSize: 20 }); const elapsed performance.now() - start; expect(elapsed).toBeLessThan(2000); }); });性能目标指标目标测试方式DataTable 20 行渲染 100msrenderSnapshot 计时DataTable 100 行渲染 300msrenderSnapshot 计时API 单次请求mock 100msmock server 计时useApi 首次加载→渲染 200msrender waitFor 计时命令模式 sessions 输出 500msparseAsync console 计时是否修改文档: 是在 test-plan.md 中新增性能基准章节。问题14: 中文宽度对齐问题 [P1]PoC 验证结果: PoC 4 验证了表格渲染时中文字符宽度对齐有问题。中文字符在终端中占 2 列宽CJK 双宽字符但 Ink 的Text width{N}按 N 个字符位计算导致含中文的列错位。严重程度: P1 — CLI 前端需要显示中文内容query、contentSummary 等列错位影响可读性。决策方案: 采用三层策略层1: 关键表格列使用英文标签— 表头和固定列用英文避免宽度问题// 列定义中 label 用英文宽度可控 const SESSION_COLUMNS [ { key: #, label: #, width: 3 }, { key: startTime, label: Date, width: 14 }, // 英文标签 { key: query, label: Query, width: 30 }, // 内容可能含中文需要截断 ];层2: 使用 string-width 库— 对含中文的动态内容计算实际显示宽度// src/cli/utils/format.ts — 新增 import stringWidth from string-width; export function padEndVisual(str: string, width: number): string { const visualWidth stringWidth(str); const padding width - visualWidth; if (padding 0) return str.substring(0, width); // 简截断中文时仍可能不精确 return str .repeat(padding); } export function truncateVisual(str: string, maxVisualWidth: number): string { if (stringWidth(str) maxVisualWidth) return str; // 逐字符截断直到视觉宽度不超过 maxVisualWidth let result ; let currentWidth 0; for (const char of str) { const charWidth stringWidth(char); if (currentWidth charWidth maxVisualWidth - 1) { return result …; } result char; currentWidth charWidth; } return result; }层3: 使用 cli-truncate 处理截断— 替代手写截断逻辑import cliTruncate from cli-truncate; export function truncateVisual(str: string, maxWidth: number): string { return cliTruncate(str, maxWidth, { trimMidspace: false }); }新增依赖:string-widthcli-truncate都是 Sindre Sorhus 维护的轻量 ESM 库是否修改文档: 是修改 design.md 的格式化工具新增 padEndVisual/truncateVisual、表格渲染工具使用视觉宽度新增中文宽度处理章节。最终决策表#问题决策修改文件优先级1Commander 与 Ink stdin 冲突runTui() 中显式 resume stdin setRawMode退出时恢复Ink v7 render 配置 exitOnCtrlCfalse需要 ESM tsxdesign.md §3.1 App.tsxP02Ink 第三方组件库成熟度全部自实现Spinner 10行、TextInput 30行不依赖 ink-table/ink-select/ink-spinner 等第三方库Ink v7 无兼容库design.md 新增 §3.10 Spinner、§3.11 TextInput删除第三方库引用P03ink-testing-library 兼容性修正保留 ink-testing-libraryPoC 验证完全兼容 Ink v7有 lastFrame()stdin测试用 ink-testing-library生产用 Ink 原生 render()test-plan.md §4.1 render-tui.tsx改用 ink-testing-library、§8.1 依赖包P04类型定义重复CLI 类型加 Api 前缀与 shared 区分TokenUsage 等子结构从 shared 导入复用注释标注来源design.md §1.1.3 Response 类型定义P15InsightClient 每次渲染重新实例化改为 useMemo 创建 client依赖 [config.server, config.timeout]design.md §3.1 App.tsx L816P16useApi 缓存 key 性能问题cacheKey 用 useMemo 包裹fetcher 用 useCallback 稳定化调用示例更新design.md §4.3 useApi.tsP17虚拟滚动 vs 分页改为服务端分页pageSize20 前端翻页n/p 键DataTable 移除虚拟滚动逻辑design.md §3.4 DataTable、§5.1 SessionList、§5.4 TurnsTab、§8.1 性能P18E2E TUI 测试在 CI 中不可靠将 E2E 测试重分类为 API 流程集成测试TUI 交互测试改为组件级ink-testing-library renderstdin真实 TUI E2E 仅本地手动test-plan.md §5 E2E 章节 → integration 目录P19GitHub Actions CI 不适用改为 GitCode/GitLab-compatible CI.gitlab-ci.yml保留 GitHub Actions 作为备用test-plan.md §8.5 CI/CD 集成P110版本号硬编码从 /lib/version.ts 导入 VERSION / VERSION_DISPLAYdesign.md §1.3 index.ts、§3.2 StatusBar.tsxP211cbin 短别名未配置package.json bin 字段配置 cannbot-insight cbin 双入口design.md 新增 §1.9 package.json bin 配置P212缺少认证机制ClientConfig 和 CliConfig 预留 authToken 字段request 方法中自动添加 Authorization header支持 CANNBOT_TOKEN 环境变量design.md §1.1 ClientConfig、§1.5 CliConfigP213缺少性能基准新增简易性能基准测试ink-testing-library lastFrame 计时、mock server 计时定义 5 个性能目标test-plan.md 新增 §9 性能基准P214中文宽度对齐问题使用 string-width 库计算显示宽度关键表格列用英文标签cli-truncate 处理截断design.md 新增 §8.5 中文宽度处理、§1.7 表格渲染工具、§1.6 格式化工具P1实施优先级建议P0 立即执行开发前必须完成: 问题1、2、3 — stdin 切换含 ESMtsx、组件自实现、测试方案保留 ink-testing-libraryP1 开发中调整第一个迭代内完成: 问题4-9、14 — 类型命名、client/useApi 修复、分页、测试重分类、CI、中文宽度对齐P2 小改进后续迭代完成: 问题10-13 — 版本号、bin 配置、authToken、性能基准【免费下载链接】cannbot-skillsCANNBot 是面向 CANN 开发的用于提升开发效率的系列智能体本仓库为其提供可复用的 Skills 模块。项目地址: https://gitcode.com/cann/cannbot-skills创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考