1. 项目概述与核心价值

如果你和我一样,在日常开发中重度依赖像 VS Code Copilot、Claude Code 或 Cursor 这类 AI 编程助手,那你一定遇到过这个痛点:一次精彩的、充满曲折推理的对话,在你关闭编辑器或开始新会话后,就彻底消失了。那些尝试过的错误路径、关键的自我修正、以及最终促成解决方案的“灵光一现”,全都留在了 AI 的短期记忆里,无法追溯。下次遇到类似问题时,你只能从头再来,或者花费大量时间在聊天记录里翻找。Contrails 就是为了解决这个问题而生的。它是一个开源桌面应用,名字来源于飞机在高空留下的“凝结尾迹”,寓意着为你的 AI 编程助手会话留下可追溯的“轨迹”。

简单来说,Contrails 会像一个安静的观察者,在后台实时监控你指定的项目目录。一旦检测到上述 AI 助手生成了新的会话文件(比如 VS Code Copilot 的 .jsonl 日志、Claude Code 的 .jsonl 转录文件,或是 Cursor 的 SQLite 数据库更新),它就会立刻解析这些文件,将其转换成清晰、易读的 Markdown 文档,并自动保存到你项目仓库的 contrails/ 目录下。这样一来,所有 AI 参与的思考过程、代码迭代和决策逻辑,都变成了项目历史的一部分,可以像普通文档一样被提交到 Git 中,供未来查阅和复用。

2. 核心功能与工作原理深度解析

Contrails 的核心价值在于其“无感”的自动化记录能力。它不是让你手动复制粘贴聊天记录,而是通过一套精巧的文件系统监控和解析引擎,自动完成捕获、转换和归档的全流程。下面我们来拆解它是如何为三种主流 AI 编程助手工作的。

2.1 对 VS Code Copilot 的支持机制

VS Code Copilot 的聊天会话数据,以事件流的形式存储在用户配置目录下的 workspaceStorage 中。每个 VS Code 工作区对应一个随机 ID 的文件夹,里面可能包含一个 chatSessions/ 子目录,存放着以会话 UUID 命名的 .jsonl 文件。

1. 项目发现与监控 当你通过 Contrails 添加一个项目时,它会扫描系统标准的 VS Code workspaceStorage 路径(macOS: ~/Library/Application Support/Code/User/workspaceStorage/ , Windows: %APPDATA%\Code\User\workspaceStorage\ , Linux: ~/.config/Code/User/workspaceStorage/ ),寻找包含 chatSessions/ 的文件夹。找到后,它会读取同目录下的 workspace.json 文件,来解析出对应的工作区路径和项目名称。添加成功后,Contrails 会使用 Go 的 fsnotify 库对这个 chatSessions/ 目录进行实时监控。

2. 智能增量处理与防误触 这里有一个非常实用的设计:为了避免在首次添加项目时,一次性处理大量历史会话文件导致卡顿,Contrails 会为每个项目记录一个 lastProcessedAt 时间戳。当项目首次被添加时,这个时间戳被设置为“现在”。此后, fsnotify 只会触发处理那些修改时间晚于 lastProcessedAt 的新文件或变更文件。如果你想处理所有历史会话,需要在项目详情页手动点击“Process All Now”。这个设计平衡了实时性和性能。

3. 复杂的事件日志解析 Copilot 的 .jsonl 文件是一种版本 3 的事件日志,并非最终状态的快照。Contrails 的解析器需要“重放”这些事件来构建完整的会话。日志中包含三种事件:

  • kind: 0 : 初始状态快照。
  • kind: 1 : 对某个键路径的标量值修补(例如修改会话的 customTitle )。
  • kind: 2 : 对数组的修补,包含拼接操作(例如向 response 数组追加新的消息片段)。

