
在 IntelliJ Platform 仓库中使用 Treehouse 工作区租约生命周期从 Skill 指南到 Go 包装器实现解析【免费下载链接】intellij-communityIntelliJ IDEA IntelliJ Platform项目地址: https://gitcode.com/GitHub_Trending/in/intellij-community本篇技术指南系统讲解 IntelliJ Platformintellij-community源码仓库中为 AI Agent 隔离工作区而设计的Treehouse 工作区租约生命周期涵盖 Skill 文档规定的read status/write acquire/write return三个受控命令、租约收据receipt的 schema 约定、错误码语义以及 build/treehouse 目录下完整 Go 包装器的源码级实现原理。读完你将掌握在大型 monorepo 中安全地获取、使用、归还隔离工作区的完整流程并理解其绝不越权、绝不绕过、以实时租约状态为准的安全设计。背景为什么大型 monorepo 需要受控的工作区隔离IntelliJ Platform 仓库体量极大Agent 在任务执行期间临时创建 Git worktree 或额外 clone 来隔离工作区成本高且容易失控。因此仓库在 .ai/workspace-isolation.md 中明确了统一策略需要隔离工作区时必须使用treehouseskill即本指南对应的 .claude/skills/treehouse/SKILL.md它承载了完整操作流程禁止绕过该 skill 的包装器wrapper直接调用裸的 Treehouse 生命周期命令也禁止运行treehouse enter、init、update、prune、destroy或--force——因为这些命令可能进入、修改甚至删除其他会话的工作区禁止在未获明确指示时自行git worktree add、clone 仓库或实现自定义隔离机制唯一例外是用户显式要求为当前任务创建 Git worktree 时可创建恰好一个、仅限定于该任务的工作区任何 Treehouse 命令失败时不得擅自回退到 worktree、clone 或其他工作区管理器若当前 checkout 下继续操作是安全的就直接继续否则请用户提供隔离工作区。这个策略文档是理解后面所有命令与源码的前提一切设计都围绕受控、可审计、不碰别人会话展开。Skill 全貌frontmatter 与受控命令面.claude/skills/treehouse/SKILL.md 是 Claude Code 的 skill 定义文件其 frontmatter 声明了元信息--- name: treehouse description: Safely acquire, inspect, and return leased Treehouse workspaces. allowed-tools: Bash(../../../community/tools/treehouse.cmd read:*), ... ---注意文件头部的一行注释Generated by community/.ai/render-guides.mjs; edit community/.agents/skills/treehouse/SKILL.md。也就是说这份 SKILL.md 是由 .ai/render-guides.mjs 渲染脚本从规范源community 版源码树中的.agents/skills/treehouse/SKILL.md生成的同时按工具Claude / Codex / Junie分发到.claude、.codex、.junie等目录。allowed-tools中的每个条目都是一个 Bash 前缀授权只允许以read:或write:开头的包装器调用从权限层面就把命令面锁死。包装器只暴露 Treehouse 的租约生命周期三命令其他能力一概不暴露命令作用read status查看资源池中所有工作区及其租约、进程列表只读write acquire获取一个带租约的工作区并做 HEAD 对齐准备write return归还租约并清理工作区它从不安装Treehouse也不暴露enter、init、update、prune、destroy与--force。这一点在 build/treehouse/main.go 的包注释中写得很明确It never installs Treehouse, never creates an ad hoc Git worktree, and never passes --force to the Treehouse CLI.运行 CLI两种 checkout 的命令拼写与输出格式Skill 文档要求从仓库根目录运行 CLIUltimate checkoutmonorepo中命令拼写为./community/tools/treehouse.cmdCommunity checkout 中拼写为./tools/treehouse.cmd去掉community/前缀。由于 Bazel 从固定的pinned源码构建 CLI会话中第一次调用会明显更慢命令输出为 JSON。文档特别提醒把一个 read 调用和一个 write 调用分开运行这样审批approval可以保持窄作用域且可复用——例如只授权read:前缀的 Bash 规则就永远无法触发write操作。从实现上看这个两段式语法是刻意设计的。看 build/treehouse/main.go 的参数解析命令文法只取两个 token第一个是访问词read/write第二个是动作词status/acquire/return。这样一条 Bash 审批可以按前缀作用域授权批准read不会连带批准write见同文件包注释An approval ofreadcannot authorize a write.。成功时命令在 stdout 输出{ok:true,data:...}失败时在 stderr 输出{ok:false,error:...,details:...}并设置退出码。查看资源池read status./community/tools/treehouse.cmd read status结果列出每一个工作区包含其租约状态与进程列表。status只做检查一个可用工作区可能显示为旧的 detachedHEAD因为它没有被 prepare。文档明确警告绝不要对status返回的路径执行 enter、edit、reset、rebase 或 synchronize——只有write acquire才负责保留并准备工作区。源码层面read status在 status.go 中实现它调用上游 CLI 的treehouse status --json然后经parseStatus严格校验每条记录的name、path、status字符串字段以及lease_id、lease_holder、processes的类型status.go。WorkspaceStatus特意保持为map[string]any这样未知字段如flavor能原样往返不会在转发中丢失。测试 treehouse_test.go 验证了 status 输出的结构化字段与底层treehouse status --json的调用关系。获取工作区write acquire./community/tools/treehouse.cmd write acquire --holder session-id持有者holder解析顺序优先使用--holder传入的当前开发/Agent 会话 ID未传时读取环境变量TREEHOUSE_LEASE_HOLDER再不行则自动生成agent-UUID标签。这正好对应 acquire.go 中holderFrom的实现逻辑。结果返回工作区路径path、租约 IDlease_id、持有者lease_holder与收据路径receipt_path。acquire 的语义来自文档 acquire.go 的executeAcquire以--no-fetch方式取得一个干净租约并在调用方当前的精确 HEAD上做 detached checkout不转移调用方的 index、工作树改动或 untracked 文件也不执行 fetch、rebase、stash、cherry-pick 或文件拷贝仅当当前 checkout 本身已持有租约时拒绝——因此一个 checkout 可以同时持有多个租约acquire 前置检查包括当前工作区是否已有活跃租约有则退出码 2 拒绝见 treehouse_test.go、目标路径必须是 Git 工作区根、且与源仓库共享同一个 Git common dir用git rev-parse --git-common-dir校验见 git.go。HEAD 对齐prepare细节prepareAcquiredWorkspaceacquire.go先确认实时租约与分配结果一致然后检查目标工作区干净若 HEAD 与源 HEAD 不同则执行git checkout --force --detach source-headgit.go。这个--force是Git的 force 而非 Treehouse CLI 的--force因为大小写不敏感文件系统会把仅大小写改名的重命名藏出git status的视野导致普通 checkout 被 Git 拒绝。安全性由前后两次校验兜底——detach 前gitChanges拒绝脏工作区detach 后再读 HEAD 并检查 changes任何一项与源 HEAD 不符即失败。测试 treehouse_test.go 专门锁定了detach 带--force但 Treehouse CLI 永远收不到--force这条规则。租约收据receiptacquire 成功后工作区内写入out/treehouse/lease.jsonschema 版本为2记录捕获的source_head。两种仓库布局都会忽略out/目录。收据字段定义在 receipt.goschema_version、path、lease_id、lease_holder、acquired_at、source_head。文档要求不要编辑、移动或复制收据。只有 schema 版本 2 的收据会被接受——测试 treehouse_test.go 验证了旧版 v1 收据来自已退役的 Bun 脚本在任何 spawn 之前就被拒绝。归还工作区write return归还前必须确认所有预期改动都已提交或保存在别处且工作区内没有预期遗留的未提交/未跟踪工作。接着停止read status报告的所有进程——包装器在 Treehouse 仍报告有进程时会拒绝归还。然后从原始 checkout 或租约工作区之外的另一个目录运行从外部运行可以避免包装器及其父 shell 出现在该工作区的进程列表里。./community/tools/treehouse.cmd write return --workspace leased-path归还的校验链returncmd.go 的executeReturn拒绝从租约工作区内部运行防止包装器自身变成活进程读取并解析工作区内收据schema v2且收据中的path必须与请求路径一致校验路径是 Git 工作区根readStatus拿实时租约收据的lease_idlease_holder必须与实时状态完全匹配双身份守卫有活进程则拒绝工作区脏git status --porcelainv1 --untracked-filesall有输出且未带--confirm-preserved时拒绝调用treehouse return path --if-lease-id id --if-lease-holder holder见 receipt.go 的returnCommandverifyReturned再次查询实时池确认租约已消失以实时租约状态为准而非退出码returncmd.go 及 status.go 的confirmLeaseGone只有确认成功后才删除收据。脏工作区的确认标志./community/tools/treehouse.cmd write return --workspace leased-path --confirm-preserved该标志直接回答 Treehouse 的Clean and return? [Y/n]提示因此无需 TTY。它只能在上面所有检查通过后使用——因为归还动作会清空工作区。包装器对脏归还拒绝且绝不替用户代劳--force。实现上returnPromptAnswer常量receipt.go就是y\n当工作区脏时包装器通过 stdin 写入这个回答来回应 CLI 的确认提示。测试 treehouse_test.go 明确验证脏归还无需 TTY--confirm-preserved就是那个回答本身。命令失败时的处理退出码 2 与 127 的语义失败时 CLI 在 stderr 输出 JSON 失败文档包含消息message、退出码与细节details。两种退出码语义必须区分见 main.go 的nativeFailure退出码 2用法错误或前置条件失败如缺少--workspace、收据与实时租约不匹配、存在活进程、工作区脏而未确认等退出码 127固定的 CLI 二进制未能解析。这是 Bazel 构建失败或 runfiles 失败而不是宿主机缺少安装。对应的错误消息明确指示不要安装 Treehouse。此外包装器会把子进程退出码钳制到进程可报告的 1–255 范围main.gonative_exit_code细节字段保留未钳制的原始值。失败后的纪律文档原话要求绝不安装 Treehouse绝不擅自回退到 Git worktree、clone 或其他工作区管理器当前 checkout 下继续操作安全时就继续否则请用户提供隔离工作区只有用户明确要求时才使用 Git worktree若租约在失败后仍然存活保留它并从错误信息中报告路径、租约 ID 与持有者。一个值得注意的设计acquire 过程自带回滚。当收据写入失败、HEAD 准备失败、实时身份变化或上游输出畸形时rollbackAcquireacquire.go会主动归还刚拿到的租约并再次以实时池确认确认失败或无法读取时保留收据并输出retain this lease identity保留此租约身份的指引。相关分支在 treehouse_test.go 中有 8 组以上测试覆盖。Codex 沙箱下的使用流程Skill 文档最后一节专门针对 Codex 环境给出了 4 步操作纪律acquire 前先检查内置的request_permissions工具是否可用不可用则不要获取租约并报告本会话无法使用 Treehouse——不得要求用户修改权限设置、以--add-dir重启或授予 Treehouse 池访问权从本 skill 目录运行并请求两个审批前缀../../../community/tools/treehouse.cmd read与../../../community/tools/treehouse.cmd writeCommunity checkout 中去掉community/。write 审批只让包装器触达 Treehouse 池并不授权acquire 或 return 本身工具的 cwd 变更不会把租约工作区加入会话可写根。保持从原始 checkout 运行用request_permissions为恰好返回的那个 path申请会话级写访问在任何编辑之前。不要申请 Treehouse 池、源码 checkout、共享 Git 目录或全量访问也不要改用逐命令提权授权后后续工具一律以该工作区路径为工作目录授权被拒时不得进入、编辑或在租约工作区中运行命令立即从原始 checkout 归还未触碰的租约也不要求用户重新配置权限。这套流程与read/write 分开审批从外部归还的设计一脉相承所有提权都收窄到最小必要范围。源码级实现Go 包装器的整体设计包装器源码集中在 build/treehouse模块为jetbrains.com/treehouse只用标准库上游 Treehouse 模块通过独立二进制//build/treehouse/cli到达绝不作为 import 依赖见 BUILD.bazel 注释。关键设计点pinned CLI 与 runfiles 解析runtime.go 的treehouseCLIPath从不搜索 PATH因此宿主机上安装的 Treehouse 无法顶替固定版本。解析顺序TREEHOUSE_CLI_BIN环境变量覆盖 → Bazel 注入的 rlocation 路径经RUNFILES_DIR、RUNFILES_MANIFEST_FILE、二进制旁的.runfiles树/清单多候选见 runtime.go。更新检查关闭每次 spawn CLI 都会附加TREEHOUSE_NO_UPDATE_CHECK1runtime.go。Git 子进程统一加-c core.fsmonitorfalse避免启动或使用工作区的文件系统监视器守护进程git.go测试对此有专门断言treehouse_test.go。可测试性Runtime接口runtime.go抽象了 cwd、环境变量、时钟、UUID、文件读写与 spawn测试用FakeRuntime完全接管副作用execute(argv, rt)是纯函数式入口treehouse_test.go 与 fakeruntime_test.go 覆盖了从成功路径到畸形 JSON、回滚失败、审批面拒绝write destroy、--force、缺--workspace等全部关键分支。统一 JSON 信封成功为{ok:true,data:...}失败为{ok:false,error:...,details:...}renderJSON关闭 HTML 转义并缩进输出main.go。结语把租约当作一等公民Treehouse skill 与其 Go 包装器回答了一个实际问题在 IntelliJ Platform 这种巨型 monorepo 中如何让 AI Agent 获得隔离工作区同时不破坏其他人的会话、不引入失控的隔离机制、不产生无法归还的租约。它给出的答案是三层约束——skill 文档约束 Agent 行为命令面 操作纪律、包装器约束底层 CLI只暴露status/get/return的租约生命周期永不--force、实时池约束每次操作的结果判定归还成功与否看实时租约状态而不是退出码。阅读本仓库时建议按 .claude/skills/treehouse/SKILL.md → .ai/workspace-isolation.md → build/treehouse 源码 → treehouse_test.go 测试的顺序深入即可完整掌握这套工作区隔离体系。【免费下载链接】intellij-communityIntelliJ IDEA IntelliJ Platform项目地址: https://gitcode.com/GitHub_Trending/in/intellij-community创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考