WorkBuddy 任务跨工作区迁移

SKILLAI 工具
AI 工具 未标注权限 许可证不明确
安装这个技能

把 WorkBuddy 桌面端「任务列表」里的某个任务(会话)连同它的产物文件迁移到指定工作区。WorkBuddy 官方 UI 没有"移动任务到其他工作区"的入口,只能直接改本地数据库与索引;归属由 sessions.cwd 与 sessions.is_playground 两个字段共同决定,少写一个就会出现"库改了、文件也搬了,但用户说没看到"。也覆盖「迁移后侧栏看不到任务」「任务在列表里找不到」「产物卡片点不开」的排查、被软删除任务的捞回、任务重命名(title 与 custom_title 必须同写)、工作区显示名(改名后侧栏名 ≠ 目录名,用户口中的「XX 空间」常是时间戳目录),以及「怎么有两个同名的 XX、能不能合并」的辨别。触发词:把任务移到XX工作区、任务归类、移动任务、任务重新分组、整理工作区、产物一起搬、move task to workspace、任务看不到、任务不见了、两个同名、合并工作区、任务改名、重命名任务、工作区改名、XX空间。收拢收尾时清理残留的空时间戳目录、已删自动化的孤儿目录、乱码目录(走回收站,带白名单与空性断言,先证明无存活会话引用)。

一句话选型结论

按资料字段整理

WorkBuddy 任务跨工作区迁移是AI 工具类的 Agent Skill,安装方式未标注,权限级别未标注,许可证不明确,有可直接复制的安装命令。技能本质是给模型读的说明书,效果取决于模型与客户端;装完先用一个小任务验证,再放进正式流程。

分类AI 工具 安装方式未标注 权限级别未标注权限 许可证许可证不明确
累计安装 未标注 本周 未标注
GitHub 星标 未标注 仓库 未标注
下载量 未标注 技能市场下载数
权限级别 未标注权限 安装前请确认风险范围
许可证 许可证不明确 未说明

安装

未标注
在支持 Skills 的客户端里执行,装完问一句「你有哪些技能」验证
skillhub install user_e9d33879/workbuddy-task-workspace-move
  1. 1
    确认客户端支持

    确认你的客户端支持 Skills(Claude、Codex、Cursor 等),并升级到较新的版本。

  2. 2
    拿到技能文件

    执行上面的命令安装;也可以把仓库里的 SKILL.md 手动放进客户端的 skills 目录。

  3. 3
    刷新技能列表

    重新打开客户端,或刷新技能列表,让新放进来的技能被加载。

  4. 4
    验证是否生效

    问一句「你有哪些技能」,确认列表里出现它,再拿一个小任务试跑。

技能档案

AI 工具
字段来自技能仓库与公开索引,中文说明由本站标注
作者 / 维护方user_e9d33879仓库 未标注 · 版本 未标注
宿主客户端未标注技能可被这些客户端加载
安装方式未标注安装包 community
权限级别未标注权限安装前请确认风险范围
许可证未说明许可证不明确,商用前请自行确认条款
仓库可用性available技能仓库当前是否可访问
最近核验未标注上游最后核验时间 · 数据更新 1791624578
正文体量37.3 KBSKILL.md 原文大小,越大说明说明书写得越细

典型使用场景

3 类
按分类整理的常见用法,具体以 SKILL.md 为准
组合多个 AI 能力

把写作、检索、总结串成一条流程。

搭工作流

把固定流程做成可重复调用的步骤。

做提示词与评测

整理提示词并对比输出效果。

选择建议

装之前先看这几条
按条目字段给出的判断,不替你决定用不用

值得选的地方

  • 有可直接复制的安装命令,不用自己翻文档找入口。

先想清楚的地方

  • 没有标注权限级别,实际能做什么要装好后确认。
  • 许可证不明确,商用或二次分发前先到仓库确认条款。
  • 没有做站内核验,内容以作者仓库为准。
  • 技能只是说明书,实际效果取决于模型与客户端,装完先用小任务试一遍。

更适合

  • 需要在AI 工具场景里给模型补一套做法的人。
  • 愿意先读一遍 SKILL.md、确认它会让模型做什么的人。

不太适合

  • 对许可证要求很严、必须明确可商用的人。
  • 只想装完就自动变强、不打算看说明书的人。

同类对比

6 个同类
同分类里挑最接近的几条,字段以各自详情页为准
技能权限级别安装方式累计安装宿主客户端安装命令
WorkBuddy 任务跨工作区迁移当前 未标注权限 未标注 未标注 未标注 有
执行计划 只读 GitHub 仓库 151.5 万 Codex、Claude、Cursor 有
Playwright MCP 测试 浏览器控制 ClawHub 市场 136.5 万 OpenClaw 有
完成前的验证 未标注权限 GitHub 仓库 131.9 万 Codex、Claude、Cursor 有
子 Agent 驱动开发 只读 GitHub 仓库 131.2 万 Codex、Claude、Cursor 有
前端设计 执行命令 GitHub 仓库 125.6 万 Codex、Claude、Cursor 有

SKILL.md 全文

37.3 KB
模型实际读到的说明书原文,来自技能仓库

WorkBuddy 任务跨工作区迁移