解析器会先“物化”出会话的最终状态。为了获得最准确的对话文本,它采用了一种双策略:对于已完成的请求,优先使用 result.metadata.toolCallRounds 作为权威文本来源。这是因为流式传输协议在处理数组拼接时可能会丢失文本片段,而 toolCallRounds 保存了完整的叙述。工具调用、文件编辑和思考块等细节,则从去重后的 response[] 数组中提取,并通过位置与 toolCallRounds 中的条目进行关联。对于仍在进行中的请求(没有 toolCallRounds ),解析器会回退到直接遍历 response[] 数组。

4. 输出与“康复”机制 解析后的会话会被写入 {项目路径}/contrails/ 目录下的 Markdown 文件。文件名优先使用会话的 customTitle ,如果未设置则回退到会话 UUID。Contrails 还实现了一个“康复”逻辑:在应用启动或执行“Process All Now”时,它会扫描 contrails/ 目录下所有以会话 UUID 命名的 .md 文件。如果发现对应的源 .jsonl 文件现在有了 customTitle ,它会自动重新处理并重命名该文件,确保归档的文档有一个友好的名称。

实操心得:文件删除处理 一个值得称赞的细节是,当源 .jsonl 文件被删除时(比如 Copilot 自动清理旧会话),Contrails 不会删除对应的 Markdown 文件,而是在文件顶部添加一个明显的“此会话已被删除”的横幅标记。这保留了历史记录的连续性,让你知道曾经有过这段对话,只是源文件不见了。

2.2 对 Claude Code 的钩子与信号机制

Claude Code 的工作方式与 VS Code 扩展不同,它更像一个独立的桌面应用,会话数据存储在 ~/.claude/projects/ 目录下。Contrails 采用了“钩子+信号”的机制来捕获其会话结束事件。

1. 项目发现 Contrails 会扫描 ~/.claude/projects/ 目录,寻找包含 .jsonl 会话文件的文件夹。你也可以通过浏览任意磁盘目录来手动添加包含 Claude Code 项目的文件夹。

2. 钩子安装与守护 这是最精巧的部分。当你添加一个包含 Claude Code 源的项目时,Contrails 会向该项目的 {项目路径}/.claude/settings.local.json 文件中写入一个“Stop hook”。这个钩子是一个 JavaScript 函数,它会在 Claude Code 会话结束时被调用。钩子的作用是将当前会话的元数据(工作目录、转录文件路径、会话ID)写入一个信号文件,存放于 ~/contrails/hook-signals/ 目录下。

为了保证钩子持久有效,Contrails 内部运行着一个 HookEnforcer 守护进程,每 5 秒检查一次钩子文件是否存在且内容正确。如果发现钩子被删除或篡改(比如 Claude Code 更新时重置了配置),它会自动重新安装。只有当你从项目中显式移除 Claude Code 源时,钩子才会被清理。

3. 信号监听与处理 Contrails 启动一个 SignalWatcher ,使用 fsnotify 监控 ~/contrails/hook-signals/ 目录。一旦有新的信号文件出现, SignalWatcher 会读取文件内容,根据其中的工作目录路径匹配到已注册的 Contrails 项目,然后解析对应的 Claude Code 转录文件(.jsonl),并生成 Markdown 文档。

4. 解析与输出 Claude Code 的转录文件格式相对直接,每行是一个 JSON 对象,代表一条消息,包含 role (human/assistant)和 content 块数组(可能是 text、tool_use、tool_result、thinking 等)。Contrails 的解析器会按顺序提取完整的对话,包括工具调用、推理过程和文件编辑内容。输出 Markdown 的标题通常取自第一条用户消息。

注意事项:钩子的持久性 这个设计意味着,即使 Contrails 应用当时没有运行,钩子也已经安装。当下一次 Claude Code 会话结束且 Contrails 启动时,它仍然能处理之前积累的信号文件。但需要注意的是,如果信号文件生成时 Contrails 未运行,它无法进行实时处理,只会在下次启动后处理积压的信号。

