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

@@ -23,7 +23,19 @@ const defineArgs = Object.entries(defines).flatMap(([k, v]) => [
// Bun --feature flags: enable feature() gates at runtime.
// 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.
// 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.')