任务列表里的一个任务,用户要求把它归到指定工作区 —— 本技能负责这件事,连产物一起。

何时用

  • 「把『XX 分析』这个任务移到『YY』工作区」
  • 「这几个任务重新归类一下」「整理一下工作区」
  • 「产物也要一起搬过去」
  • 「我在 YY 工作区没看到刚移过去的任务」「某个任务在列表里找不到了」
  • 「怎么有两个 XX,能合并成一个吗」—— 先别急着认定是重复的工作区,见下方「同名 ≠ 重复」

同名 ≠ 重复:先查「工作区显示名登记表」

侧栏里显示的工作区名字不一定等于目录名 —— 用户可以在 UI 里给工作区改名,改名落在
~/.workbuddy/workspace-display-names.json({id, path, displayName, updatedAt},id 是小写正斜杠路径)。
「侧栏有两个 XX」的常见成因因此有两种,且可能同时发生:

  1. 一个目录名就叫「XX」的工作区,与一个被改名成「XX」的时间戳工作区(如 2026-03-05-10-21-33);
  2. 上述任一个工作区里,还有个任务标题也叫「XX」。

排查顺序(别急着找同名目录去删):

python wb_move.py list             # 现在会打印显示名与「孤儿显示名」段
python wb_move.py find "示例项目A"  # 是否只是"任务"标题重名

用户口中的「XX 空间」(如 示例项目A)如果在 list 的路径列里搜不到,先用显示名去
workspace-display-names.json 定位真实路径
,再对这个路径做迁移 —— 否则会误以为
"找不到这个工作区"。

方向别搞反:不是去找两个同名目录删一个,而是把散落的同类任务并进一个工作区
(迁完那个时间戳/临时工作区里的任务清空后,它会自动从侧栏消失)。并进去之后工作区与任务会同名,
建议顺手 rename 掉任务名,侧栏才不会继续出现两个一样的字样。

孤儿显示名(可清):某个时间戳工作区的任务被全部迁走后,它会从侧栏消失,但
workspace-display-names.json 里的改名登记不会自动删除 —— 成了无主条目(wb_move.py list
会单列「孤儿显示名」一段)。它不影响任何功能,纯属脏数据。清法:编辑该 JSON、删掉对应的
workspaces 键(先备份),判据是"该 path 下已无任何未删除会话"。

> rename 必须同时写 title 和 custom_title。用户当年手动重命名过的任务,custom_title
> 一定有值,而侧栏优先显示 custom_title —— 只改 title 的典型症状是"改完没生效",
> 且反复查 title 都是对的,极易误判成"应用缓存没刷新"。

30 秒上手

命令都在本技能的 scripts/ 下,用 Python 直接跑(无第三方依赖)。

cd <本技能目录>/scripts

python wb_move.py list                        # 看有哪些工作区、任务怎么分布
python wb_move.py find "关键词"                # 按标题找到任务的 sessionId
python wb_move.py move --to "D:\WorkBuddy\我的工作区" --id <sessionId>
python wb_move.py check --to "D:\WorkBuddy\我的工作区"   # 复核会出现在侧栏的成员

迁移后不需要让用户重启 WorkBuddy —— 侧栏由轮询器按 30 秒周期做 diff 自动收敛,
改库这种"绕过应用写路径"的修改同样能被兜住(见「核心机制 4」)。先让用户看一眼,久等不对再排查数据。

命令一览

命令 用途
list 列出工作区及各自任务数(含显示名与孤儿显示名);未归入工作区的任务按原因分类计数
find <关键词> 标题/自定义标题模糊匹配,拿到 sessionId
move --to <路径> (--id <sid> \| --task <关键词>) 核心:迁移任务到目标工作区
check (--id <sid> \| --to <路径>) [--fix] 复核归属;--fix 把留在「独立任务」里的会话转正(仅 --id 分支有效,见下方陷阱)
troubleshoot (--id <sid> \| --task <关键词>) 任务看不见时逐项排查,每项给 PASS/WARN + 原因
restore --id <sid> [--to <路径>] 捞回被软删除的任务(客户端无回收站)
rename --id <sid> --title <新标题> 改任务标题;title 与 custom_title 一起写(见下)
move_artifacts.py --old-cwd <旧> --to <新> 单独搬产物(move --with-artifacts 内部也调它)
repair_index_pointers.py --old-root <旧> --new-base <新> 修复索引里指向旧目录的绝对路径指针
recycle_remove.py (--scan <根> \| --plan <json>) [--apply] 收尾清残留目录;默认只预演,--apply 才真删,且只进回收站(见文末「删除残留目录前的检查单」)

两个会误导你的行为(实测确认,2026-09-22)

1. check --to <路径> --fix 是静默空操作。

cmd_check 里 --fix 的 UPDATE 挂在 if rows: 下,而 --id 才填充 rows;
走 --to 时 rows = [],于是一行都不改、也不报错,只打印一次只读干跑
(=== 干跑:<路径> 工作区块内共 N 个任务 ===)。看起来"跑过了、没报错",
实际什么都没发生 —— 最坏的情况是任务仍留在「独立任务」里,而你以为是别的问题。