2.3 对 Cursor 的双数据库解析策略

Cursor 作为基于 VS Code 的衍生品,其数据存储方式更为复杂,它使用了两个 SQLite 数据库来管理会话数据,因此 Contrails 的解析策略也最为独特。

1. 项目发现 类似于 VS Code,Contrails 会扫描 Cursor 的 workspaceStorage 目录,寻找每个工作区对应的 state.vscdb 文件,并通过 workspace.json 解析出工作区路径。

2. 双重监控与防抖 Cursor 的数据写入非常频繁。Contrails 需要同时监控两个位置:

  • 工作区级数据库 :每个工作区下的 state.vscdb 文件,主要存储 composer(会话)列表。
  • 全局数据库 :位于 Cursor 配置目录下 globalStorage/ 中的 state.vscdb 文件。所有会话的详细消息内容(bubbles)都写在这里,无论哪个工作区发起的会话。

Contrails 使用 fsnotify 监控这两个路径。由于 Cursor 的写入是爆发式的,Contrails 引入了 2 秒的防抖机制:当检测到文件变更后,会等待 2 秒没有新事件才触发处理,以避免不必要的重复解析。

3. 复杂的 SQLite 数据关联解析 解析器需要关联两个数据库的信息:

  1. 工作区数据库 ItemTable 中,读取 composer.composerData 键对应的值,获取该工作区下的 composer(会话)列表及其元数据(如会话名称)。
  2. 全局数据库 cursorDiskKV 表中,提取两种关键数据:
    • composerData:{composerId} :包含会话元数据和 fullConversationHeadersOnly 数组,这个数组提供了消息的权威顺序。
    • bubbleId:{composerId}:{bubbleId} :存储每条消息(气泡)的详细内容,包括用户消息(type=1)、AI消息(type=2)以及其中包含的富媒体内容(如工具调用、思考块、代码片段)。这些内容通过 capabilityType 等字段编码,需要额外解码。

解析器会按照 fullConversationHeadersOnly 提供的顺序,依次读取对应的 bubble 数据,重建完整的对话流。

4. 输出 最终,解析出的会话会以同样的 Markdown 格式输出到项目的 contrails/ 目录。文件名优先使用 composer 的 name 字段。

踩坑记录:全局数据库是关键 最初尝试只解析工作区数据库时,会发现只能拿到会话列表,拿不到具体的对话内容。必须同时读取全局数据库的 cursorDiskKV 表,才能拼凑出完整的对话。这是理解 Cursor 数据模型的关键。

3. 从零开始:安装、配置与核心工作流

理解了原理,我们来看看如何将它用起来。Contrails 提供了 macOS、Windows 和 Linux 三端的原生应用,开箱即用。

3.1 各平台安装详解

macOS

  1. 从 GitHub Releases 页面下载 Contrails-macos.zip
  2. 解压后,将 contrails.app 拖拽到“应用程序”文件夹。
  3. 关键一步 :在终端中执行 xattr -cr /Applications/contrails.app 。这是因为 macOS 对未经过公证(Notarization)的开发者应用有安全限制,此命令可以移除这些限制属性。项目作者也坦诚地指出,苹果的公证服务每年需要 99 美元,这对独立开发者是一笔不小的开销,因此目前采用了这个变通方案。
  4. 之后就可以像启动普通应用一样启动 Contrails 了。

Windows

  1. 下载 Contrails-windows.zip 并解压。
  2. 直接运行解压出的 contrails.exe 即可。Windows 通常没有额外的安全限制。

Linux

  1. 下载 Contrails-linux.zip 并解压。
  2. 在终端中,进入解压目录,执行 chmod +x contrails 赋予可执行权限。
  3. 运行 ./contrails
  4. 依赖检查 :Contrails 基于 Wails 框架,需要 WebKit2GTK 和 GTK3 运行时库。在 Ubuntu/Debian 上,可以运行 sudo apt install libgtk-3-0 libwebkit2gtk-4.1-0 来安装。大多数现代 Linux 桌面发行版已经预装了这些库。

