feat: enable GrowthBook local gate defaults for P0/P1 features

Add LOCAL_GATE_DEFAULTS mapping in growthbook.ts with 27 feature gate
defaults (25 boolean + 2 object config). Insert local defaults into the
fallback chain of all getter functions so they work regardless of whether
GrowthBook is enabled or disabled:

  env overrides → config overrides → in-memory cache → disk cache
  → LOCAL_GATE_DEFAULTS → caller defaultValue

P0 (local): keybindings, streaming tool exec, cron, JSON tools,
ultrathink, explore/plan agents, deep link, immediate model switch
P1 (API): session memory, auto memory, prompt suggestions, brief mode,
verification agent, away summary, auto dream, idle return prompt
Kill switches: 10 gates kept true to prevent remote disable

New compile flags: AGENT_TRIGGERS, ULTRATHINK, BUILTIN_EXPLORE_PLAN_AGENTS,
LODESTONE, EXTRACT_MEMORIES, VERIFICATION_AGENT, KAIROS_BRIEF, AWAY_SUMMARY

Bypass all local defaults: CLAUDE_CODE_DISABLE_LOCAL_GATES=1
This commit is contained in:
unraid
2026-04-06 17:00:30 +08:00
parent ca630488c6
commit 1b47333d72
7 changed files with 675 additions and 18 deletions

View File