> 把一个任务的 cwd 已经在目标工作区目录、只是 is_playground=1 的情况转正(典型场景:
> 该目录本身已是别人的工作区),正确做法是 move --to <同一目录> --id <sid>,不要加 --with-artifacts:
> cmd_move 中 same_cwd=True → need_files=False(跳过文件搬迁,会话记录本来就在对的地方);
> is_playground=1 → need_db=True → 把 cwd 与 is_playground=0 一起写。
> 实测对比:check --to X --fix 只输出"工作区块内共 2 个任务";move --to X --id S 输出"将迁移"。
>
> (move_artifacts.run 里那句"旧目录与目标工作区相同,无需迁移"只会在带产物时触发,
> 不带 --with-artifacts 走不到那里,所以不会被拒。)

2. move --dry-run 不覆盖产物预检。

cmd_move 的 dry-run 分支打印完 [dry-run] 未做任何改动 就 return,
早于产物预检(if args.with_artifacts: 那段 peers_of 检查)与 move_artifacts.run 的调用。
于是 dry-run 打印的"将迁移"只证明会话侧三项校验通过(deleted_at / project_id / same_cwd),
不证明产物能搬,也不告诉你到底会搬走什么(print_report 里那行 [dry-run] 将搬迁:
属 move_artifacts.run 的输出,而这条路径根本没被调用)。

> 要真正预演产物搬迁,自己独立复算两件事:move_artifacts.peers_of(old_cwd, sid)
> (共用者数量,>0 且无 --force-artifacts 就会被拦)+ os.listdir(old_cwd) 顶层清单与体积。
> 别把 dry-run 的"OK"当成"可以放心执行"。

move 的常用附加参数:

--dry-run            只演练,不落盘不改库(动手前建议先跑一次)
--with-artifacts     连同旧目录的产物一并迁移
--layout per-task    每个任务一个子目录(默认) / flat 平铺进目标根目录
--subdir <名>        per-task 布局下的子目录名,默认取任务标题
--clear-project      把 project_id 置空,使其归入工作区而非「项目」分组
--allow-deleted      允许迁移已删除任务,并顺带清除删除标记
--touch              同时更新 updated_at(默认不动,保持最小改动)

核心机制(这四条决定了「为什么容易看起来没生效」)

1. 归属由两个字段共同决定,缺一不可

字段 作用
sessions.cwd 归到哪个工作区
sessions.is_playground 是否参与工作区分组;必须为 0

只写 cwd 是最经典的坑:库看起来对了、文件也搬了,但侧栏里任务仍在顶层的「独立任务」下面。历史遗留任务(自动创建的 WorkBuddy\<时间戳>\ 目录下的会话)绝大多数 is_playground = 1,这正是"移过去了但看不到"的头号原因。

反过来说,工作区列表是"从会话反推"的,不是一张独立清单(主进程
listSavedWorkspacePaths 拼 workspaces 表):

// 每个会话算出的 space.autoGenerated,决定它的 cwd 是否算一个"工作区"
const autoGenerated = summary.isPlayground ?? isAutoGeneratedWorkspace(cwd); // cwd 末段匹配 YYYY-MM-DD-HH-mm-ss
// 只收 type==="local" 且 autoGenerated !== true 的 cwd
paths.push(...items.filter(i => i.space.type === "local" && !!i.space.cwd && i.space.autoGenerated !== true).map(i => i.space.cwd));

两个推论,都是实操里会用到的:

  • 把一个工作区的任务全部移走,它就从侧栏消失了(该 cwd 不再被任何会话引用),无需清理目录;
    但 workspace-display-names.json 里的改名登记不会跟着删,成为无害孤儿(list 会单独列出)。
  • is_playground=0 的时间戳目录会显示成工作区(这就是本技能用 --with-artifacts 搬空后
    用户仍能在侧栏看到「XX」的原因);只有 is_playground=1 的时间戳目录才落入「独立任务」。

2. 会话记录放在按 cwd 派生的目录里,转义规则必须一次到位

C:\Users\demo\WorkBuddy\某工作区  ->  c-Users-demo-WorkBuddy-某工作区

必须一次性替换 : \ / 三者。分步 .replace() 会得到 c--Users-...(双短横线),目录定位落空 —— 表现为"库改了、文件没搬",任务点开是空的。