3.2 核心工作流:添加与管理项目

安装并启动后,你会看到一个简洁的侧边栏和主界面。核心操作就是添加项目。

  1. 添加项目 :点击侧边栏的 “+” 按钮,会弹出“添加项目”对话框。Contrails 会自动扫描系统中已存在的、支持 AI 助手的工作区:

    • VS Code/Cursor 工作区 :它会列出在 workspaceStorage 中发现的所有有效工作区。
    • Claude Code 项目 :它会列出在 ~/.claude/projects/ 中发现的项目目录。 你也可以点击“浏览”手动选择磁盘上的任意文件夹作为项目根目录。
  2. 项目配置 :选择项目后,你需要为其命名(默认会使用文件夹名或工作区名),并确认输出目录(默认为 {项目路径}/contrails/ )。一个项目可以同时包含多种 AI 助手源。例如,如果你在一个项目里既用 VS Code Copilot 又用 Claude Code,Contrails 会同时监控两者,并将所有会话统一归档到同一个 contrails/ 目录下。

  3. 状态监控 :添加成功后,项目会出现在侧边栏列表。项目名称旁边有一个状态指示点:

    • 绿色圆点 :表示正在监控中。
    • 灰色圆点 :表示监控已暂停。 你可以通过右键菜单对项目进行“重命名”、“暂停/恢复监控”、“立即处理所有会话”或“移除”操作。
  4. 查看成果 :在主界面选择项目后,你可以看到该项目的详细信息,包括监控路径、输出路径、已发现的会话源类型等。所有生成的 Markdown 文件都会保存在你配置的输出目录(默认是 contrails/ )下。文件命名清晰,内容结构良好,包含了完整的对话、代码差异和 AI 的思考过程。

3.3 输出文件管理与版本控制

生成的 Markdown 文件设计得非常友好,适合直接纳入版本控制。

  • 目录结构 :所有会话文件都平铺在 contrails/ 目录下,没有多层嵌套,便于浏览。
  • 文件命名 :优先使用会话标题(如 实现用户登录功能.md ),无标题时使用会话ID(如 session_abc123.md )。
  • 内容格式 :Markdown 内容清晰地区分了用户消息、AI 回复、思考过程、工具调用和代码块。代码差异会以 diff 格式呈现,可读性极高。
  • 提交建议 :强烈建议将 contrails/ 目录添加到你的 .gitignore 排除规则中,而是选择性地将有价值的会话记录提交到仓库。你可以将其视为一种“开发日志”或“决策记录”,只提交那些对理解项目关键决策有重要意义的对话。

4. 高级特性与实用技巧

除了核心的监控与转换功能,Contrails 还包含一些提升体验和可靠性的高级特性。

4.1 匿名遥测与隐私控制

Contrails 集成了 PostHog 用于收集匿名使用数据,目的是帮助开发者了解应用的整体使用情况(如活跃用户数、创建的会话轨迹数量、各助手的使用比例等)。

  • 收集什么 :仅收集聚合指标,如“应用启动”、“项目添加”、“会话轨迹创建”。 绝不收集任何个人数据、文件内容、对话文本或项目路径
  • 设备标识 :首次启动时会在本地生成一个随机 UUID 作为设备标识,存储在 ~/.config/contrails/device_id 。没有账户系统,无法关联到个人。
  • 完全可控 :你可以在侧边栏底部找到一个“Telemetry on/off”开关,随时开启或关闭遥测。这个偏好设置会立即保存并在重启后生效。
  • 故障安全 :所有遥测调用都是非阻塞的。如果 PostHog 服务不可用,事件会被静默丢弃,绝不会影响应用功能。
  • 开发构建 :在本地开发构建( wails dev )或没有注入 API 密钥的构建中,遥测功能会被完全禁用。

