中文
English version see README.en.md
每天自动汇聚 GitHub 上「分配给我 / 与我相关」的任务到本地跨平台桌面应用(Windows / macOS / Linux)看板,状态随 AI 执行自动流转,并支持记录可恢复的中断会话(session id)。
目前处于 开发者预览 阶段,正在快速迭代。未来将出现破坏兼容性的变更。
最终形态(v0.3):已落地为本地 Tauri 桌面应用(
app/目录),数据存本地 SQLite,不创建 GitHub Issue / Project,不写回 GitHub。此前 PRD 讨论的「GitHub Projects v2 看板」方案因组织限制与个人偏好已放弃,演进记录见PRD.md。
跨平台 Tauri 桌面应用,前端 React、后端 Rust(rusqlite 本地数据库)。macOS 上为菜单栏常驻应用(系统托盘),Windows / Linux 亦以托盘图标常驻。
cd app
npm install # 首次安装前端依赖
npm run tauri dev # 开发模式(前端热更新)
npm run tauri build # 产出当前平台的 release 安装包产物位置(按当前平台):app/src-tauri/target/release/bundle/{macos,debian,rpm,nsis}/TaskBoard*
macOS 打包未配置 Apple 开发者签名。首次打开若被 Gatekeeper 拦截:右键「打开」,或在终端执行
xattr -cr "/path/to/TaskBoard.app"后双击。
发布 Release 时由 GitHub Actions 自动构建多平台安装包。发布流程、签名前提与 runner 配置详见 docs/design-and-release.md。
| 平台 | 架构 | 格式 | 状态 |
|---|---|---|---|
| macOS | ARM (Apple Silicon) | .dmg / .app | ✅ 支持 |
| macOS | x64 (Intel) | .dmg / .app | ✅ 支持 |
| Windows | x64 | .exe (NSIS) | ✅ 支持 |
| Windows | ARM64 | .exe (NSIS) | ✅ 支持 |
| Linux (Debian/Ubuntu) | amd64 | .deb | ✅ 支持 |
| Linux (通用) | x86_64 | .AppImage | ✅ 支持 |
⚠️ 更新提醒:v0.3.24 及以下版本因仓库迁移问题,应用内「检查更新」无法获取 Release 信息,不能自动更新。请到 GitHub Releases 下载最新版安装包(或查看应用内「关于」页提示的下载链接)。
| 能力 | 操作 |
|---|---|
| 菜单栏 | 单击切换看板窗口;右键菜单含「显示看板 / 立即同步 / 退出」 |
| 定时更新 | 设置里调「定时同步间隔」(5–240 分钟),应用常驻时自动按间隔拉取 |
| 手动更新 | 主界面右上「立即同步」按钮 |
| 四态看板 | 待处理 / 处理中 / 已处理 / 已完成;点卡片在右栏切换状态 |
| 远程状态联动 | GitHub 已关闭的 issue 自动归入「已完成」(以远程真实状态为准,覆盖本地手动态);仍打开但不再与你相关的任务自动移出看板 |
| 归属筛选 | 顶部下拉按 分配给我 / 无人认领 / 分配给他人 过滤 |
| 搜索 / 仓库筛选 | 顶部搜索框按 仓库名 / 编号 / 标题 实时过滤;仓库下拉按仓库隔离;右侧「重置」一键清除所有筛选 |
| 中断会话 | 选中卡片 → 输入 session id + 选 agent(claude-code / workbuddy / doubao / opencode / codex / zcode / gemini-cli / cursor / aider / qwen-code 等)→ 记录;可复制、可清空 |
| 交接任务 | 选中卡片 → 「交接任务」区块录入详情并保存(后续可由接入的 agent 在识别「生成交接任务」类意图时自动写入) |
| 同步日志 | 顶栏「同步日志」按钮 → 查看最近同步历史(时间、触发方式、耗时、状态、新增/更新/移除数量、错误信息);支持手动清理过期日志,超过 7 天的日志自动清理 |
| 本地数据 | 见下方「本地数据路径」 |
session id 与任务状态只存本地 SQLite,绝不写回 GitHub。 不创建 Issue、不 创建 Project、不改 Issue 标题 / label / 评论。
数据库文件默认位置(由 dirs::data_dir() + com.shawnliu.taskboard 组合):
| 平台 | 默认路径 |
|---|---|
| macOS | ~/Library/Application Support/com.shawnliu.taskboard/taskboard.db |
| Windows | %APPDATA%\com.shawnliu.taskboard\taskboard.db(即 C:\Users\<user>\AppData\Roaming\com.shawnliu.taskboard\taskboard.db) |
| Linux | $XDG_CONFIG_HOME/com.shawnliu.taskboard/taskboard.db(缺省 ~/.config/com.shawnliu.taskboard/taskboard.db) |
可通过环境变量 TASKBOARD_DB 覆盖为任意路径。
应用支持简体中文 / English 双语界面:设置页可切换「跟随系统 / 简体中文 / English」,选择持久化在本地;翻译文件位于 app/src/i18n/locales/(zh-CN.json / en-US.json)。
贡献翻译:复制 en-US.json 为新语言文件(如 ja-JP.json),翻译 value(保留 {placeholder} 占位符原样),然后在 app/src/i18n/index.tsx 的 DICTS 中注册即可。提交前运行 cd app && npm run i18n:check 校验两份语言文件的 key 集合与占位符一致;CI(.github/workflows/i18n-check.yml)会在 PR 时自动执行同样校验。
纯本地、与 GitHub 解耦的核心设计(多源拉取去重、归属三分、四态维护、closed 权威覆盖、PR 反向关联、应用内定时)详见 docs/design-and-release.md。
PRD §6 规划了「MCP Server + Skill」让 AI Agent 在执行任务时自动维护看板。本版落地 MCP Server 部分(D5:先 MCP,后包 Skill)。
与 PRD 的关键偏离:PRD 原设想把 session / 状态写到 GitHub Project v2 自定义字段;但本 App 的最终形态是纯本地 SQLite、绝不写回 GitHub。因此 MCP Server 直接读写本地
taskboard.db,零 GitHub 调用——这是 PRD 设计在当前架构下的正确适配。
两种运行形态(同一份工具契约):
- 内置二进制(推荐,v0.3.12 起):
taskboard二进制新增mcp子命令——main.rs在 argv 含mcp时直接进入 stdio JSON-RPC 循环,不启动 GUI。它复用与 App 完全相同的db.rsschema 与同一份taskboard.db,零 Python 依赖、无散落文件夹、无 schema 漂移。装了 app 即自带 MCP,mcp.json 直接指向 app 内二进制即可(见下方配置)。 - 独立
server.py(便携 / 开发兜底):mcp_server/server.py仍保留——仅用 Python 标准库(手写 JSON-RPC 2.0 + LSP 风格Content-Length分帧),无第三方依赖。适用于非 macOS / 未装 app 时让 Agent 读写同一数据库;其工具与内置二进制保持兼容。数据库路径默认同上(三平台标准位置),可用环境变量TASKBOARD_DB覆盖;启动时会幂等补齐branch/handoff两列(与 App 的db.rs::init迁移一致),故即使 App 还没启动过也能直接用。
提供的工具(与 PRD §6.2 对齐):
| 工具 | 入参 | 说明 |
|---|---|---|
list_my_tasks |
status? / ownership? |
列出看板任务,可按四态 / 归属过滤 |
get_task_status |
issue |
查询某任务当前状态 + 已记录的 session / handoff |
update_task_status |
issue, status |
改本地看板状态(todo/doing/processed/done 或中文四态) |
record_session |
issue, session_id, agent?, branch? |
记录中断会话 id + 工作分支(branch 非空写 work_branch,不碰 GitHub) |
record_handoff |
issue, text |
记录「交接任务」详情(不碰 GitHub) |
clear_session |
issue |
任务完成后清空 session 字段(保留 session_at 审计) |
set_work_branch |
issue, branch |
#279:创建 / 切换 issue 分支后纠正 work_branch(只写该列、不碰 PR branch) |
issue 接受 repo#number / owner/repo#number / GitHub URL 三种形式。status 接受 todo/doing/processed/done 或中文「待处理/处理中/已处理/已完成」。
Agent 使用范式(对应 PRD §6.4 时序):任务开始 → 先切到该 issue 的工作分支(feature/issue-N-xxx),再 update_task_status(issue,"处理中") + record_session(issue, <会话id>, <agent>, <当前分支>);若先跑了开始命令、之后才切分支,切完补一次 set_work_branch(issue, <当前分支>);中途停止 → record_session;识别到「生成交接任务」→ record_handoff(issue, <详情>);完成 → update_task_status(issue,"已完成") → clear_session(issue)。
接入各 Agent(配置 snippet):内置二进制已注册进 WorkBuddy 的 ~/.workbuddy/mcp.json(taskboard 项)。其他本地 Agent 在其 MCP 配置里加同一条即可,例如 claude-code 的 ~/.claude.json:
{
"mcpServers": {
"taskboard": {
"type": "stdio",
"command": "/Applications/TaskBoard.app/Contents/MacOS/taskboard",
"args": ["mcp"]
}
}
}各平台
command路径:
平台 默认路径 macOS /Applications/TaskBoard.app/Contents/MacOS/taskboardWindows C:\Program Files\TaskBoard\taskboard.exeLinux (deb) /usr/bin/taskboard若安装到了非默认位置,把
command改成实际taskboard二进制的绝对路径即可。未安装 app、改用server.py兜底时,配置改为"command": "python3", "args": ["/path/to/mcp_server/server.py"]。WorkBuddy 已内置注册;opencode 在本仓库开箱即用(项目级
opencode.json已注册);其余 agent(codex / cursor 等)按各自 MCP 配置位置填入上述command+args即可。
MCP Server 只提供工具(能力层);要让 Agent 在「开始 / 中断 / 说『生成交接任务』/ 完成」时自动调用,还需要一份触发规则被 Agent 加载(触发层)。两者缺一不可:没有 MCP,hooks 无处可调;没有触发逻辑,工具只能被动等人调。已在仓库内置:
-
mcp_server/AGENT_INSTRUCTIONS.md—— 跨 Agent 通用的指令规范:触发时机 → 精确 MCP 工具调用、issue 引用格式、状态枚举、会话 id 来源约定。可直接整体喂给 claude-code / codex / opencode / zcode / helix / cursor / doubao。 -
CLAUDE.md(仓库根) —— 给 claude-code 的自动加载入口,指向上述指令文件并给出速记规则;在本仓库跑 claude-code 时会自动生效。 -
.claude/(#177,claude-code 确定性触发) ——settings.json注册SessionStart(注入$TASKBOARD_SESSION_ID/${CLAUDE_SESSION_ID}+ 看板规则)与UserPromptSubmit(仅提到 issue 时轻提醒)两个 hooks(bash + python3零依赖,不写 DB);commands/task-start|task-done|task-handoff.md提供显式一键命令。#279:/task-start已改为「先切 issue 工作分支、再记录会话」,避免work_branch被记成基线分支。开始处理先/task-start <repo#num>,做完/task-done。 -
.opencode/(#177,opencode 确定性触发) ——opencode.json已注册taskboardMCP(python3 mcp_server/server.py,跨平台、免装 app);plugins/taskboard.js(零依赖)在tool.execute.before自动补record_session的session_id/agent/branch;commands/task-start|task-done|task-handoff.md同 claude 侧语义(分支用!git branch --show-current`` 自动填入)。#279:先切 issue 工作分支再调/task-start,否则展开时填入的仍是基线分支;已切再补 `taskboard_set_work_branch`。同样先 `/task-start`,做完 `/task-done`。 -
App 设置 → Agent 接入(#177,一键安装/卸载,实现参考 clawd-on-desk 的 Settings → Agents) —— 任务详情 session 下拉的 39 个 agent 全量可选:
- 一键安装(hook 机制已逐项验证):
claude-code/opencode/workbuddy/codebuddy/trae; - 其余 34 个:选中后返回手动配置指引(含 codex / cursor / copilot / gemini / qwen / kimi / zcode 的已知配置路径),不伪造成功;
- 作用域两档:全局(
~/.claude、~/.config/opencode等用户目录,所有仓库生效,启动时自动补齐缺失项)与指定仓库; - 合并安装、卸载只摘 TaskBoard 部分(改动前留
.taskboard-bak);未安装过的 agent 自动跳过。
- 一键安装(hook 机制已逐项验证):
-
其他(无 hook 机制的)Agent:把
AGENT_INSTRUCTIONS.md的内容并入其 system prompt / 项目指令即可(codex 的AGENTS.md、helix 的技能/系统提示、cursor 的.cursorrules等同理)。
这样即完成 PRD D5 的「先 MCP,后包 Skill」:MCP 是能力层(已就位),指令文件是「Skill」等价物(跨 Agent 复用),Agent 侧按意图编排调用。
-
PRD.md— 需求文档与决策演进(含已放弃的 Projects v2 方案、归属维度设计、API 避坑点) -
docs/API-Architecture.md— GitHub API 与本地看板状态的架构说明:数据流向、同步机制、三种获取方式、与 Projects v2 的关系、常见误区 -
docs/design-and-release.md— 设计要点(多源拉取、归属三分、四态维护、PR 关联)与 GitHub Actions 在线打包说明 -
docs/CHANGELOG.md— 各版本的更新说明与修复记录(v0.3.1 → 最新 v0.6.9) -
docs/issue-327-p0-functional-defects.md— code review P0 批次:About 小窗「确定」按钮因 capability 未覆盖about窗口 +core:window:default不含allow-close而静默失效;设置面板「界面语言」切换器被误复制成重复的主题选择器;记事重复内容暴露原始UNIQUE constraint报错;projects.number_of_items误取项目编号(而非items.totalCount),使多 Project 时写错写回目标 -
docs/issue-328-p1-data-safety.md— code review P1 批次(数据安全与健壮性,9 项):tasks 物理重建自称「单事务」实则无事务(中断即丢本地态、失败后无自愈路径);MCP 一行坏 JSON / 超大Content-Length直接结束或 abort 进程;同步全败仍返回Ok谎报成功且跳过的账号日志永久停在 running;graphql()无限流处理致项目状态与父子关系静默降级;GUI 写命令吞掉「0 行受影响」;查询错误被折叠成「不在任何 Project 中」;403 一律当限流使权限问题白等 30s;search()单条坏数据拖垮整个数据源 -
docs/issue-339-taskcard-select-identity.md— 深度 review 批次(#339–#346,8 项)· P0 卡片点击完全失灵:#329 把前端任务身份升级为issueKey@accountId(聚合视图下同一 issue 来自两个账号时issueKey会重复),消费端全改用taskIdentity,但生产端TaskCard.tsx根本没进那次 diff、仍在发裸issueKey⇒ 两者永不相等 ⇒selectedTask恒null⇒ 详情面板不可达。既有panel-wiring.test.ts用正则只断言消费端、从不检查生产者,给出虚假安全感。修复后写操作仍用issueKey(后端按issue_key定位,边界不变)。本批共同根因模式是「只改了一半」:另 7 项含 db.rs 崩溃残留不可恢复(#340)、全局opencode.jsonc被写成非法 JSON(#341)、仓库级 GraphQL 失败致父子关联清空(#342)、matchMedia解绑 no-op(#343)、Esc 层注册放在不稳定 deps(#344)、MCP 分帧只修 Rust 侧(#345)、synced_at漏出ENSURE_COLUMNS(#346) -
docs/issue-329-p2-quality.md— code review P2 批次(一致性 / 工程质量,18 项):merge-cleanup.py多编号提取丢中间编号、SKIP_DELETE_MARKERS子串误伤(latest命中test);check-workflow-yaml.py误报read-all/on: [a,b]并补 4 类漏报(浮动分支@main、有runs-on无steps、顶层 key 重复、needs指向不存在 job);server.py::ensure_schema列清单仅有SELECT_COLS的三分之一导致旧库no such column;MCPhandoff_len字节数 vs 字符数;open_db每次建连写库致 UI 最长 5s 卡顿(改user_version门控 + 只读自愈探测、稳态零写锁);5 个 GUI 命令移出 Tauri 主线程;前端任务唯一键跨账号不唯一、Esc 冒泡双触发、复制定时器泄漏、每键 2 次 IPC、加载中整块替换、编辑草稿被重置、清筛选绕过合并器、CSS 未定义变量、主题监听泄漏 -
docs/issue-336-docs-ci-reality-alignment.md— CI 门禁盲区 + 文档与仓库现状对齐:quality-check.yml的push只挂已废弃的develop⇒ 直接 push 到main跳过全部重型门禁(#330 加固后的遗留缺口);AGENTS.md/CONTRIBUTING.md/AGENT_INSTRUCTIONS{,.en}.md/.claude+.opencode的task-start命令 /set_work_branch的 MCP tool description 共 30+ 处仍指示「从develop新开分支」,照错做会产生错误分支;docs/release-backmerge-policy.md前提失效加横幅;旧仓库名task-dashborad4 处陈旧链接 +blob/develop2 处真断链。含「只改指导动作的文本、保留历史记录」的边界口径 -
docs/issue-345-python-mcp-framing.md— 深度 review 批次 · Python MCP 一行坏数据即终止进程:Rustmcp.rs早在 #328 就改为四态ReadOutcome(mcp.rs:750-753也点名了这个症状),但只修了 Rust 侧;Pythonserver.py仍把畸形与 EOF 折叠成(None, None)、主循环见None即break,且json.loads(body)未包 try。移植四态(关键区分MALFORMED已完整消费可 continue vsFATAL帧边界丢失只能终止),并补上 Python 侧原本完全没有的两个 DoS 上限(NDJSON 分支连长度上限都没有,长行可无界撑爆堆)。测试含 3 例端到端驱动main()—— 首次反向验证暴露「症状由循环决定而非分类决定,只测分类会漏掉半修状态」这一缺口 -
docs/issue-344-esc-layer-stable-deps.md— 深度 review 批次 · 确认框按 Esc 直接关掉父面板(本批唯一「正确机制因实现细节失效」项):#329 的 Esc 分层栈要求子层晚于父层注册,但SyncLogsPanel([onClose])与ConfirmDialog([onCancel])把层注册放进带不稳定回调依赖的 effect,确认框打开期间一次父重渲染(自动同步 / 4 秒横幅计时器 / 20 秒轮询)就让两层按「destroy 子→父、create 子→父」整体重排 ⇒ 面板压过自己的子层 ⇒ 一次 Esc 跳过「取消」直接关面板。修复新增useWindowEscLayer,把层注册([]依赖)与业务回调(ref)解耦;测试把不变式直接钉在栈原语上(含把缺陷形态写成期望值的反面对照) -
docs/issue-343-theme-mql-identity.md— 深度 review 批次 · 显式主题选择仍被系统覆盖(#329 的修复实际没生效):按 CSSOM View 规范Window.matchMedia(q)每次返回 new MediaQueryList(各自独立监听列表),而 #329 只记函数引用、解绑时重新matchMedia()取新对象去 remove ⇒ 恒为 no-op ⇒ 既覆盖用户显式选择、又把监听器泄漏进每个新对象。旧打桩matchMedia: () => media永远返回同一对象,与平台行为正好相反,故藏身;「解绑用同一函数引用」那条用例只比函数身份、从不比 MediaQueryList 身份。修复持有实例本身;测试先按平台语义重写打桩 + 新增liveListeners()真实度量 -
docs/issue-340-recover-orphan-tasks-new.md— 深度 review 批次 · 整个看板静默丢失(本批唯一数据永久丢失项):重建事务停在DROP TABLE tasks(已提交)与RENAME(未执行)之间 ⇒ 留下tasks缺失、tasks_new保有全量数据。#328 加的DROP TABLE IF EXISTS tasks_new自愈只在重建函数内部可达,而前置条件在该状态下为 false ⇒ 自愈分支恰好在最需要时不可达 ⇒SCHEMA建出空表、版本号盖到最新、迁移此后再不重跑。实测二次打开也不自愈。修复在迁移门控前探测并RENAME回收;实现中新发现「探测块须早于SCHEMA且须加issue_key列指纹,否则列不全的残留表会让整个库打不开」这一坑 -
docs/issue-355-require-affected-remaining-writes.md— 深度 review 第二批 · #355:clear_session/record_handoff未守require_affected⇒ key 不存在时返回成功却什么都没改,且与 MCP 侧行为不一致(#328 只覆盖5 条写路径里的 3 条)。并修掉一个本来就失效的防回归测试 ——write_commands_check_affected_rows用src.matches()对整份源码计数,而mod tests里自身的用例也含同样调用把计数抬高,>= 3阈值形同虚设(反向验证时真的被骗过一次);改为过滤注释 + 在mod tests处截断 +assert_eq!(…, 5) -
docs/issue-396-tasks-new-fingerprint-untested.md— 断言强度审计 ·db.rs(本仓首次审计):#340 恢复探测的issue_key指纹保护无任何测试:db.rs是本仓唯一「最坏事故类别 + 零审计」的组合 —— #340 是永久数据丢失(tasks被 DROP、tasks_new保有全量数据却打不开库、版本号盖到最新、迁移此后再不重跑)。审计 8 个目标,最关键的判据只被守住一半:open_db里逐字写着「⚠️ 必须确认 tasks_new 确实是 tasks 布局才 RENAME,否则 SCHEMA 的CREATE INDEX ... ON tasks(ownership)会因缺列而整个 batch 失败 —— 那比『看板为空但能打开』更糟(库直接打不开)」,但这条保护没有任何测试。已有用例只覆盖正向(真实 tasks 布局 ⇒ 应恢复),反向情形(tasks_new存在但并非 tasks 布局)完全没测。后果实测:指纹在 ⇒tasks_new保留原状(安全侧);指纹去掉 ⇒ 被当成 tasks 升为看板主表。与 #376taskSig完全同型:契约被逐字写在注释里,却只守住契约的一半- 与 #390/#392/#394 同系列的第 4 例:#390 枚举只手写 6 个组件、#392 正则只匹配一种写法、#394 解析只认一种语法形态、本项只守正向不守反向 ⇒ 归纳为 断言覆盖面必须覆盖契约的全部实例,而不只是那个最常写的实例
- 方法论:为什么
db.rs适合 mutation。它虽依赖真实 SQLite,但可测的是纯逻辑 —— 迁移门控条件(版本号比较、fresh/needs_migration)、恢复探测的可达性(只需构造 schema 变体)、SCHEMA与迁移列表的一致性(读文本)。不需要对 SQL 执行做 mutation,要测的是「这段代码在什么状态下才会跑」—— 而 #340 的形态(自愈分支不可达)恰是可达性缺陷,注入「把前置条件改成永不成立」一测就暴露,读代码极易漏判 ⚠️ 我又犯了 #380 的同一个错误(当时已写进 KB,本次仍重犯):插入点替换的 anchor 只取fn xxx() {一行,上方 doc +#[test]留在原地 ⇒duplicated attribute+ 原函数失去#[test]变 dead code,而cargo test仍通过(27 passed),只有 clippy 暴露。教训固化:插入点替换必须连#[test]一起锚定,或插入后立即跑clippy --all-targets—— 这是 clippy 门禁(#366)的第二重价值- 另记 1 个存活但不是缺陷的等价变异:去掉
table_exists(tasks_new)判据 —— 全新库上两表都不存在,而table_has_column对不存在的表返回false,条件仍为假
-
docs/issue-394-css-decls-selector-shape.md— 断言强度审计 · 前端 CSS 静态断言:helper 只认精确选择器,@media 内规则完全不可见:审notes-layout.test.ts与styles.test.ts(两者都用?raw读styles.css做静态断言,因 vitest 跑在 node 环境无 DOM/布局引擎、§2.5 不引入 jsdom)。同一个decls()helper 在两个文件里各写了一份(30 + 25 = 55 个守卫受影响),且只匹配精确选择器字面量/(?:^|[},])\s*${esc}\s*\{([^}]*)\}/。盲区 1(最讽刺):锚点(?:^|[},])不含{⇒@media内规则的 selector 前驱是{⇒ 完全不可见;而 #259 的缺陷本体正是「窄屏四列被压成 ~18px 竖条」 —— 要防的问题所在的空间恰好是守卫的盲区。盲区 2:只认字面量相等 ⇒ 后代选择器.notes-page .notes-card-cols(作用于同一元素、特异性更高、实际生效)与合并选择器.notes-card-cols, .sidebar全被漏。注入验证 5 种形态漏网 4 种,修复后 8/8 全捕获- 方案:匹配语义改为「规则选择器按逗号拆开、去掉祖先前缀后以调用方选择器结尾」,一次覆盖四种写法(完全相同 / 后代 / 合并其中一项 / 调用方自带后代)。用
endsWith而非startsWith/包含,避免.note-col误命中.note-col--p1。规则提取用/([^{}]+)\{([^{}]*)\}/g—— 外壳因声明体含{被自然跳过,内层规则直接取到,无需专门写嵌套解析 - 归纳出更一般的规律(本系列第 3 例,成因各不相同):#390 枚举只手写 6 个组件、#392 正则只匹配一种写法、#394 解析只认一种语法形态 ⇒ 断言的实现形式(枚举 / 字面量 / 语法子集)必须覆盖问题出现的全部语法形式,否则同一问题的「换个写法」就会静默逃逸
- 方案:匹配语义改为「规则选择器按逗号拆开、去掉祖先前缀后以调用方选择器结尾」,一次覆盖四种写法(完全相同 / 后代 / 合并其中一项 / 调用方自带后代)。用
-
docs/issue-392-esc-deps-regex-too-narrow.md— 断言强度审计 · 同一文件第二类盲区:#344 Esc 依赖守卫正则只匹配一种写法:守卫原文not.toMatch(/\}, \[on(Close|Cancel)\]\)/)只匹配依赖数组恰好等于[onClose]。而 #344 要防的是「不稳定的回调依赖导致 Esc 层整体重排」这一类问题(面板压过子层 ⇒ 一次 Esc 跳过取消直接关面板),不是「恰好等于[onClose]」这一个写法。注入验证:[onClose, t]是完全自然的写法 —— 任何人多留一个变量就绕过守卫,而失效机制照样发生。修复后[onClose]/[onClose, t]/[t, onClose]/[onClose, onClose]/[onCancel]5 种形态全捕获,而[deps]/[]不误报。关键取舍:间接依赖(useCallback吃进回调)当前无法被字面量正则发现,如实标注为遗留局限而非强上- 排查过程含两个假阳性,如实记录:①逐一核对 14 个
?raw变量使用次数 ⇒ 无死 import;②用组件名 grep 找未覆盖组件 ⇒ 误报(notesRaw/boardRaw/agentRaw走变量而非字符串),改按「raw 变量使用次数」才得到正确结论。找覆盖缺口时「按名字 grep」与「按实际引用」结论可能相反 ⚠️ 本轮第八、九次「测量手段本身出错」,也是最该记住的一条:判结果只用退出码,不要解析输出文本。⑧用grep -oE "Tests .*"|head -1抓到的是失败输出行Tests 1 ⎯⎯⎯(非汇总行)⇒ 把 5 个形态全判反;⑨自写脚本用grep -q "No tests failed"判全绿,而该字符串并非 vitest 输出、恢复态就误报 ⇒ 整张表结论作废。最终改用(npx vitest run >/dev/null 2>&1); echo $?(0=全绿)一次跑完全部形态- 与 #390 根因同源:守卫覆盖范围窄于它要防的问题域 —— #390 是「枚举只有 6 个组件」,本项是「正则只匹配一种写法」。两者印证:测试断言的措辞形式(枚举/相等/正则)必须匹配它要防的问题的粒度,否则会在同一问题的其他写法前静默失效
- 排查过程含两个假阳性,如实记录:①逐一核对 14 个
-
docs/issue-390-openbrowser-guard-enumeration.md— 断言强度审计 · 前端组件测试:openExternal 守卫只覆盖手写 6 项枚举,新增组件裸调完全漏网:先确立结构性事实 —— 3 个组件测试文件全部用renderToStaticMarkup(19 处),而服务端渲染完全丢弃事件处理器,且全仓零交互模拟(无fireEvent/userEvent/dispatchEvent)⇒ 任何组件测试都不可能抓到事件绑定缺陷。在 §2.5 不引入 jsdom 的约束下,panel-wiring.test.ts的静态正则守卫是唯一防线,但它只遍历手写的 6 项枚举(该文件已 import 12 个组件,清单只有 6 个;components/下共 16 个.tsx)。实测注入验证:AgentPanel/SettingsPanel各注入一处api.openInBrowser(⇒ panel-wiring 全绿,而清单内的SessionsPanel注入才失败⚠️ 「只改了一半」模式的第 5 次实例,且递归了一层:#370 修的正是SessionsPanel裸调并加了这条守卫,但守卫只覆盖当时已知的 6 个组件 ⇒ 前四次是「只改了一半的代码」,这次是「只覆盖了一半的守卫」。修法与 #376/#380 同源:手写枚举 →import.meta.glob自动枚举(覆盖components/*.tsx+App.tsx),新增组件天然在范围内、无需记得同步维护清单- 两条防恒真守卫(#367 教训):实测
import.meta.glob路径写错时静默匹配 0 个文件不报错,守卫会变恒真断言。故加names.length > 10下限 + 反向契约(自动枚举范围须是手写清单的严格超集)。反向验证:6 个原漏网组件 +App.tsx全部捕获,破坏路径也捕获 - 我犯了两次同类错误,都被当场抓出:①第一版只写
'./components/*.tsx'漏了根目录的App.tsx⇒ 由我自己写的反向契约当场报出(若无它,这个盲区会随修复合入 main,与本 issue 修的正是同一类问题);②验证时误判「守卫存活」,实际是 vitest 变换缓存返回旧结果 —— 本轮第六次「测量手段本身出错」 - 如实记录遗留局限:
renderToStaticMarkup使组件测试无法验证事件绑定(onClick绑错函数、回调内部逻辑错误当前无任何测试能发现),修它需引入 jsdom 违反 §2.5,须作独立提案评估。本 issue 只把「静态可查」那部分的覆盖从 6 个扩到 16 个 +App.tsx
-
docs/issue-388-iso-parity-two-implementations.md— 断言强度审计 · 双实现一致性:server.py::_iso_to_secs与 Rustiso8601_to_secs自称对齐、实际 5 处分歧,且其中一处是真实缺陷:两份完全独立的实现(一个手写闭式公式、一个调strptime),Python docstring 明确声称「对齐 Rust」。真实缺陷:Python 直接返回calendar.timegm(...),1969-01-01得到负数-31536000,而 Rust 有显式y<1970守卫返回0⇒ 同一 issue 被两条路径先后写入时时间戳取决于谁最后动手,下游「相对时间」遇到负值显示荒谬文案。另 4 处分歧:2024-02-30(strptime 校验逐月天数 vs Rust 只查1..=31)、2024-1-01与2024-01-01T0:00:00Z(strptime 要求零填充 vsparse::<u32>()宽松)—— 这四处 Python 都更严格且严格方向都是「返回 0」= 失败关闭,故保持现状并显式记录,不为对齐而改行为⚠️ 最值得记录的一点:2024-01-01T00:00:60Z带跨平台性质。实测(macOS arm64 / glibc / Python 3.14.8)%S接受 60 和 61(闰秒),timegm再进位到下一分钟;而 musl(Alpine)与 Windows 的 C 库未必接受 ⇒ 同一份server.py在 Linux/macOS 与 Windows 上结果可能不同。而server.py正是 Windows/Linux 的兜底实现,故该值不能当稳定契约;GitHub 从不发闰秒故当前不可达,但若将来要支持闰秒语义必须两侧同时改且不能依赖strptime的平台行为- 方案:共享 fixture
mcp_server/fixtures/iso_parity.json(must_agree27 条 +known_divergence4 条),两侧测试各读同一个文件。不各写一份表的理由:#155(tasks.key→issue_key改名只改一侧、Python 读路径静默失效几版)已证明双副本必然漂移,且server.py不参与 Tauri 构建,Rust CI 跑不到 Python 测试。known_divergence两侧各自锁定并带note,行为变更时提示「须确认有意为之」 - 反向验证双向生效:Python 退回负时间戳 → failures=2;改坏 fixture 里
2100-03-01期望值 → Python failures=1 且 Rust 1 failed 同时报警。附插曲:第一版 fixture 我照 Rust 抄了sec=60的期望值,测试当场报1704067260 != 0—— 共享表第一道价值生效,这是「期望值必须实测」的第三次生效(#378 我手算2024-02-30出错是第一次)
-
docs/issue-386-python-mcp-parse-ref-coverage.md— 断言强度审计 · Python 侧(第一批):MCP 引用解析形态覆盖缺口,且两侧实现测试覆盖不对称:mcp_server/server.py::parse_issue_ref_parts是 agent 每次调用 MCP 工具的入口(update_task_status(issue,...)等全部走它),而 AGENTS.md §8.6 要求它与 Ruston_demand.rs行为等价。实测 4 个变异存活,最关键的是(?:issues|pull)退化为(?:issues)⇒/pull/{n}链接全部解析失败,且失败方式是抛「无法解析 issue 引用」——看起来像「用户填错了引用」,不会有任何告警。另 3 个:漏rstrip("/")使owner/repo/#N解析出错误 repo、rpartition→split改多#行为、删空引用守卫改错误消息。跨侧覆盖不对称(本项额外发现):on_demand.rs:459,485已有/pull/断言而 Python 侧完全没有 —— Rust 改正则时 Python 侧无人发现,与 #155(改名漏改 Python 侧、读路径静默失效几个版本)同类风险面。关键技术点:断言必须断言错误消息而非只断言异常类型 —— 我第一版只写assertRaises(ValueError),结果rpartition→split依旧存活,因为后者也抛ValueError(unpack 长度不匹配),两种写法在测试眼里完全一样 —— 这正是 #376「断言看起来合理 ≠ 有判别力」的教训落在自己身上。另诚实标注 1 个存活不是缺陷:[^/#?]+放宽为[^/]+对合法 URL 是等价变异(路径段本就不含?/#)⚠️ 本轮第五次「测量手段本身出错」:验证脚本的 grep 只匹配FAILED (failures=,而url-pull产生的是FAILED (errors=1)(异常 vs 断言失败)⇒ 被报成??。至此五条纪律共同点:先验证测量手段本身,再采信结论
-
docs/issue-384-parse-links-alias-guard.md— 断言强度审计 · Rust 侧(第四批):GraphQL 父子链接解析的别名守卫「空洞为真」:审计前parse_links_from_graphql只有 2 条平凡断言({}与{"data":{}}→ 空),整条解析路径几无直接覆盖。发现真实漏洞:!key.starts_with('a') || !key.chars().skip(1).all(is_ascii_digit)—— Rust 的Iterator::all对空迭代器返回true,故光秃秃的"a"被当成合法别名放行,与紧邻注释声明的契约("别名固定为a<序号>")相悖;实测{"a":{...},"a1":{...}}解析出[994,101]。如实标注严重性:当前不可达(build_links_query生成的别名永远是a1..aN),是潜在缺陷而非线上 bug —— 但它出现在一段专门用于防御意外字段的守卫里,恰好在最该生效的场景失效,且name是字符串、as_object()恰好返None挡住 ⇒ 连报错都没有。修复加key.len() < 2。另发现缺失title/url的默认值(unwrap_or("")改成"X")无测试守护,而该默认值直接进 UI。补 4 例:真实响应形状(含name/owner仓库字段须被跳过)/ 逐个点名守卫判据 / 9 种脏形状不 panic / 默认值回落空串。反向验证 3/3 全捕获⚠️ 本轮第四次「测量手段本身出错」:cargo test --lib parse_links的过滤条件不含新用例名link_from_node_defaults_...,根本没跑就报「存活」;改跑全量后 3/3 全捕获。至此形成四条纪律:①注入须确认生效 ②变异方向须表达真实缺陷 ③期望值须外部来源 ④过滤条件须覆盖被测用例 —— 共同点是先验证测量手段本身,再采信结论
-
docs/issue-382-browser-url-whitelist-boundary.md— 断言强度审计 · Rust 侧(第三批,唯一一项安全边界):validate_browser_url子域边界无守护,两处「无害简化」即造成白名单逃逸:该函数是open_in_browser命令的唯一闸门,而 URL 并非纯内部输入 —— 来自 issue 正文 / PR 链接 / agent session 工作目录(#370 已确认SessionsPanel走这条路)。实测 4 个变异存活,其中 2 个是真实逃逸:把ends_with(".ghe.com")改成ends_with("ghe.com")会放行evilghe.com/notghe.com(任何人可注册的域),改成contains("ghe.com")还会放行ghe.com.attacker.net/a.ghe.com.evil.net。这是最危险的变异形态:去掉那个点看起来只是无害简化,Linter 不报、code review 极易放过(读者会脑补「当然是指子域」),却把边界从「ghe.com 的子域」放宽成「任何含/结尾 ghe.com 的域」。另 2 个存活是大小写方向(属失败关闭,不放进危险域),但仍要锁定 —— 若有人为「修大写被拒」而把判据改成contains,会一并放宽域匹配,从功能修复变成安全逃逸。另验证 userinfo 逃逸向量:host 提取改取@前段会放行github.com@attacker.net,新用例成功捕获。反向验证 4/4 + userinfo 全捕获。KB 里另记一个变异方向教训:取@后段也存活,但它不该被捕获 —— 那本就是正确行为(真实 host 是后段),差点误判成「测试仍弱」 -
docs/issue-380-status-map-table-contract.md— 断言强度审计 · Rust 侧(第二批):Project Status 映射表 33/45 条目无测试守护(静默降级):map_project_status/map_project_status_en是 #335 修复的核心产出,函数注释逐字点名了「按整值精确匹配、不做子串匹配」「不认识的返回None,绝不臆造」这份契约,但逐条 mutation 后只有 7 个条目被用例点名(done/completed/closed/released/ready for release/in review/in testing),实测 33 个条目删掉后无任何测试失败。缺陷形态是静默降级而非报错 —— 删掉一个条目后落_ => None⇒「保持本地手动态」,而这本就是许多 Status 的正确表现,故无任何异常信号。中文表尤其脆弱:现有用例map_project_status("🎉完成/上线")一个字符串同时含两个判据词,删掉任一个另一个仍命中 —— 与 #376taskSig的「字段组断言」完全同型。修复为表驱动(把表搬进测试):39 个英文条目 + 9 个中文判据词逐条锁定,中文用例每个只命中一个判据词;另加反向契约断言表外值须None,含注释承诺的陷阱样本(release notes含release但≠ready for release)。表驱动的额外价值:增删条目时漏更新测试即编译失败,从根上消除「改了表没改测试」这个盲区本身。反向验证 0/33 → 33/33 全捕获 -
docs/issue-378-iso8601-test-coverage.md— 断言强度审计 · Rust 侧(第一批):iso8601_to_secs零测试覆盖:该函数被sync.rs/on_demand.rs/github.rs三个生产模块调用,含 Gregorian 闰年算术(month_adjust+ 世纪年规则)与 6 项输入范围校验,却没有任何测试 —— 唯一间接覆盖是sync.rs里一处> 0断言,无法区分「解析正确」与「解析出荒谬但为正的值」。实测 6 个变异全部存活(闰年规则退化为朴素%4、month_adjust漏m>2、天数差一天、去掉y<1970守卫、月份上界放到 13、时区偏移写错),CI 全绿。风险不是「当前实现有 bug」(已用 Pythondatetime核对,实现是正确的),而是无守护:重构即静默偏移所有 issue 时间戳一天。补 3 例,期望值一律由 Pythondatetime算出(不用闭式公式自证 —— 否则测试与实现同源、同样错则同样过)。第三例跨闰日逐日核对相邻间隔恒为 86400,把「闰年规则」与「month_adjust」两个易错点组合验证(单点用例可能碰巧对上,连续性不会)。反向验证 0/6 → 6/6 全捕获。附记:实现只校验1..=31、不做逐月天数校验,2024-02-30会算出无意义但确定的值 —— 测试注释显式锁定该真实行为并声明它不是完整日历校验 -
docs/issue-413-close-keyword-boundary.md— 断言强度审计 ·match_close_keyword手写扫描器:后词边界与文本末尾两条分支无断言:审parse_issue_refs的核心 —— 逐字节走text.as_bytes()的手写扫描器。实测 7 个变异2 个真实缺口存活:①后词边界检查被删 ⇒fixedX/closed_foo被当成关闭标记(可能误关闭无关 issue);②文本末尾return Some(end)改成None⇒ 关键词位于正文最末时匹配不到。另 3 项已有覆盖。💡 等价变异判别清单第二次命中(中文分支eq_ignore_ascii_case)。⚠️ 反向契约陷阱:Fix本身就是完整关键词,不是截断 —— 必须用真正的截断输入(Fi/fixe) — 断言强度审计 · #215 写回路径(TaskBoard 唯一向GitHub 写入的地方):mutation 形状断言过弱,子串匹配挡不住名字拼写错误:审project_status_mutation。现有断言是contains("updateProjectV2ItemFieldValue")—— 改成...FieldValues(拼写错误)断言仍通过。实测 8 个变异5 个存活:mutation→query、响应选集丢弃、input:包装丢弃、mutation 名字拼错(另 3 个捕获)。严重性如实界定为低于 #409 —— 这 5 处失效在运行期都是响亮失败(GitHub 直接拒绝;且set_project_item_status明确校验projectV2Item.id,为空即报「GitHub 未返回确认」),不存在静默数据损坏- 仍需锁定的两条理由:①响应选集是查询与调用方之间的契约 —— 漏掉它则每次写回都报「GitHub 未返回确认」,#215 整体不可用,而该症状极具误导性(代码里的错误提示会把排查者引向 PAT 权限,不会想到是查询少选一个字段);②本函数存在的全部意义就是「纯函数、可单测」,让形状错误在 CI 就暴露(#278 的立论)。修复为 1 例六层,含反向契约「不得出现名字+多余字符的变体」。反向验证 7/7
- 💡 方法论新增纪律 3b:名称类断言必须配反向契约 ——
contains("someName")只要求包含,故拼写错误(...Value→...Values)、版本后缀(v1→v1Beta)、前缀重复(item→itemItem)全部逃逸。与 #407 同族:断言了「包含某物」,没断言「恰好是某物」 ⚠️ 过程中一次事故:为验证「还原是否干净」我跑了git checkout <file>,把自己的 68 行测试删掉了(靠事先留的备份恢复)。git status/git diff --stat是安全的,git checkout <file>是破坏性的 —— 它不区分「变异残留」与「我自己的改动」;正确顺序是先git diff --stat看清内容再决定 — 断言强度审计 · Project 条目查询的字段选集几乎全无守护:漏pageInfo会让分页静默停在第 50 条:审project_items_query(#356 抽成纯函数)。关键背景是这个函数已被同类缺陷咬过一次 —— 注释写着「⚠️ issue 分支的updatedAt不可删…该缺陷已真实发生过一次」,但 #356 当时只补了updatedAt一条断言,其余字段选集全部无人守护。实测 9 个变异全部存活:漏pageInfo/hasNextPage/endCursor⇒ 分页在第 50 条停住、之后的 issue 永不出现;items/fieldValues/assignees/labels(first:N)改成first:0⇒ 各自功能静默失效;comments{totalCount:0}、author{login:""}⇒ 评论数恒 0、作者列空白。全部不报语法错 ——first:0与totalCount: 0都是合法 GraphQL,请求成功、字段为空、客户端回落默认值,无任何错误信号- 修复:1 例四层断言 —— 分页驱动 / 7 条
(字段, 支撑什么功能)表驱动 / 反向契约「任何first:0都不得出现」(语法层面唯一能拦它的手段)/ 分支结构(updatedAt不得进 PullRequest 分支,多选字段的代价是查询直接报错而非静默降级)。反向验证 9/9 ⚠️ 纪律 2 补上「位置也要断言」:s.replace(old, new, 1)命中的是第一处同名片段 ——pageInfo {{...}}在 1083 行与 1154 行各出现一次、前者属于另一个函数 ⇒ 目标函数毫发无损 ⇒ 5 个变异「存活」。改为s.index(old, FUNC)后 9/9 全捕获。这比纪律 1「注入须确认生效」更隐蔽:注入确实生效了,只是生效在错误位置。本系列已三次犯「变异落到错误位置」(#380/#396 插入点 anchor、#405 空操作、#409 同名片段) — 断言强度审计 · GraphQL 链接查询的顶层字段选集无人断言,去掉number会让父子关系整体静默丢失:选build_links_query/repo_level_failure是因为 #278 抽它们时注释就写明「GraphQL 语法错只在真实请求时才暴露,代价高」—— 价值全在预防性断言上。14 个变异:10 捕获(含 #342 缺陷本体与它注释里警告的「错用顶层 data 判据」)、1 等价变异(is_null() || !is_object()≡!is_object(),因Null.is_object()恒 false)、2 真实存活- 根因:只断言了参数、没断言字段选集 —— 原有断言只有
q.contains("a0: issue(number: 278)"),那是 issue 的参数,从未断言节点选了什么字段。耐人寻味的是parent与subIssues内部的number title url都有断言,唯独顶层漏了 —— 而顶层恰是parse_links_from_graphql建键的依据 - 后果是静默降级而非语法错:去掉
number⇒n.get("number")拿不到值 ⇒continue⇒ 父子关系整体丢失且无任何报错;去掉title/url⇒ 回落空串 ⇒ 子 issue 卡片与父链接渲染成空白文案。与 #376 的「字段组断言」同族:断言了容器,没断言被取用的字段 - 修复用前缀断言(
starts_with("number title url parent"))而非解析嵌套花括号 —— 第一版用split_once("}")把parent { ... }的嵌套内容一起吃进来了(left多出 7 个 token)。本仓无 GraphQL 解析器且 §2.5 不引入新依赖,故只校验前缀顺序。反向验证 6/6 — 断言强度审计 ·Board.tsx列顺序零覆盖:回落分支的orderMap是死代码**:选它是因为projectKeys决定看板列顺序、且其回落路径正是 #372 的「看板列静默错序」点。实测 5 个变异全部存活,两个原因都需记录:①测试只传 1 个 status 且tasks={[]},没有任何「两列以上 + 需重排」的输入;②更根本 —— 全仓唯一调用点Board.tsx:145不传第二个参数projectStatuses,而orderMap分支(54–66 行)只在传了该参数时可达,主路径压根不经过这个函数(直接projectStatuses.map(ps => ps.name)取表顺序)⇒ 对orderIndex的 4 个变异天然无效 ⚠️ 「签名承诺了、调用点用不上」:sortProjectStatusKeys(keys, projectStatuses?)承诺可按orderIndex排序,但该能力实际不可用。可能是有意备用、也可能是重构残留 —— 属产品判断,本次不改代码,KB 里给出两个选项(保留则注释写明是备用路径 / 清理则删第二参数与死分支,回落行为不变)- 与盲区 E 类(#400)的区别:E 类是不可达因测试数据构造不出,本项是被测代码本身有一段不执行。判别方法相同(删掉看是否全绿)但结论不同 —— 本项需要「记录 + 产品判断」而非补断言
- 💡 补测试 → 再 mutation → 发现新缺口,两次迭代才收敛:补完 3 例后重验,发现两个我自己也没覆盖的存活变异(主路径漏
done列、回落不过滤空列)⇒ 又补 2 例。断言写完不等于有效,仍要用 mutation 验收新测试本身(呼应 #376)。另踩一坑:合成列列头走 i18n(已完成/未标注)而非内部 key,第一版按/done/i匹配失败,被断言消息里的实际列序点出来才发现 —— 断言渲染结果须按渲染文案写 — 断言强度审计 · 悬空项收尾:legacy 判据可证明永不决定**(强等价变异,无代码变更):#402 标注了「未能构造的窄场景」,本 issue 收尾。探针迭代 4 次:①手工造表→open_db失败 ②从真实库改名issue_key→探针无效** ③补 6 列但漏建 1 个索引名 ④补齐 6 列 + 8 个索引名全建(已是能构造的最窄状态)⇒ 两侧仍相同。此时正确结论不是「守卫无用」,而是转向可构造性分析。决定性结构事实:open_db里schema_is_current在 427 行求值、execute_batch(SCHEMA)在 438 行 —— 探测发生在建表之前;而SCHEMA的tasks是现代布局、没有key列。故要让它成为决定性因素需「全现代结构 + 遗留key」,而tasks拿到现代结构只有SCHEMA与migrate_tasks_v2_rebuild两条途径,两者都不创建key⇒ 对任何迁移流程可达的状态,它都不是决定性因素。结论:不建议删掉它 —— 廉价纵深防御、符合代码注释声明的「兜底」定位、删除无收益 - 💡 方法论真正的产出:等价变异也有强弱之分。弱等价(「我试了几个状态都没差异」)不足以下结论;强等价(代码路径分析 + 可达性论证,任何可达状态都等价)才可下结论。并沉淀存活变异的完整排查路径:①变异方向对吗 ②能构造出差异状态吗 ③有第二个等价守卫吗 ④该状态可达吗 —— 四步全过才判定为等价。连续 N 次换构造仍无差异时,别再换构造 —— 该问「这个状态可达吗?」
— 断言强度审计 · 悬空项落实:
schema_is_current的 legacy 判据是冗余守卫**,删掉不改变行为(无代码变更):#400 审计时我把变异 ⑤「schema_is_current去掉!tasks_uses_legacy_key」标注为「推测未实测」,推测必须落实 —— 留着不验证就是给审计留一个未验证的断言。核对后发现我原先的推测是错的**:REQUIRED_COLUMNS实际不含issue_key。但用legacy_tasks_db的真实 DDL 造库探针实测后,结论是变异为等价变异 ——missing_columns对真实 pre-#155 表必然非空 ⇒ 同样强制needs_migration = true;且run_migrations里独立地再检查一次tasks_uses_legacy_key。同一条件被检查两次,删掉一次不改变行为 —— 这类冗余本身是好事(纵深防御),mutation 存活正是它的表现 ⚠️ 探针本身也要先验证:第二次探针我从真实库把issue_key改名成key,结果两侧仍相同,我差点据此判「守卫无用」 —— 但那个构造不是真正的 legacy 布局(缺gh_state/updated_at TEXT)⇒ 重建INSERT..SELECT读不到列 ⇒ 两种情况都停在坏状态 ⇒ 看起来等价,实为探针无效。改用真实 DDL 才得到有效数据。「两种情况结果相同」有两种可能:真的等价,或探针没测到差异;判别办法是换一个更接近真实的构造再看- 如实记录一个未能构造的窄场景:守卫真正不可替代的情形是「
key仍在但 6 个REQUIRED_COLUMNS已补齐且索引齐全」,我未能构造(需在 legacy 表上建引用issue_key的索引,会报no such column)。故既不能断言必要、也不能断言多余,建议作后续独立任务
-
docs/issue-400-agent-groups-helper-coupling.md— 断言强度审计 ·agent-groups:测试辅助函数把present耦合到kind,使!info.present守卫永远走不到(新盲区类型 E):审计 14 个变异,13 个捕获良好(groupOf全部 6 分支、newlyRemoved优先级、summarize三项、GROUP_ORDER顺序、deviceDetail拼接顺序 —— 这套测试质量很高),唯一存活的那个暴露了一个测试设计缺陷:辅助函数host(agent, kind)写成present: kind !== 'none',于是present === false必然蕴含kind === 'none'⇒ 删掉deviceStateOf里的!info.present守卫后行为完全一致 ⇒ mutation 存活。但types.ts里present与kind是两个独立字段、类型系统不强制一致,「present === false但kind非 none」是类型允许的输入,而守卫存在的意义正是处理它(扫描端一旦报出这种组合,界面会把并未安装的 agent 显示成「已安装」)。契约在代码里不在类型里,而测试辅助函数替生产代码把这个不变量补上了- 新增盲区类型 E 类(原有 A 枚举不全 / B 匹配形式单一 / C 解析子集太窄 / D 只守正向都不覆盖):测试辅助函数补上了生产代码没有的不变量,使某个分支在测试数据里不可构造。与 #399(模块根本没在测试环境跑)同属「代码路径没走到」但根因不同:#399 是环境缺打桩,本项是测试数据的构造方式排除掉了分支。教训:辅助函数越「方便」,它替生产代码做的假设就越多 —— 写
host(agent, kind)这类糖时要问「它有没有把两个本应独立的字段绑在一起」 ⚠️ 测量错误的新变体:npx tsc --noEmit 2>&1 | tail -1 && echo "tsc 干净"——tail的退出码覆盖了tsc的,我在实际报 TS2739 时输出了「tsc 干净」。改为(npx tsc --noEmit >/dev/null 2>&1); echo $?后暴露并修掉两处类型错误。「判结果只用退出码」不仅适用于测试失败,「检查是否通过」本身也必须看退出码 —— 中间插一个管道信号就丢了
- 新增盲区类型 E 类(原有 A 枚举不全 / B 匹配形式单一 / C 解析子集太窄 / D 只守正向都不覆盖):测试辅助函数补上了生产代码没有的不变量,使某个分支在测试数据里不可构造。与 #399(模块根本没在测试环境跑)同属「代码路径没走到」但根因不同:#399 是环境缺打桩,本项是测试数据的构造方式排除掉了分支。教训:辅助函数越「方便」,它替生产代码做的假设就越多 —— 写
-
docs/issue-399-theme-moduleload-untestable.md— 断言强度审计 ·theme.ts:模块加载期的逻辑结构上不可测**,删掉整段仍全绿**:按方法论文档流程续审,选它的理由是缺陷史 —— #343 在这里找到过真实 bug(matchMedia每次返回新对象 ⇒ 解绑恒 no-op),有缺陷史的模块值得复查。审计 8 个目标,#343 / #329 本体均被捕获(回归良好),但发现theme.test.ts顶层import './theme'让模块体在beforeEach的stubEnv()之前就执行一次 ⇒window/localStorage不存在 ⇒ 模块尾部的applyTheme(storedTheme)(防 FOUC)与if (storedTheme === 'auto') bindSystemThemeListener()(首屏跟随系统)被try/catch静默吞掉。后果实测:把后一行整段删掉,theme.test.ts全绿 —— 即「首屏 auto 模式下系统主题变化不再跟随应用」这个用户可见缺陷当前无任何测试能发现,而它正落在 #329 / #343 这条反复出问题的时间线上⚠️ 「有测试」不等于「测到了」 —— 该文件有 7 例 29 行断言、覆盖setMode/bind/unbind/resolveTheme都很好,但那段代码在测试环境里从未执行过。测试量与覆盖范围是两回事- 解法:
vi.resetModules()+ 动态import(),让打桩先于模块体建立(vitest 内置,§2.5 不引入新依赖)。补 4 例:stored=auto须恰好注册 1 个监听(不多不少)/stored=light/dark不绑定(#329 核心不变量)/ 缺失与抛错回落 auto(回落值本身即契约)/ 模块加载期写data-theme(防 FOUC)。配套加stubEnvWithStored(stored)—— 原stubEnv恒返回'auto',只适合测setMode路径 - 反向对照:与「不绑定」方向相反的「恒绑定」(显式 light/dark 也绑 ⇒ #329 被改坏)也被捕获 —— 双向都验才知道断言方向没反(纪律 2 的实践)
- 方法论定位:纪律 4「信号覆盖被测对象」的新形态 —— 之前 #384 是「
vitest run <file>过滤条件不含用例名」,本项是「模块根本没在测试环境里跑」。共同点:信号(测试通过)覆盖了对象,但没覆盖对象在该环境下的实际行为;检测手段同为纪律 1 的「删掉整段看是否全绿」
-
docs/methodology-assertion-strength-audit.md— 断言强度审计方法论(11 项审计的沉淀,任何人接手审计前先读):核心结论是断言强度靠读代码判断极易出错 —— #376 的表驱动用例读起来完全合理,只有 mutation 才暴露它漏守 8 个字段。沉淀五条纪律(注入确认生效 / 变异方向与位置 / 期望值外部来源 / 信号覆盖被测对象 / 判结果只用退出码)、四类盲区归纳(枚举覆盖不全 / 匹配形式单一 / 解析语法子集太窄 / 只守正向不守反向)、等价变异判别清单(7 条已确认的「存活但不该捕获」,勿重复排查)与可复用流程。⚠️ 文档里也记了我自己犯过的十余次「测量手段本身出错」,因为方法的失效方式比方法本身更值得沉淀- 💡 11 项里只有 3 项是真实的代码缺陷(#376 漏字段 · #384 空洞为真 · #388 负时间戳),另有 3 项是守卫自身有盲区(#390/#392/#394)、5 项仅缺守护(#378/#380/#382/#386/#396)。这个分布本身说明:测试的主要作用不是抓 bug,是把隐含契约显式化
-
docs/issue-376-tasksig-field-contract-test.md— 断言强度审计 · 契约字段清单无人守护:对taskSig.ts逐字段 mutation(每次删一个字段跑测试),15 个字段中 8 个删掉后无任何测试失败 —— 含两处真实 bug:漏updatedAt⇒ issue 被评论后卡片日期不刷新;漏workDir⇒ agent 设的工作目录不刷新(后者正是 #287 引入该字段要解决的问题)。根因是测试按字段组断言(session 三件套一次改三个 ⇒ 删掉任意一个仍会变指纹),而taskSig.ts注释逐个点名了「必须纳入哪些字段」这份显式契约却无人守。修复为表驱动测试 + 一条反向契约(不在签名里的字段须不改变指纹,防契约漂移)。方法论:断言强度靠读代码判断极易出错 —— 这条用例读起来完全合理,只有 mutation 才暴露问题 -
docs/issue-374-boardmode-change-no-error-handling.md— 深度 review 第三批 · 乐观更新无回滚,UI 与后端状态分叉:切换看板列模式时setBoardMode(mode)先乐观更新、await setAccountBoardMode无try/catch⇒ 保存失败时<select>仍显示新值(看起来成功)、刷新后跳回旧值,且无任何提示。原因是AccountCard自身没有错误状态(外层SettingsPanel的err它取不到),而同文件saveSettings/saveColumns等路径都有出口 —— 只有这一条漏了。修复补catch+ 回滚prev+reportError。测试特意覆盖「加了try/catch但没回滚」的半修状态 -
docs/issue-372-aggregate-load-silent-failure.md— 深度 review 第三批 · 看板列静默错序 / 静默退回 project 模式:聚合视图(全部账号)下listProjectStatuses/listAccountColumns的单账号失败只落console.warn⇒projectStatuses为空使sortProjectStatusKeys退化为字母序;accountColumns为空使resolveBoardView从'custom'整体退回'project'—— 后者比bug-audit-2026-09P2-#7 记录的更严重(审计漏了第二处)。改为reportError上抛(与 #370openExternal同源同解),保留 #145「单账号失败不断整板」的隔离语义。附历史审计逐条复核结论(P0-#5/P0-#6 已修或已不成立,审计文档部分过期) -
docs/issue-370-sessions-panel-open-browser.md— 深度 review 第三批 · 打开失败时界面静默无提示:void api.openInBrowser(...)只丢弃 Promise、不吞 rejection,而api.ts早有收口好的openExternal(内部.catch(reportError))——SessionsPanel是全仓 6 个组件里唯一绕过它的漏网之处。可达性非理论:validate_browser_url仅放行github.com/*.ghe.com、spawn()亦可能失败。docs/bug-audit-2026-09.md的 P0-#11 早已记录该模式,#329 完成封装并替换 5 处却漏了这一处 —— 「只改了一半」模式的第四次实例(#339TaskCard、#345 MCP 分帧、#357get_opt、#370)。测试把「封装 + 全部替换」变成可机械校验的不变量,并额外锁定openExternal自身必须带.catch(否则收口形同虚设而第 1 条断言仍全绿) -
docs/issue-367-static-guard-self-diagnosis.md— 静态守卫自诊断加固 + 全仓静态断言审计:#355 修掉失效守卫后留下的新脆弱点 ——take_while("mod tests {")依赖「该文件只有唯一测试模块」这一未被守护的前提,模块改名/拆分后截断点消失 ⇒ 静默退化成修复前的失效状态。改用#[cfg(test)]作截断点并补「失去意义」守卫(范式取自lint-config.test.ts)。按实测更正了问题判断:新守卫的价值不在「能否失败」(两种实现都能失败),而在「计数失去意义时报错是否自诊断」。附全仓 6 处静态断言的反向验证审计(panel-wiring/styles/lint-config/about-window/notes-layout),逐个注入缺陷形态确认如期失败 —— 均真正承重,无同类失效 -
docs/issue-356-project-issue-updated-at.md— 深度 review 第二批 · #356:仅经 Project 发现的 issueupdated_at恒为 0、卡片日期永久空白。根因双重:项目条目查询没选updatedAt+RawTask.updated_at写死空串。修复同时把内联查询串抽成纯函数(沿用 #327 先例)——这正是该缺陷长期潜伏的原因:查询串内联在网络函数里,没有任何测试能看到它选了什么字段 -
docs/issue-357-mcp-framing-and-args.md— 深度 review 第二批 · #357:#345 只修了 Python 侧,Rust 侧(正式路径)仍有三处 —— 缺Content-Length误判Malformed(正文长度未知、流位置未确定,应Fatal,与紧邻的len == 0口径本就不一致)、NDJSON 分支无单帧上限(同 #328 的 DoS 类别)、非字符串参数静默丢弃(list_my_tasks({status:123})返回整块看板且isError:false,而 Python 侧报错 ⇒ 正式路径给错数据、兜底路径给错误) -
docs/issue-358-issue-url-anchor.md— 深度 review 第二批 · #358:issue 永久链接的尾部锚点(.../issues/7#issuecomment-1,GitHub UI 复制链接的标准形式)在正式 MCP 路径被拒。两处与 Python 侧不一致:编号整体parse(只剥前导#)、完全忽略第 3 段(discussions/7被误接受)。并纠正一条名不副实的既有测试注释("尾部锚点"其实只测了空白) -
docs/issue-359-tooling-hygiene.md— 深度 review 第二批 · #359(工具链):serverInfo.version硬编码0.6.1(实际 0.6.5)且无门禁 → 改为读Cargo.toml单一来源;CI clippy 只跑--lib⇒ 测试代码完全没被检查(main上积压 5 处 error)→ 修掉并扩到--all-targets;check-mcp-columns.py未覆盖写列清单TASK_INSERT_COLS→ 新增断言(性质为防御性冗余,非修 bug,见 #346) -
docs/issue-342-issue-links-repo-level-err.md— 深度 review 批次 · 父子 issue 关联被静默清空:#328 把fetch_issue_links改为宽松模式,放行判据是v["data"].is_null(),但data是仓库包装层 —— 仓库改名/转移/删除/token 失权时 GitHub 返回{"data":{"r":null},"errors":[…]},data非 null ⇒ 守卫不触发 ⇒ 解析器返回空 map 而非Err⇒links_failed_repos收不到该仓库 ⇒TASK_CONFLICT_UPDATE无条件覆盖关联 ⇒ 父子关系清空且无报错。修复把判据落到data.r这一层(新增repo_level_failure),并保留 #328 的宽松容错(仓库有效 + 个别名失败仍采信其余编号,有反向对照用例防修复过度) -
docs/issue-341-opencode-jsonc-empty-mcp.md— 深度 review 批次 · 全局opencode.jsonc被写成非法 JSON:find_top_object_span返回键起始引号位置而非{位置,而唯一调用方按{位置使用 ⇒ 空对象守卫恒 false(死代码)⇒ 把,插进{后面得"mcp": {,,用户全局配置被写坏、opencode 无法启动,且安装流程仍返回Ok(UI 报成功)。「注释型 JSONC + 空mcp」是 opencode 标准配置形态;非空mcp不暴露缺陷,故既有测试抓不到。修复需两处同改(返回值改{位置 + 空对象分支格式串,否则只是把{,换成{{) -
docs/issue-335-closed-state-case.md— 已关闭 issue 滞留看板:tasks.issue_state同一列存在 4 种大小写(GraphQL 的IssueState是大写OPEN/CLOSED,REST 是小写),而三处 closed 判据写死小写 ⇒AGENTS.md §2.2优先级第 1 条「closed → done 远程权威覆盖」对 Project 来源的 issue 完全失效;Project Status 的英文选项又因map_project_status()只认中文而兜底失败,实测 29 行滞留(closed小写侧 0 行异常,反证缺陷只在大写)。修复为四层:判据归一(common::is_closed_state)、落库归一(normalize_issue_state)、英文映射补全(整值全等防Ready for release误判)、存量数据修复(随SCHEMA_VERSION3→4 门控,刻意不臆造done_at) -
docs/issue-330-p3-quality-gates.md— code review P3 批次(规范 / 文档 / CI 门禁,7 项):版本号分散在 5 个文件却零自动化校验(实测package-lock.json落后两个大版本);15 篇知识库文档既不在 README 也不在 CHANGELOG(孤岛);ESLint--max-warnings 20只剩 2 条余量、门禁形同「不许再写第 3 条 warning」;CI 不跑vite build与cargo fmt --check、action 版本 v4/v5 混杂;release 无超时(挂死按 6 小时计费)与并发控制;4 个只读 workflow 未声明permissions;check-i18n.mjs硬编码两个语种致新增语言漏检。含新增scripts/check-versions.py、check-doc-links.py孤岛检测、全仓库cargo fmt归一化(263 hunk / 11 文件) -
docs/issue-285-sync-empty-board.md— 立即同步后看板空白、重启才恢复:rows_to_tasks两处缺陷——归属筛选分支漏 2 列(Row::get(25)越界报错)+my-created误读恒空的meta.login恒返回空集;统一 SELECT 列清单 + 从accounts表取 login;doSync走合并器并同步后重拉项目状态列 -
docs/v0.3.15-pat-auth.md— v0.3.15 PAT 认证与 visual polish 设计文档(gh 替换、卡片配色、多账号规划) -
docs/troubleshoot-mcp-timeout.md— 排障:MCP 连接超时(30000ms)——macOS Gatekeeper / quarantine 隔离属性排查与修复 -
docs/issue-181-auto-refresh.md— 外部写入(MCP)后 App 任务界面自动刷新:聚焦/轮询/指纹跳过 +tasks-changed事件 -
docs/release-backmerge-policy.md—⚠️ 已失效(保留作历史记录):原「发布回合策略」要求 release 合入main后把main回合develop;develop分支已废弃并从远端删除(2026-09-30 核实),该策略随之作废 —— 现为「所有 PR 直接进main」的单主干流程 -
docs/issue-256-update-check.md— 检查更新双通道并发:updater 通道无超时导致的串行慢 + 静默 fallback;并发 + 单路 30s 封顶,手动下载时展示失败原因 -
docs/issue-259-sidebar-nav.md— 左右分栏布局:左侧固定 Sidebar(记事本 / 账号点选 / 设置 / Agent 接入 / 同步日志 / 账号登录 / 关于)承载全部入口,顶栏精简;设置 / 账号 / 同步日志由 Modal 改为主区内嵌全高页面,Agent 接入面板展示 MCP 配置与看板工具说明 -
docs/issue-263-agent-device-scan.md— Agent 设备扫描:刷新升级为「扫本机已安装 / 已卸载的 agent」——PATH + 配置目录 + macOS 应用包三类信号 + 快照对比判卸载,含「疑似已卸载」分组与防漂移测试 -
docs/issue-265-sidebar-collapse.md— 侧边栏窄窗收起:窗口宽度 < 900px 时 Sidebar 自动收起为纯图标模式(~56px),隐藏文字标签 / 分组标题,账号靠title提示辨识;纯响应式、不持久化 -
docs/issue-266-test-flake.md— 修复 Rust 测试随机 disk I/O error:mem_conn()临时库路径加每调用递增序号、连接存活期不再删文件,消除并行测试互相 unlink 的 CI flake;新增防回归断言 -
docs/issue-281-license.md— 配置开源协议(MIT):根目录新建LICENSE+package.json/Cargo.toml补license字段 + README 徽章与协议章节 + CONTRIBUTING 贡献者协议说明;含 MIT / Apache-2.0 / GPL / AGPL 决策对比与「为何不用MIT-0、不动两个 lockfile」的取舍 -
docs/issue-284-merge-cleanup.md— PR 合并后自动收尾:删源分支 + 关闭关联 issue(develop合入不触发 GitHub 自动关闭),提取规则刻意保守防误关;顺带新增 workflow 语法检查器与scriptsCI job,补齐此前对.github/workflows/的零覆盖 -
docs/issue-279-work-branch-not-updated.md— 开始任务后 work_branch 仍关联基线分支:/task-start在 agent 尚处 develop/master 时就录分支,导致看板详情误导;改写为「先切 issue 分支再记录」+ 新增set_work_branch工具补偿纠正 -
docs/issue-278-issue-links.md— 详情关联 parent / sub issue:批量 alias GraphQL 同步父子关系(25 个/请求 + 按仓库 best-effort 保留既有值),详情面板新增「关联 Issue」块支持打开与复制;含 26 列 MCP 双实现与迁移补列教训 -
docs/issue-262-multi-account-sync.md— 多账号同步修复:同步范围与视图模式解耦,恒覆盖全部账号(不再受view_mode限制);恢复 topbar 显示模式切换,消除死代码 / 死 key;SyncResult新增accountsSynced可观测性字段 -
docs/issue-235-in-app-api-log.md— 应用内 API 调用明细:api_logs新表 + 可选 sink 收集器,同步/认领/状态写回的请求与返回参数可在日志面板展开查看(承接 #228 的 stderr 埋点) -
docs/issue-237-card-creator-row.md— 看板卡片结构调整:移除顶部账号徽章行、新增「创建人」行(tasks.author)、加大repo #编号字号;含 v2 重建后必须补列的迁移教训 -
docs/issue-239-doc-integrity.md— 文档完整性修复:断链 / 不可移植file://路径 / 失效行号锚点 + v0.3.50 CHANGELOG 补录;含scripts/check-doc-links.py防回归 -
docs/issue-252-ci-green.md— 修好既有 CI:prettier 全量格式化(以产物 sha256 不变证明零影响)+ 把 Tauri 的 Linux 系统依赖抽成 composite action,quality-check.yml四个 job 恢复全绿 -
docs/issue-250-ondemand-issue-pull.md— 未同步 issue 按需拉取:MCP 写路径命中「任务不存在」时下拉单个 issue 落库(只读 GitHub、不触发全量同步);含 owner 匹配与DO NOTHING落库的取舍、真机验证记录 -
docs/issue-248-synclogs-hscroll.md— 同步日志表格横向滚动:容器overflow: hidden静默裁掉右侧列(错误 / 明细);滚动职责收敛到紧贴表格的容器,含src/styles.test.ts静态回归测试 -
docs/perf-audit-optimization.md— 性能 / 安全优化批次索引:P0-1…P2-2编号到 issue 与落地文档的映射(#143–#150,随 v0.3.50 发布) -
docs/issue-118-expand-platform-support.md— 扩展平台支持:release 矩阵显式声明target,新增 macOS 双架构与 Windows ARM64 构建 -
docs/issue-119-expand-release-matrix.md— 扩展 Release 打包矩阵:Linux arm64、rpm、Windows msi;并记录zip作为 Tauri 2 bundle 类型被当日回滚的教训 -
docs/issue-317-session-clear-confirm-modal.md— 删除会话二次确认弹框被铺满成全屏页面:.panel-page .modal覆盖规则误伤ConfirmDialog,收窄为:not(.confirm-modal)+ 加confirm-mask类恢复居中弹框 -
docs/issue-319-kb-doc-links.md— 补全 #313/#314/#315 缺失的 KB 文档:CHANGELOG 引用的三篇文档此前从未创建导致 6 处断链,依据已合并改动补齐,恢复check-doc-links.py通过 -
docs/issue-322-note-edit-autosize.md— 编辑记事文本框不随内容长度自适应高度:useAutoSize由被动useEffect改为useLayoutEffect+ 进入编辑态主动测量,修复进入编辑态长内容停在 1 行的缺陷 -
docs/issue-325-about-window.md— 菜单栏 About TaskBoard 改为自定义独立小窗(对齐 WorkBuddy):macOS 自定义应用菜单接管默认 About + 新增about固定小窗(图标 / 粗体名 / 版本·Tauri·WebView 三行 / 全宽确定);WebView 版本前端从navigator.userAgent推导(Tauri 2 核心不暴露、不引新依赖)
#330 起
docs/*.md必须被 README / CHANGELOG 索引(否则check-doc-links.py报「孤岛文档」)。 以下 15 篇此前既不在 README 也不在 CHANGELOG,只能靠「知道文件名」才找得到,现补齐反链。
docs/issue-62-bug-audit-fixes.md— Bug 审计遗留 9 项修复:2026-09-06 对 develop 做三路并行全量排查(前端静态审查 + 后端静态审查 + 工具链冒烟)发现的 13 条中的 9 条docs/issue-191-auto-start.md— opencode 插件自动执行可靠性:autoStart失败时报喜不报忧且永久抑制重试,还会把processed任务打回doingdocs/issue-193-polish.md— 启动自动注册 dev 路径 +workBranch可见性:开发态首次启动免手填仓库路径,看板详情展示工作分支docs/issue-197-card-session-row.md— 卡片 session id 独立行展示:原先有 session 就挤掉更新时间(三元二选一),改为分配人下一行独立展示、时间恒显示docs/issue-204-multi-task.md— 单窗口多任务自动执行失活:插件用整会话累计 buffer 判定「唯一引用」,累计 >1 后永久不再自动执行、session_id存不进去docs/issue-207-group-toggle.md— 设置页 agent 分组下拉展开:四组平铺在 agent 多时页面过长,改为每组可展开 / 收起docs/issue-212-dead-code-warnings.md— 清死代码 warning:tauri dev常驻的fetch_state is never used与测试态的unused conndocs/issue-216-del-last-account.md— 仅剩一个账号时允许删除:delete_account禁止删默认账号,而只剩一个时它必是默认 ⇒ 想清掉重配 token 都做不到docs/issue-220-sig-project-status.md— 指纹缺projectStatus致写回后不刷新:#215 写回成功但页面不动 ——taskListSignature未覆盖projectStatus且status恰好无变化 ⇒ 指纹相同跳过渲染docs/issue-221-account-switch-stale.md— 切换账号请求被吞、看板滞留旧账号:App.load()防重入在忙时直接丢弃请求且无重试,最长延迟到下轮 20s 轮询docs/issue-224-log-account.md— 同步日志展示所属账号:同步范围跟视图走(single / all),但日志面板不显示账号,多账号下分不清docs/issue-226-close-custom-columns.md— 自定义列映射页签暂关闭:owner 要求先下掉该入口docs/issue-258-sync-warn-banner.md— 部分失败仍显示绿色成功 banner:warning非空时改为琥珀色警告 banner,与成功 banner 互斥展示docs/issue-276-auto-update-check.md— 每日自动检查更新 + 「仅显示我创建」筛选:免手动点「检查更新」;并区分「自己创建的」与「分配给自己的」issuedocs/v0.3.16-multi-account.md— v0.3.16 多 GitHub 账号支持设计文档:在 v0.3.15 PAT 认证基础上扩展为「账号池」,任务按account_id归属,支持单账号 / 全部账号两种视图
本项目采用 MIT License,完整文本见 LICENSE。可自由使用、复制、修改、合并、发布、分发、再授权乃至出售本软件的副本,前提是在本软件或其大部分副本中保留上述版权声明与许可声明(即根目录 LICENSE 文件的内容)。
软件按「原样」提供,不作任何明示或默示的保证;作者与版权持有者不对使用本软件所引发的任何主张、损害或责任负责。详见 LICENSE 中的免责条款。
版本 v0.6.9 · 本地跨平台桌面 App(Windows / macOS / Linux),2026-09-29