> 该规则只在 Windows 上验证过。为避免其他平台的转义差异,脚本按规则推导后还会按内容兜底反查(用 projects/*/<sid>.* 找真实目录),不把规则当唯一真理。

3. 产物文件不会跟着任务走,索引里存的是绝对路径

工作区就是文件夹。任务的历史输出(脚本、outputs、报告)留在旧工作区目录里。只改 cwd 是"半程迁移"。

搬完产物后,~/.workbuddy/ 下三套按 sessionId 落盘的索引仍存着旧绝对路径,不改就是死链(产物卡片点不开):

文件 字段
artifact-index/{sid}.json artifacts[].uri、artifacts[].fileChangeInfo.uri
file-tree-manifests/{sid}.json requests.<reqId>.files[].path(含 children[].path)
changes-index/{sid}.json changes[].files[].filePath

其中 artifact-index 是产物面板的 canonical 数据源,三套都要管。

4. 侧栏靠 30 秒轮询 diff 自动收敛,改库不需要重启

daemon 侧单例 ConversationListPoller(app.asar 内的 packages/workbuddy-core)负责把列表变化推给渲染层:

  • intervalMs 默认 30000;连续失败按 intervalMs * 2^n 退避,上限 300000。
  • 每轮拉一次整页列表(CONVERSATION_LIST_POLL_PAGE_SIZE = 100),与上轮快照按指纹做 diff,
    只在有 added / updated / deleted 时才广播 wb:conversations:listChanged。
  • 生命周期由订阅者引用计数驱动:第一个订阅者到来时起跑(schedule(0),即立刻跑一轮),
    最后一个退订时停表。渲染层通过 {event:"listChanged", active} 控制订阅 —— 列表可见时才订阅。
  • 源码注释里写得很直白:这类事件「覆盖跨进程/跨端的增删改 —— 这些变化不经过本地 SDK 的写操作」,
    而「跨进程/跨账号变化(比如小程序端归档)仍靠 poller 兜底」。

我们的做法(直连 sqlite 改 cwd / is_playground)正属于"不经过应用写路径",所以:
无需重启,最迟 30 秒后侧栏自动正确;若窗口失焦期间轮到停表,回到窗口时重新订阅会立刻补跑一轮。

> 边界(据源码,非实测):轮询每轮只拉首页 100 条(CONVERSATION_LIST_POLL_PAGE_SIZE = 100),
> 排序 COALESCE(last_activity_at, updated_at) DESC,所以排在该窗口之外的会话不在 diff 范围内,
> 改完可能等不到 30 秒收敛。实践上通常碰不到 —— 迁移不改排序位次,"改前在侧栏看得见"基本等价于"在窗口内"。
> 判据:若迟迟不变,先用 check --to 确认数据是否正确;数据已对而界面没动,就是视图窗口的问题,
> 让用户切走再切回该工作区(触发视图自己的重载),不是迁移失败。

> 曾经的经验教训:早先误以为"必须重启",是因为当时没修复 is_playground。
> 重启给了"修复生效"的错觉,真正的修复其实是改库那一刀。别把因果关系记反。
> 用户可以主动拒绝重启 —— 只要他已经在侧栏看到任务,就说明 poller 已经收敛,不必再折腾。

标准流程

第 1 步 · 定位任务

python wb_move.py find "用户说的标题关键词"

命中多个时,把候选列给用户让他指定(move 在歧义时会自己拒绝并要求明确,不会猜)。

第 2 步 · 确认目标工作区

python wb_move.py list

先看是否已有同名/近名的工作区。目标路径不必预先存在 —— move --to 会自动创建目录;一旦有任务归入,该目录就会作为工作区出现在侧栏。

用户给的是"空间名"时,先把它翻译成路径:侧栏名多数等于目录名,但被改过名的工作区另有
displayName(见「同名 ≠ 重复」)。list 现在会同时打印显示名;若显示名与路径对不上,
再读 ~/.workbuddy/workspace-display-names.json 反查。

这一步值得跟用户确认一次,因为新建工作区时目录名即侧栏名。若用户已明确给了路径,直接用。

第 3 步 · 迁移

python wb_move.py move --to "<目标路径>" --id <sid> --dry-run   # 先看计划
python wb_move.py move --to "<目标路径>" --id <sid>             # 再执行

> ⚠️ dry-run 只覆盖会话侧,不覆盖产物侧(见「两个会误导你的行为」第 2 条)。
> 要带产物迁移,dry-run 全绿不等于执行不会翻车 —— 执行前请自行复算
> move_artifacts.peers_of(old_cwd, sid) 确认共用者数量,并看一眼旧目录顶层清单与体积。
> 批量迁移时建议写一个执行器,把这些预检和逐任务日志一起管起来。

脚本自己会:备份数据库(WAL 下用 backup API 取一致性快照,不直接 copy 文件)→ 搬会话记录 → 改库(cwd + is_playground=0 一起写)→ 重新读库复核 → 做一次分组干跑。备份目录与 move-manifest.json 会打印出来,回滚有据。

第 4 步 · 复核

python wb_move.py check --to "<目标路径>"

列出会出现在该工作区块下的任务。空列表不是"应该没问题",是必须查 —— 用 troubleshoot 看是哪一项没过。

> 复核用 check --to,不要自己拼 SQL。Windows 路径里全是反斜杠,把它塞进 python -c "..."
> 的双引号串时,shell 会把 \\ 塌成 / 之类,得到 30 字符 vs 42 字符的字面量、判等恒为假 ——
> 于是"查不到会话"的假否定,让人误以为迁移失败。要么用本命令,要么把路径从 sys.argv 传进脚本。

第 5 步 ·(可选)产物一并迁移

python wb_move.py move --to "<目标路径>" --id <sid> --with-artifacts --layout per-task

先问用户摆放方式(per-task 分子目录 / flat 平铺),两者结构性差别大、不好事后改。

⚠️ 搬产物前必须确认旧目录是否被多个任务共用。 本技能最早的使用场景里每个任务独占一个自动创建的时间戳目录,搬空它没有副作用;但用户自建的工作区(WorkBuddy\我的项目\)下往往有几十个任务,目录里的产物属于所有这些任务,整体搬走会让留在原地的任务的产物卡片全部变成死链。

脚本会查库统计旧 cwd 下的其他未删除任务,数量 > 0 直接拒绝,必须在知情后加 --force-artifacts。这个预检在所有改动之前完成,不会留下半完成状态。

> 两个任务同处一个目录、又都要迁走时的实操顺序(否则会白挨一次拒绝):
> 预检是按单个 sid 排除自身的,所以 A、B 两个待迁任务会互相把对方当作"共用者"而双双被拒。
> 此时不必急着 --force-artifacts —— 强搬会让产物落到"被处理的第一个任务"的子目录里,
> 若它并非产物的真正主人,就成了张冠李戴。更稳的顺序是:
> 先迁走不带产物的那个任务,再迁带产物的那个。第二次迁移时对端已离开该 cwd,预检自然放行,
> 产物也就干净地落进它真正所属任务的子目录。
> 判断"谁带产物":看 ~/.workbuddy/artifact-index/{sid}.json 是否存在及其 artifacts 条目数。

内部还包括:修正产物脚本里硬编码的旧绝对路径(二进制改写,文本模式会把 LF 悄悄变成 CRLF,让字节校验失效)、把旧工作区自己的 .workbuddy/memory/* 并入目标工作区(同名加来源后缀)、逐文件比对字节数(路径变长带来的增量是可预测的,预言值对不上就是有文件被改坏)。

第 6 步 · 索引重指(搬过产物才需要)

python repair_index_pointers.py --old-root "<旧目录>" --new-base "<新目录>" --scan
python repair_index_pointers.py --old-root "<旧目录>" --new-base "<新目录>" --scan --verify

per-task 布局下必须把落点信息一起给出,否则记忆指针解析不了(记忆文件合并时加了来源后缀):

python repair_index_pointers.py --old-root "<旧目录>" \
    --to "<目标工作区根>" --subdir "<子目录名>" \
    --ws-root "<目标工作区根>" \
    --mem-dir "<目标工作区>\.workbuddy\memory" --mem-suffix "<子目录名>" \
    --scan

--scan 会扫三套索引、凡正文里出现旧目录的都处理。不要用 --id 逐个列 —— 按内容扫描不会漏。脚本幂等,可反复跑。

> ⚠️ --ws-root / --mem-dir / --mem-suffix 三个都不能省,漏一个就产生"假残留"。
> per-task 布局下 .workbuddy、.workbuddy\memory、.workbuddy\memoryutomations 这些是
> 工作区级的路径,真实落点在工作区根,不在任务子目录里;不给 --ws-root 它们一律解析不出,
> 会被 --verify 记成"仍指旧工作区",看着像脚本没修干净,其实是参数没给全。
> (2026-09-22 实测:某任务只给 --to/--subdir 时 15 条指针全报残留,补齐三个参数后 15/15 改写、残留 0。)

> ⚠️ 但"扫三套"这件事本身曾经是坏的,且症状极其安静 —— 修过一次,别改回去:
>
> 1. JSON 转义导致另两套索引全被漏选。 file-tree-manifests / changes-index 存的是裸
> Windows 路径,落在 JSON 正文里写作 C:\\Users\\...(双反斜杠)。若拿原始文本直接跟
> 单反斜杠的 needle 比对,这两套索引一个都匹配不上;而 artifact-index 存的是
> file:///C:/... 正斜杠形态,不受影响。于是症状是「artifact-index 修得好好的、另两套
> 明明报"无需处理"、实际全是死链」。匹配前必须先做一层 JSON 反转义(raw.replace("\\\\","\\"))
> 再解百分号编码、再统一分隔符。
> 2. .workbuddy/ 是工作区级的,不在 per-task 子目录里。 per-task 布局下 --new-base
> 是任务子目录,但 .workbuddy、.workbuddy\memory 这些目录指针的真实落点在工作区根。
> 若不给工作区根,它们在 --scan 里全被判成"未能解析"(本轮实测一次就漏 10 条)。
> 脚本现已按 --to(或显式 --ws-root)解析,规则是:.workbuddy\** → 工作区根;
> .workbuddy\memory\** → 目标工作区记忆目录(带来源后缀的候选优先)。
>
> 教训:不要拿"修复脚本自己的计数"当作完备性证明。修完后再独立复核一遍
> (--verify:重新读盘、只比指针键,见收尾第 4 条;别用 grep 扫全文——diff 正文必然命中,是假阳性)。

  • 只改指针字段:diff / patch / content / cachedContent / hunks / text 里是当年的真实记录,改了就是篡改历史;会话正文 projects/**/{sid}.jsonl 同理,一律不动。
  • 只改写"落点真实存在"的条目。迁移前就已被删除的临时文件,其指针无解 —— 保留原值并记账,不要为了"零残留"去编造路径。
  • uri 有四种形态,一个都不能漏:file:///C:/...(正斜杠)、file:///c:/...(小写盘符)、C:\...(裸路径)、file-changes://C%3A%5C...(整条百分号编码)。
  • "未能解析"里有一类是正常残留,不要硬修:同一个文件在索引里可能有多个历史版本条目
    (例如某脚本既有一条指向任务根的旧版本条目,又有一条指向 scripts\ 的新版本条目)。
    旧版本那几条指向的文件早已不存在,--verify 会报"仍指旧工作区"。不能把新版文件的落点配给旧版条目 —— 那是张冠李戴。
    判断方法:比对索引条目的 size / createdAt / updatedAt 与磁盘文件,不一致就说明不是同一个版本。
  • 已删除任务的索引也会被 --scan 一并处理。它引用的文件往往早被清理,解析不出落点 —— 属预期残留,
    不要硬修。识别方法:--verify 报的"仍指旧工作区"若全部落在某个 deleted_at 非空的 sid 上,
    且那些路径在磁盘上确实不存在(find 全盘也搜不到),就是这一类。
  • 第三类残留:.workbuddy\tmp / .workbuddy\.trash 根本没随迁(2026-09-22 实测)。
    move_artifacts 并入 .workbuddy 时只处理 memory/,tmp/(会话临时目录,如 tmp\eval_img\*.PNG)
    与 .trash/(应用自己的回收站)留在旧目录原地。于是索引里那些指针"物理文件还在、却解析不出落点" ——
    --verify 会把它记成"仍指旧工作区",与真正的"已删除文件"混在同一个计数里,
    只看数字会误判成脚本没修干净。识别与分诊:

python # rel 为去掉旧目录前缀后的相对路径 # 旧处仍在 → 属这一类;旧处已不存在 → 属前两类(预期的历史残留) os.path.exists(os.path.join(old_root, rel))

这一类不是脚本 bug,也不该硬修:指针仍指向旧目录,只要旧目录不删就一直有效。
真正的后果是它挡住了"删掉空壳时间戳目录"这一步 —— 想彻底收尾,必须先把这两个目录挪到工作区根
(.workbuddy 属工作区级,脚本的 candidates() 正好把 .workbuddy\** 映射到 ws_root),
挪完再跑一次 repair_index_pointers.py 即可自动解析(脚本幂等,第二次对已修好的条目改写 0 条)。
挪/删都属额外动作,要先跟用户确认 —— 它们是应用管理的临时目录,不是产物。

★ 旧目录一旦真的删掉,① 类(旧处文件仍在)会从"临时有效"变成"永久死链",而 --verify
的输出不会跟着变:它照旧报「仍指旧工作区 = N」,且照旧打印"① 旧目录里文件仍在…"的定性提示 ——
那句提示此时已经过时、会把你带偏(判据 os.path.exists(旧目录/<相对路径>) 此时恒假,
① 类不可能再存在,剩下的全是③④类死链)。实测(2026-09-22 第三轮):删掉 2 个 tmp/trash 外壳后,
两个会话的索引里仍留着数百条此类指针。
这类死链不影响功能(指向的文件早已不存在,UI 不会展示),只是校验噪音;要清就得按
「旧目录前缀」删索引条目,属写索引动作 —— 必须单独取得授权并先备份,不要顺手做。

实测量级供参考:数十个任务收拢共改写数千条指针,残留约三分之一,其中约 85% 都是
第三类(tmp/trash),其余是 outputs 内已删/改名与根目录临时脚本。若不分类,
会得出"脚本漏修了大量指针"的错误结论。

第 7 步 · 收尾

  1. 让用户直接看一眼侧栏。多数情况下 30 秒内已自动生效,不需要重启(见「核心机制 4」);
    只有久等仍不对,才建议重新加载窗口 —— 而且这时更该先怀疑数据(is_playground / deleted_at / project_id),
    用 troubleshoot 排查,别拿重启当解释。
  2. 告诉用户三件事:任务去了哪、产物是否一起搬了、备份在哪;
  3. 若搬过产物,一并说明索引已重指、以及有多少条指针因指向已删除的临时文件而无法修复。
  4. 独立复核索引 —— 不要拿修复脚本自己的计数当完备性证明(第 6 步那两个坑正是这样被漏掉的)。
    ⚠️ 不要用 grep -rl "<旧目录名>" 三套索引(2026-09-22 实测会必然误报):
    artifact-index 里每条的 fileChangeInfo.diff 是当年的 diff 正文,头部天生带旧路径,
    而它属 NEVER_KEYS、按铁律本就不该改。于是 grep 一定命中,与"有没有漏修指针"无关 ——
    实测 19 个目录里 grep 命中 14 个,而按指针键判定只有 4 个有残留。
    正确做法是按结构比"指针键":解析 JSON → 只取 PATH_KEYS(path/filePath/uri/…)减去 NEVER_KEYS(diff/patch/content/…)
    → 断言无一指向旧目录。直接跑脚本的 --verify 即可(它重新读盘、只比指针键,与改写逻辑不共享状态):

bash python repair_index_pointers.py --old-root "<旧目录>" --new-base "<新目录>" --verify

--verify 说"仍指旧工作区 = 0"才算这一类修干净。若仍非 0,核对它打印的 · 未能解析: <路径> 行,
按下方三类定性 —— 只有能归到那三类的才算"预期残留",归不进去就是真漏了。

批量收拢「独立任务」(侧栏一次清干净)

存量场景:侧栏有一堆「独立任务」,每个独占一个自动生成的时间戳目录(list 会报数量)。
它们通常都是干净可迁的,适合一次性收拢。此时不要一个个手敲命令。

先看清既有工作区的落点惯例(决定 --subdir 怎么取):

# 看某个工作区里既有任务的 cwd 长什么样
python wb_move.py list          # 再对照:这些任务的 cwd 是工作区根,还是工作区下的项目子目录?

实测到的惯例(2026-09-22):既有工作区里所有任务的 cwd 都等于工作区根目录
(如 …\某工作区),而项目文件夹(…\某工作区\某项目\)是工作区内部的子目录,
不是独立工作区。因此把任务迁进来时应统一用:

python wb_move.py move --to "<工作区根>" --id <sid> \
    --with-artifacts --layout per-task --subdir "<项目名>"

即 会话 cwd → 工作区根(这样才归入该工作区),*产物 → 工作区\项目名*。

批量执行要点(本技能不附带批量执行器,需自建;以下是数十个任务的实证做法):

  1. 一份 plan.json 记录 {n, sid, title, to, subdir},人可逐条勾选;
  2. 先跑独立预检(补 dry-run 的盲区):peers_of(old, sid) 共用者 + 旧目录顶层清单 + 体积;
  3. 再逐任务串行跑 move --dry-run,输出与日志落盘;
  4. 先小批再全量:随附脚本只支持逐任务(--id)执行,批量必须由你的执行器按序号分组,
    先跑几个确认无误再放开全量;
  5. 每个时间戳目录通常只装 1 个未删除会话 → peers_of 全为 0,无需 --force-artifacts。
    若两个待迁任务同处一个目录,预检会互相把对方算成共用者而双双被拒,处理顺序见第 5 步的提示。

记忆文件重名要提前算:merge_memory 现按相对路径递归搬迁,含 automations/<automation-id>/memory.md
(自动化自己的持久记忆)。早期版本只搬顶层文件、直接跳过子目录 —— 这类记忆被静默留在旧目录里,
而含文件的分支不会被 prune_empty 清掉,于是旧目录永远"看起来只剩空壳"却删不干净,
一旦手工删掉就把自动化记忆一起丢了。策略仍是"同名 → 加 .来源后缀 → 若还同名则 skip-exists(会丢)"。
重名只可能发生在"源目录与目标工作区
同一天**的日志"上,加后缀后仍重名的情形要在动手前逐条排掉
(做法:把每个旧目录的 .workbuddy/memory/* 与目标工作区的文件名列表做一次集合运算)。

演练后必须独立验"什么都没变",而不是相信脚本自称只读:比对 is_playground=1 计数、
各工作区计数、旧目录是否仍在、是否新建了目标子目录、~/.workbuddy 是否新增备份目录。

时间戳目录的第二类主人:自动化任务

判断一个"看起来是孤儿"的时间戳目录能不能动,不能看目录名像不像时间戳,要看谁在用。
把 sessions.cwd 与 automations.cwds(JSON 数组)对一遍,按三类处置:

情况 判据 处置
目录是某自动化的 cwd,且该自动化 deleted_at 非空 自动化已删 纯孤儿,按普通任务收拢/删除
同上,但自动化仍存活(deleted_at 为空) 定时还会跑 删了下次运行原样重建。要真正收拢,只能把该自动化的 cwds 改指到工作区根
目录名是时间戳,但本身就是某个工作区(有活跃会话) sessions.cwd 里有它 不可删,它是别人的家

改 cwds 必须走 automation_update 工具,绝不许改库(工具 list 只返回未删除的自动化,
所以"工具里查不到"本身就说明它已删 —— 但仍建议拿 automations.deleted_at 复核一次,两者应一致)。

> 实测(2026-09-22):三个时间戳目录里,一个其实是工作区「WB 积分领取」的根(3 个活跃会话,
> 纯属命名巧合)、两个的自动化已删、一个是仍存活的月度任务。只看目录名会做错决定。

删除残留目录前的检查单(2026-09-22 第三轮实证)

收拢收尾时,用户通常会要求"把剩的空目录一起删了"。删除是不可逆动作的放大器 —— 先证明"没人用它",再动手。

  1. 白名单写死。扫描只用于发现候选,删谁由人逐条确认;把清单硬编码进脚本,
    不要让 os.walk 的结果直接决定删除对象(否则目录结构一变就误伤)。
    > 实测:第三轮清完 40 项后,扫描根目录仍报出 2 个空目录 —— 正是用户明确要求"不动"的两个
    > 存活自动化目录
    。若拿扫描结果直接删,就会误删。
  2. 三项断言,缺一都可能误删:
    - 空性:目标是 files==0 and dirs==0。若它含空的子目录,要放宽为 files==0
    (实测乱码目录里挂着一个空 render\,用严格断言会被拦下 —— 看着像失败,其实是断言过严);
    - 白名单:目标不在保留名单里,且不是名单项的后代(防"删子目录时顺带端掉父目录");
    - 无引用:sessions 按 cwd 分组后,该 cwd 的存活会话数必须为 0
    (SUM(CASE WHEN deleted_at IS NULL THEN 1 ELSE 0 END) = 0)。这一步才是真正的安全闸 ——
    前两步只保证"删了不心疼",这一步保证"删了不出事"。
  3. 拿 automations.cwds 交叉核对:若该目录是某自动化的 cwd,先看 deleted_at。
    仍存活的自动化 → 目录不能删,删了下次运行会原样重建(见上一节)。
  4. 警惕"名字相近":实测同时存在 示例项目B(真实目录,有内容)与一个 mojibake 目录
    (璁捐 + 私用区字符 U+E178 + 浼樺寲,内部全空)。按子串匹配会把两个都删掉。
    正确做法:用互斥的特征字符识别乱码目录("\ue178" in name),并把真实目录写进白名单断言。
  5. 走回收站,不走 rm:SHFileOperationW(FO_DELETE | FOF_ALLOWUNDO),pFrom 是 \0 连接的
    多重路径串、结尾再加一个 \0。⚠️ PowerShell 侧的 Add-Type 在本机被安全策略拦下
    (提示 "compiles and loads .NET code at runtime")→ 改在 Python 侧用 ctypes 调 shell32 最省事。
  6. 分批(每批 ≤10 项)+ 每批复验:SHFileOperationW 返回 0 不代表每项都删掉了
    (被占用会静默跳过)→ 必须逐项 os.path.exists 复核,并把结果落 JSON 日志。
  7. 删完再扫一遍根目录:确认"只少了该少的",并专门回头看那几个相近名 / 白名单目录还在不在。

可复用实现:scripts/recycle_remove.py(白名单 + 三断言 + 回收站 + 分批 + 日志,计划走 JSON)。

必须遵守

  • 改库前一定备份(脚本已自动做,不要绕过)。
  • 只改 cwd 不算迁移。is_playground=0 必须同时写。
  • deleted_at 非空的会话迁移后仍然不可见。要么先用 restore 捞回,要么用 --allow-deleted 连删除标记一起清 —— 但清删除标记是比迁移更重的动作,要跟用户说清楚。
  • project_id 非空的会话会归到「项目」分组而不是工作区。迁移前提示用户是否需要 --clear-project,不要静默改。
  • 不要动 workspaces 表。它只影响"最近打开"排序,归属只看 sessions.cwd;贸然插行可能造出重复工作区。另:把一个工作区的任务全部移走后,它会自动从侧栏消失,无需清理。
  • 不要张口就让用户重启。侧栏 30 秒轮询会自动收敛(核心机制 4);把"看不到"直接归因于"没重启"会掩盖真正的数据问题(is_playground 没改 / deleted_at 非空 / project_id 非空),也白白消耗用户的耐心。先 check,再 troubleshoot。
  • 不要用本技能删任务。删除是软删除(写 deleted_at 毫秒时间戳),客户端没有回收站也没有恢复入口,只能改库捞回。
  • 调用脚本时不要接管道(| head、| tail)。实测本机 MSYS 环境会偶发
    head:\ ls:\ dirname: command not found(PATH 解析异常),管道会让整条命令失败并掩盖真实输出;
    要截断输出就在 Python 侧做。同理,带反斜杠的路径一律走 --to / --old-root 参数,
    不要塞进 python -c "..."
    (shell 会把 \\ 塌成 /,得到长度都不对的内联字面量,判等恒假 → "查不到会话"的假否定)。

验证方式:沙盒演练

不要在真实数据上试错。scripts/ 支持用环境变量把技能指向一份副本:

# 复制 ~/.workbuddy 到某处,然后
WB_CFG_DIR=/path/to/copy/.workbuddy python wb_move.py list

配合一个清空过 sessions 表、只插入合成数据的副本,就能把 move / check / with-artifacts / repair_index_pointers / restore 整条链路端到端跑一遍,全程不碰真实会话。

给维护者:本技能的泛用性约定

本技能要给别人用,因此不得出现:

  • 任何本机绝对路径(含用户名)、任何具体工作区名或任务标题;
  • 要求使用者先手工查 SQL 拿到 sessionId、再把值填进源码 —— 参数化必须走命令行;
  • 对"一个任务独占一个目录""旧目录可以随便搬空"这类特例的默认假设(见第 5 步的共用目录预检)。

新增脚本时沿用 wb_common.py 的路径/DB/分组判定,不要另起一套转义或连接逻辑。

参考

  • references/mechanism.md —— 机制依据:字段语义、渲染层判定、后端查询、目录转义、索引结构。
  • references/troubleshooting.md —— 「看不到任务」的逐项排查、已删除任务的捞回、常见误判。

常见问题

5 问
答案由本站依据条目字段整理
WorkBuddy 任务跨工作区迁移怎么安装?

在支持 Skills 的客户端里执行:skillhub install user_e9d33879/workbuddy-task-workspace-move。安装方式为未标注。装完刷新技能列表或重开客户端。

WorkBuddy 任务跨工作区迁移支持哪些客户端?

资料没有标注宿主客户端,但只要客户端支持 Skills(Claude、Codex、Cursor 等),一般都能加载。

WorkBuddy 任务跨工作区迁移权限高吗,安全吗?

权限级别为未标注权限。安装前先看它会读取或写入哪些内容。

WorkBuddy 任务跨工作区迁移收费吗?

本站不代收费用,也没有标注具体价格。是否收费取决于技能本身和所用的模型或客户端,正式使用前按作者说明确认。

WorkBuddy 任务跨工作区迁移用什么许可证?

许可证标注为 未说明(许可证不明确)。商用或二次分发前请到仓库确认条款原文。

热门专题

按场景挑技能
全部专题 →

技能内容以作者仓库为准;分类、权限级别与许可证状态的中文说明为中文整理,最近核验 未标注。