Awesome Copilot 的 where-was-i 插件:用 Git 上下文画布实现开发中断快速恢复
【免费下载链接】awesome-copilotCommunity-contributed instructions, agents, skills, and configurations to help you make the most of GitHub Copilot.项目地址: https://gitcode.com/GitHub_Trending/aw/awesome-copilot
本篇文章深入解析 GitHub Copilot 插件市场 Awesome Copilot 社区中的 where-was-i 插件:它是一款面向「中断恢复」场景的交互式 Canvas 扩展,能自动重建你离开时的开发上下文(当前分支、工作区提交、未提交改动、打开中的 PR/Issue 线索),并在画布上一键把完整上下文发送给 Copilot Agent,生成针对性的续接提示。读完本文你将掌握该插件的安装方法、四个核心 Agent 动作的职责、底层 Git 上下文采集的实现原理、安全设计以及测试验证方式,并可直接在 Copilot CLI 中投入使用。
一、插件定位:给中断的开发状态拍一张「快照」
在日常开发中,中断随时发生——开会、处理告警、切换任务,回来时往往要花很长时间回忆「我刚才在哪个分支、改到哪了、还有哪些事没做完」。where-was-i 插件解决的正是这一痛点。
根据 插件元数据,它的官方描述是:
Reconstruct your dev context (branch, commits, uncommitted work, PR clues) and trigger a resume prompt to continue quickly.
即:重建你的开发上下文(分支、提交、未提交工作、PR 线索),并触发一个续接提示以快速继续。该插件由 Aaron Powell 维护,当前版本 1.1.0,关键词覆盖branch-state、developer-context、git-history、interrupt-recovery、pull-request-context、resume-work等场景标签。
从结构上看,它是一个典型的 Copilot Extension + Canvas 组合:扩展入口位于 extensions/where-was-i/extension.mjs,核心的 Git 采集逻辑独立在 extensions/where-was-i/git-context.mjs,并有配套测试 extensions/where-was-i/git-context.test.mjs。插件目录 plugins/where-was-i/README.md 则面向使用者提供安装说明。
二、安装与启用
Awesome Copilot 本身是 Copilot CLI 与 VS Code 的默认插件市场(详见 docs/README.plugins.md),无需额外配置市场源。where-was-i 的安装与所有 Awesome Copilot 插件一致,使用 Copilot CLI 执行:
copilot plugin install where-was-i@awesome-copilot安装完成后,在 Copilot CLI 会话中即可找到并使用该插件。也可以在 VS Code 中打开扩展搜索视图(输入@agentPlugins)或通过命令面板运行Chat: Plugins浏览并启用。插件本身以 MIT 协议开源。
三、工作流程:从「重建上下文」到「一键续接」
整个插件的交互流程可以概括为四个阶段,均可以在 extension.mjs 中找到对应实现:
- 打开画布:
open回调启动一个本地 HTTP 服务器,并调用gatherContext()立即采集一次当前工作区的 Git 上下文,返回画布 URL(extension.mjs)。 - 渲染上下文:浏览器端通过
/context接口获取上下文 JSON,渲染出分支栏、提交列表/Git 图、未提交变更列表、打开的 PR/Issue 卡片。 - 按需刷新:点击画布中的 Refresh 按钮或触发
refreshAgent 动作,会重新采集并推送最新数据。 - 一键续接:点击「Resume where I left off」按钮,或点击某张 PR/Issue 卡片,插件把完整上下文(或该线程上下文)组装成一段续接提示,通过
sessionRef.send(prompt)直接发送给当前 Copilot 会话,让 Agent 基于真实状态给出「接下来先做什么」的建议。
四、Git 上下文采集:四个 Agent 动作与数据模型
插件在 Canvas 上注册了四个 Agent 动作(actions),外部 Agent 或模型可以随时调用:
| 动作名 | 职责 | 关键实现 |
|---|---|---|
refresh | 重新采集全部 git/项目上下文并推送更新到画布 | 调gatherContext(cwd),更新缓存并broadcast |
get_context | 返回当前已组装的开发上下文(JSON) | 直接返回contextCache中缓存的数据 |
get_file_diff | 返回某个变更文件的 staged / unstaged / untracked 补丁 | 调getFileDiff(cwd, path, code) |
resume | 向 Agent 发送带开发状态的「续接」消息 | 组装提示词并sessionRef.send(prompt) |
4.1 上下文数据结构
gatherContext()在 extension.mjs 中把两部分数据合并为一份完整上下文:一部分来自gatherGitContext()(Git 仓库状态),另一部分通过ghCLI 并发查询当前用户打开的 PR 与指派给自己的 Issue(各取前 10 条,分别调用gh pr list --author=@me --state=open --limit=10与gh issue list --assignee=@me --state=open --limit=10)。即使gh未安装或未登录,PR/Issue 查询失败也不会阻断 Git 上下文采集——这里使用Promise.allSettled容错,失败信息会汇总进warnings数组,最终以 JSON 形式返回,包含:
worktreeRoot/worktreeName:仓库根目录与目录名;branch:当前分支(detached HEAD时为 null);head:当前 HEAD 短哈希(空仓库时为 null);baseRef:基准分支(依次尝试origin/HEAD、origin/main、origin/master、main、master,见 git-context.mjs);ahead/behind:与基准分支的分叉计数;branchCommits:当前分支领先于 merge-base 的提交;recentCommits:最近 10 条提交;commitGraph:带 ASCII 图形的提交历史(最多 40 条,含--decorate引用标注);uncommitted/changes:解析后的未提交变更(含状态码、路径、重命名原路径);diffStat/stagedDiffStat/unstagedDiffStat:diff 统计;openPrs/assignedIssues:打开的 PR 与指派 Issue;gatheredAt:采集时间戳。
4.2 状态解析:porcelain -z 与重命名处理
未提交变更的解析是上下文可靠性的关键。git-context.mjs 中定义了采集命令git status --porcelain=v1 -z --untracked-files=all,随后由parseStatusOutput()按 NUL 分隔解析。使用-z模式的核心收益是:路径永远不会被引号包裹,含空格、特殊字符的文件名都能被准确还原;同时对重命名/复制条目(状态码含R/C)会顺带读取紧随其后的ORIG_PATH字段(git-context.mjs),从而保留旧路径 -> 新路径的完整信息。
测试 git-context.test.mjs 专门验证了含空格的路径与git mv重命名的解析,断言 diff 中必须出现rename from/rename to。
4.3 边界情况:空仓库、SHA-256 与基准分支
- 未出生分支(fresh
git init):没有 HEAD 提交时,head为空、baseRef为 null、提交相关字段为空数组,但暂存与未跟踪文件仍会采集,diff 统计则以空树对象(hash-object -t tree --stdin输入空串)为基线计算(git-context.mjs)。对应测试见 git-context.test.mjs。 - SHA-256 对象格式仓库:不会硬编码 SHA-1 空树哈希,而是动态计算,测试覆盖了
git init --object-format=sha256场景(git-context.test.mjs)。 - 基准分支探测:
resolveBaseRef()会逐一对候选 ref 执行rev-parse --verify --quiet,只返回真实存在的引用,避免在只有main没有origin的本地仓库中误判。
4.4 Git 图的可折叠历史
为让画布聚焦当前分支的提交,splitCommitGraph()计算「基准历史折叠点」:由于--topo-order可能把更新的基准提交穿插在分支提交之上,折叠点必须取最后一个分支提交之后的第一个基准提交行(git-context.mjs)。测试断言了分叉图上分支提交永远不会落入折叠区(git-context.test.mjs),保证用户始终能一眼看到自己分支的提交,而早期基准历史被收进可展开的<details>区域。
五、画布界面与交互
插件的 HTML 界面由 renderHtml() 渲染,采用 DM Sans 与 IBM Plex Mono 字体,白底浅色卡片风格。界面从上到下依次展示:
- 状态区:标题「Where was I?」、基于
gatheredAt计算的中断时长标签(如 "You're still in the zone"、"Away for 2h 15m")以及 Refresh 按钮; - 分支栏:当前 Worktree 名称、分支名、以及相对基准分支的
N ahead · M behind分叉信息; - Git 图 / 最近提交:优先展示带装饰引用的 Git 提交图(工作区提交高亮),无图时降级为最近提交列表;
- 未提交变更:每个文件一行,带状态徽章(Modified / Staged / Added / Deleted / Renamed / Untracked / Conflicted),点击「View diff」可弹出右侧抽屉查看该文件的完整补丁;
- 打开中的线程:当前用户打开的 PR(PR 徽章)与指派给自己的 Issue(Issue 徽章),点击即可针对该线程发起续接;
- 续接区:醒目的「↩ Resume where I left off」按钮。
前端通过 SSE(/events接口)订阅上下文推送,refresh动作或服务端广播会实时更新画布内容,无需手动刷新页面(extension.mjs)。
六、续接提示的组装
点击「Resume」或某张线程卡片时,服务端在/resume接口中根据当前上下文动态组装提示词(extension.mjs):
- 普通续接:包含 Worktree、Branch、Worktree commits、Uncommitted changes、Diff stat、Open PRs、Assigned issues,结尾明确要求 Agent「Help me pick up where I left off. What should I focus on first?」;
- 线程续接:以
I was working on #123: 标题 and got interrupted...开头,聚焦指定 PR/Issue,让 Agent 优先处理该线程。
组装完成后调用sessionRef.send(prompt)把提示注入当前 Copilot 会话,从而把「人肉回忆上下文」变成「Agent 基于事实数据给出续接建议」。resumeAgent 动作内部也实现了同样的组装逻辑,可供 Agent 程序化调用(extension.mjs)。
七、安全设计:本地服务、令牌校验与路径防护
Canvas 界面运行在插件启动的本地 HTTP 服务器上(仅监听127.0.0.1),并对每个实例生成 32 字节随机令牌(randomBytes(32).toString("base64url"))。所有请求必须携带该令牌(URL 查询参数k),并用timingSafeEqual进行恒定时间比较,同时校验Host与Origin头,杜绝跨站请求伪造(extension.mjs)。
页面本身使用严格的 CSP(default-src 'none'、脚本仅允许 nonce、connect-src 'self')防止注入。文件 diff 接口则有路径穿越防护:assertRepositoryPath()会把请求路径解析后重新relative()回仓库根目录,任何逃逸出工作区的路径都会抛出「The requested file must be inside the current worktree」错误(git-context.mjs)。此外,未跟踪文件预览拒绝跟随符号链接——lstat检查到 symlink 时只展示链接目标文本本身,而不是解引用读取目标文件,测试用指向仓库外secret.txt的链接验证了敏感内容不会被泄露(git-context.test.mjs)。未跟踪文件还有 512 KB 的大小上限与二进制内容检测,防止把超大或二进制文件渲染进 diff。
八、测试与质量保障
插件对最容易出错的 Git 解析逻辑编写了完整测试套件,运行方式为:
node --test extensions/where-was-i/git-context.test.mjs测试覆盖的关键场景包括:
- 分支提交、staged/unstaged/untracked 变更的完整采集与 diff 正确性(git-context.test.mjs);
- 含空格路径与重命名的解析;
- 特殊字符文件名(如
[ab].txt)使用--literal-pathspecs作为字面量处理,避免被误当作 glob(git-context.test.mjs); ..notes这类以双点开头的合法仓库文件名;- 未出生分支与 SHA-256 仓库;
- 未跟踪符号链接不解引用、可执行位保留(
100755); - 同一路径同时出现 staged 删除与 untracked 重建时,可按状态码精确选取对应 diff 记录(git-context.test.mjs)。
九、典型使用场景
- 中断后快速续接:开会、处理紧急事务回来,打开 where-was-i 画布即可一眼看到分支、提交与未提交改动,点击 Resume 让 Agent 直接给出下一步行动建议;
- 多任务切换:通过打开中的 PR/Issue 卡片,定位到多个并行任务的各自线程,按线索逐条续接;
- 交接与自检:在开始新任务或提交前,用画布核对当前工作区是否有遗漏的未提交改动、是否有被遗忘的分支提交尚未推送。
十、小结
where-was-i 是 Awesome Copilot 插件生态中一个聚焦「上下文恢复」的轻量而完整的范例:它以 Git 仓库为单一事实来源,通过精心设计的 porcelain 解析、基准分支探测、图折叠与容错查询,把开发中断时的散落状态汇总成结构化上下文;再通过 Canvas 界面与resume动作,把这份上下文无缝注入 Agent 会话,实现从「回忆状态」到「直接续接」的工作流闭环。无论你是想在日常开发中提升中断恢复效率,还是想参考一个完整的 Copilot Canvas 扩展实现(本地服务、SSE 推送、令牌鉴权、Agent 动作注册),这个插件都值得直接上手尝试。
【免费下载链接】awesome-copilotCommunity-contributed instructions, agents, skills, and configurations to help you make the most of GitHub Copilot.项目地址: https://gitcode.com/GitHub_Trending/aw/awesome-copilot
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考