4.2 自动更新检查

Contrails 在启动时会自动检查 GitHub Releases 页面,看是否有新版本发布。如果检测到更新,界面会显示一个一键更新按钮。点击后,应用会自动下载新版应用包(如 macOS 的 .app 包)并进行原子化替换,然后重启应用。这确保了用户能方便地获取功能改进和错误修复。

4.3 配置与数据存储

了解数据存储位置有助于调试和备份。

  • 项目列表 :保存在平台的标准配置目录下。
    • macOS: ~/Library/Application Support/contrails/projects.json
    • Windows: %APPDATA%/contrails/projects.json
    • Linux: ~/.config/contrails/projects.json
  • 遥测设置与设备ID
    • ~/.config/contrails/settings.json :存储 analyticsEnabled 布尔值。
    • ~/.config/contrails/device_id :存储设备 UUID。
  • Claude Code 信号文件 :临时信号文件存储在 ~/contrails/hook-signals/ ,处理完成后会被清理。

4.4 故障排查与常见问题

在实际使用中,你可能会遇到一些小问题。以下是一些排查思路:

1. 添加项目后无反应,监控不到新会话。

  • 检查路径权限 :确认 Contrails 有权限读取目标目录(如 VS Code 的 workspaceStorage )。
  • 确认会话源 :在项目详情页检查是否成功识别出了预期的 AI 助手源(如 VS Code Copilot)。如果没有,可能是工作区存储路径不标准。
  • 手动触发处理 :尝试在项目右键菜单点击“Process All Now”,看是否能处理出已有的历史会话。这可以测试解析功能是否正常。
  • 查看日志 :Contrails 目前没有图形界面的日志窗口,但你可以通过命令行启动应用,在终端中查看输出信息,可能会包含错误提示。

2. Claude Code 会话没有被捕获。

  • 检查钩子安装 :前往你的项目目录,查看 .claude/settings.local.json 文件是否存在,以及其中是否包含 Contrails 写入的 stop 钩子函数。
  • 检查信号目录 :查看 ~/contrails/hook-signals/ 目录下是否有新生成的 .json 信号文件。如果有文件但没被处理,可能是 Contrails 的信号监控服务没有正常运行。
  • 重启 Claude Code :有时 Claude Code 需要重启后才能加载新的钩子配置。

3. Cursor 会话内容不完整或顺序错乱。

  • 等待防抖 :Cursor 写入频繁,Contrails 有 2 秒防抖。完成一次对话后,稍等几秒再检查输出文件。
  • 全局数据库访问 :确保 Contrails 有权限读取 Cursor 的全局 globalStorage 目录。在 Linux 上,可能需要检查 Flatpak 或 Snap 包安装的 Cursor 其数据目录是否在非标准位置。

4. 生成的 Markdown 文件名为会话ID而非标题。

  • 这是正常现象。对于没有设置自定义标题的会话,Contrails 会使用会话ID作为文件名。如果后续该会话被赋予了标题(例如在 Copilot 聊天界面重命名),你可以在 Contrails 中对该项目执行“Process All Now”,触发“康复”逻辑,文件会被自动重命名。

5. 开发者指南:构建、贡献与扩展

Contrails 是一个开源项目,技术栈清晰(Go + React + TypeScript,使用 Wails 框架),非常适合开发者深入研究、自行构建甚至参与贡献。

5.1 本地开发环境搭建

前置条件

  • Go ≥ 1.23
  • Node.js ≥ 18
  • Wails CLI : go install github.com/wailsapp/wails/v2/cmd/wails@latest
  • Lefthook (用于 Git 钩子): go install github.com/evilmartians/lefthook@latest

启动开发模式

# 克隆项目
git clone https://github.com/ThreePalmTrees/Contrails.git
cd Contrails

# 安装前端依赖(忽略可能存在的 postinstall 脚本)
cd frontend && yarn --ignore-scripts && cd ..

