diff --git a/build.ts b/build.ts index f65804a94..9b7817cc0 100644 --- a/build.ts +++ b/build.ts @@ -121,23 +121,7 @@ const vendorDir = join(outdir, 'vendor', 'audio-capture') await cp('vendor/audio-capture', vendorDir, { recursive: true }) console.log(`Copied vendor/audio-capture/ → ${vendorDir}/`) -// Step 5: Bundle download-ripgrep script as standalone JS for postinstall -const rgScript = await Bun.build({ - entrypoints: ['scripts/download-ripgrep.ts'], - outdir, - target: 'node', -}) -if (!rgScript.success) { - console.error('Failed to bundle download-ripgrep script:') - for (const log of rgScript.logs) { - console.error(log) - } - // Non-fatal — postinstall fallback to bun run scripts/download-ripgrep.ts -} else { - console.log(`Bundled download-ripgrep script to ${outdir}/`) -} - -// Step 6: Generate cli-bun and cli-node executable entry points +// Step 5: Generate cli-bun and cli-node executable entry points const cliBun = join(outdir, 'cli-bun.js') const cliNode = join(outdir, 'cli-node.js') diff --git a/scripts/download-ripgrep.ts b/scripts/download-ripgrep.ts deleted file mode 100644 index edf12c3d7..000000000 --- a/scripts/download-ripgrep.ts +++ /dev/null @@ -1,335 +0,0 @@ -/** - * Download ripgrep binary from GitHub releases. - * - * Run automatically via `bun install` (postinstall hook), - * or manually: `bun run scripts/download-ripgrep.ts [--force]` - * - * Idempotent — skips download if the binary already exists. - * Use --force to re-download. - * - * Environment: - * - HTTPS_PROXY / HTTP_PROXY — when set, download uses `undici` + EnvHttpProxyAgent. - * - RIPGREP_DOWNLOAD_BASE — override release URL prefix, e.g. mirror: - * `https://ghproxy.net/https://github.com/microsoft/ripgrep-prebuilt/releases/download/v15.0.1` - */ - -import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, statSync } from 'fs' -import { setDefaultResultOrder } from 'node:dns' -import { tmpdir } from 'os' -import { chmodSync } from 'fs' -import { spawnSync } from 'child_process' -import * as path from 'path' -import { fileURLToPath } from 'url' - -const __filename = fileURLToPath(import.meta.url) -const __dirname = path.dirname(__filename) - -// Prefer IPv4 first — Bun on Windows sometimes fails GitHub over broken IPv6 paths. -try { - setDefaultResultOrder('ipv4first') -} catch { - /* ignore */ -} - -const RG_VERSION = '15.0.1' -const DEFAULT_RELEASE_BASE = `https://github.com/microsoft/ripgrep-prebuilt/releases/download/v${RG_VERSION}` -const RELEASE_BASE = (process.env.RIPGREP_DOWNLOAD_BASE ?? DEFAULT_RELEASE_BASE).replace(/\/$/, '') - -// --- Platform mapping --- - -type PlatformMapping = { - target: string - ext: 'tar.gz' | 'zip' -} - -function getPlatformMapping(): PlatformMapping { - const arch = process.arch - const platform = process.platform - - if (platform === 'darwin') { - if (arch === 'arm64') return { target: 'aarch64-apple-darwin', ext: 'tar.gz' } - if (arch === 'x64') return { target: 'x86_64-apple-darwin', ext: 'tar.gz' } - throw new Error(`Unsupported macOS arch: ${arch}`) - } - - if (platform === 'win32') { - if (arch === 'x64') return { target: 'x86_64-pc-windows-msvc', ext: 'zip' } - if (arch === 'arm64') return { target: 'aarch64-pc-windows-msvc', ext: 'zip' } - throw new Error(`Unsupported Windows arch: ${arch}`) - } - - if (platform === 'linux') { - const isMusl = detectMusl() - if (arch === 'x64') { - // x64 Linux always uses musl (statically linked, most portable) - return { target: 'x86_64-unknown-linux-musl', ext: 'tar.gz' } - } - if (arch === 'arm64') { - return isMusl - ? { target: 'aarch64-unknown-linux-musl', ext: 'tar.gz' } - : { target: 'aarch64-unknown-linux-gnu', ext: 'tar.gz' } - } - throw new Error(`Unsupported Linux arch: ${arch}`) - } - - throw new Error(`Unsupported platform: ${platform}`) -} - -function detectMusl(): boolean { - const muslArch = process.arch === 'x64' ? 'x86_64' : 'aarch64' - try { - statSync(`/lib/libc.musl-${muslArch}.so.1`) - return true - } catch { - return false - } -} - -// --- Target vendor path (must match ripgrep.ts logic) --- - -function getVendorDir(): string { - const packageRoot = path.resolve(__dirname, '..') - - // Dev mode: package root has src/ directory - // ripgrep.ts at src/utils/ripgrep.ts: __dirname = src/utils/ - // vendor path = src/utils/vendor/ripgrep/ - if (existsSync(path.join(packageRoot, 'src'))) { - return path.resolve(packageRoot, 'src', 'utils', 'vendor', 'ripgrep') - } - - // Published mode: compiled chunks are flat in dist/ - // ripgrep chunk at dist/xxxx.js: __dirname = dist/ - // vendor path = dist/vendor/ripgrep/ - return path.resolve(packageRoot, 'dist', 'vendor', 'ripgrep') -} - -function getBinaryPath(): string { - const dir = getVendorDir() - const subdir = `${process.arch}-${process.platform}` - const binary = process.platform === 'win32' ? 'rg.exe' : 'rg' - return path.resolve(dir, subdir, binary) -} - -// --- Download & extract --- - -function proxyEnvSet(): boolean { - const v = (s: string | undefined) => (s ?? '').trim() - return !!( - v(process.env.HTTPS_PROXY) || - v(process.env.HTTP_PROXY) || - v(process.env.ALL_PROXY) || - v(process.env.https_proxy) || - v(process.env.http_proxy) - ) -} - -async function fetchRelease(url: string): Promise { - if (proxyEnvSet()) { - const { EnvHttpProxyAgent, fetch: undiciFetch } = await import('undici') - return (await undiciFetch(url, { - redirect: 'follow', - dispatcher: new EnvHttpProxyAgent(), - })) as unknown as Response - } - return await fetch(url, { redirect: 'follow' }) -} - -function tryPowerShellDownload(url: string, dest: string): boolean { - const u = url.replace(/'/g, "''") - const d = dest.replace(/'/g, "''") - const cmd = `Invoke-WebRequest -Uri '${u}' -OutFile '${d}' -UseBasicParsing` - const result = spawnSync( - 'powershell.exe', - ['-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-Command', cmd], - { stdio: 'pipe', windowsHide: true }, - ) - return result.status === 0 && existsSync(dest) && statSync(dest).size > 0 -} - -function tryCurlDownload(url: string, dest: string): boolean { - const curl = process.platform === 'win32' ? 'curl.exe' : 'curl' - const result = spawnSync(curl, ['-fsSL', '-L', '--fail', '-o', dest, url], { - stdio: 'pipe', - windowsHide: true, - }) - return result.status === 0 && existsSync(dest) && statSync(dest).size > 0 -} - -/** Bun `fetch` on Windows can fail while browser / WinINET still works — use subprocess fallbacks. */ -async function downloadUrlToBuffer(url: string): Promise { - const response = await fetchRelease(url) - if (!response.ok) { - throw new Error(`Download failed: ${response.status} ${response.statusText}`) - } - return Buffer.from(await response.arrayBuffer()) -} - -async function downloadUrlToBufferWithFallback(url: string): Promise { - let firstError: unknown - try { - return await downloadUrlToBuffer(url) - } catch (e) { - firstError = e - } - - const tmpRoot = path.join(tmpdir(), `ripgrep-dl-${process.pid}-${Date.now()}`) - const tmpFile = path.join(tmpRoot, 'archive') - mkdirSync(tmpRoot, { recursive: true }) - try { - if (process.platform === 'win32' && tryPowerShellDownload(url, tmpFile)) { - return readFileSync(tmpFile) - } - if (tryCurlDownload(url, tmpFile)) { - return readFileSync(tmpFile) - } - } finally { - rmSync(tmpRoot, { recursive: true, force: true }) - } - - throw firstError -} - -function findZipEntryKey(files: Record, want: string): string | undefined { - return Object.keys(files).find(k => { - const norm = k.replace(/\\/g, '/') - return norm === want || norm.endsWith(`/${want}`) - }) -} - -async function downloadAndExtract(): Promise { - const { target, ext } = getPlatformMapping() - const assetName = `ripgrep-v${RG_VERSION}-${target}.${ext}` - const downloadUrl = `${RELEASE_BASE}/${assetName}` - - const binaryPath = getBinaryPath() - const binaryDir = path.dirname(binaryPath) - - // Idempotent: skip if binary exists and has content - const force = process.argv.includes('--force') - if (!force && existsSync(binaryPath)) { - const stat = statSync(binaryPath) - if (stat.size > 0) { - console.log(`[ripgrep] Binary already exists at ${binaryPath}, skipping.`) - return - } - } - - console.log(`[ripgrep] Downloading v${RG_VERSION} for ${target}...`) - console.log(`[ripgrep] URL: ${downloadUrl}`) - - const extractedBinary = process.platform === 'win32' ? 'rg.exe' : 'rg' - const { writeFileSync } = await import('fs') - - try { - const buffer = await downloadUrlToBufferWithFallback(downloadUrl) - console.log(`[ripgrep] Downloaded ${Math.round(buffer.length / 1024)} KB`) - - mkdirSync(binaryDir, { recursive: true }) - - if (ext === 'tar.gz') { - const tmpDir = path.join(binaryDir, '.tmp-download') - rmSync(tmpDir, { recursive: true, force: true }) - mkdirSync(tmpDir, { recursive: true }) - try { - const archivePath = path.join(tmpDir, assetName) - writeFileSync(archivePath, buffer) - const result = spawnSync('tar', ['xzf', archivePath, '-C', tmpDir], { - stdio: 'pipe', - }) - if (result.status !== 0) { - throw new Error(`tar extract failed: ${result.stderr?.toString()}`) - } - const srcBinary = path.join(tmpDir, extractedBinary) - if (!existsSync(srcBinary)) { - throw new Error(`Binary not found at expected path: ${srcBinary}`) - } - renameSync(srcBinary, binaryPath) - } finally { - rmSync(tmpDir, { recursive: true, force: true }) - } - } else { - let fflateError: unknown - try { - const { unzipSync } = await import('fflate') - const unzipped = unzipSync(new Uint8Array(buffer)) - const key = findZipEntryKey(unzipped, extractedBinary) - if (!key) { - throw new Error(`Binary ${extractedBinary} not found in zip`) - } - writeFileSync(binaryPath, Buffer.from(unzipped[key])) - fflateError = undefined - } catch (e) { - fflateError = e - } - - if (fflateError) { - // fflate failed — try PowerShell Expand-Archive on Windows, then unzip CLI - const tmpDir = path.join(binaryDir, '.tmp-download') - rmSync(tmpDir, { recursive: true, force: true }) - mkdirSync(tmpDir, { recursive: true }) - try { - const archivePath = path.join(tmpDir, assetName) - writeFileSync(archivePath, buffer) - - let extracted = false - - // On Windows, prefer PowerShell Expand-Archive - if (process.platform === 'win32') { - const psCmd = `Expand-Archive -Path '${archivePath.replace(/'/g, "''")}' -DestinationPath '${tmpDir.replace(/'/g, "''")}' -Force` - const psResult = spawnSync( - 'powershell.exe', - ['-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-Command', psCmd], - { stdio: 'pipe', windowsHide: true }, - ) - if (psResult.status === 0) { - extracted = true - } else { - const psErr = psResult.stderr?.toString().trim() || 'unknown error' - console.log(`[ripgrep] PowerShell Expand-Archive failed: ${psErr}`) - } - } - - // Fallback: unzip CLI (Git for Windows, MSYS2, or Unix) - if (!extracted) { - const result = spawnSync('unzip', ['-o', archivePath, '-d', tmpDir], { - stdio: 'pipe', - }) - if (result.status !== 0) { - const unzipErr = result.stderr?.toString().trim() || 'command not found' - const fflateMsg = fflateError instanceof Error ? fflateError.message : String(fflateError) - throw new Error(`zip extraction failed (fflate: ${fflateMsg}; unzip: ${unzipErr})`) - } - } - - const srcBinary = path.join(tmpDir, extractedBinary) - if (!existsSync(srcBinary)) { - throw new Error(`Binary not found at expected path: ${srcBinary}`) - } - renameSync(srcBinary, binaryPath) - } finally { - rmSync(tmpDir, { recursive: true, force: true }) - } - } - } - - if (process.platform !== 'win32') { - chmodSync(binaryPath, 0o755) - } - - console.log(`[ripgrep] Installed to ${binaryPath}`) - } catch (e) { - const msg = e instanceof Error ? e.message : String(e) - const hint = - 'Check network or set HTTPS_PROXY. If GitHub is blocked, set RIPGREP_DOWNLOAD_BASE to a mirror (see script header).' - throw new Error(`${msg} ${hint}`) - } -} - -// --- Main --- - -downloadAndExtract().catch(error => { - console.error(`[ripgrep] Download failed: ${error.message}`) - console.error(`[ripgrep] You can install ripgrep manually: https://github.com/BurntSushi/ripgrep#installation`) - // Don't exit with error code — postinstall should not break bun install - process.exit(0) -}) diff --git a/scripts/health-check.ts b/scripts/health-check.ts deleted file mode 100644 index 458420989..000000000 --- a/scripts/health-check.ts +++ /dev/null @@ -1,163 +0,0 @@ -#!/usr/bin/env bun -/** - * 代码健康度检查脚本 - * - * 汇总项目各维度指标,输出健康度报告: - * - 代码规模(文件数、代码行数) - * - Lint 问题数(Biome) - * - 测试结果(Bun test) - * - 冗余代码(Knip) - * - 构建状态 - */ - -import { $ } from "bun"; - -const DIVIDER = "─".repeat(60); - -interface Metric { - label: string; - value: string | number; - status: "ok" | "warn" | "error" | "info"; -} - -const metrics: Metric[] = []; - -function add(label: string, value: string | number, status: Metric["status"] = "info") { - metrics.push({ label, value, status }); -} - -function icon(status: Metric["status"]): string { - switch (status) { - case "ok": - return "[OK]"; - case "warn": - return "[!!]"; - case "error": - return "[XX]"; - case "info": - return "[--]"; - } -} - -// --------------------------------------------------------------------------- -// 1. 代码规模 -// --------------------------------------------------------------------------- -async function checkCodeSize() { - const tsFiles = await $`find src -name '*.ts' -o -name '*.tsx' | grep -v node_modules`.text(); - const fileCount = tsFiles.trim().split("\n").filter(Boolean).length; - add("TypeScript 文件数", fileCount, "info"); - - const loc = await $`find src -name '*.ts' -o -name '*.tsx' | grep -v node_modules | xargs wc -l | tail -1`.text(); - const totalLines = loc.trim().split(/\s+/)[0] ?? "?"; - add("总代码行数 (src/)", totalLines, "info"); -} - -// --------------------------------------------------------------------------- -// 2. Lint 检查 -// --------------------------------------------------------------------------- -async function checkLint() { - try { - const result = await $`bunx biome check src/ 2>&1`.quiet().nothrow().text(); - const errorMatch = result.match(/Found (\d+) errors?/); - const warnMatch = result.match(/Found (\d+) warnings?/); - const errors = errorMatch ? Number.parseInt(errorMatch[1]) : 0; - const warnings = warnMatch ? Number.parseInt(warnMatch[1]) : 0; - add("Lint 错误", errors, errors === 0 ? "ok" : errors < 100 ? "warn" : "info"); - add("Lint 警告", warnings, warnings === 0 ? "ok" : "info"); - } catch { - add("Lint 检查", "执行失败", "error"); - } -} - -// --------------------------------------------------------------------------- -// 3. 测试 -// --------------------------------------------------------------------------- -async function checkTests() { - try { - const result = await $`bun test 2>&1`.quiet().nothrow().text(); - const passMatch = result.match(/(\d+) pass/); - const failMatch = result.match(/(\d+) fail/); - const pass = passMatch ? Number.parseInt(passMatch[1]) : 0; - const fail = failMatch ? Number.parseInt(failMatch[1]) : 0; - add("测试通过", pass, pass > 0 ? "ok" : "warn"); - add("测试失败", fail, fail === 0 ? "ok" : "error"); - } catch { - add("测试", "执行失败", "error"); - } -} - -// --------------------------------------------------------------------------- -// 4. 冗余代码 -// --------------------------------------------------------------------------- -async function checkUnused() { - try { - const result = await $`bunx knip-bun 2>&1`.quiet().nothrow().text(); - const unusedFiles = result.match(/Unused files \((\d+)\)/); - const unusedExports = result.match(/Unused exports \((\d+)\)/); - const unusedDeps = result.match(/Unused dependencies \((\d+)\)/); - add("未使用文件", unusedFiles?.[1] ?? "0", "info"); - add("未使用导出", unusedExports?.[1] ?? "0", "info"); - add("未使用依赖", unusedDeps?.[1] ?? "0", unusedDeps && Number(unusedDeps[1]) > 0 ? "warn" : "ok"); - } catch { - add("冗余代码检查", "执行失败", "error"); - } -} - -// --------------------------------------------------------------------------- -// 5. 构建 -// --------------------------------------------------------------------------- -async function checkBuild() { - try { - const result = await $`bun run build 2>&1`.quiet().nothrow(); - if (result.exitCode === 0) { - // 获取产物大小 - const stat = Bun.file("dist/cli.js"); - const mb = (stat.size / 1024 / 1024).toFixed(1); - const size = `${mb} MB`; - add("构建状态", "成功", "ok"); - add("产物大小 (dist/cli.js)", size, "info"); - } else { - add("构建状态", "失败", "error"); - } - } catch { - add("构建", "执行失败", "error"); - } -} - -// --------------------------------------------------------------------------- -// Run -// --------------------------------------------------------------------------- -console.log(""); -console.log(DIVIDER); -console.log(" 代码健康度检查报告"); -console.log(` ${new Date().toLocaleString("zh-CN")}`); -console.log(DIVIDER); - -await checkCodeSize(); -await checkLint(); -await checkTests(); -await checkUnused(); -await checkBuild(); - -console.log(""); -for (const m of metrics) { - const tag = icon(m.status); - console.log(` ${tag} ${m.label.padEnd(20)} ${m.value}`); -} - -const errorCount = metrics.filter((m) => m.status === "error").length; -const warnCount = metrics.filter((m) => m.status === "warn").length; - -console.log(""); -console.log(DIVIDER); -if (errorCount > 0) { - console.log(` 结果: ${errorCount} 个错误, ${warnCount} 个警告`); -} else if (warnCount > 0) { - console.log(` 结果: 无错误, ${warnCount} 个警告`); -} else { - console.log(" 结果: 全部通过"); -} -console.log(DIVIDER); -console.log(""); - -process.exit(errorCount > 0 ? 1 : 0); diff --git a/scripts/post-build.ts b/scripts/post-build.ts index 9f3c4793a..132f8c2e6 100644 --- a/scripts/post-build.ts +++ b/scripts/post-build.ts @@ -4,8 +4,7 @@ * * 1. Patch globalThis.Bun destructuring in third-party deps for Node.js compat * 2. Copy native addon files - * 3. Bundle standalone scripts (download-ripgrep) - * 4. Generate dual entry points (cli-bun.js, cli-node.js) + * 3. Generate dual entry points (cli-bun.js, cli-node.js) */ import { readdir, readFile, writeFile, cp } from "node:fs/promises"; import { chmodSync } from "node:fs"; @@ -41,35 +40,7 @@ async function postBuild() { await cp("vendor/audio-capture", vendorDir, { recursive: true } as never); console.log(`Copied vendor/audio-capture/ → ${vendorDir}/`); - // Step 3: Bundle standalone scripts via Bun.build (kept for simplicity) - try { - const { default: Bun } = await import("bun"); - const rgScript = await Bun.build({ - entrypoints: ["scripts/download-ripgrep.ts"], - outdir, - target: "node", - }); - if (rgScript.success) { - console.log(`Bundled download-ripgrep script to ${outdir}/`); - } else { - console.warn("Failed to bundle download-ripgrep script (non-fatal)"); - } - } catch { - // Bun not available — try esbuild fallback - try { - execSync( - `npx esbuild scripts/download-ripgrep.ts --bundle --platform=node --outfile=${outdir}/download-ripgrep.js --format=esm`, - { stdio: "inherit" }, - ); - console.log(`Bundled download-ripgrep script via esbuild to ${outdir}/`); - } catch { - console.warn( - "Failed to bundle download-ripgrep script — skipping (non-fatal)", - ); - } - } - - // Step 4: Generate dual entry points + // Step 3: Generate dual entry points const cliBun = join(outdir, "cli-bun.js"); const cliNode = join(outdir, "cli-node.js"); diff --git a/scripts/rcs.ts b/scripts/rcs.ts index e57111e97..5e2e284ba 100644 --- a/scripts/rcs.ts +++ b/scripts/rcs.ts @@ -10,8 +10,6 @@ import { config } from "../packages/remote-control-server/src/config"; console.log(`[RCS] Starting Remote Control Server...`); console.log(`[RCS] Port: ${config.port}`); console.log(`[RCS] API Key configuration loaded`); -console.log(`[RCS] JWT Secret: ${config.jwtSecret === "change-me-in-production" ? "default (set RCS_JWT_SECRET)" : "custom"}`); -console.log(`[RCS] DB: ${config.dbPath}`); const server = await import("../packages/remote-control-server/src/index.ts"); diff --git a/scripts/verify-gates.ts b/scripts/verify-gates.ts deleted file mode 100644 index dbef1c8a4..000000000 --- a/scripts/verify-gates.ts +++ /dev/null @@ -1,107 +0,0 @@ -#!/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.') diff --git a/vite.config.ts b/vite.config.ts index 0aef1a91e..fec927ae5 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -45,7 +45,7 @@ export default defineConfig({ build: { emptyOutDir: true, outDir: "dist", - target: "esnext", + target: "es2020", copyPublicDir: false, sourcemap: false, minify: false,