@@ -1,5 +1,103 @@
# DEV-LOG # DEV-LOG
## GrowthBook Local Gate Defaults + P0/P1 Feature Enablement (2026-04-06)
**分支**: `feat/growthbook-enablement`
### 背景
Claude Code 使用 GrowthBookAnthropic 自建 proxy at api.anthropic.com进行远程功能开关控制代码中使用 `tengu_*` 前缀命名。在反编译版本中 GrowthBook 不启动analytics 空实现),导致 70+ 个功能被 gate 拦截。
经 4 个并行研究代理深度分析,确认**所有被 gate 控制的功能代码都是真实现**(非 stub
### 实现方案
**Commit 1** (`feat`): 在 `growthbook.ts` 中添加 `LOCAL_GATE_DEFAULTS` 映射表25+ boolean gates + 2 object config gates修改 4 个 getter 函数在 `isGrowthBookEnabled() === false` 时查找本地默认值。
**Commit 2** (`fix`): 发现 `LOCAL_GATE_DEFAULTS` 在有 API key 的用户环境下无效——因为 `isGrowthBookEnabled()` 返回 `true`analytics 未禁用),代码走 GrowthBook 路径但缓存为空,直接返回 `defaultValue` 跳过了本地默认值。修复:在 3 个 getter 函数的缓存 miss 路径中插入 `LOCAL_GATE_DEFAULTS` 查找。同时修复 `tengu_onyx_plover` 值类型(`JSON.stringify` → 直接对象)和新增 `tengu_kairos_brief_config` 对象型 gate。
修复后的 fallback 链:
```
env overrides → config overrides → [GrowthBook 启用?]
→ 内存缓存 → 磁盘缓存 → LOCAL_GATE_DEFAULTS → defaultValue
```
可通过 `CLAUDE_CODE_DISABLE_LOCAL_GATES=1` 环境变量一键禁用。
### 启用的功能
**P0 — 纯本地功能7 个 gate**
| Gate | 功能 |
|------|------|
| `tengu_keybinding_customization_release` | 自定义快捷键(~/.claude/keybindings.json |
| `tengu_streaming_tool_execution2` | 流式工具执行(边收边执行) |
| `tengu_kairos_cron` | 定时任务系统 |
| `tengu_amber_json_tools` | Token 高效 JSON 工具格式(省 ~4.5% |
| `tengu_immediate_model_command` | 运行中即时切换模型 |
| `tengu_basalt_3kr` | MCP 指令增量传输 |
| `tengu_pebble_leaf_prune` | 会话存储叶剪枝优化 |
**P1 — API 依赖功能8 个 gate**
| Gate | 功能 |
|------|------|
| `tengu_session_memory` | 会话记忆(跨会话上下文持久化) |
| `tengu_passport_quail` | 自动记忆提取 |
| `tengu_chomp_inflection` | 提示建议 |
| `tengu_hive_evidence` | 验证代理(对抗性验证) |
| `tengu_kairos_brief` | Brief 精简输出模式 |
| `tengu_sedge_lantern` | 离开摘要 |
| `tengu_onyx_plover` | 自动梦境(记忆巩固) |
| `tengu_willow_mode` | 空闲返回提示 |
**Kill Switch10 个 gate 保持 true**
`tengu_turtle_carbon``tengu_amber_stoat``tengu_amber_flint``tengu_slim_subagent_claudemd``tengu_birch_trellis``tengu_collage_kaleidoscope``tengu_compact_cache_prefix``tengu_kairos_cron_durable``tengu_attribution_header``tengu_slate_prism`
**新增编译 flag**
| Flag | build.ts | dev.ts | 用途 |
|------|:--------:|:------:|------|
| `AGENT_TRIGGERS` | ON | ON | 定时任务系统 |
| `EXTRACT_MEMORIES` | ON | ON | 自动记忆提取 |
| `VERIFICATION_AGENT` | ON | ON | 对抗性验证代理 |
| `KAIROS_BRIEF` | ON | ON | Brief 精简模式 |
| `AWAY_SUMMARY` | ON | ON | 离开摘要 |
| `ULTRATHINK` | ON | ON | Ultrathink 扩展思考(双重门控修复) |
| `BUILTIN_EXPLORE_PLAN_AGENTS` | ON | ON | 内置 Explore/Plan agents双重门控修复 |
| `LODESTONE` | ON | ON | Deep link 协议注册(双重门控修复) |
**排除的编译 flag**
- `KAIROS` — 拉入 `useProactive.js`(缺失文件),`KAIROS_BRIEF` 足够
- `TERMINAL_PANEL` — 拉入 `TerminalCaptureTool`(缺失文件)
**双重门控修复说明:**
部分功能同时被编译 flag 和 GrowthBook gate 控制(双重门控),仅开 GrowthBook gate 不够。
审计发现 3 个被卡住的:`ULTRATHINK``BUILTIN_EXPLORE_PLAN_AGENTS``LODESTONE`
### 修改文件
| 文件 | 变更 |
|------|------|
| `build.ts` | `DEFAULT_BUILD_FEATURES` 新增 8 个编译 flag |
| `scripts/dev.ts` | `DEFAULT_FEATURES` 新增 8 个编译 flag |
| `src/services/analytics/growthbook.ts` | 新增 `LOCAL_GATE_DEFAULTS` 映射27 gates+ `getLocalGateDefault()` + 修改 4 个 getter 的 fallback 链 |
| `scripts/verify-gates.ts` | 新增 gate 验证脚本30 gates |
| `docs/features/growthbook-enablement-plan.md` | 完整研究报告和启用计划 |
| `docs/features/feature-flags-audit-complete.md` | 更新启用状态表 |
### 验证
| 项目 | 结果 |
|------|------|
| `bun run build` | ✅ 成功 (481 files) |
| `bun test` | ✅ 2106 pass / 23 fail均为已有问题/ 0 新增失败 |
| `verify-gates.ts` | ✅ 30/30 PASS |
| `/brief` 手动测试 | ✅ 可用fallback 修复后) |
---
## Enable SHOT_STATS, TOKEN_BUDGET, PROMPT_CACHE_BREAK_DETECTION (2026-04-05) ## Enable SHOT_STATS, TOKEN_BUDGET, PROMPT_CACHE_BREAK_DETECTION (2026-04-05)
**PR**: [claude-code-best/claude-code#140](https://github.com/claude-code-best/claude-code/pull/140) **PR**: [claude-code-best/claude-code#140](https://github.com/claude-code-best/claude-code/pull/140)

View File

@@ -17,6 +17,16 @@ const DEFAULT_BUILD_FEATURES = [
'SHOT_STATS', 'SHOT_STATS',
'PROMPT_CACHE_BREAK_DETECTION', 'PROMPT_CACHE_BREAK_DETECTION',
'TOKEN_BUDGET', 'TOKEN_BUDGET',
// P0: local features
'AGENT_TRIGGERS',
'ULTRATHINK',
'BUILTIN_EXPLORE_PLAN_AGENTS',
'LODESTONE',
// P1: API-dependent features
'EXTRACT_MEMORIES',
'VERIFICATION_AGENT',
'KAIROS_BRIEF',
'AWAY_SUMMARY',
] ]
// Collect FEATURE_* env vars → Bun.build features // Collect FEATURE_* env vars → Bun.build features

View File

@@ -38,18 +38,24 @@ Claude Code 使用三层门控系统:
--- ---
## 当前启用状态 (2026-04-05) ## 当前启用状态 (2026-04-06)
> 经 Codex CLI 独立复核验证,详见 `feature-flags-codex-review.md` > 经 Codex CLI 独立复核验证,详见 `feature-flags-codex-review.md`
> GrowthBook gate 启用详见 `growthbook-enablement-plan.md`
| 标志 | build.ts | dev.ts | 实际验证状态 | 备注 | | 标志 | build.ts | dev.ts | 实际验证状态 | 备注 |
|------|:--------:|:------:|:----------:|------| |------|:--------:|:------:|:----------:|------|
| AGENT_TRIGGERS_REMOTE | **ON** | **ON** | compile-only | 环境标记,原始即启用 | | AGENT_TRIGGERS_REMOTE | **ON** | **ON** | compile-only | 环境标记,原始即启用 |
| CHICAGO_MCP | **ON** | **ON** | compile-only | Computer Use原始即启用 | | CHICAGO_MCP | **ON** | **ON** | compile-only | Computer Use原始即启用 |
| VOICE_MODE | **ON** | **ON** | compile-only | 语音模式,原始即启用 | | VOICE_MODE | **ON** | **ON** | compile-only | 语音模式,原始即启用 |
| SHOT_STATS | **ON** | **ON** | compile-only, 已验证 | 本轮新增,纯本地统计 | | SHOT_STATS | **ON** | **ON** | compile-only, 已验证 | 纯本地统计 |
| PROMPT_CACHE_BREAK_DETECTION | **ON** | **ON** | compile-only, 已验证 | 本轮新增,内部诊断 | | PROMPT_CACHE_BREAK_DETECTION | **ON** | **ON** | compile-only, 已验证 | 内部诊断 |
| TOKEN_BUDGET | **ON** | **ON** | compile-only, 已验证 | 本轮新增,支持 `+500k` 语法 | | TOKEN_BUDGET | **ON** | **ON** | compile-only, 已验证 | 支持 `+500k` 语法 |
| AGENT_TRIGGERS | **ON** | **ON** | compile+GB gate, 已验证 | 本轮新增,定时任务系统 |
| EXTRACT_MEMORIES | **ON** | **ON** | compile+GB gate, 已验证 | 本轮新增,自动记忆提取 |
| VERIFICATION_AGENT | **ON** | **ON** | compile+GB gate, 已验证 | 本轮新增,对抗性验证代理 |
| KAIROS_BRIEF | **ON** | **ON** | compile+GB gate, 已验证 | 本轮新增Brief 精简模式 |
| AWAY_SUMMARY | **ON** | **ON** | compile+GB gate, 已验证 | 本轮新增,离开摘要 |
| BUDDY | off | **ON** | compile+GrowthBook | 仅 dev 模式 | | BUDDY | off | **ON** | compile+GrowthBook | 仅 dev 模式 |
| TRANSCRIPT_CLASSIFIER | off | **ON** | compile+GrowthBook | 仅 dev 模式 | | TRANSCRIPT_CLASSIFIER | off | **ON** | compile+GrowthBook | 仅 dev 模式 |
| BRIDGE_MODE | off | **ON** | compile+remote | 仅 dev 模式,需 claude.ai 订阅 | | BRIDGE_MODE | off | **ON** | compile+remote | 仅 dev 模式,需 claude.ai 订阅 |

View File

@@ -0,0 +1,334 @@
# GrowthBook 功能启用计划
> 编制日期: 2026-04-06
> 基于: feature-flags-codex-review.md + 4 个并行研究代理的深度分析
> 前提: 我们是付费订阅用户,拥有有效的 Anthropic API key
---
## 背景
Claude Code 使用三层门控系统:
1. **编译时 feature flag**`feature('FLAG_NAME')` from `bun:bundle`
2. **GrowthBook 远程开关**`tengu_*` 前缀,通过 SDK 连接 Anthropic 服务端
3. **运行时环境变量**`USER_TYPE``CLAUDE_CODE_*`
在我们的反编译版本中GrowthBook 不启动analytics 链空实现),导致所有 `tengu_*` 检查默认返回 `false`
**核心发现:所有被 GrowthBook 门控的功能代码都是真实现,没有 stub。**
---
## 启用方式说明
### 方式 1硬编码绕过推荐先用
`src/services/analytics/growthbook.ts``getFeatureValueInternal()` 函数中添加默认值映射。
### 方式 2自建 GrowthBook 服务器
```bash
docker run -p 3100:3100 growthbook/growthbook
# 设置环境变量
CLAUDE_GB_ADAPTER_URL=http://localhost:3100
CLAUDE_GB_ADAPTER_KEY=sdk-xxx
```
### 方式 3恢复原生 1P 连接
`is1PEventLoggingEnabled()` 返回 `true`,连接 Anthropic 的 GrowthBook 服务端。
注意:会发送使用统计(不含代码/对话内容)。
---
## 优先级 P0纯本地功能零外部依赖立即可用
这些功能不需要 API 调用,开启 gate 即可工作。
### P0-1. 自定义快捷键
- **Gate**: `tengu_keybinding_customization_release``true`
- **编译 flag**: 无(已内置)
- **代码量**: 473 行,完整实现
- **功能**: 加载 `~/.claude/keybindings.json`,支持热重载、重复键检测、结构验证
- **效果**: 用户可自定义所有快捷键
- **风险**: 无
### P0-2. 流式工具执行
- **Gate**: `tengu_streaming_tool_execution2``true`
- **编译 flag**: 无(已内置)
- **代码量**: 577 行StreamingToolExecutor完整实现
- **功能**: API 响应还在流式返回时就开始执行工具,减少等待时间
- **效果**: 显著提升交互速度
- **风险**: 低(生产级代码,有错误处理)
### P0-3. 定时任务系统
- **Gate**: `tengu_kairos_cron``true`(额外:`tengu_kairos_cron_durable` 默认 `true`
- **编译 flag**: `AGENT_TRIGGERS`(需新增)或 `AGENT_TRIGGERS_REMOTE`(已启用)
- **代码量**: 1025 行cronTasks + cronScheduler完整实现
- **功能**: 本地 cron 调度,支持一次性/周期性任务、防雷群效应 jitter、自动过期
- **效果**: 可设置定时执行的 Claude 任务
- **风险**: 低
### P0-4. Agent 团队 / Swarm
- **Gate**: `tengu_amber_flint``true`(这是 kill switch默认已 `true`
- **编译 flag**: 无(已内置)
- **代码量**: 45 行gate 层),实际 swarm 实现在 teammate tools 中
- **功能**: 多 agent 协作,需额外设置 `--agent-teams``CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1`
- **效果**: 允许创建和管理 agent 团队
- **风险**: 无kill switch 默认就是 true
### P0-5. Token 高效 JSON 工具格式
- **Gate**: `tengu_amber_json_tools``true`
- **编译 flag**: 无(已内置)
- **代码量**: betas.ts 中几行 gate 检查
- **功能**: 启用 FC v3 格式,减少约 4.5% 的输出 token
- **效果**: 省钱
- **风险**: 低(需要模型支持该 beta header
### P0-6. Ultrathink 扩展思考
- **Gate**: `tengu_turtle_carbon``true`(默认已 `true`kill switch
- **编译 flag**: 无
- **功能**: 通过关键词触发扩展思考模式
- **效果**: 已默认启用,确保不被远程关闭即可
- **风险**: 无
### P0-7. 即时模型切换
- **Gate**: `tengu_immediate_model_command``true`
- **编译 flag**: 无
- **功能**: 在 query 运行过程中即时执行 `/model``/fast``/effort` 命令
- **效果**: 无需等当前任务完成就能切换
- **风险**: 低
---
## 优先级 P1需要 Claude API 的功能(有 API key 即可用)
这些功能需要调用 Claude API使用 forked subagent 或 queryModel有订阅即可。
### P1-1. 会话记忆
- **Gate**: `tengu_session_memory``true`(配置:`tengu_sm_config``{}`
- **编译 flag**: 无(已内置)
- **代码量**: 1127 行,完整实现
- **功能**: 跨会话上下文持久化。用 forked agent 定期提取会话笔记到 markdown 文件
- **效果**: Claude 记住跨会话的工作上下文
- **依赖**: Claude APIforked subagent
- **风险**: 低(额外 API token 消耗)
### P1-2. 自动记忆提取
- **Gate**: `tengu_passport_quail``true`(相关:`tengu_moth_copse``tengu_coral_fern`
- **编译 flag**: `EXTRACT_MEMORIES`(需新增)
- **代码量**: 616 行,完整实现
- **功能**: 对话中自动提取持久记忆到 `~/.claude/projects/<path>/memory/`
- **效果**: 自动构建项目知识库
- **依赖**: Claude APIforked subagent
- **风险**: 低
### P1-3. 提示建议
- **Gate**: `tengu_chomp_inflection``true`
- **编译 flag**: 无(已内置)
- **代码量**: 525 行,完整实现
- **功能**: 自动生成下一步操作建议带投机预取speculation prefetch
- **效果**: 更流畅的交互体验
- **依赖**: Claude APIforked subagent
- **风险**: 低(额外 API 消耗,但有缓存感知)
### P1-4. 验证代理
- **Gate**: `tengu_hive_evidence``true`
- **编译 flag**: `VERIFICATION_AGENT`(需新增)
- **代码量**: 153 行agent 定义),完整实现
- **功能**: 对抗性验证 agent主动尝试打破你的实现只读模式
- **效果**: 自动化代码验证
- **依赖**: Claude APIsubagent
- **风险**: 低(只读,不修改代码)
### P1-5. Brief 模式
- **Gate**: `tengu_kairos_brief``true`
- **编译 flag**: `KAIROS``KAIROS_BRIEF`(需新增)
- **代码量**: 335 行,完整实现
- **功能**: `/brief` 命令切换精简输出模式
- **效果**: 减少冗余输出
- **依赖**: Claude API
- **风险**: 低
### P1-6. 离开摘要
- **Gate**: `tengu_sedge_lantern``true`
- **编译 flag**: `AWAY_SUMMARY`(需新增)
- **代码量**: 176 行,完整实现
- **功能**: 离开终端 5 分钟后返回时自动总结期间发生了什么
- **效果**: 快速恢复上下文
- **依赖**: Claude API + 终端焦点事件支持
- **风险**: 低
### P1-7. 自动梦境
- **Gate**: `tengu_onyx_plover``{"enabled": true}`
- **编译 flag**: 无(已内置,但检查 auto-memory 是否启用)
- **代码量**: 349 行,完整实现
- **功能**: 后台自动整理/巩固记忆(等同于自动执行 `/dream`
- **效果**: 记忆自动保持整洁有序
- **依赖**: Claude APIforked subagent+ auto-memory 启用
- **风险**: 低
### P1-8. 空闲返回提示
- **Gate**: `tengu_willow_mode``"dialog"``"hint"`
- **编译 flag**: 无
- **功能**: 对话太大且缓存过期时,提示用户开新会话
- **效果**: 避免在过期缓存上浪费 token
- **风险**: 无
---
## 优先级 P2增强型功能提升体验但非必须
### P2-1. MCP 指令增量传输
- **Gate**: `tengu_basalt_3kr``true`
- **功能**: 只发送变化的 MCP 指令而非全量
- **效果**: 减少 token 消耗
- **风险**: 低
### P2-2. 叶剪枝优化
- **Gate**: `tengu_pebble_leaf_prune``true`
- **功能**: 会话存储中移除死胡同消息分支
- **效果**: 减少存储和加载时间
- **风险**: 低
### P2-3. 消息合并
- **Gate**: `tengu_chair_sermon``true`
- **功能**: 合并相邻的 tool_result + text 块
- **效果**: 减少 token 消耗
- **风险**: 低
### P2-4. 深度链接
- **Gate**: `tengu_lodestone_enabled``true`
- **功能**: 注册 `claude://` URL 协议处理器
- **效果**: 可从浏览器直接打开 Claude Code
- **风险**: 低
### P2-5. Agent 自动转后台
- **Gate**: `tengu_auto_background_agents``true`
- **功能**: Agent 任务运行 120s 后自动转为后台
- **效果**: 不再阻塞主交互
- **风险**: 低
### P2-6. 细粒度工具状态
- **Gate**: `tengu_fgts``true`
- **功能**: 系统提示中包含细粒度工具状态信息
- **效果**: 模型更好地理解工具可用性
- **风险**: 低
### P2-7. 文件操作 git diff
- **Gate**: `tengu_quartz_lantern``true`
- **功能**: 文件写入/编辑时计算 git diff仅远程会话
- **效果**: 更好的变更追踪
- **风险**: 低
---
## 优先级 P3需要自建服务或 Anthropic OAuth
### P3-1. 团队记忆
- **Gate**: `tengu_herring_clock``true`
- **编译 flag**: `TEAMMEM`(需新增)
- **代码量**: 1180+ 行,完整实现
- **功能**: 跨 agent 共享记忆,同步到 Anthropic API
- **依赖**: Anthropic OAuth + GitHub remote
- **状态**: 需要 Anthropic 的 `/api/claude_code/team_memory` 端点
- **可行性**: 除非自建兼容 API否则无法使用
### P3-2. 设置同步
- **Gate**: `tengu_enable_settings_sync_push` + `tengu_strap_foyer``true`
- **编译 flag**: `UPLOAD_USER_SETTINGS` / `DOWNLOAD_USER_SETTINGS`(需新增)
- **代码量**: 582 行,完整实现
- **功能**: 跨设备设置同步
- **依赖**: Anthropic OAuth + `/api/claude_code/user_settings`
- **可行性**: 同上
### P3-3. Bridge 远程控制
- **Gate**: `tengu_ccr_bridge``true`(已有编译 flag `BRIDGE_MODE` dev 模式启用)
- **代码量**: 12,619 行,完整实现
- **功能**: claude.ai 网页端远程控制 CLI
- **依赖**: claude.ai 订阅 + WebSocket 后端
- **可行性**: 需要 Anthropic 的 CCR 后端
### P3-4. 远程定时 Agent
- **Gate**: `tengu_surreal_dali``true`
- **功能**: 创建在远程执行的定时 agent
- **依赖**: Anthropic CCR 基础设施
- **可行性**: 需要远程服务
---
## Kill Switch 清单(确保不被远程关闭)
这些 gate 默认为 `true`,是 kill switch。应确保它们保持 `true`
| Gate | 默认 | 控制什么 |
|---|---|---|
| `tengu_turtle_carbon` | `true` | Ultrathink 扩展思考 |
| `tengu_amber_stoat` | `true` | 内置 Explore/Plan agent |
| `tengu_amber_flint` | `true` | Agent 团队/Swarm |
| `tengu_slim_subagent_claudemd` | `true` | 子 agent 精简 CLAUDE.md |
| `tengu_birch_trellis` | `true` | tree-sitter bash 安全分析 |
| `tengu_collage_kaleidoscope` | `true` | macOS 剪贴板图片读取 |
| `tengu_compact_cache_prefix` | `true` | 压缩时复用 prompt cache |
| `tengu_kairos_cron_durable` | `true` | 持久化 cron 任务 |
| `tengu_attribution_header` | `true` | API 请求署名 |
| `tengu_slate_prism` | `true` | Agent 进度摘要 |
---
## 需要新增的编译 flag
以下编译时 flag 尚未在 `build.ts` / `scripts/dev.ts` 中启用,但功能代码完整:
| Flag | 用于 | 优先级 |
|---|---|---|
| `AGENT_TRIGGERS` | 定时任务系统P0-3 | P0 |
| `EXTRACT_MEMORIES` | 自动记忆提取P1-2 | P1 |
| `VERIFICATION_AGENT` | 验证代理P1-4 | P1 |
| `KAIROS``KAIROS_BRIEF` | Brief 模式P1-5 | P1 |
| `AWAY_SUMMARY` | 离开摘要P1-6 | P1 |
| `TEAMMEM` | 团队记忆P3-1 | P3 |
---
## 实施路线图
### Phase 1硬编码 P0 纯本地 gate最快见效
1. 在 growthbook.ts 添加默认值映射
2. 在 build.ts / dev.ts 添加 `AGENT_TRIGGERS` 编译 flag
3. 验证 7 个 P0 功能正常工作
4. 预计工作量1-2 小时
### Phase 2启用 P1 API 依赖功能
1. 添加编译 flag`EXTRACT_MEMORIES``VERIFICATION_AGENT``KAIROS_BRIEF``AWAY_SUMMARY`
2. 添加 P1 gate 默认值
3. 验证 8 个 P1 功能正常工作
4. 预计工作量2-3 小时
### Phase 3评估自建 GrowthBook可选
1. Docker 部署 GrowthBook 服务器
2. 迁移硬编码值到 GrowthBook 后台管理
3. 获得 Web UI 管理所有 flag 的能力
4. 预计工作量:半天
### Phase 4评估远程功能可选
1. 研究是否可以使用 Anthropic OAuth
2. 评估团队记忆、设置同步的自建可行性
3. 预计工作量:待评估
---
## 隐私说明
### 硬编码绕过(方案 A
- **零数据外发**
- GrowthBook SDK 不启动
- 完全离线运行
### 自建 GrowthBook方案 B
- 数据仅发送到你自己的服务器
- Anthropic 无法获取任何数据
- 可通过 Web UI 实时管理所有 flag
### 恢复原生 1P方案 C
- 会发送使用统计到 `api.anthropic.com`
- **不发送**代码、对话内容、API key
- **会发送**:邮箱、设备 ID、机器指纹、仓库哈希、订阅类型
- 可用 `DISABLE_TELEMETRY=1` 关闭遥测(但同时关闭 GrowthBook

View File

@@ -23,7 +23,19 @@ const defineArgs = Object.entries(defines).flatMap(([k, v]) => [
// Bun --feature flags: enable feature() gates at runtime. // Bun --feature flags: enable feature() gates at runtime.
// Default features enabled in dev mode. // Default features enabled in dev mode.
const DEFAULT_FEATURES = ["BUDDY", "TRANSCRIPT_CLASSIFIER", "BRIDGE_MODE", "AGENT_TRIGGERS_REMOTE", "CHICAGO_MCP", "VOICE_MODE", "SHOT_STATS", "PROMPT_CACHE_BREAK_DETECTION", "TOKEN_BUDGET"]; const DEFAULT_FEATURES = [
"BUDDY", "TRANSCRIPT_CLASSIFIER", "BRIDGE_MODE",
"AGENT_TRIGGERS_REMOTE", "CHICAGO_MCP", "VOICE_MODE",
"SHOT_STATS", "PROMPT_CACHE_BREAK_DETECTION", "TOKEN_BUDGET",
// P0: local features
"AGENT_TRIGGERS",
"ULTRATHINK",
"BUILTIN_EXPLORE_PLAN_AGENTS",
"LODESTONE",
// P1: API-dependent features
"EXTRACT_MEMORIES", "VERIFICATION_AGENT",
"KAIROS_BRIEF", "AWAY_SUMMARY",
];
// Any env var matching FEATURE_<NAME>=1 will also enable that feature. // Any env var matching FEATURE_<NAME>=1 will also enable that feature.
// e.g. FEATURE_PROACTIVE=1 bun run dev // e.g. FEATURE_PROACTIVE=1 bun run dev

107
scripts/verify-gates.ts Normal file
View File

@@ -0,0 +1,107 @@
#!/usr/bin/env bun
/**
* Verify GrowthBook gate defaults and compile-time feature flags.
*
* Usage:
* bun run scripts/verify-gates.ts
*
* This script checks that LOCAL_GATE_DEFAULTS are being returned correctly
* when GrowthBook is not connected, and that compile-time feature flags
* are properly enabled.
*/
// We can't import feature() from bun:bundle in a standalone script,
// so we test the GrowthBook layer directly.
import {
getFeatureValue_CACHED_MAY_BE_STALE,
checkStatsigFeatureGate_CACHED_MAY_BE_STALE,
} from '../src/services/analytics/growthbook.js'
interface GateCheck {
name: string
gate: string
expected: unknown
category: string
/** If set, this compile flag must also be enabled at build time */
compileFlag?: string
}
const gates: GateCheck[] = [
// P0: Pure local
{ name: 'Custom keybindings', gate: 'tengu_keybinding_customization_release', expected: true, category: 'P0' },
{ name: 'Streaming tool exec', gate: 'tengu_streaming_tool_execution2', expected: true, category: 'P0' },
{ name: 'Cron tasks', gate: 'tengu_kairos_cron', expected: true, category: 'P0' },
{ name: 'JSON tools format', gate: 'tengu_amber_json_tools', expected: true, category: 'P0' },
{ name: 'Immediate model cmd', gate: 'tengu_immediate_model_command', expected: true, category: 'P0' },
{ name: 'MCP delta', gate: 'tengu_basalt_3kr', expected: true, category: 'P0' },
{ name: 'Leaf pruning', gate: 'tengu_pebble_leaf_prune', expected: true, category: 'P0' },
{ name: 'Message smooshing', gate: 'tengu_chair_sermon', expected: true, category: 'P0' },
{ name: 'Deep link', gate: 'tengu_lodestone_enabled', expected: true, category: 'P0', compileFlag: 'LODESTONE' },
{ name: 'Auto background', gate: 'tengu_auto_background_agents', expected: true, category: 'P0' },
{ name: 'Fine-grained tools', gate: 'tengu_fgts', expected: true, category: 'P0' },
// P1: API-dependent
{ name: 'Session memory', gate: 'tengu_session_memory', expected: true, category: 'P1' },
{ name: 'Auto memory extract', gate: 'tengu_passport_quail', expected: true, category: 'P1', compileFlag: 'EXTRACT_MEMORIES' },
{ name: 'Memory skip index', gate: 'tengu_moth_copse', expected: true, category: 'P1' },
{ name: 'Memory search section', gate: 'tengu_coral_fern', expected: true, category: 'P1' },
{ name: 'Prompt suggestions', gate: 'tengu_chomp_inflection', expected: true, category: 'P1' },
{ name: 'Verification agent', gate: 'tengu_hive_evidence', expected: true, category: 'P1', compileFlag: 'VERIFICATION_AGENT' },
{ name: 'Brief mode', gate: 'tengu_kairos_brief', expected: true, category: 'P1', compileFlag: 'KAIROS_BRIEF' },
{ name: 'Away summary', gate: 'tengu_sedge_lantern', expected: true, category: 'P1', compileFlag: 'AWAY_SUMMARY' },
{ name: 'Idle return prompt', gate: 'tengu_willow_mode', expected: 'dialog', category: 'P1' },
// Kill switches
{ name: 'Ultrathink', gate: 'tengu_turtle_carbon', expected: true, category: 'KS', compileFlag: 'ULTRATHINK' },
{ name: 'Explore/Plan agents', gate: 'tengu_amber_stoat', expected: true, category: 'KS', compileFlag: 'BUILTIN_EXPLORE_PLAN_AGENTS' },
{ name: 'Agent teams', gate: 'tengu_amber_flint', expected: true, category: 'KS' },
{ name: 'Slim subagent CLAUDE.md', gate: 'tengu_slim_subagent_claudemd', expected: true, category: 'KS' },
{ name: 'Bash security', gate: 'tengu_birch_trellis', expected: true, category: 'KS' },
{ name: 'macOS clipboard', gate: 'tengu_collage_kaleidoscope', expected: true, category: 'KS' },
{ name: 'Compact cache prefix', gate: 'tengu_compact_cache_prefix', expected: true, category: 'KS' },
{ name: 'Durable cron', gate: 'tengu_kairos_cron_durable', expected: true, category: 'KS' },
{ name: 'Attribution header', gate: 'tengu_attribution_header', expected: true, category: 'KS' },
{ name: 'Agent progress', gate: 'tengu_slate_prism', expected: true, category: 'KS' },
]
console.log('=== GrowthBook Local Gate Verification ===\n')
let pass = 0
let fail = 0
for (const category of ['P0', 'P1', 'KS']) {
const label = category === 'KS' ? 'Kill Switches' : category
console.log(`--- ${label} ---`)
for (const check of gates.filter(g => g.category === category)) {
const actual = typeof check.expected === 'boolean'
? checkStatsigFeatureGate_CACHED_MAY_BE_STALE(check.gate)
: getFeatureValue_CACHED_MAY_BE_STALE(check.gate, null)
const matches = typeof check.expected === 'boolean'
? actual === check.expected
: actual === check.expected || JSON.stringify(actual) === JSON.stringify(check.expected)
const status = matches ? '\x1b[32mPASS\x1b[0m' : '\x1b[31mFAIL\x1b[0m'
const flagNote = check.compileFlag ? ` [needs feature('${check.compileFlag}')]` : ''
console.log(` ${status} ${check.name}: ${check.gate} = ${JSON.stringify(actual)}${flagNote}`)
if (matches) pass++
else fail++
}
console.log()
}
console.log(`\nResult: ${pass} passed, ${fail} failed out of ${pass + fail} gates`)
if (fail > 0) {
console.log('\n\x1b[31mSome gates are not returning expected values!\x1b[0m')
console.log('If CLAUDE_CODE_DISABLE_LOCAL_GATES=1 is set, all gates will return defaults.')
process.exit(1)
}
console.log('\n\x1b[32mAll GrowthBook gates returning expected local defaults.\x1b[0m')
console.log('\nNote: Compile-time feature() flags cannot be verified in this script.')
console.log('Use "bun run dev" and test manually for features with [needs feature()] markers.')

View File

@@ -416,6 +416,72 @@ function syncRemoteEvalToDisk(): void {
})) }))
} }
/**
* Local default overrides for GrowthBook feature gates.
*
* When GrowthBook is not connected (e.g. no 1P event logging, no adapter),
* these values are used instead of the hard-coded defaults (usually false).
* This allows enabling features that have real implementations without
* requiring a GrowthBook server connection.
*
* Set CLAUDE_CODE_DISABLE_LOCAL_GATES=1 to bypass these defaults.
*
* Categories:
* P0 — Pure local features (no external dependencies)
* P1 — Requires Claude API (works with any valid API key)
* KS — Kill switches (default true, keep them true)
*/
const LOCAL_GATE_DEFAULTS: Record<string, unknown> = {
// ── P0: Pure local features ──────────────────────────────────────
tengu_keybinding_customization_release: true, // Custom keybindings
tengu_streaming_tool_execution2: true, // Streaming tool execution
tengu_kairos_cron: true, // Cron/scheduled tasks
tengu_amber_json_tools: true, // Token-efficient JSON tools (~4.5% savings)
tengu_immediate_model_command: true, // Instant /model, /fast, /effort during query
tengu_basalt_3kr: true, // MCP instructions delta (send only changes)
tengu_pebble_leaf_prune: true, // Session storage leaf pruning
tengu_chair_sermon: true, // Message smooshing (merge adjacent blocks)
tengu_lodestone_enabled: true, // Deep link protocol (claude://)
tengu_auto_background_agents: true, // Auto-background agents after 120s
tengu_fgts: true, // Fine-grained tool state in system prompt
// ── P1: API-dependent features ───────────────────────────────────
tengu_session_memory: true, // Session memory (cross-session persistence)
tengu_passport_quail: true, // Auto memory extraction
tengu_moth_copse: true, // Skip memory index, use prefetched memories
tengu_coral_fern: true, // "Searching past context" section
tengu_chomp_inflection: true, // Prompt suggestions
tengu_hive_evidence: true, // Verification agent
tengu_kairos_brief: true, // Brief mode
tengu_kairos_brief_config: { enable_slash_command: true }, // Brief /slash command visibility
tengu_sedge_lantern: true, // Away summary
tengu_onyx_plover: { enabled: true }, // Auto dream (memory consolidation)
tengu_willow_mode: 'dialog', // Idle return prompt
// ── Kill switches (keep true to prevent remote disable) ──────────
tengu_turtle_carbon: true, // Ultrathink extended thinking
tengu_amber_stoat: true, // Built-in Explore/Plan agents
tengu_amber_flint: true, // Agent teams/swarms
tengu_slim_subagent_claudemd: true, // Slim CLAUDE.md for subagents
tengu_birch_trellis: true, // Tree-sitter bash security analysis
tengu_collage_kaleidoscope: true, // macOS clipboard image reading
tengu_compact_cache_prefix: true, // Reuse prompt cache during compaction
tengu_kairos_cron_durable: true, // Persistent cron tasks
tengu_attribution_header: true, // API request attribution header
tengu_slate_prism: true, // Agent progress summaries
}
/**
* Look up a local gate default. Returns undefined if not configured,
* allowing the caller to fall through to the original defaultValue.
*/
function getLocalGateDefault(feature: string): unknown | undefined {
if (process.env.CLAUDE_CODE_DISABLE_LOCAL_GATES) {
return undefined
}
return LOCAL_GATE_DEFAULTS[feature]
}
/** /**
* Check if GrowthBook operations should be enabled * Check if GrowthBook operations should be enabled
*/ */
@@ -500,11 +566,13 @@ const getGrowthBookClient = memoize(
const attributes = getUserAttributes() const attributes = getUserAttributes()
const clientKey = getGrowthBookClientKey() const clientKey = getGrowthBookClientKey()
const baseUrl = const baseUrl =
process.env.CLAUDE_GB_ADAPTER_URL process.env.CLAUDE_GB_ADAPTER_URL ||
|| (process.env.USER_TYPE === 'ant' (process.env.USER_TYPE === 'ant'
? process.env.CLAUDE_CODE_GB_BASE_URL || 'https://api.anthropic.com/' ? process.env.CLAUDE_CODE_GB_BASE_URL || 'https://api.anthropic.com/'
: 'https://api.anthropic.com/') : 'https://api.anthropic.com/')
const isAdapterMode = !!(process.env.CLAUDE_GB_ADAPTER_URL && process.env.CLAUDE_GB_ADAPTER_KEY) const isAdapterMode = !!(
process.env.CLAUDE_GB_ADAPTER_URL && process.env.CLAUDE_GB_ADAPTER_KEY
)
if (process.env.USER_TYPE === 'ant') { if (process.env.USER_TYPE === 'ant') {
logForDebugging( logForDebugging(
`GrowthBook: Creating client with clientKey=${clientKey}, attributes: ${jsonStringify(attributes)}`, `GrowthBook: Creating client with clientKey=${clientKey}, attributes: ${jsonStringify(attributes)}`,
@@ -537,7 +605,9 @@ const getGrowthBookClient = memoize(
// remoteEval only works with Anthropic internal API, GrowthBook Cloud doesn't support it // remoteEval only works with Anthropic internal API, GrowthBook Cloud doesn't support it
remoteEval: !isAdapterMode, remoteEval: !isAdapterMode,
// cacheKeyAttributes only valid with remoteEval // cacheKeyAttributes only valid with remoteEval
...(!isAdapterMode ? { cacheKeyAttributes: ['id', 'organizationUUID'] } : {}), ...(!isAdapterMode
? { cacheKeyAttributes: ['id', 'organizationUUID'] }
: {}),
// Add auth headers if available // Add auth headers if available
...(authHeaders.error ...(authHeaders.error
? {} ? {}
@@ -691,12 +761,14 @@ async function getFeatureValueInternal<T>(
} }
if (!isGrowthBookEnabled()) { if (!isGrowthBookEnabled()) {
return defaultValue const localDefault = getLocalGateDefault(feature)
return localDefault !== undefined ? (localDefault as T) : defaultValue
} }
const growthBookClient = await initializeGrowthBook() const growthBookClient = await initializeGrowthBook()
if (!growthBookClient) { if (!growthBookClient) {
return defaultValue const localDefault = getLocalGateDefault(feature)
return localDefault !== undefined ? (localDefault as T) : defaultValue
} }
// Use cached remote eval values if available (workaround for SDK bug) // Use cached remote eval values if available (workaround for SDK bug)
@@ -754,7 +826,8 @@ export function getFeatureValue_CACHED_MAY_BE_STALE<T>(
} }
if (!isGrowthBookEnabled()) { if (!isGrowthBookEnabled()) {
return defaultValue const localDefault = getLocalGateDefault(feature)
return localDefault !== undefined ? (localDefault as T) : defaultValue
} }
// Log experiment exposure if data is available, otherwise defer until after init // Log experiment exposure if data is available, otherwise defer until after init
@@ -776,7 +849,16 @@ export function getFeatureValue_CACHED_MAY_BE_STALE<T>(
// Fall back to disk cache (survives across process restarts) // Fall back to disk cache (survives across process restarts)
try { try {
const cached = getGlobalConfig().cachedGrowthBookFeatures?.[feature] const cached = getGlobalConfig().cachedGrowthBookFeatures?.[feature]
return cached !== undefined ? (cached as T) : defaultValue if (cached !== undefined) {
return cached as T
}
// Disk cache miss — use local gate defaults before falling back to
// the caller's defaultValue. This covers the common case where
// GrowthBook is "enabled" (user has an API key and analytics are on)
// but has never connected to the remote server, so both in-memory
// and disk caches are empty.
const localDefault = getLocalGateDefault(feature)
return localDefault !== undefined ? (localDefault as T) : defaultValue
} catch { } catch {
return defaultValue return defaultValue
} }
@@ -823,7 +905,8 @@ export function checkStatsigFeatureGate_CACHED_MAY_BE_STALE(
} }
if (!isGrowthBookEnabled()) { if (!isGrowthBookEnabled()) {
return false const localDefault = getLocalGateDefault(gate)
return localDefault !== undefined ? Boolean(localDefault) : false
} }
// Log experiment exposure if data is available, otherwise defer until after init // Log experiment exposure if data is available, otherwise defer until after init
@@ -841,7 +924,13 @@ export function checkStatsigFeatureGate_CACHED_MAY_BE_STALE(
return Boolean(gbCached) return Boolean(gbCached)
} }
// Fallback to Statsig cache for migration period // Fallback to Statsig cache for migration period
return config.cachedStatsigGates?.[gate] ?? false const statsigCached = config.cachedStatsigGates?.[gate]
if (statsigCached !== undefined) {
return statsigCached
}
// Neither cache has a value — use local gate defaults
const localDefault = getLocalGateDefault(gate)
return localDefault !== undefined ? Boolean(localDefault) : false
} }
/** /**
@@ -923,7 +1012,8 @@ export async function checkGate_CACHED_OR_BLOCKING(
} }
if (!isGrowthBookEnabled()) { if (!isGrowthBookEnabled()) {
return false const localDefault = getLocalGateDefault(gate)
return localDefault !== undefined ? Boolean(localDefault) : false
} }
// Fast path: disk cache already says true — trust it // Fast path: disk cache already says true — trust it