diff --git a/biome.json b/biome.json index debb5e3e49..b3b63ed7f9 100644 --- a/biome.json +++ b/biome.json @@ -1,114 +1,144 @@ { - "$schema": "https://biomejs.dev/schemas/2.4.10/schema.json", - "vcs": { - "enabled": true, - "clientKind": "git", - "useIgnoreFile": true - }, - "files": { - "includes": ["**", "!!**/dist", "!!**/packages/@ant"] - }, - "formatter": { - "enabled": true, - "indentStyle": "space", - "indentWidth": 2, - "lineWidth": 80 - }, - "linter": { - "enabled": true, - "rules": { - "recommended": true, - "suspicious": { - "noExplicitAny": "off", - "noAssignInExpressions": "off", - "noDoubleEquals": "off", - "noRedeclare": "off", - "noImplicitAnyLet": "off", - "noGlobalIsNan": "off", - "noFallthroughSwitchClause": "off", - "noShadowRestrictedNames": "off", - "noArrayIndexKey": "off", - "noConsole": "off", - "noConfusingLabels": "off", - "useIterableCallbackReturn": "off" - }, - "style": { - "useConst": "off", - "noNonNullAssertion": "off", - "noParameterAssign": "off", - "useDefaultParameterLast": "off", - "noUnusedTemplateLiteral": "off", - "useTemplate": "off", - "useNumberNamespace": "off", - "useNodejsImportProtocol": "off", - "useImportType": "off" - }, - "complexity": { - "noForEach": "off", - "noBannedTypes": "off", - "noUselessConstructor": "off", - "noStaticOnlyClass": "off", - "useOptionalChain": "off", - "noUselessSwitchCase": "off", - "noUselessFragments": "off", - "noUselessTernary": "off", - "noUselessLoneBlockStatements": "off", - "noUselessEmptyExport": "off", - "useArrowFunction": "off", - "useLiteralKeys": "off" - }, - "correctness": { - "noUnusedVariables": "off", - "noUnusedImports": "off", - "useExhaustiveDependencies": "off", - "noSwitchDeclarations": "off", - "noUnreachable": "off", - "useHookAtTopLevel": "off", - "noVoidTypeReturn": "off", - "noConstantCondition": "off", - "noUnusedFunctionParameters": "off" - }, - "a11y": { - "recommended": false - }, - "nursery": { - "recommended": false - } - } - }, - "json": { - "formatter": { - "enabled": false - } - }, - "javascript": { - "formatter": { - "quoteStyle": "single", - "semicolons": "asNeeded", - "arrowParentheses": "asNeeded", - "trailingCommas": "all" - } - }, - "overrides": [ - { - "includes": ["**/*.tsx"], - "javascript": { - "formatter": { - "semicolons": "always" - } - }, - "formatter": { - "lineWidth": 120 - } - }, - { - "includes": ["scripts/**", "packages/**", "**/*.js", "**/*.mjs", "**/*.jsx"], - "formatter": { - "enabled": false - } - } - ], - "assist": { - "enabled": false - } + "$schema": "https://biomejs.dev/schemas/2.4.10/schema.json", + "vcs": { + "enabled": true, + "clientKind": "git", + "useIgnoreFile": true + }, + "files": { + "includes": [ + "**", + "!!**/dist", + "!!**/packages/@ant" + ] + }, + "formatter": { + "enabled": true, + "indentStyle": "space", + "indentWidth": 2, + "lineWidth": 80 + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "suspicious": { + "noExplicitAny": "off", + "noAssignInExpressions": "off", + "noDoubleEquals": "off", + "noRedeclare": "off", + "noImplicitAnyLet": "off", + "noGlobalIsNan": "off", + "noFallthroughSwitchClause": "off", + "noShadowRestrictedNames": "off", + "noArrayIndexKey": "off", + "noConsole": "off", + "noConfusingLabels": "off", + "useIterableCallbackReturn": "off", + "noControlCharactersInRegex": "off", + "noSelfCompare": "off", + "noTemplateCurlyInString": "off", + "noAsyncPromiseExecutor": "off" + }, + "style": { + "useConst": "off", + "noNonNullAssertion": "off", + "noParameterAssign": "off", + "useDefaultParameterLast": "off", + "noUnusedTemplateLiteral": "off", + "useTemplate": "off", + "useNumberNamespace": "off", + "useNodejsImportProtocol": "off", + "useImportType": "off" + }, + "complexity": { + "noForEach": "off", + "noBannedTypes": "off", + "noUselessConstructor": "off", + "noStaticOnlyClass": "off", + "useOptionalChain": "off", + "noUselessSwitchCase": "off", + "noUselessFragments": "off", + "noUselessTernary": "off", + "noUselessLoneBlockStatements": "off", + "noUselessEmptyExport": "off", + "useArrowFunction": "off", + "useLiteralKeys": "off" + }, + "correctness": { + "noUnusedVariables": "off", + "noUnusedImports": "off", + "useExhaustiveDependencies": "off", + "noSwitchDeclarations": "off", + "noUnreachable": "off", + "useHookAtTopLevel": "off", + "noVoidTypeReturn": "off", + "noConstantCondition": "off", + "noUnusedFunctionParameters": "off", + "useYield": "off", + "noUnusedPrivateClassMembers": "off", + "noUnusedLabels": "off" + }, + "a11y": { + "recommended": false + }, + "nursery": { + "recommended": false + }, + "security": { + "noDangerouslySetInnerHtml": "off" + } + } + }, + "json": { + "formatter": { + "enabled": false + } + }, + "javascript": { + "formatter": { + "quoteStyle": "single", + "semicolons": "asNeeded", + "arrowParentheses": "asNeeded", + "trailingCommas": "all" + } + }, + "overrides": [ + { + "includes": [ + "**/*.tsx" + ], + "javascript": { + "formatter": { + "semicolons": "always" + } + }, + "formatter": { + "lineWidth": 120 + } + }, + { + "includes": [ + "scripts/**", + "packages/**", + "**/*.js", + "**/*.mjs", + "**/*.jsx" + ], + "formatter": { + "enabled": false + } + }, + { + "includes": [ + "**/*.css" + ], + "linter": { + "enabled": false + } + } + ], + "assist": { + "enabled": false + } } diff --git a/packages/@ant/ink/src/core/ink.tsx b/packages/@ant/ink/src/core/ink.tsx index f3fdb8e6f5..9e460b94ea 100644 --- a/packages/@ant/ink/src/core/ink.tsx +++ b/packages/@ant/ink/src/core/ink.tsx @@ -1843,7 +1843,6 @@ export default class Ink { } patchConsole(): () => void { - // biome-ignore lint/suspicious/noConsole: intentionally patching global console const con = console const originals: Partial> = {} const toDebug = (...args: unknown[]) => diff --git a/packages/@ant/ink/src/core/reconciler.ts b/packages/@ant/ink/src/core/reconciler.ts index 4d75923b82..4db2223461 100644 --- a/packages/@ant/ink/src/core/reconciler.ts +++ b/packages/@ant/ink/src/core/reconciler.ts @@ -35,7 +35,6 @@ if (process.env.NODE_ENV === 'development') { // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (error: unknown) { if (error instanceof Error && (error as NodeJS.ErrnoException).code === 'ERR_MODULE_NOT_FOUND') { - // biome-ignore lint/suspicious/noConsole: intentional warning console.warn( ` The environment variable DEV is set to true, so Ink tried to import \`react-devtools-core\`, @@ -197,7 +196,6 @@ let _prepareAt = 0 /** Debug log helper — replaces fs.appendFileSync with console.warn. */ function debugLog(message: string): void { - // biome-ignore lint/suspicious/noConsole: debug instrumentation console.warn(`[ink-commit] ${message}`) } // --- END --- diff --git a/packages/@ant/ink/src/core/root.ts b/packages/@ant/ink/src/core/root.ts index 36df98c706..69caf8bef2 100644 --- a/packages/@ant/ink/src/core/root.ts +++ b/packages/@ant/ink/src/core/root.ts @@ -114,7 +114,6 @@ const wrappedRender = async ( await Promise.resolve() const instance = renderSync(node, options) if (process.env.CLAUDE_CODE_DEBUG_REPAINTS === '1') { - // biome-ignore lint/suspicious/noConsole: debug instrumentation console.warn( `[render] first ink render: ${Math.round(process.uptime() * 1000)}ms since process start`, ) diff --git a/packages/builtin-tools/src/tools/BashTool/BashTool.tsx b/packages/builtin-tools/src/tools/BashTool/BashTool.tsx index 5966ba418c..cd14b24d77 100644 --- a/packages/builtin-tools/src/tools/BashTool/BashTool.tsx +++ b/packages/builtin-tools/src/tools/BashTool/BashTool.tsx @@ -1140,7 +1140,7 @@ async function* runShellCommand({ let lastProgressOutput = '' let lastTotalLines = 0 let lastTotalBytes = 0 - let backgroundShellId: string | undefined = undefined + let backgroundShellId: string | undefined let assistantAutoBackgrounded = false // Progress signal: resolved by onProgress callback from the shared poller, @@ -1314,7 +1314,7 @@ async function* runShellCommand({ // Wait for the initial threshold before showing progress const startTime = Date.now() - let foregroundTaskId: string | undefined = undefined + let foregroundTaskId: string | undefined { const initialResult = await Promise.race([ diff --git a/packages/builtin-tools/src/tools/FileReadTool/imageProcessor.ts b/packages/builtin-tools/src/tools/FileReadTool/imageProcessor.ts index de7ea49054..6ee07a7461 100644 --- a/packages/builtin-tools/src/tools/FileReadTool/imageProcessor.ts +++ b/packages/builtin-tools/src/tools/FileReadTool/imageProcessor.ts @@ -49,7 +49,6 @@ export async function getImageProcessor(): Promise { return sharpFn } catch { // Fall back to sharp if native module is not available - // biome-ignore lint/suspicious/noConsole: intentional warning console.warn( 'Native image processor not available, falling back to sharp', ) diff --git a/packages/builtin-tools/src/tools/NotebookEditTool/NotebookEditTool.ts b/packages/builtin-tools/src/tools/NotebookEditTool/NotebookEditTool.ts index ac828efc83..9ffe206531 100644 --- a/packages/builtin-tools/src/tools/NotebookEditTool/NotebookEditTool.ts +++ b/packages/builtin-tools/src/tools/NotebookEditTool/NotebookEditTool.ts @@ -377,7 +377,7 @@ export const NotebookEditTool = buildTool({ } const language = notebook.metadata.language_info?.name ?? 'python' - let new_cell_id = undefined + let new_cell_id if ( notebook.nbformat > 4 || (notebook.nbformat === 4 && notebook.nbformat_minor >= 5) diff --git a/packages/builtin-tools/src/tools/PowerShellTool/PowerShellTool.tsx b/packages/builtin-tools/src/tools/PowerShellTool/PowerShellTool.tsx index 2b5d7f7fbc..226ddf2b27 100644 --- a/packages/builtin-tools/src/tools/PowerShellTool/PowerShellTool.tsx +++ b/packages/builtin-tools/src/tools/PowerShellTool/PowerShellTool.tsx @@ -908,7 +908,7 @@ async function* runPowerShellCommand({ let lastProgressOutput = '' let lastTotalLines = 0 let lastTotalBytes = 0 - let backgroundShellId: string | undefined = undefined + let backgroundShellId: string | undefined let interruptBackgroundingStarted = false let assistantAutoBackgrounded = false @@ -1109,7 +1109,7 @@ async function* runPowerShellCommand({ // Set up progress yielding with periodic checks const startTime = Date.now() let nextProgressTime = startTime + PROGRESS_THRESHOLD_MS - let foregroundTaskId: string | undefined = undefined + let foregroundTaskId: string | undefined // Progress loop: wrap in try/finally so stopPolling is called on every exit // path — normal completion, timeout/interrupt backgrounding, and Ctrl+B diff --git a/packages/remote-control-server/src/__tests__/sse-writer.test.ts b/packages/remote-control-server/src/__tests__/sse-writer.test.ts index c2ed6f37b8..b43dc46968 100644 --- a/packages/remote-control-server/src/__tests__/sse-writer.test.ts +++ b/packages/remote-control-server/src/__tests__/sse-writer.test.ts @@ -117,7 +117,7 @@ describe("SSE Writer", () => { app.get("/stream/:sessionId", (c) => { const sessionId = c.req.param("sessionId"); - const fromSeq = parseInt(c.req.query("fromSeq") || "0"); + const fromSeq = parseInt(c.req.query("fromSeq") || "0", 10); return createSSEStream(c, sessionId, fromSeq); }); diff --git a/packages/remote-control-server/src/config.ts b/packages/remote-control-server/src/config.ts index 39ba2c4003..f2503cf4ac 100644 --- a/packages/remote-control-server/src/config.ts +++ b/packages/remote-control-server/src/config.ts @@ -1,20 +1,20 @@ export const config = { version: process.env.RCS_VERSION || "0.1.0", - port: parseInt(process.env.RCS_PORT || "3000"), + port: parseInt(process.env.RCS_PORT || "3000", 10), host: process.env.RCS_HOST || "0.0.0.0", apiKeys: (process.env.RCS_API_KEYS || "").split(",").filter(Boolean), baseUrl: process.env.RCS_BASE_URL || "", - pollTimeout: parseInt(process.env.RCS_POLL_TIMEOUT || "8"), - heartbeatInterval: parseInt(process.env.RCS_HEARTBEAT_INTERVAL || "20"), - jwtExpiresIn: parseInt(process.env.RCS_JWT_EXPIRES_IN || "3600"), - disconnectTimeout: parseInt(process.env.RCS_DISCONNECT_TIMEOUT || "300"), + pollTimeout: parseInt(process.env.RCS_POLL_TIMEOUT || "8", 10), + heartbeatInterval: parseInt(process.env.RCS_HEARTBEAT_INTERVAL || "20", 10), + jwtExpiresIn: parseInt(process.env.RCS_JWT_EXPIRES_IN || "3600", 10), + disconnectTimeout: parseInt(process.env.RCS_DISCONNECT_TIMEOUT || "300", 10), /** Bun WebSocket idle timeout (seconds). Bun sends protocol-level pings after * this many seconds of no received data. Must be shorter than any reverse * proxy's idle timeout (nginx default 60s, Cloudflare 100s). Default 30s. */ - wsIdleTimeout: parseInt(process.env.RCS_WS_IDLE_TIMEOUT || "30"), + wsIdleTimeout: parseInt(process.env.RCS_WS_IDLE_TIMEOUT || "30", 10), /** Server→client keep_alive data-frame interval (seconds). Keeps reverse * proxies from closing idle connections. Default 20s. */ - wsKeepaliveInterval: parseInt(process.env.RCS_WS_KEEPALIVE_INTERVAL || "20"), + wsKeepaliveInterval: parseInt(process.env.RCS_WS_KEEPALIVE_INTERVAL || "20", 10), } as const; export function getBaseUrl(): string { diff --git a/packages/remote-control-server/src/routes/acp/index.ts b/packages/remote-control-server/src/routes/acp/index.ts index faef347eaa..a7400644ca 100644 --- a/packages/remote-control-server/src/routes/acp/index.ts +++ b/packages/remote-control-server/src/routes/acp/index.ts @@ -100,7 +100,7 @@ app.get("/channel-groups/:id/events", async (c) => { // Support Last-Event-ID / from_sequence_num for reconnection const lastEventId = c.req.header("Last-Event-ID"); const fromSeq = c.req.query("from_sequence_num"); - const fromSeqNum = fromSeq ? parseInt(fromSeq) : lastEventId ? parseInt(lastEventId) : 0; + const fromSeqNum = fromSeq ? parseInt(fromSeq, 10) : lastEventId ? parseInt(lastEventId, 10) : 0; return createAcpSSEStream(c, groupId, fromSeqNum); }); diff --git a/packages/remote-control-server/src/routes/v2/worker-events-stream.ts b/packages/remote-control-server/src/routes/v2/worker-events-stream.ts index 02b605a438..21e86ab945 100644 --- a/packages/remote-control-server/src/routes/v2/worker-events-stream.ts +++ b/packages/remote-control-server/src/routes/v2/worker-events-stream.ts @@ -16,7 +16,7 @@ app.get("/:id/worker/events/stream", acceptCliHeaders, sessionIngressAuth, async // Support Last-Event-ID / from_sequence_num for reconnection const lastEventId = c.req.header("Last-Event-ID"); const fromSeq = c.req.query("from_sequence_num"); - const fromSeqNum = fromSeq ? parseInt(fromSeq) : lastEventId ? parseInt(lastEventId) : 0; + const fromSeqNum = fromSeq ? parseInt(fromSeq, 10) : lastEventId ? parseInt(lastEventId, 10) : 0; return createWorkerEventStream(c, sessionId, fromSeqNum); }); diff --git a/packages/remote-control-server/src/routes/web/sessions.ts b/packages/remote-control-server/src/routes/web/sessions.ts index 8f2f4380aa..f77adf16d8 100644 --- a/packages/remote-control-server/src/routes/web/sessions.ts +++ b/packages/remote-control-server/src/routes/web/sessions.ts @@ -111,7 +111,7 @@ app.get("/sessions/:id/events", uuidAuth, async (c) => { } const lastEventId = c.req.header("Last-Event-ID"); - const fromSeqNum = lastEventId ? parseInt(lastEventId) : 0; + const fromSeqNum = lastEventId ? parseInt(lastEventId, 10) : 0; return createSSEStream(c, sessionId, fromSeqNum); }); diff --git a/packages/remote-control-server/src/services/automationState.ts b/packages/remote-control-server/src/services/automationState.ts index 544d885cc1..2ef5c313bd 100644 --- a/packages/remote-control-server/src/services/automationState.ts +++ b/packages/remote-control-server/src/services/automationState.ts @@ -29,7 +29,7 @@ function readAutomationStateValue(metadata: Record | null | und if (!metadata || typeof metadata !== "object") { return undefined; } - if (!Object.prototype.hasOwnProperty.call(metadata, "automation_state")) { + if (!Object.hasOwn(metadata, "automation_state")) { return undefined; } return metadata.automation_state; diff --git a/src/bridge/bridgeMain.ts b/src/bridge/bridgeMain.ts index b1fa496edb..1873657c7e 100644 --- a/src/bridge/bridgeMain.ts +++ b/src/bridge/bridgeMain.ts @@ -1675,7 +1675,7 @@ async function stopWorkWithRetry( } const errMsg = errorMessage(err) if (attempt < MAX_ATTEMPTS) { - const delay = addJitter(baseDelayMs * Math.pow(2, attempt - 1)) + const delay = addJitter(baseDelayMs * 2 ** (attempt - 1)) logger.logVerbose( `Failed to stop work ${workId} (attempt ${attempt}/${MAX_ATTEMPTS}), retrying in ${formatDelay(delay)}: ${errMsg}`, ) @@ -1963,7 +1963,6 @@ NOTES - You must be logged in with a Claude account that has a subscription - Run \`claude\` first in the directory to accept the workspace trust dialog ${serverNote}` - // biome-ignore lint/suspicious/noConsole: intentional help output console.log(help) } @@ -2002,7 +2001,6 @@ export async function bridgeMain(args: string[]): Promise { return } if (parsed.error) { - // biome-ignore lint/suspicious/noConsole: intentional error output console.error(`Error: ${parsed.error}`) // eslint-disable-next-line custom-rules/no-process-exit process.exit(1) @@ -2041,7 +2039,6 @@ export async function bridgeMain(args: string[]): Promise { const { PERMISSION_MODES } = await import('../types/permissions.js') const valid: readonly string[] = PERMISSION_MODES if (!valid.includes(permissionMode)) { - // biome-ignore lint/suspicious/noConsole: intentional error output console.error( `Error: Invalid permission mode '${permissionMode}'. Valid modes: ${valid.join(', ')}`, ) @@ -2084,7 +2081,6 @@ export async function bridgeMain(args: string[]): Promise { Promise.all([shutdown1PEventLogging(), shutdownDatadog()]), sleep(500, undefined, { unref: true }), ]).catch(() => {}) - // biome-ignore lint/suspicious/noConsole: intentional error output console.error( 'Error: Multi-session Remote Control is not enabled for your account yet.', ) @@ -2101,7 +2097,6 @@ export async function bridgeMain(args: string[]): Promise { // The bridge bypasses main.tsx (which renders the interactive TrustDialog via showSetupScreens), // so we must verify trust was previously established by a normal `claude` session. if (!checkHasTrustDialogAccepted()) { - // biome-ignore lint/suspicious/noConsole:: intentional console output console.error( `Error: Workspace not trusted. Please run \`claude\` in ${dir} first to review and accept the workspace trust dialog.`, ) @@ -2118,7 +2113,6 @@ export async function bridgeMain(args: string[]): Promise { const bridgeToken = getBridgeAccessToken() if (!bridgeToken) { - // biome-ignore lint/suspicious/noConsole:: intentional console output console.error(BRIDGE_LOGIN_ERROR) // eslint-disable-next-line custom-rules/no-process-exit process.exit(1) @@ -2137,7 +2131,6 @@ export async function bridgeMain(args: string[]): Promise { input: process.stdin, output: process.stdout, }) - // biome-ignore lint/suspicious/noConsole:: intentional console output console.log( '\nRemote Control lets you access this CLI session from the web (claude.ai/code)\nor the Claude app, so you can pick up where you left off on any device.\n\nYou can disconnect remote access anytime by running /remote-control again.\n', ) @@ -2169,7 +2162,6 @@ export async function bridgeMain(args: string[]): Promise { ) const found = await readBridgePointerAcrossWorktrees(dir) if (!found) { - // biome-ignore lint/suspicious/noConsole: intentional error output console.error( `Error: No recent session found in this directory or its worktrees. Run \`claude remote-control\` to start a new one.`, ) @@ -2180,7 +2172,6 @@ export async function bridgeMain(args: string[]): Promise { const ageMin = Math.round(pointer.ageMs / 60_000) const ageStr = ageMin < 60 ? `${ageMin}m` : `${Math.round(ageMin / 60)}h` const fromWt = pointerDir !== dir ? ` from worktree ${pointerDir}` : '' - // biome-ignore lint/suspicious/noConsole: intentional info output console.error( `Resuming session ${pointer.sessionId} (${ageStr} ago)${fromWt}\u2026`, ) @@ -2201,7 +2192,6 @@ export async function bridgeMain(args: string[]): Promise { !baseUrl.includes('localhost') && !baseUrl.includes('127.0.0.1') ) { - // biome-ignore lint/suspicious/noConsole:: intentional console output console.error( 'Error: Remote Control base URL uses HTTP. Only HTTPS or localhost HTTP is allowed.', ) @@ -2237,7 +2227,6 @@ export async function bridgeMain(args: string[]): Promise { ? getCurrentProjectConfig().remoteControlSpawnMode : undefined if (savedSpawnMode === 'worktree' && !worktreeAvailable) { - // biome-ignore lint/suspicious/noConsole: intentional warning output console.error( 'Warning: Saved spawn mode is worktree but this directory is not a git repository. Falling back to same-dir.', ) @@ -2264,7 +2253,6 @@ export async function bridgeMain(args: string[]): Promise { input: process.stdin, output: process.stdout, }) - // biome-ignore lint/suspicious/noConsole: intentional dialog output console.log( `\nClaude Remote Control is launching in spawn mode which lets you create new sessions in this project from Claude Code on Web or your Mobile app. Learn more here: https://code.claude.com/docs/en/remote-control\n\n` + `Spawn mode for this project:\n` + @@ -2343,7 +2331,6 @@ export async function bridgeMain(args: string[]): Promise { // Only reachable via explicit --spawn=worktree (default is same-dir); // saved worktree pref was already guarded above. if (spawnMode === 'worktree' && !worktreeAvailable) { - // biome-ignore lint/suspicious/noConsole: intentional error output console.error( `Error: Worktree mode requires a git repository or WorktreeCreate hooks configured. Use --spawn=session for single-session mode.`, ) @@ -2378,7 +2365,6 @@ export async function bridgeMain(args: string[]): Promise { try { validateBridgeId(resumeSessionId, 'sessionId') } catch { - // biome-ignore lint/suspicious/noConsole: intentional error output console.error( `Error: Invalid session ID "${resumeSessionId}". Session IDs must not contain unsafe characters.`, ) @@ -2404,7 +2390,6 @@ export async function bridgeMain(args: string[]): Promise { const { clearBridgePointer } = await import('./bridgePointer.js') await clearBridgePointer(resumePointerDir) } - // biome-ignore lint/suspicious/noConsole: intentional error output console.error( `Error: Session ${resumeSessionId} not found. It may have been archived or expired, or your login may have lapsed (run \`claude /login\`).`, ) @@ -2416,7 +2401,6 @@ export async function bridgeMain(args: string[]): Promise { const { clearBridgePointer } = await import('./bridgePointer.js') await clearBridgePointer(resumePointerDir) } - // biome-ignore lint/suspicious/noConsole: intentional error output console.error( `Error: Session ${resumeSessionId} has no environment_id. It may never have been attached to a bridge.`, ) @@ -2470,7 +2454,6 @@ export async function bridgeMain(args: string[]): Promise { status: err instanceof BridgeFatalError ? err.status : undefined, }) // Registration failures are fatal — print a clean message instead of a stack trace. - // biome-ignore lint/suspicious/noConsole:: intentional console output console.error( err instanceof BridgeFatalError && err.status === 404 ? 'Remote Control environments are not available for your account.' @@ -2495,7 +2478,6 @@ export async function bridgeMain(args: string[]): Promise { `Bridge resume env mismatch: requested ${reuseEnvironmentId}, backend returned ${environmentId}. Falling back to fresh session.`, ), ) - // biome-ignore lint/suspicious/noConsole: intentional warning output console.warn( `Warning: Could not resume session ${resumeSessionId} — its environment has expired. Creating a fresh session instead.`, ) @@ -2546,7 +2528,6 @@ export async function bridgeMain(args: string[]): Promise { const { clearBridgePointer } = await import('./bridgePointer.js') await clearBridgePointer(resumePointerDir) } - // biome-ignore lint/suspicious/noConsole: intentional error output console.error( isFatal ? `Error: ${errorMessage(err)}` diff --git a/src/cli/exit.ts b/src/cli/exit.ts index 99e56f97b5..b31fcf9041 100644 --- a/src/cli/exit.ts +++ b/src/cli/exit.ts @@ -17,7 +17,6 @@ /** Write an error message to stderr (if given) and exit with code 1. */ export function cliError(msg?: string): never { - // biome-ignore lint/suspicious/noConsole: centralized CLI error output if (msg) console.error(msg) process.exit(1) return undefined as never diff --git a/src/cli/handlers/agents.ts b/src/cli/handlers/agents.ts index f02ce8e1db..5f2a404933 100644 --- a/src/cli/handlers/agents.ts +++ b/src/cli/handlers/agents.ts @@ -59,12 +59,9 @@ export async function agentsHandler(): Promise { } if (lines.length === 0) { - // biome-ignore lint/suspicious/noConsole:: intentional console output console.log('No agents found.') } else { - // biome-ignore lint/suspicious/noConsole:: intentional console output console.log(`${totalActive} active agents\n`) - // biome-ignore lint/suspicious/noConsole:: intentional console output console.log(lines.join('\n').trimEnd()) } } diff --git a/src/cli/handlers/mcp.tsx b/src/cli/handlers/mcp.tsx index ab543af111..f9d8b3ba19 100644 --- a/src/cli/handlers/mcp.tsx +++ b/src/cli/handlers/mcp.tsx @@ -190,12 +190,10 @@ export async function mcpListHandler(): Promise { logEvent('tengu_mcp_list', {}) const { servers: configs } = await getAllMcpConfigs() if (Object.keys(configs).length === 0) { - // biome-ignore lint/suspicious/noConsole:: intentional console output console.log( 'No MCP servers configured. Use `claude mcp add` to add a server.', ) } else { - // biome-ignore lint/suspicious/noConsole:: intentional console output console.log('Checking MCP server health...\n') // Check servers concurrently @@ -213,18 +211,14 @@ export async function mcpListHandler(): Promise { for (const { name, server, status } of results) { // Intentionally excluding sse-ide servers here since they're internal if (server.type === 'sse') { - // biome-ignore lint/suspicious/noConsole:: intentional console output console.log(`${name}: ${server.url} (SSE) - ${status}`) } else if (server.type === 'http') { - // biome-ignore lint/suspicious/noConsole:: intentional console output console.log(`${name}: ${server.url} (HTTP) - ${status}`) } else if (server.type === 'claudeai-proxy') { - // biome-ignore lint/suspicious/noConsole:: intentional console output console.log(`${name}: ${server.url} - ${status}`) } else if (!server.type || server.type === 'stdio') { const stdioServer = server as { command: string; args: string[]; type?: string } const args = Array.isArray(stdioServer.args) ? stdioServer.args : [] - // biome-ignore lint/suspicious/noConsole:: intentional console output console.log(`${name}: ${stdioServer.command} ${args.join(' ')} - ${status}`) } } @@ -244,27 +238,20 @@ export async function mcpGetHandler(name: string): Promise { cliError(`No MCP server found with name: ${name}`) } - // biome-ignore lint/suspicious/noConsole:: intentional console output console.log(`${name}:`) - // biome-ignore lint/suspicious/noConsole:: intentional console output console.log(` Scope: ${getScopeLabel(server.scope)}`) // Check server health const status = await checkMcpServerHealth(name, server) - // biome-ignore lint/suspicious/noConsole:: intentional console output console.log(` Status: ${status}`) // Intentionally excluding sse-ide servers here since they're internal if (server.type === 'sse') { - // biome-ignore lint/suspicious/noConsole:: intentional console output console.log(` Type: sse`) - // biome-ignore lint/suspicious/noConsole:: intentional console output console.log(` URL: ${server.url}`) if (server.headers) { - // biome-ignore lint/suspicious/noConsole:: intentional console output console.log(' Headers:') for (const [key, value] of Object.entries(server.headers)) { - // biome-ignore lint/suspicious/noConsole:: intentional console output console.log(` ${key}: ${value}`) } } @@ -277,19 +264,14 @@ export async function mcpGetHandler(name: string): Promise { } if (server.oauth.callbackPort) parts.push(`callback_port ${server.oauth.callbackPort}`) - // biome-ignore lint/suspicious/noConsole:: intentional console output console.log(` OAuth: ${parts.join(', ')}`) } } else if (server.type === 'http') { - // biome-ignore lint/suspicious/noConsole:: intentional console output console.log(` Type: http`) - // biome-ignore lint/suspicious/noConsole:: intentional console output console.log(` URL: ${server.url}`) if (server.headers) { - // biome-ignore lint/suspicious/noConsole:: intentional console output console.log(' Headers:') for (const [key, value] of Object.entries(server.headers)) { - // biome-ignore lint/suspicious/noConsole:: intentional console output console.log(` ${key}: ${value}`) } } @@ -302,27 +284,20 @@ export async function mcpGetHandler(name: string): Promise { } if (server.oauth.callbackPort) parts.push(`callback_port ${server.oauth.callbackPort}`) - // biome-ignore lint/suspicious/noConsole:: intentional console output console.log(` OAuth: ${parts.join(', ')}`) } } else if (server.type === 'stdio') { - // biome-ignore lint/suspicious/noConsole:: intentional console output console.log(` Type: stdio`) - // biome-ignore lint/suspicious/noConsole:: intentional console output console.log(` Command: ${server.command}`) const args = Array.isArray(server.args) ? server.args : [] - // biome-ignore lint/suspicious/noConsole:: intentional console output console.log(` Args: ${args.join(' ')}`) if (server.env) { - // biome-ignore lint/suspicious/noConsole:: intentional console output console.log(' Environment:') for (const [key, value] of Object.entries(server.env)) { - // biome-ignore lint/suspicious/noConsole:: intentional console output console.log(` ${key}=${value}`) } } } - // biome-ignore lint/suspicious/noConsole:: intentional console output console.log( `\nTo remove this server, run: claude mcp remove "${name}" -s ${server.scope}`, ) diff --git a/src/cli/handlers/plugins.ts b/src/cli/handlers/plugins.ts index 9236abe0a6..8a3c48a30a 100644 --- a/src/cli/handlers/plugins.ts +++ b/src/cli/handlers/plugins.ts @@ -72,27 +72,21 @@ export function handleMarketplaceError(error: unknown, action: string): never { function printValidationResult(result: ValidationResult): void { if (result.errors.length > 0) { - // biome-ignore lint/suspicious/noConsole:: intentional console output console.log( `${figures.cross} Found ${result.errors.length} ${plural(result.errors.length, 'error')}:\n`, ) result.errors.forEach(error => { - // biome-ignore lint/suspicious/noConsole:: intentional console output console.log(` ${figures.pointer} ${error.path}: ${error.message}`) }) - // biome-ignore lint/suspicious/noConsole:: intentional console output console.log('') } if (result.warnings.length > 0) { - // biome-ignore lint/suspicious/noConsole:: intentional console output console.log( `${figures.warning} Found ${result.warnings.length} ${plural(result.warnings.length, 'warning')}:\n`, ) result.warnings.forEach(warning => { - // biome-ignore lint/suspicious/noConsole:: intentional console output console.log(` ${figures.pointer} ${warning.path}: ${warning.message}`) }) - // biome-ignore lint/suspicious/noConsole:: intentional console output console.log('') } } @@ -106,7 +100,6 @@ export async function pluginValidateHandler( try { const result = await validateManifest(manifestPath) - // biome-ignore lint/suspicious/noConsole:: intentional console output console.log(`Validating ${result.fileType} manifest: ${result.filePath}\n`) printValidationResult(result) @@ -120,7 +113,6 @@ export async function pluginValidateHandler( if (basename(manifestDir) === '.claude-plugin') { contentResults = await validatePluginContents(dirname(manifestDir)) for (const r of contentResults) { - // biome-ignore lint/suspicious/noConsole:: intentional console output console.log(`Validating ${r.fileType}: ${r.filePath}\n`) printValidationResult(r) } @@ -139,13 +131,11 @@ export async function pluginValidateHandler( : `${figures.tick} Validation passed`, ) } else { - // biome-ignore lint/suspicious/noConsole:: intentional console output console.log(`${figures.cross} Validation failed`) process.exit(1) } } catch (error) { logError(error) - // biome-ignore lint/suspicious/noConsole:: intentional console output console.error( `${figures.cross} Unexpected error during validation: ${errorMessage(error)}`, ) @@ -358,7 +348,6 @@ export async function pluginListHandler(options: { } if (pluginIds.length > 0) { - // biome-ignore lint/suspicious/noConsole:: intentional console output console.log('Installed plugins:\n') } @@ -383,25 +372,18 @@ export async function pluginListHandler(options: { const version = installation.version || 'unknown' const scope = installation.scope - // biome-ignore lint/suspicious/noConsole:: intentional console output console.log(` ${figures.pointer} ${pluginId}`) - // biome-ignore lint/suspicious/noConsole:: intentional console output console.log(` Version: ${version}`) - // biome-ignore lint/suspicious/noConsole:: intentional console output console.log(` Scope: ${scope}`) - // biome-ignore lint/suspicious/noConsole:: intentional console output console.log(` Status: ${status}`) for (const error of pluginErrors) { - // biome-ignore lint/suspicious/noConsole:: intentional console output console.log(` Error: ${getPluginErrorMessage(error)}`) } - // biome-ignore lint/suspicious/noConsole:: intentional console output console.log('') } } if (inlinePlugins.length > 0 || inlineLoadErrors.length > 0) { - // biome-ignore lint/suspicious/noConsole:: intentional console output console.log('Session-only plugins (--plugin-dir):\n') for (const p of inlinePlugins) { // Same dirName≠manifestName fallback as the JSON path above — error @@ -413,19 +395,13 @@ export async function pluginListHandler(options: { pErrors.length > 0 ? `${figures.cross} loaded with errors` : `${figures.tick} loaded` - // biome-ignore lint/suspicious/noConsole:: intentional console output console.log(` ${figures.pointer} ${p.source}`) - // biome-ignore lint/suspicious/noConsole:: intentional console output console.log(` Version: ${p.manifest.version ?? 'unknown'}`) - // biome-ignore lint/suspicious/noConsole:: intentional console output console.log(` Path: ${p.path}`) - // biome-ignore lint/suspicious/noConsole:: intentional console output console.log(` Status: ${status}`) for (const e of pErrors) { - // biome-ignore lint/suspicious/noConsole:: intentional console output console.log(` Error: ${getPluginErrorMessage(e)}`) } - // biome-ignore lint/suspicious/noConsole:: intentional console output console.log('') } // Path-level failures: no LoadedPlugin object exists. Show them so @@ -433,7 +409,6 @@ export async function pluginListHandler(options: { for (const e of inlineLoadErrors.filter(e => e.source.startsWith('inline['), )) { - // biome-ignore lint/suspicious/noConsole:: intentional console output console.log( ` ${figures.pointer} ${e.source}: ${figures.cross} ${getPluginErrorMessage(e)}\n`, ) @@ -489,12 +464,10 @@ export async function marketplaceAddHandler( } } - // biome-ignore lint/suspicious/noConsole:: intentional console output console.log('Adding marketplace...') const { name, alreadyMaterialized, resolvedSource } = await addMarketplaceSource(marketplaceSource, message => { - // biome-ignore lint/suspicious/noConsole:: intentional console output console.log(message) }) @@ -555,33 +528,25 @@ export async function marketplaceListHandler(options: { cliOk('No marketplaces configured') } - // biome-ignore lint/suspicious/noConsole:: intentional console output console.log('Configured marketplaces:\n') names.forEach(name => { const marketplace = config[name] - // biome-ignore lint/suspicious/noConsole:: intentional console output console.log(` ${figures.pointer} ${name}`) if (marketplace?.source) { const src = marketplace.source if (src.source === 'github') { - // biome-ignore lint/suspicious/noConsole:: intentional console output console.log(` Source: GitHub (${src.repo})`) } else if (src.source === 'git') { - // biome-ignore lint/suspicious/noConsole:: intentional console output console.log(` Source: Git (${src.url})`) } else if (src.source === 'url') { - // biome-ignore lint/suspicious/noConsole:: intentional console output console.log(` Source: URL (${src.url})`) } else if (src.source === 'directory') { - // biome-ignore lint/suspicious/noConsole:: intentional console output console.log(` Source: Directory (${src.path})`) } else if (src.source === 'file') { - // biome-ignore lint/suspicious/noConsole:: intentional console output console.log(` Source: File (${src.path})`) } } - // biome-ignore lint/suspicious/noConsole:: intentional console output console.log('') }) @@ -620,11 +585,9 @@ export async function marketplaceUpdateHandler( if (options.cowork) setUseCoworkPlugins(true) try { if (name) { - // biome-ignore lint/suspicious/noConsole:: intentional console output console.log(`Updating marketplace: ${name}...`) await refreshMarketplace(name, message => { - // biome-ignore lint/suspicious/noConsole:: intentional console output console.log(message) }) @@ -644,7 +607,6 @@ export async function marketplaceUpdateHandler( cliOk('No marketplaces configured') } - // biome-ignore lint/suspicious/noConsole:: intentional console output console.log(`Updating ${marketplaceNames.length} marketplace(s)...`) await refreshAllMarketplaces() diff --git a/src/cli/structuredIO.ts b/src/cli/structuredIO.ts index fba44e61bd..1566a5458d 100644 --- a/src/cli/structuredIO.ts +++ b/src/cli/structuredIO.ts @@ -462,7 +462,6 @@ export class StructuredIO { } return message } catch (error) { - // biome-ignore lint/suspicious/noConsole:: intentional console output console.error(`Error parsing streaming input line: ${line}: ${error}`) // eslint-disable-next-line custom-rules/no-process-exit process.exit(1) @@ -687,7 +686,6 @@ export class StructuredIO { ) return result } catch (error) { - // biome-ignore lint/suspicious/noConsole:: intentional console output console.error(`Error in hook callback ${callbackId}:`, error) return {} } @@ -781,7 +779,6 @@ export class StructuredIO { } function exitWithMessage(message: string): never { - // biome-ignore lint/suspicious/noConsole:: intentional console output console.error(message) // eslint-disable-next-line custom-rules/no-process-exit process.exit(1) diff --git a/src/cli/transports/SSETransport.ts b/src/cli/transports/SSETransport.ts index ca9c396da9..21725fdc9b 100644 --- a/src/cli/transports/SSETransport.ts +++ b/src/cli/transports/SSETransport.ts @@ -518,7 +518,7 @@ export class SSETransport implements Transport { this.reconnectAttempts++ const baseDelay = Math.min( - RECONNECT_BASE_DELAY_MS * Math.pow(2, this.reconnectAttempts - 1), + RECONNECT_BASE_DELAY_MS * 2 ** (this.reconnectAttempts - 1), RECONNECT_MAX_DELAY_MS, ) // Add ±25% jitter @@ -668,7 +668,7 @@ export class SSETransport implements Transport { } const delayMs = Math.min( - POST_BASE_DELAY_MS * Math.pow(2, attempt - 1), + POST_BASE_DELAY_MS * 2 ** (attempt - 1), POST_MAX_DELAY_MS, ) await sleep(delayMs) diff --git a/src/cli/transports/WebSocketTransport.ts b/src/cli/transports/WebSocketTransport.ts index 5d5d8fd75e..74985ac75a 100644 --- a/src/cli/transports/WebSocketTransport.ts +++ b/src/cli/transports/WebSocketTransport.ts @@ -516,7 +516,7 @@ export class WebSocketTransport implements Transport { this.reconnectAttempts++ const baseDelay = Math.min( - DEFAULT_BASE_RECONNECT_DELAY * Math.pow(2, this.reconnectAttempts - 1), + DEFAULT_BASE_RECONNECT_DELAY * 2 ** (this.reconnectAttempts - 1), DEFAULT_MAX_RECONNECT_DELAY, ) // Add ±25% jitter to avoid thundering herd diff --git a/src/commands/bridge/bridge.tsx b/src/commands/bridge/bridge.tsx index a9d9bc5ac5..2fe96a2822 100644 --- a/src/commands/bridge/bridge.tsx +++ b/src/commands/bridge/bridge.tsx @@ -54,7 +54,6 @@ function BridgeToggle({ onDone, name }: Props): React.ReactNode { const replBridgeOutboundOnly = useAppState(s => s.replBridgeOutboundOnly) const [showDisconnectDialog, setShowDisconnectDialog] = useState(false) - // biome-ignore lint/correctness/useExhaustiveDependencies: bridge starts once, should not restart on state changes useEffect(() => { // If already connected or enabled in full bidirectional mode, show // disconnect confirmation. Outbound-only (CCR mirror) doesn't count — diff --git a/src/commands/ide/ide.tsx b/src/commands/ide/ide.tsx index d5944636d2..a8267d3525 100644 --- a/src/commands/ide/ide.tsx +++ b/src/commands/ide/ide.tsx @@ -61,7 +61,7 @@ function IDEScreen({ } else if (value === 'None' && shouldShowDisableAutoConnectDialog()) { setShowDisableAutoConnectDialog(true) } else { - onSelect(availableIDEs.find(ide => ide.port === parseInt(value))) + onSelect(availableIDEs.find(ide => ide.port === parseInt(value, 10))) } }, [availableIDEs, onSelect], @@ -216,7 +216,7 @@ function IDEOpenSelection({ const handleSelectIDE = useCallback( (value: string) => { const selectedIDE = availableIDEs.find( - ide => ide.port === parseInt(value), + ide => ide.port === parseInt(value, 10), ) onSelectIDE(selectedIDE) }, diff --git a/src/commands/insights.ts b/src/commands/insights.ts index 1e5e40dd5a..81fd9edcee 100644 --- a/src/commands/insights.ts +++ b/src/commands/insights.ts @@ -3058,7 +3058,6 @@ const usageReport: Command = { // Show collection message if collecting if (collectRemote && hasRemoteHosts) { - // biome-ignore lint/suspicious/noConsole: intentional console.error( `Collecting sessions from ${remoteHosts.length} homespace(s): ${remoteHosts.join(', ')}...`, ) diff --git a/src/commands/model/model.tsx b/src/commands/model/model.tsx index f3523305c3..d7c86fd446 100644 --- a/src/commands/model/model.tsx +++ b/src/commands/model/model.tsx @@ -78,7 +78,7 @@ function ModelPickerWrapper({ } // Turn off fast mode if switching to unsupported model - let wasFastModeToggledOn = undefined + let wasFastModeToggledOn if (isFastModeEnabled()) { clearFastModeCooldown() if (!isFastModeSupportedByModel(model) && isFastMode) { @@ -214,7 +214,7 @@ function SetModelAndClose({ })) let message = `Set model to ${chalk.bold(renderModelLabel(modelValue))}` - let wasFastModeToggledOn = undefined + let wasFastModeToggledOn if (isFastModeEnabled()) { clearFastModeCooldown() if (!isFastModeSupportedByModel(modelValue) && isFastMode) { diff --git a/src/commands/plugin/AddMarketplace.tsx b/src/commands/plugin/AddMarketplace.tsx index 7a91383331..a7807bf83d 100644 --- a/src/commands/plugin/AddMarketplace.tsx +++ b/src/commands/plugin/AddMarketplace.tsx @@ -133,7 +133,6 @@ export function AddMarketplace({ void handleAdd() } // eslint-disable-next-line react-hooks/exhaustive-deps - // biome-ignore lint/correctness/useExhaustiveDependencies: intentional }, []) // Only run once on mount return ( diff --git a/src/commands/plugin/ManageMarketplaces.tsx b/src/commands/plugin/ManageMarketplaces.tsx index 5ec3dbe80e..b36b558964 100644 --- a/src/commands/plugin/ManageMarketplaces.tsx +++ b/src/commands/plugin/ManageMarketplaces.tsx @@ -190,7 +190,6 @@ export function ManageMarketplaces({ } void loadMarketplaces() // eslint-disable-next-line react-hooks/exhaustive-deps - // biome-ignore lint/correctness/useExhaustiveDependencies: intentional }, [targetMarketplace, action, error]) // Check if there are any pending changes diff --git a/src/components/AutoUpdater.tsx b/src/components/AutoUpdater.tsx index 6e9898adab..f6c1863bee 100644 --- a/src/components/AutoUpdater.tsx +++ b/src/components/AutoUpdater.tsx @@ -204,7 +204,6 @@ export function AutoUpdater({ // instead so the guard is always current without changing callback // identity (which would re-trigger the initial-check useEffect below). // eslint-disable-next-line react-hooks/exhaustive-deps - // biome-ignore lint/correctness/useExhaustiveDependencies: isUpdating read via ref }, [onAutoUpdaterResult]) // Initial check diff --git a/src/components/CustomSelect/use-multi-select-state.ts b/src/components/CustomSelect/use-multi-select-state.ts index a089a20d46..66ca78d70a 100644 --- a/src/components/CustomSelect/use-multi-select-state.ts +++ b/src/components/CustomSelect/use-multi-select-state.ts @@ -381,7 +381,7 @@ export function useMultiSelectState({ // Handle numeric keys (1-9) for direct selection if (!hideIndexes && /^[0-9]+$/.test(normalizedInput)) { - const index = parseInt(normalizedInput) - 1 + const index = parseInt(normalizedInput, 10) - 1 if (index >= 0 && index < options.length) { const value = options[index]!.value const newValues = selectedValues.includes(value) diff --git a/src/components/CustomSelect/use-select-input.ts b/src/components/CustomSelect/use-select-input.ts index b289056ee2..0dcccc3c1f 100644 --- a/src/components/CustomSelect/use-select-input.ts +++ b/src/components/CustomSelect/use-select-input.ts @@ -255,7 +255,7 @@ export const useSelectInput = ({ disableSelection !== 'numeric' && /^[0-9]+$/.test(normalizedInput) ) { - const index = parseInt(normalizedInput) - 1 + const index = parseInt(normalizedInput, 10) - 1 if (index >= 0 && index < state.options.length) { const selectedOption = state.options[index]! if (selectedOption.disabled === true) { diff --git a/src/components/MemoryUsageIndicator.tsx b/src/components/MemoryUsageIndicator.tsx index 92bc9e419a..11efa4a224 100644 --- a/src/components/MemoryUsageIndicator.tsx +++ b/src/components/MemoryUsageIndicator.tsx @@ -13,7 +13,6 @@ export function MemoryUsageIndicator(): React.ReactNode { } // eslint-disable-next-line react-hooks/rules-of-hooks - // biome-ignore lint/correctness/useHookAtTopLevel: USER_TYPE is a build-time constant const memoryUsage = useMemoryUsage() if (!memoryUsage) { diff --git a/src/components/NativeAutoUpdater.tsx b/src/components/NativeAutoUpdater.tsx index 77d84e9684..5aaafda9f1 100644 --- a/src/components/NativeAutoUpdater.tsx +++ b/src/components/NativeAutoUpdater.tsx @@ -169,7 +169,6 @@ export function NativeAutoUpdater({ // instead so the guard is always current without changing callback // identity (which would re-trigger the initial-check useEffect below). // eslint-disable-next-line react-hooks/exhaustive-deps - // biome-ignore lint/correctness/useExhaustiveDependencies: isUpdating read via ref }, [onAutoUpdaterResult, channel]) // Initial check diff --git a/src/components/PromptInput/Notifications.tsx b/src/components/PromptInput/Notifications.tsx index 6ddccb3ccb..9c1509df4f 100644 --- a/src/components/PromptInput/Notifications.tsx +++ b/src/components/PromptInput/Notifications.tsx @@ -254,18 +254,17 @@ function NotificationContent({ // Voice state (VOICE_MODE builds only, runtime-gated by GrowthBook) const voiceState = feature('VOICE_MODE') - ? // biome-ignore lint/correctness/useHookAtTopLevel: feature() is a compile-time constant + ? useVoiceState(s => s.voiceState) : ('idle' as const) - // biome-ignore lint/correctness/useHookAtTopLevel: feature() is a compile-time constant const voiceEnabled = feature('VOICE_MODE') ? useVoiceEnabled() : false const voiceError = feature('VOICE_MODE') - ? // biome-ignore lint/correctness/useHookAtTopLevel: feature() is a compile-time constant + ? useVoiceState(s => s.voiceError) : null const isBriefOnly = feature('KAIROS') || feature('KAIROS_BRIEF') - ? // biome-ignore lint/correctness/useHookAtTopLevel: feature() is a compile-time constant + ? useAppState(s => s.isBriefOnly) : false diff --git a/src/components/PromptInput/PromptInput.tsx b/src/components/PromptInput/PromptInput.tsx index 3d43a1faeb..5535e6ea3b 100644 --- a/src/components/PromptInput/PromptInput.tsx +++ b/src/components/PromptInput/PromptInput.tsx @@ -451,7 +451,7 @@ function PromptInput({ // its own marginTop, so the gap stays even without ours. const briefOwnsGap = feature('KAIROS') || feature('KAIROS_BRIEF') - ? // biome-ignore lint/correctness/useHookAtTopLevel: feature() is a compile-time constant + ? useAppState(s => s.isBriefOnly) && !viewingAgentTaskId : false const mainLoopModel_ = useAppState(s => s.mainLoopModel) @@ -2546,7 +2546,7 @@ function PromptInput({ useBuddyNotification() const companionSpeaking = feature('BUDDY') - ? // biome-ignore lint/correctness/useHookAtTopLevel: feature() is a compile-time constant + ? useAppState(s => s.companionReaction !== undefined) : false const { columns, rows } = useTerminalSize() diff --git a/src/components/PromptInput/PromptInputFooterLeftSide.tsx b/src/components/PromptInput/PromptInputFooterLeftSide.tsx index 8130c8ef13..bdae47d7d5 100644 --- a/src/components/PromptInput/PromptInputFooterLeftSide.tsx +++ b/src/components/PromptInput/PromptInputFooterLeftSide.tsx @@ -238,14 +238,13 @@ function ModeIndicator({ proactiveModule?.getNextTickAt ?? NULL, NULL, ) - // biome-ignore lint/correctness/useHookAtTopLevel: feature() is a compile-time constant const voiceEnabled = feature('VOICE_MODE') ? useVoiceEnabled() : false const voiceState = feature('VOICE_MODE') - ? // biome-ignore lint/correctness/useHookAtTopLevel: feature() is a compile-time constant + ? useVoiceState(s => s.voiceState) : ('idle' as const) const voiceWarmingUp = feature('VOICE_MODE') - ? // biome-ignore lint/correctness/useHookAtTopLevel: feature() is a compile-time constant + ? useVoiceState(s => s.voiceWarmingUp) : false const hasSelection = useHasSelection() @@ -282,7 +281,7 @@ function ModeIndicator({ 'ctrl+x ctrl+k', ) const voiceKeyShortcut = feature('VOICE_MODE') - ? // biome-ignore lint/correctness/useHookAtTopLevel: feature() is a compile-time constant + ? useShortcutDisplay('voice:pushToTalk', 'Chat', 'Space') : '' // Captured at mount so the hint doesn't flicker mid-session if another @@ -291,14 +290,13 @@ function ModeIndicator({ // shown" without tracking the exact render-time condition (which depends // on parts/hintParts computed after the early-return hooks boundary). const [voiceHintUnderCap] = feature('VOICE_MODE') - ? // biome-ignore lint/correctness/useHookAtTopLevel: feature() is a compile-time constant + ? useState( () => (getGlobalConfig().voiceFooterHintSeenCount ?? 0) < MAX_VOICE_HINT_SHOWS, ) : [false] - // biome-ignore lint/correctness/useHookAtTopLevel: feature() is a compile-time constant const voiceHintIncrementedRef = feature('VOICE_MODE') ? useRef(false) : null useEffect(() => { if (feature('VOICE_MODE')) { diff --git a/src/components/PromptInput/PromptInputQueuedCommands.tsx b/src/components/PromptInput/PromptInputQueuedCommands.tsx index e96ba35322..a351f87ce9 100644 --- a/src/components/PromptInput/PromptInputQueuedCommands.tsx +++ b/src/components/PromptInput/PromptInputQueuedCommands.tsx @@ -100,7 +100,7 @@ function PromptInputQueuedCommandsImpl(): React.ReactNode { // component early-returns when viewing a teammate. const useBriefLayout = feature('KAIROS') || feature('KAIROS_BRIEF') - ? // biome-ignore lint/correctness/useHookAtTopLevel: feature() is a compile-time constant + ? useAppState(s => s.isBriefOnly) : false diff --git a/src/components/ScrollKeybindingHandler.tsx b/src/components/ScrollKeybindingHandler.tsx index cdaac32561..527e21e18e 100644 --- a/src/components/ScrollKeybindingHandler.tsx +++ b/src/components/ScrollKeybindingHandler.tsx @@ -258,7 +258,7 @@ export function computeWheelStep( // the curve handles it (gap=1000ms → m≈0.01 → mult≈1). No frac — // rounding loss is minor at high mult, and frac persisting across idle // was causing off-by-one on the first click back. - const m = Math.pow(0.5, gap / WHEEL_DECAY_HALFLIFE_MS) + const m = 0.5 ** (gap / WHEEL_DECAY_HALFLIFE_MS) const cap = Math.max(WHEEL_MODE_CAP, state.base * 2) const next = 1 + (state.mult - 1) * m + WHEEL_MODE_STEP * m state.mult = Math.min(cap, next, state.mult + WHEEL_MODE_RAMP) @@ -299,7 +299,7 @@ export function computeWheelStep( state.mult = 2 state.frac = 0 } else { - const m = Math.pow(0.5, gap / WHEEL_DECAY_HALFLIFE_MS) + const m = 0.5 ** (gap / WHEEL_DECAY_HALFLIFE_MS) const cap = gap >= WHEEL_DECAY_GAP_MS ? WHEEL_DECAY_CAP_SLOW : WHEEL_DECAY_CAP_FAST state.mult = Math.min(cap, 1 + (state.mult - 1) * m + WHEEL_DECAY_STEP * m) diff --git a/src/components/Spinner.tsx b/src/components/Spinner.tsx index c2ebc9b67a..96e4466729 100644 --- a/src/components/Spinner.tsx +++ b/src/components/Spinner.tsx @@ -95,7 +95,7 @@ export function SpinnerWithVerb(props: Props): React.ReactNode { // Hoisted to mount-time — this component re-renders at animation framerate. const briefEnvEnabled = feature('KAIROS') || feature('KAIROS_BRIEF') - ? // biome-ignore lint/correctness/useHookAtTopLevel: feature() is a compile-time constant + ? useMemo(() => isEnvTruthy(process.env.CLAUDE_CODE_BRIEF), []) : false diff --git a/src/components/StatusLine.tsx b/src/components/StatusLine.tsx index b2f2c7c919..2effdb89c7 100644 --- a/src/components/StatusLine.tsx +++ b/src/components/StatusLine.tsx @@ -370,7 +370,6 @@ function StatusLineInner({ } } // eslint-disable-next-line react-hooks/exhaustive-deps - // biome-ignore lint/correctness/useExhaustiveDependencies: intentional }, []) // Only run once on mount - settings stable for initial logging // Initial update on mount + cleanup on unmount @@ -384,7 +383,6 @@ function StatusLineInner({ } } // eslint-disable-next-line react-hooks/exhaustive-deps - // biome-ignore lint/correctness/useExhaustiveDependencies: intentional }, []) // Only run once on mount, not when doUpdate changes // Get padding from settings or default to 0 diff --git a/src/components/TextInput.tsx b/src/components/TextInput.tsx index 0e13083366..9edc95c232 100644 --- a/src/components/TextInput.tsx +++ b/src/components/TextInput.tsx @@ -48,20 +48,20 @@ export default function TextInput(props: Props): React.ReactNode { const reducedMotion = settings.prefersReducedMotion ?? false const voiceState = feature('VOICE_MODE') - ? // biome-ignore lint/correctness/useHookAtTopLevel: feature() is a compile-time constant + ? useVoiceState(s => s.voiceState) : ('idle' as const) const isVoiceRecording = voiceState === 'recording' const audioLevels = feature('VOICE_MODE') - ? // biome-ignore lint/correctness/useHookAtTopLevel: feature() is a compile-time constant + ? useVoiceState(s => s.voiceAudioLevels) : [] const smoothedRef = useRef(new Array(CURSOR_WAVEFORM_WIDTH).fill(0)) const needsAnimation = isVoiceRecording && !reducedMotion const [animRef, animTime] = feature('VOICE_MODE') - ? // biome-ignore lint/correctness/useHookAtTopLevel: feature() is a compile-time constant + ? useAnimationFrame(needsAnimation ? 50 : null) : [() => {}, 0] diff --git a/src/components/WorktreeExitDialog.tsx b/src/components/WorktreeExitDialog.tsx index 83ea8d5544..52fe46eb8e 100644 --- a/src/components/WorktreeExitDialog.tsx +++ b/src/components/WorktreeExitDialog.tsx @@ -63,7 +63,7 @@ export function WorktreeExitDialog({ '--count', `${worktreeSession.originalHeadCommit}..HEAD`, ]) - const count = parseInt(commitsStr.trim()) || 0 + const count = parseInt(commitsStr.trim(), 10) || 0 setCommitCount(count) // If no changes and no commits, clean up silently @@ -94,7 +94,6 @@ export function WorktreeExitDialog({ } void loadChanges() // eslint-disable-next-line react-hooks/exhaustive-deps - // biome-ignore lint/correctness/useExhaustiveDependencies: intentional }, [worktreeSession]) useEffect(() => { diff --git a/src/components/mcp/MCPSettings.tsx b/src/components/mcp/MCPSettings.tsx index b350bf91e1..ff22967e95 100644 --- a/src/components/mcp/MCPSettings.tsx +++ b/src/components/mcp/MCPSettings.tsx @@ -60,7 +60,7 @@ export function MCPSettings({ onComplete }: Props): React.ReactNode { const isSSE = client.config.type === 'sse' const isHTTP = client.config.type === 'http' const isClaudeAIProxy = client.config.type === 'claudeai-proxy' - let isAuthenticated: boolean | undefined = undefined + let isAuthenticated: boolean | undefined if (isSSE || isHTTP) { const authProvider = new ClaudeAuthProvider( diff --git a/src/components/mcp/MCPToolListView.tsx b/src/components/mcp/MCPToolListView.tsx index ad3eb3695c..9e27629138 100644 --- a/src/components/mcp/MCPToolListView.tsx +++ b/src/components/mcp/MCPToolListView.tsx @@ -88,7 +88,7 @@ export function MCPToolListView({