# 安装 Git 钩子,用于提交前自动运行代码检查和测试
lefthook install

# 启动开发服务器,支持前端热重载
wails dev

执行 wails dev 后,会打开一个开发窗口,前端代码的修改会实时刷新。

5.2 项目架构回顾

作为开发者,理解项目架构能帮助你快速定位代码。

  • 后端 (Go) :

    • app.go : 组合根,协调项目 CRUD、驱动注册、处理分发。
    • agent/ 包: 核心。定义了 AgentDriver 接口和 SessionParser 接口。 vscode/ , claudecode/ , cursor/ 子包分别实现了对三种助手的支持。
    • watcher.go : 封装 fsnotify ,负责文件系统监控。
    • analytics.go updater.go : 分别处理遥测和自动更新。
    • runtime.go : 定义了 Logger , EventEmitter 等接口,便于测试时注入模拟实现。
  • 前端 (React + TypeScript) :

    • useProjects.ts : 核心状态管理 Hook,加载项目、监听后端事件。
    • ProjectList.tsx , ProjectDetail.tsx : 侧边栏项目和主详情视图组件。
    • 整体采用相对简单的组件结构,状态通过 Wails 的运行时与前端的绑定进行通信。

5.3 测试与构建

运行测试 项目包含针对各 Agent 解析器的单元测试,使用 t.TempDir() 进行文件系统隔离。

go test ./... -v

测试数据位于 testdata/fixtures/ 目录,按助手类型分文件夹存放。Cursor 的测试使用内存 SQLite 数据库。

构建发布版本 项目提供了针对三个平台的构建脚本,简化了流程。

# 构建开发版(无遥测,无更新检查)
./buildMacOS.sh
./buildWindows.sh
./buildLinux.sh  # Linux 需要安装 GTK 和 WebKit 开发库

# 构建带版本号和遥测的发布版(CI/CD 使用)
./buildMacOS.sh 1.2.3 phc_your_posthog_key
./buildWindows.sh 1.2.3 phc_your_posthog_key
./buildLinux.sh 1.2.3 phc_your_posthog_key

构建脚本内部调用了 wails build ,并通过 -ldflags 注入版本号和 PostHog API 密钥。当版本号设为 "dev" 时,遥测和更新检查功能会被禁用。

5.4 如何贡献

项目欢迎贡献。一个有趣的特色是,它鼓励贡献者提交由 AI 编码助手生成的“会话轨迹”(即 contrails/ 目录下的记录),作为工作过程的证明。

  1. Fork 仓库
  2. 安装前置条件 并配置好开发环境。
  3. 创建特性分支
  4. 进行修改
  5. 提交更改 。配置的 Lefthook 会在提交前自动运行 yarn build (前端构建)、 go vet go test ,确保代码质量。
  6. 包含会话轨迹 :将你开发此功能时 AI 助手的对话记录(项目中的 contrails/ 目录)一并提交,这很有趣地成为了“开发过程的元数据”。
  7. 发起 Pull Request 到主仓库的 main 分支。

5.5 扩展支持新的 AI 助手

Contrails 的架构设计使得支持新的 AI 助手变得相对清晰。你需要:

  1. agent/ 目录下创建一个新的子包(例如 agent/githubcopilotchat )。
  2. 实现 agent/driver.go 中定义的 AgentDriver 接口,主要负责该助手的会话发现、监控逻辑。
  3. 实现 agent/contrail.go 中定义的 SessionParser 接口,负责将助手的原生数据格式解析为统一的 ParsedSession 结构。
  4. app.go 的驱动注册处,将新驱动的工厂函数注册进去。
  5. 在前端 AddProjectDialog.tsx 中,添加对新助手类型的自动发现支持(如果需要)。

整个过程中,最复杂的部分通常是逆向工程目标助手的会话存储格式(JSON、SQLite 或其他),并将其映射到 Contrails 的通用消息模型。

更多推荐