## ADDED Requirements ### Requirement: 解析 env 引用 启动流程在"并集清理"之前 SHALL 先对父进程环境取一次快照(`os.Environ()`)。对于选中 provider 的每一项 `env` value,若匹配 `^env:[A-Za-z_][A-Za-z0-9_]*$`,SHALL 从该快照中查找同名变量: - 若存在,该 key 的实际 value 替换为快照中的值; - 若不存在,工具 MUST 以非零退出码退出,stderr 指出**哪个 key 引用的哪个 VAR** 缺失,且此时 MUST NOT 启动 `claude`、MUST NOT 对 env 做任何 mutation。 解析完成后得到的"物化"env map 作为后续"并集清理 + 注入"的输入。解析到的值 MUST 不做任何 shell 扩展或二次引用解析(不支持链式 `env:A` → `env:B`)。 #### Scenario: 引用成功 - **WHEN** 父 shell 已 export `MY_KEY=sk-abc`,provider `foo.env.ANTHROPIC_API_KEY=env:MY_KEY`,用户执行 `cc-switch use foo` - **THEN** 子进程 `claude` 收到 `ANTHROPIC_API_KEY=sk-abc` #### Scenario: 引用变量缺失 - **WHEN** 父 shell 未 export `MY_KEY`,provider `foo.env.ANTHROPIC_API_KEY=env:MY_KEY` - **THEN** 工具非零退出;stderr 形如 `ANTHROPIC_API_KEY references env var MY_KEY which is not set`;`claude` 未被启动 #### Scenario: 引用的 VAR 恰好在并集中 - **WHEN** provider A 的 env key 含 `ANTHROPIC_API_KEY`,provider B 的 `env.ANTHROPIC_API_KEY=env:ANTHROPIC_API_KEY`,父 shell 已 export `ANTHROPIC_API_KEY=sk-real`,用户执行 `cc-switch use B` - **THEN** 由于解析发生在清理之前,子进程 `claude` 正确收到 `ANTHROPIC_API_KEY=sk-real`(来自快照) #### Scenario: 不做链式解析 - **WHEN** 父 shell 有 `A=env:B` 与 `B=plain`,provider 的 value 为 `env:A` - **THEN** 工具解析一次,子进程收到字面 `env:B`(不继续解析 B) ### Requirement: 环境变量并集清理 启动 `claude` 前,工具 SHALL 计算所有已配置 provider 的 env key 的并集(记为 `K`),并从即将传给子进程的环境中**移除** `K` 中每一个 key。该清理 MUST 在注入选中 provider 的 env 之前完成。工具 MUST NOT 修改 `cc-switch` 自身进程的 `os.Environ`,也 MUST NOT 尝试修改父 shell 的环境。 #### Scenario: 父 shell 存在旧变量 - **WHEN** 父 shell 已 export `ANTHROPIC_API_KEY=old` 且该 key 出现在某个 provider 的 env 中,用户执行 `cc-switch use foo`(`foo.env` 不含 `ANTHROPIC_API_KEY`) - **THEN** 子进程 `claude` 的环境中 `ANTHROPIC_API_KEY` **不存在**(被清理、且 `foo` 未重新设置) #### Scenario: 与 provider 无关的变量原样继承 - **WHEN** 父 shell 有 `HOME`、`PATH`、`LANG` 等变量,它们不在任何 provider 的 env key 集合中 - **THEN** 子进程 `claude` 继承这些变量、值与父进程一致 #### Scenario: 并集跨 provider 生效 - **WHEN** provider A 的 env 含 `ANTHROPIC_API_KEY`、provider B 的 env 含 `ANTHROPIC_AUTH_TOKEN`,用户 `cc-switch use A` - **THEN** 子进程环境中 `ANTHROPIC_AUTH_TOKEN` 被清理(因其属于并集),同时 provider A 的 `ANTHROPIC_API_KEY` 被写入 ### Requirement: 选中 provider 的 env 注入 清理完成后,工具 SHALL 将选中 provider 的 `env` 中每一个 key=value 写入子进程环境。若 key 与清理后的继承环境中某个 key 同名,provider 的 value SHALL 胜出(注入覆盖)。Value MUST 原样传递,不做任何 shell 扩展或变量替换。 #### Scenario: 注入覆盖继承值 - **WHEN** 父 shell 有 `ANTHROPIC_MODEL=x`,该 key 不在并集中但选中 provider 设置了 `ANTHROPIC_MODEL=y` - **THEN** 子进程环境中 `ANTHROPIC_MODEL=y` #### Scenario: Value 不做 shell 扩展 - **WHEN** 选中 provider 的 `ANTHROPIC_BASE_URL=$HOME/api` - **THEN** 子进程收到的值字面量就是 `$HOME/api`,不会被展开为用户 home 目录 ### Requirement: 解析 claude 可执行文件路径 工具 SHALL 按以下顺序解析要执行的路径: 1. 若 `claude_path` 已配置,使用之(支持 `~` 展开),并校验可执行位; 2. 否则使用 `exec.LookPath("claude")`。 任一步找到且可执行即使用该路径;均失败时 SHALL 以非零退出码终止,并输出清晰错误(指出用户可执行 `cc-switch config set claude-path ` 来解决)。 #### Scenario: 使用配置路径 - **WHEN** `claude_path=~/.claude/local/claude` 且该文件存在且有执行位 - **THEN** 工具以该路径启动子进程 #### Scenario: 回退到 PATH 查找 - **WHEN** `claude_path` 未配置且 `PATH` 中存在 `claude` - **THEN** 工具使用 `exec.LookPath` 结果启动子进程 #### Scenario: 无处可寻 - **WHEN** `claude_path` 未配置且 `PATH` 中无 `claude` - **THEN** 工具非零退出并打印引导用户配置 `claude-path` 的提示 ### Requirement: 子进程透传 stdio 启动 `claude` 时,工具 SHALL 将 `cc-switch` 自身的 stdin / stdout / stderr 直接继承给子进程(`cmd.Stdin/Stdout/Stderr = os.Stdin/Stdout/Stderr`),不做任何缓冲或改写。 #### Scenario: 交互输入透传 - **WHEN** 用户在 `claude` 运行期间敲入字符 - **THEN** 字符被 `claude` 即时读取,与直接运行 `claude` 行为一致 #### Scenario: 输出无额外前缀 - **WHEN** `claude` 写入 stdout - **THEN** 终端看到的字节与直接运行 `claude` 完全一致,`cc-switch` 不添加前缀、颜色或时间戳 ### Requirement: 信号转发 `cc-switch` SHALL 捕获 SIGINT、SIGTERM、SIGHUP 并转发给子进程 `claude`;收到信号后 MUST NOT 立即退出,而是继续等待子进程自行收尾后读取其退出码。 #### Scenario: Ctrl+C 传递 - **WHEN** 用户在 `claude` 运行时按下 Ctrl+C(SIGINT) - **THEN** 子进程 `claude` 收到 SIGINT,进入自己的取消流程;`cc-switch` 不立即退出 #### Scenario: 终端关闭 - **WHEN** 用户关闭终端窗口(SIGHUP 送达 `cc-switch`) - **THEN** `cc-switch` 向子进程转发 SIGHUP,等待其退出后再退出 ### Requirement: 退出码透传 `cc-switch` SHALL 使用子进程 `claude` 的退出码作为自身退出码。若子进程因信号终止,`cc-switch` SHALL 退出码 128 + signum(符合 POSIX 约定)。若 `cc-switch` 自身在调用 `claude` 前失败(如配置错误、路径解析失败),退出码 SHALL 为非零且**不是** 0–125 范围内容易与 `claude` 混淆的值(使用 64、70 之类的约定值或 `>= 1`,在实现中固定)。 #### Scenario: 正常退出码 - **WHEN** `claude` 以退出码 0 退出 - **THEN** `cc-switch` 以退出码 0 退出 #### Scenario: 非零退出码 - **WHEN** `claude` 以退出码 42 退出 - **THEN** `cc-switch` 以退出码 42 退出 #### Scenario: 信号终止 - **WHEN** `claude` 被 SIGKILL(9)杀死 - **THEN** `cc-switch` 以退出码 137(128+9)退出 ### Requirement: 退出后清理语义 `claude` 子进程退出后,并集中的 env key MUST 不残留在任何与 `cc-switch` 运行相关的 shell 状态中。由于工具采用"构造子进程 Env 切片"的实现,子进程退出即意味着其私有环境被销毁,`cc-switch` 本身未修改过 `os.Environ`,父 shell 未受影响,该要求天然满足。工具 SHALL 在 `--verbose` 模式下打印一条 trace 日志确认"cleanup complete"。 #### Scenario: 父 shell 不受影响 - **WHEN** 用户 `cc-switch use foo` 运行并退出后,回到 shell 执行 `env | grep ANTHROPIC_` - **THEN** 父 shell 中没有任何由 `foo` 新引入的 env 变量(只保留用户自己 export 的那些) #### Scenario: verbose trace - **WHEN** 用户执行 `cc-switch -v use foo`,`claude` 正常退出 - **THEN** stderr 中可见一条 `cleanup complete` 或等价语义的 trace 行