From 939b75294a19385d85d0d2865b97697f6bb08bcf Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 23 Feb 2026 11:15:14 +0000 Subject: [PATCH 1/2] Initial plan From 872cedce418280fa811217f79e2be9278addf988 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 23 Feb 2026 11:32:49 +0000 Subject: [PATCH 2/2] =?UTF-8?q?=E2=98=95=20chore:=20fix=20all=2014=20bun?= =?UTF-8?q?=20lint=20errors=20using=20Biome?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/compactor/src/dictionary.ts | 2 +- packages/compactor/src/optimizer.ts | 4 +- packages/compactor/src/rules.ts | 5 +- packages/compactor/tests/compactor.test.ts | 20 +++---- packages/config/src/tools.ts | 2 +- packages/core/src/database.ts | 12 ++--- packages/core/src/loop.ts | 5 +- packages/core/src/update-checker.ts | 2 +- packages/core/tests/update-checker.test.ts | 14 ++--- packages/delegation/src/background.ts | 9 +--- packages/delegation/src/compat.ts | 6 +-- packages/delegation/src/lifecycle.ts | 5 +- packages/delegation/src/orientation.ts | 2 +- packages/delegation/src/runner.ts | 16 ++++-- packages/delegation/src/templates.ts | 1 - packages/delegation/src/tools.ts | 8 ++- packages/delegation/src/types.ts | 2 +- packages/delegation/tests/blackboard.test.ts | 14 ++--- packages/delegation/tests/db.test.ts | 54 +++++++++---------- packages/delegation/tests/lifecycle.test.ts | 26 ++++----- packages/delegation/tests/templates.test.ts | 8 +-- .../tests/timeout-estimator.test.ts | 6 +-- packages/delegation/tests/tools.test.ts | 8 +-- packages/heartware/src/audit.ts | 44 ++++++++------- packages/heartware/src/backup.ts | 10 ++-- packages/heartware/src/loader.ts | 9 ++-- packages/heartware/src/manager.ts | 8 +-- packages/heartware/src/meta.ts | 8 +-- packages/heartware/src/sandbox.ts | 2 +- packages/heartware/src/soul-generator.ts | 2 +- packages/heartware/src/soul-traits.ts | 2 +- packages/heartware/src/tools.ts | 6 +-- packages/heartware/tests/meta.test.ts | 10 ++-- .../heartware/tests/soul-generator.test.ts | 11 ++-- packages/intercom/src/index.ts | 2 +- packages/intercom/tests/intercom.test.ts | 6 +-- packages/learning/src/index.ts | 8 +-- packages/matcher/tests/matcher.test.ts | 6 +-- packages/memory/tests/memory-engine.test.ts | 48 ++++++++--------- packages/nudge/tests/nudge.test.ts | 6 +-- packages/sandbox/src/index.ts | 4 +- packages/shell/src/permissions.ts | 4 +- packages/shell/tests/executor.test.ts | 6 +-- packages/shield/src/matcher.ts | 9 ++-- packages/shield/src/parser.ts | 22 +++++--- packages/shield/tests/parser.test.ts | 22 ++++---- .../tests/index.test.ts | 2 +- .../plugin-channel-friends/src/index.ts | 6 +-- .../plugin-channel-friends/src/server.ts | 2 +- .../plugin-channel-friends/src/tools.ts | 2 +- src/cli/src/commands/backup.ts | 45 +++++++--------- src/cli/src/commands/config.ts | 10 ++-- src/cli/src/commands/purge.ts | 10 ++-- src/cli/src/commands/seed.ts | 8 +-- src/cli/src/commands/setup-web.ts | 6 +-- src/cli/src/commands/setup.ts | 10 ++-- src/cli/src/commands/start.ts | 16 +++--- src/cli/src/index.ts | 6 +-- src/cli/src/supervisor.ts | 2 +- src/cli/src/ui/banner.ts | 4 +- src/cli/tests/cli-router.test.ts | 2 +- src/cli/tests/commands/backup.test.ts | 4 +- src/cli/tests/commands/setup.test.ts | 10 ++-- src/cli/tests/commands/start.test.ts | 4 +- src/cli/tests/config.test.ts | 2 +- src/cli/tests/purge.test.ts | 6 +-- src/cli/tests/ui/banner.test.ts | 2 +- src/landing/vite.config.ts | 2 +- src/web/src/security-db.ts | 4 +- src/web/src/server.ts | 24 ++++----- src/web/tests/main.test.ts | 2 +- src/web/tests/security-db.test.ts | 6 +-- src/web/tests/server.test.ts | 10 ++-- src/web/vite.config.ts | 2 +- 74 files changed, 344 insertions(+), 351 deletions(-) diff --git a/packages/compactor/src/dictionary.ts b/packages/compactor/src/dictionary.ts index ca23867..204147b 100644 --- a/packages/compactor/src/dictionary.ts +++ b/packages/compactor/src/dictionary.ts @@ -38,7 +38,7 @@ function generateCodes(n: number): string[] { // 2-letter codes: $AA .. $ZZ (676) for (let i = 0; i < 26 && codes.length < n; i++) { for (let j = 0; j < 26 && codes.length < n; j++) { - codes.push('$' + String.fromCharCode(A + i) + String.fromCharCode(A + j)); + codes.push(`$${String.fromCharCode(A + i)}${String.fromCharCode(A + j)}`); } } diff --git a/packages/compactor/src/optimizer.ts b/packages/compactor/src/optimizer.ts index aa10aa9..a96019a 100644 --- a/packages/compactor/src/optimizer.ts +++ b/packages/compactor/src/optimizer.ts @@ -22,7 +22,7 @@ const ITALIC_RE = /(?= 5) { // Wide tables: preserve rows without header/separator for (const row of rows) { - result.push('| ' + row.join(' | ') + ' |'); + result.push(`| ${row.join(' | ')} |`); } } else if (headers.length === 2) { // 2-column: key: value format diff --git a/packages/compactor/tests/compactor.test.ts b/packages/compactor/tests/compactor.test.ts index 862d972..35532f7 100644 --- a/packages/compactor/tests/compactor.test.ts +++ b/packages/compactor/tests/compactor.test.ts @@ -1,9 +1,9 @@ import { afterEach, beforeEach, describe, expect, it } from 'bun:test'; +import { existsSync, unlinkSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; import { createDatabase } from '@tinyclaw/core'; import type { Database } from '@tinyclaw/types'; -import { existsSync, unlinkSync } from 'fs'; -import { tmpdir } from 'os'; -import { join } from 'path'; import { createCompactor } from '../src/compactor.js'; // --------------------------------------------------------------------------- @@ -98,9 +98,9 @@ describe('createCompactor', () => { const result = await compactor.compactIfNeeded('user1', provider); expect(result).not.toBeNull(); - expect(result!.summary.l2).toContain('TypeScript'); - expect(result!.metrics.messagesBefore).toBe(10); - expect(result!.metrics.messagesKept).toBe(3); + expect(result?.summary.l2).toContain('TypeScript'); + expect(result?.metrics.messagesBefore).toBe(10); + expect(result?.metrics.messagesKept).toBe(3); }); it('saves compaction and allows retrieval', async () => { @@ -141,7 +141,7 @@ describe('createCompactor', () => { const result = await compactor.compactIfNeeded('user1', provider); expect(result).not.toBeNull(); - expect(result!.metrics.messagesKept).toBe(1); + expect(result?.metrics.messagesKept).toBe(1); }); it('returns metrics with compression ratio', async () => { @@ -154,9 +154,9 @@ describe('createCompactor', () => { const result = await compactor.compactIfNeeded('user1', provider); expect(result).not.toBeNull(); - expect(result!.metrics.compressionRatio).toBeGreaterThan(0); - expect(result!.metrics.compressionRatio).toBeLessThanOrEqual(1); - expect(result!.metrics.durationMs).toBeGreaterThanOrEqual(0); + expect(result?.metrics.compressionRatio).toBeGreaterThan(0); + expect(result?.metrics.compressionRatio).toBeLessThanOrEqual(1); + expect(result?.metrics.durationMs).toBeGreaterThanOrEqual(0); }); it('handles provider failure gracefully', async () => { diff --git a/packages/config/src/tools.ts b/packages/config/src/tools.ts index 254ddb8..cd416f1 100644 --- a/packages/config/src/tools.ts +++ b/packages/config/src/tools.ts @@ -87,7 +87,7 @@ export function createConfigTools(manager: ConfigManager): Tool[] { const trimmed = raw.trim(); if (trimmed === 'true') value = true; else if (trimmed === 'false') value = false; - else if (trimmed !== '' && !isNaN(Number(trimmed))) value = Number(trimmed); + else if (trimmed !== '' && !Number.isNaN(Number(trimmed))) value = Number(trimmed); else { try { value = JSON.parse(trimmed); diff --git a/packages/core/src/database.ts b/packages/core/src/database.ts index d334cef..504aea2 100644 --- a/packages/core/src/database.ts +++ b/packages/core/src/database.ts @@ -1,4 +1,6 @@ import { Database as BunDatabase, type SQLQueryBindings } from 'bun:sqlite'; +import { mkdirSync } from 'node:fs'; +import { dirname } from 'node:path'; import type { BackgroundTask, BlackboardEntry, @@ -11,14 +13,12 @@ import type { SubAgentRecord, TaskMetricRecord, } from '@tinyclaw/types'; -import { mkdirSync } from 'fs'; -import { dirname } from 'path'; export function createDatabase(path: string): Database { // Ensure directory exists try { mkdirSync(dirname(path), { recursive: true }); - } catch (err) { + } catch (_err) { // Directory might already exist } @@ -336,12 +336,12 @@ export function createDatabase(path: string): Database { LIMIT ? `); - const updateEpisodicAccessStmt = db.prepare(` + const _updateEpisodicAccessStmt = db.prepare(` UPDATE episodic_memory SET access_count = access_count + 1, last_accessed_at = ? WHERE id = ? `); - const pruneEpisodicEventsStmt = db.prepare(` + const _pruneEpisodicEventsStmt = db.prepare(` DELETE FROM episodic_memory WHERE user_id = ? AND importance < ? AND access_count <= ? AND created_at < ? `); @@ -793,7 +793,7 @@ export function createDatabase(path: string): Database { const tags = `${record.eventType} ${record.userId}`; insertFTSStmt.run( record.id, - record.content + (record.outcome ? ' ' + record.outcome : ''), + record.content + (record.outcome ? ` ${record.outcome}` : ''), tags, ); }, diff --git a/packages/core/src/loop.ts b/packages/core/src/loop.ts index af940e2..9223eca 100644 --- a/packages/core/src/loop.ts +++ b/packages/core/src/loop.ts @@ -5,7 +5,6 @@ import type { AgentContext, Message, PendingApproval, - ShieldDecision, ShieldEvent, ToolCall, } from '@tinyclaw/types'; @@ -306,7 +305,7 @@ function emitDelegationComplete( // delegate_to_existing: "... () [task: ] ..." // delegate_tasks: multi-line output with multiple agent:/task: pairs const UUID_RE = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi; - const allUUIDs = result.match(UUID_RE) || []; + const _allUUIDs = result.match(UUID_RE) || []; // For delegate_task: "agent: , task: " → agentId is matched first, taskId second // For delegate_background: "[id: ]\nSub-agent: Role ()" → taskId first, agentId second @@ -1001,7 +1000,7 @@ export async function agentLoop( // can craft a natural, conversational response instead of the // generic "Done!" that was causing a feedback loop in the history. const writeResult = toolResults[0]?.result || 'completed'; - const writeSummary = summarizeToolResults([toolCall], toolResults); + const _writeSummary = summarizeToolResults([toolCall], toolResults); messages.push({ role: 'assistant', content: `I used ${toolCall.name} and the result was: ${writeResult}`, diff --git a/packages/core/src/update-checker.ts b/packages/core/src/update-checker.ts index 75f5cc1..d04fd3a 100644 --- a/packages/core/src/update-checker.ts +++ b/packages/core/src/update-checker.ts @@ -123,7 +123,7 @@ export function isNewerVersion(current: string, latest: string): boolean { .split('.') .map((s) => { const n = Number(s); - return isNaN(n) ? 0 : n; + return Number.isNaN(n) ? 0 : n; }) .slice(0, 3); const [cMaj = 0, cMin = 0, cPat = 0] = parse(current); diff --git a/packages/core/tests/update-checker.test.ts b/packages/core/tests/update-checker.test.ts index ed0f4be..4dbcab1 100644 --- a/packages/core/tests/update-checker.test.ts +++ b/packages/core/tests/update-checker.test.ts @@ -6,9 +6,9 @@ */ import { afterEach, beforeEach, describe, expect, test } from 'bun:test'; -import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'fs'; -import { tmpdir } from 'os'; -import { join } from 'path'; +import { existsSync, mkdirSync, rmSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; import { buildUpdateContext, checkForUpdate, @@ -197,8 +197,8 @@ describe('checkForUpdate', () => { const result = await checkForUpdate('1.0.0', tempDir); expect(result).not.toBeNull(); - expect(result!.latest).toBe('1.1.0'); - expect(result!.updateAvailable).toBe(true); + expect(result?.latest).toBe('1.1.0'); + expect(result?.updateAvailable).toBe(true); }); test('re-evaluates updateAvailable against current version', async () => { @@ -208,8 +208,8 @@ describe('checkForUpdate', () => { const result = await checkForUpdate('1.1.0', tempDir); expect(result).not.toBeNull(); - expect(result!.updateAvailable).toBe(false); - expect(result!.current).toBe('1.1.0'); + expect(result?.updateAvailable).toBe(false); + expect(result?.current).toBe('1.1.0'); }); test('returns null on network failure with no cache', async () => { diff --git a/packages/delegation/src/background.ts b/packages/delegation/src/background.ts index 89b7308..8cf4dba 100644 --- a/packages/delegation/src/background.ts +++ b/packages/delegation/src/background.ts @@ -7,17 +7,10 @@ */ import { logger } from '@tinyclaw/logger'; -import type { Message, Provider, Tool } from '@tinyclaw/types'; import { runSubAgentV2 } from './runner.js'; import type { DelegationIntercom, DelegationQueue, DelegationStore } from './store.js'; import type { TimeoutEstimator } from './timeout-estimator.js'; -import type { - BackgroundRunner, - BackgroundTaskRecord, - LifecycleManager, - OrientationContext, - TemplateManager, -} from './types.js'; +import type { BackgroundRunner, LifecycleManager, TemplateManager } from './types.js'; // --------------------------------------------------------------------------- // Constants diff --git a/packages/delegation/src/compat.ts b/packages/delegation/src/compat.ts index 7076b74..92ab726 100644 --- a/packages/delegation/src/compat.ts +++ b/packages/delegation/src/compat.ts @@ -6,8 +6,8 @@ */ import { logger } from '@tinyclaw/logger'; -import type { ProviderOrchestrator, QueryTier } from '@tinyclaw/router'; -import type { Tool } from '@tinyclaw/types'; +import type { QueryTier } from '@tinyclaw/router'; +import type { Provider, Tool } from '@tinyclaw/types'; import { runSubAgent } from './runner.js'; import type { DelegationToolConfig } from './types.js'; @@ -84,7 +84,7 @@ export function createDelegationTool(config: DelegationToolConfig): Tool { } // 1. Resolve provider - let provider; + let provider: Provider; try { if (tierOverride) { provider = orchestrator.getRegistry().getForTier(tierOverride as QueryTier); diff --git a/packages/delegation/src/lifecycle.ts b/packages/delegation/src/lifecycle.ts index e2a9c22..36666ad 100644 --- a/packages/delegation/src/lifecycle.ts +++ b/packages/delegation/src/lifecycle.ts @@ -6,11 +6,10 @@ * and message persistence. */ -import type { QueryTier } from '@tinyclaw/router'; -import type { Message, SubAgentRecord } from '@tinyclaw/types'; +import type { SubAgentRecord } from '@tinyclaw/types'; import { formatOrientation } from './orientation.js'; import type { DelegationStore } from './store.js'; -import type { LifecycleManager, OrientationContext } from './types.js'; +import type { LifecycleManager } from './types.js'; // --------------------------------------------------------------------------- // Constants diff --git a/packages/delegation/src/orientation.ts b/packages/delegation/src/orientation.ts index ffba34b..9d50e9e 100644 --- a/packages/delegation/src/orientation.ts +++ b/packages/delegation/src/orientation.ts @@ -115,5 +115,5 @@ export function formatOrientation(ctx: OrientationContext): string { function truncate(text: string, maxChars: number): string { if (text.length <= maxChars) return text; - return text.slice(0, maxChars) + '...'; + return `${text.slice(0, maxChars)}...`; } diff --git a/packages/delegation/src/runner.ts b/packages/delegation/src/runner.ts index 4d857c4..13df731 100644 --- a/packages/delegation/src/runner.ts +++ b/packages/delegation/src/runner.ts @@ -9,7 +9,15 @@ */ import { logger } from '@tinyclaw/logger'; -import type { Message, Provider, ShieldEngine, ShieldEvent, Tool, ToolCall } from '@tinyclaw/types'; +import type { + LLMResponse, + Message, + Provider, + ShieldEngine, + ShieldEvent, + Tool, + ToolCall, +} from '@tinyclaw/types'; import { formatOrientation } from './orientation.js'; import type { TimeoutEstimator } from './timeout-estimator.js'; import type { @@ -130,7 +138,7 @@ function buildSubAgentPrompt(role: string, orientation?: OrientationContext): st let prompt = ''; if (orientation) { - prompt += formatOrientation(orientation) + '\n\n'; + prompt += `${formatOrientation(orientation)}\n\n`; } prompt += @@ -166,7 +174,7 @@ interface AdaptiveLoopConfig { shield?: ShieldEngine; } -async function runAgentLoop( +async function _runAgentLoop( provider: Provider, tools: Tool[], messages: Message[], @@ -248,7 +256,7 @@ async function runAdaptiveAgentLoop( iterations = i + 1; - let response; + let response: LLMResponse; try { response = await raceAbort(provider.chat(messages, tools)); } catch (err: any) { diff --git a/packages/delegation/src/templates.ts b/packages/delegation/src/templates.ts index 89f1dcb..bbf4b89 100644 --- a/packages/delegation/src/templates.ts +++ b/packages/delegation/src/templates.ts @@ -6,7 +6,6 @@ * agent learning how to hire better freelancers through experience. */ -import type { QueryTier } from '@tinyclaw/router'; import type { RoleTemplate } from '@tinyclaw/types'; import type { DelegationStore } from './store.js'; import type { TemplateManager } from './types.js'; diff --git a/packages/delegation/src/tools.ts b/packages/delegation/src/tools.ts index 014e20d..8aeec69 100644 --- a/packages/delegation/src/tools.ts +++ b/packages/delegation/src/tools.ts @@ -18,10 +18,8 @@ import { createBackgroundRunner } from './background.js'; import { DELEGATION_TOOL_NAMES } from './handbook.js'; import { createLifecycleManager } from './lifecycle.js'; import { buildOrientationContext } from './orientation.js'; -import { runSubAgentV2 } from './runner.js'; -import type { DelegationIntercom, DelegationQueue, DelegationStore } from './store.js'; +import type { DelegationIntercom, DelegationQueue } from './store.js'; import { createTemplateManager } from './templates.js'; -import type { TimeoutEstimator } from './timeout-estimator.js'; import type { BackgroundRunner, DelegationV2Config, @@ -47,7 +45,7 @@ const DEFAULT_SAFE_TOOLS = new Set([ const MAX_BATCH_SIZE = 10; /** Fallback background sub-agent timeout (used when no estimator). */ -const BACKGROUND_TIMEOUT_MS_FALLBACK = 60_000; +const _BACKGROUND_TIMEOUT_MS_FALLBACK = 60_000; // --------------------------------------------------------------------------- // Helper: filter tools for sub-agents @@ -787,7 +785,7 @@ export function createDelegationTools(config: DelegationToolsConfig): { // --------------------------------------------------------------------------- /** Extract keyword tags from role + task text. */ -function extractTags(role: string, task: string): string[] { +function _extractTags(role: string, task: string): string[] { const text = `${role} ${task}`.toLowerCase(); const words = text .replace(/[^a-z0-9\s]/g, ' ') diff --git a/packages/delegation/src/types.ts b/packages/delegation/src/types.ts index 8dba7a5..8f3cb1a 100644 --- a/packages/delegation/src/types.ts +++ b/packages/delegation/src/types.ts @@ -7,7 +7,7 @@ import type { ProviderOrchestrator, QueryTier } from '@tinyclaw/router'; import type { LearningEngine, Message, Provider, Tool } from '@tinyclaw/types'; -import type { DelegationQueue, DelegationStore } from './store.js'; +import type { DelegationStore } from './store.js'; import type { TimeoutEstimator } from './timeout-estimator.js'; // --------------------------------------------------------------------------- diff --git a/packages/delegation/tests/blackboard.test.ts b/packages/delegation/tests/blackboard.test.ts index 2fa1024..53f1898 100644 --- a/packages/delegation/tests/blackboard.test.ts +++ b/packages/delegation/tests/blackboard.test.ts @@ -1,10 +1,10 @@ import { afterEach, beforeEach, describe, expect, it } from 'bun:test'; +import { existsSync, unlinkSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; import { createDatabase } from '@tinyclaw/core'; import { createIntercom, type Intercom, type IntercomMessage } from '@tinyclaw/intercom'; import type { Database } from '@tinyclaw/types'; -import { existsSync, unlinkSync } from 'fs'; -import { tmpdir } from 'os'; -import { join } from 'path'; import { type Blackboard, createBlackboard } from '../src/index.js'; // --------------------------------------------------------------------------- @@ -76,9 +76,9 @@ describe('Blackboard', () => { const problem = blackboard.getProblem(problemId); expect(problem).not.toBeNull(); - expect(problem!.problemText).toBe('Which database is best for this use case?'); - expect(problem!.userId).toBe('user1'); - expect(problem!.status).toBe('open'); + expect(problem?.problemText).toBe('Which database is best for this use case?'); + expect(problem?.userId).toBe('user1'); + expect(problem?.status).toBe('open'); }); it('shows up in active problems', () => { @@ -260,7 +260,7 @@ describe('Blackboard', () => { const problem = blackboard.getProblem(problemId); expect(problem).not.toBeNull(); - expect(problem!.problemText).toBe('My problem'); + expect(problem?.problemText).toBe('My problem'); }); }); diff --git a/packages/delegation/tests/db.test.ts b/packages/delegation/tests/db.test.ts index bb7cddb..87e1925 100644 --- a/packages/delegation/tests/db.test.ts +++ b/packages/delegation/tests/db.test.ts @@ -37,14 +37,14 @@ describe('sub_agents table', () => { const record = db.getSubAgent('sa-1'); expect(record).not.toBeNull(); - expect(record!.id).toBe('sa-1'); - expect(record!.userId).toBe('user-1'); - expect(record!.role).toBe('Research Analyst'); - expect(record!.toolsGranted).toEqual(['heartware_read', 'memory_recall']); - expect(record!.tierPreference).toBe('complex'); - expect(record!.status).toBe('active'); - expect(record!.performanceScore).toBe(0.5); - expect(record!.templateId).toBeNull(); + expect(record?.id).toBe('sa-1'); + expect(record?.userId).toBe('user-1'); + expect(record?.role).toBe('Research Analyst'); + expect(record?.toolsGranted).toEqual(['heartware_read', 'memory_recall']); + expect(record?.tierPreference).toBe('complex'); + expect(record?.status).toBe('active'); + expect(record?.performanceScore).toBe(0.5); + expect(record?.templateId).toBeNull(); db.close(); }); @@ -163,10 +163,10 @@ describe('sub_agents table', () => { }); const updated = db.getSubAgent('sa-1'); - expect(updated!.status).toBe('soft_deleted'); - expect(updated!.performanceScore).toBe(0.8); - expect(updated!.totalTasks).toBe(5); - expect(updated!.successfulTasks).toBe(4); + expect(updated?.status).toBe('soft_deleted'); + expect(updated?.performanceScore).toBe(0.8); + expect(updated?.totalTasks).toBe(5); + expect(updated?.successfulTasks).toBe(4); db.close(); }); @@ -247,9 +247,9 @@ describe('role_templates table', () => { const template = db.getRoleTemplate('rt-1'); expect(template).not.toBeNull(); - expect(template!.name).toBe('Research Analyst'); - expect(template!.defaultTools).toEqual(['heartware_read']); - expect(template!.tags).toEqual(['research', 'analysis']); + expect(template?.name).toBe('Research Analyst'); + expect(template?.defaultTools).toEqual(['heartware_read']); + expect(template?.tags).toEqual(['research', 'analysis']); db.close(); }); @@ -308,10 +308,10 @@ describe('role_templates table', () => { }); const updated = db.getRoleTemplate('rt-1'); - expect(updated!.name).toBe('Technical Writer'); - expect(updated!.tags).toEqual(['writing', 'technical']); - expect(updated!.timesUsed).toBe(3); - expect(updated!.avgPerformance).toBe(0.85); + expect(updated?.name).toBe('Technical Writer'); + expect(updated?.tags).toEqual(['writing', 'technical']); + expect(updated?.timesUsed).toBe(3); + expect(updated?.avgPerformance).toBe(0.85); db.close(); }); @@ -364,9 +364,9 @@ describe('background_tasks table', () => { const task = db.getBackgroundTask('bt-1'); expect(task).not.toBeNull(); - expect(task!.id).toBe('bt-1'); - expect(task!.status).toBe('running'); - expect(task!.result).toBeNull(); + expect(task?.id).toBe('bt-1'); + expect(task?.status).toBe('running'); + expect(task?.result).toBeNull(); db.close(); }); @@ -390,9 +390,9 @@ describe('background_tasks table', () => { db.updateBackgroundTask('bt-1', 'completed', 'Task result here', now + 5000); const updated = db.getBackgroundTask('bt-1'); - expect(updated!.status).toBe('completed'); - expect(updated!.result).toBe('Task result here'); - expect(updated!.completedAt).toBe(now + 5000); + expect(updated?.status).toBe('completed'); + expect(updated?.result).toBe('Task result here'); + expect(updated?.completedAt).toBe(now + 5000); db.close(); }); @@ -469,8 +469,8 @@ describe('background_tasks table', () => { db.markTaskDelivered('bt-1'); const task = db.getBackgroundTask('bt-1'); - expect(task!.status).toBe('delivered'); - expect(task!.deliveredAt).not.toBeNull(); + expect(task?.status).toBe('delivered'); + expect(task?.deliveredAt).not.toBeNull(); db.close(); }); diff --git a/packages/delegation/tests/lifecycle.test.ts b/packages/delegation/tests/lifecycle.test.ts index 5f5346c..f75d02a 100644 --- a/packages/delegation/tests/lifecycle.test.ts +++ b/packages/delegation/tests/lifecycle.test.ts @@ -53,8 +53,8 @@ describe('Lifecycle Manager', () => { const fetched = lm.get(agent.id); expect(fetched).not.toBeNull(); - expect(fetched!.id).toBe(agent.id); - expect(fetched!.role).toBe('Writer'); + expect(fetched?.id).toBe(agent.id); + expect(fetched?.role).toBe('Writer'); db.close(); }); @@ -101,7 +101,7 @@ describe('Lifecycle Manager', () => { // Should match the research analyst const match = lm.findReusable('u1', 'Research Analyst'); expect(match).not.toBeNull(); - expect(match!.role).toBe('Technical Research Analyst'); + expect(match?.role).toBe('Technical Research Analyst'); // No match for unrelated role const noMatch = lm.findReusable('u1', 'Database Administrator Expert'); @@ -126,9 +126,9 @@ describe('Lifecycle Manager', () => { lm.recordTaskResult(agent.id, false); const updated = lm.get(agent.id); - expect(updated!.totalTasks).toBe(3); - expect(updated!.successfulTasks).toBe(2); - expect(updated!.performanceScore).toBeCloseTo(2 / 3); + expect(updated?.totalTasks).toBe(3); + expect(updated?.successfulTasks).toBe(2); + expect(updated?.performanceScore).toBeCloseTo(2 / 3); db.close(); }); @@ -147,13 +147,13 @@ describe('Lifecycle Manager', () => { // Suspend lm.suspend(agent.id); const suspended = lm.get(agent.id); - expect(suspended!.status).toBe('suspended'); + expect(suspended?.status).toBe('suspended'); // Revive const revived = lm.revive(agent.id); expect(revived).not.toBeNull(); - expect(revived!.status).toBe('active'); - expect(revived!.deletedAt).toBeNull(); + expect(revived?.status).toBe('active'); + expect(revived?.deletedAt).toBeNull(); db.close(); }); @@ -188,8 +188,8 @@ describe('Lifecycle Manager', () => { const match = lm.findReusable('u1', 'Research Analyst'); expect(match).not.toBeNull(); - expect(match!.id).toBe(agent.id); - expect(match!.status).toBe('suspended'); + expect(match?.id).toBe(agent.id); + expect(match?.status).toBe('suspended'); db.close(); }); @@ -208,8 +208,8 @@ describe('Lifecycle Manager', () => { const match = lm.findReusable('u1', 'Research Analyst'); expect(match).not.toBeNull(); - expect(match!.id).toBe(agent.id); - expect(match!.status).toBe('soft_deleted'); + expect(match?.id).toBe(agent.id); + expect(match?.status).toBe('soft_deleted'); db.close(); }); diff --git a/packages/delegation/tests/templates.test.ts b/packages/delegation/tests/templates.test.ts index 2f1a1e6..e795f3c 100644 --- a/packages/delegation/tests/templates.test.ts +++ b/packages/delegation/tests/templates.test.ts @@ -53,12 +53,12 @@ describe('Template Manager', () => { // Should match research analyst const match = tm.findBestMatch('u1', 'research technical analysis task'); expect(match).not.toBeNull(); - expect(match!.name).toBe('Research Analyst'); + expect(match?.name).toBe('Research Analyst'); // Should match creative writer const match2 = tm.findBestMatch('u1', 'write creative blog post'); expect(match2).not.toBeNull(); - expect(match2!.name).toBe('Creative Writer'); + expect(match2?.name).toBe('Creative Writer'); db.close(); }); @@ -108,8 +108,8 @@ describe('Template Manager', () => { }); expect(updated).not.toBeNull(); - expect(updated!.name).toBe('Technical Writer'); - expect(updated!.tags).toEqual(['writing', 'technical', 'documentation']); + expect(updated?.name).toBe('Technical Writer'); + expect(updated?.tags).toEqual(['writing', 'technical', 'documentation']); db.close(); }); diff --git a/packages/delegation/tests/timeout-estimator.test.ts b/packages/delegation/tests/timeout-estimator.test.ts index 4cc162a..4a5001b 100644 --- a/packages/delegation/tests/timeout-estimator.test.ts +++ b/packages/delegation/tests/timeout-estimator.test.ts @@ -1,9 +1,9 @@ import { afterEach, beforeEach, describe, expect, it } from 'bun:test'; +import { existsSync, unlinkSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; import { createDatabase } from '@tinyclaw/core'; import type { Database } from '@tinyclaw/types'; -import { existsSync, unlinkSync } from 'fs'; -import { tmpdir } from 'os'; -import { join } from 'path'; import { createTimeoutEstimator, type TimeoutEstimator } from '../src/index.js'; // --------------------------------------------------------------------------- diff --git a/packages/delegation/tests/tools.test.ts b/packages/delegation/tests/tools.test.ts index b4198d7..6055c62 100644 --- a/packages/delegation/tests/tools.test.ts +++ b/packages/delegation/tests/tools.test.ts @@ -182,8 +182,8 @@ describe('delegate_task', () => { // Task stats updated by background runner const updated = result.lifecycle.get(agents[0].id); - expect(updated!.totalTasks).toBe(1); - expect(updated!.successfulTasks).toBe(1); + expect(updated?.totalTasks).toBe(1); + expect(updated?.successfulTasks).toBe(1); db.close(); }); @@ -220,7 +220,7 @@ describe('delegate_task', () => { await Bun.sleep(400); const updated = result.lifecycle.get(agents[0].id); - expect(updated!.totalTasks).toBe(2); + expect(updated?.totalTasks).toBe(2); db.close(); }); @@ -564,7 +564,7 @@ describe('delegate_to_existing', () => { // Agent should have 2 completed tasks const updated = result.lifecycle.get(agentId); - expect(updated!.totalTasks).toBe(2); + expect(updated?.totalTasks).toBe(2); db.close(); }); diff --git a/packages/heartware/src/audit.ts b/packages/heartware/src/audit.ts index 39597c9..553c472 100644 --- a/packages/heartware/src/audit.ts +++ b/packages/heartware/src/audit.ts @@ -8,10 +8,10 @@ * - Non-blocking failures (logs to console if file write fails) */ +import { createHash } from 'node:crypto'; +import { appendFileSync, mkdirSync } from 'node:fs'; +import { join } from 'node:path'; import type { ShieldDecision } from '@tinyclaw/types'; -import { createHash } from 'crypto'; -import { appendFileSync, mkdirSync } from 'fs'; -import { join } from 'path'; import type { AuditLogEntry } from './types.js'; /** @@ -34,7 +34,7 @@ export class AuditLogger { // Ensure audit directory exists try { mkdirSync(auditDir, { recursive: true }); - } catch (err) { + } catch (_err) { // Directory might already exist - this is fine } } @@ -46,11 +46,10 @@ export class AuditLogger { * but the original operation is not blocked */ log(entry: AuditLogEntry): void { - const logLine = - JSON.stringify({ - ...entry, - timestamp: entry.timestamp || new Date().toISOString(), - }) + '\n'; + const logLine = `${JSON.stringify({ + ...entry, + timestamp: entry.timestamp || new Date().toISOString(), + })}\n`; try { // Append-only write with explicit append flag @@ -148,20 +147,19 @@ export class AuditLogger { outcome: 'blocked' | 'approved' | 'denied' | 'logged', userId?: string, ): void { - const logLine = - JSON.stringify({ - timestamp: new Date().toISOString(), - type: 'shield', - action: decision.action, - outcome, - scope: decision.scope, - threatId: decision.threatId, - fingerprint: decision.fingerprint, - matchedOn: decision.matchedOn, - matchValue: decision.matchValue, - reason: decision.reason, - userId: userId ?? 'unknown', - }) + '\n'; + const logLine = `${JSON.stringify({ + timestamp: new Date().toISOString(), + type: 'shield', + action: decision.action, + outcome, + scope: decision.scope, + threatId: decision.threatId, + fingerprint: decision.fingerprint, + matchedOn: decision.matchedOn, + matchValue: decision.matchValue, + reason: decision.reason, + userId: userId ?? 'unknown', + })}\n`; try { appendFileSync(this.auditLogPath, logLine, { diff --git a/packages/heartware/src/backup.ts b/packages/heartware/src/backup.ts index b62f024..13eef87 100644 --- a/packages/heartware/src/backup.ts +++ b/packages/heartware/src/backup.ts @@ -16,8 +16,8 @@ import { readFileSync, statSync, unlinkSync, -} from 'fs'; -import { basename, join } from 'path'; +} from 'node:fs'; +import { basename, join } from 'node:path'; import { computeContentHash } from './audit.js'; import type { BackupMetadata } from './types.js'; @@ -41,7 +41,7 @@ export class BackupManager { // Create backup directory if it doesn't exist try { mkdirSync(this.backupDir, { recursive: true }); - } catch (err) { + } catch (_err) { // Directory might already exist - this is fine } } @@ -100,7 +100,7 @@ export class BackupManager { .filter((f) => f.startsWith(`${filename}.`) && f.endsWith('.bak')) .sort() .reverse(); // Most recent first - } catch (err) { + } catch (_err) { return []; } } @@ -206,7 +206,7 @@ export class BackupManager { } return totalSize; - } catch (err) { + } catch (_err) { return 0; } } diff --git a/packages/heartware/src/loader.ts b/packages/heartware/src/loader.ts index 45c5369..c2eaf14 100644 --- a/packages/heartware/src/loader.ts +++ b/packages/heartware/src/loader.ts @@ -16,7 +16,6 @@ import type { HeartwareManager } from './manager.js'; import { loadCachedCreatorMeta } from './meta.js'; -import { generateSoul } from './soul-generator.js'; /** Label used for creator meta in heartware context */ const META_CACHE_LABEL = 'CREATOR.md — About My Creator'; @@ -57,7 +56,7 @@ export async function loadHeartwareContext(manager: HeartwareManager): Promise 0) { results.push({ file, matches }); } - } catch (err) {} + } catch (_err) {} } this.auditLogger.logSuccess(this.config.userId, 'search', 'heartware/', undefined, undefined); diff --git a/packages/heartware/src/meta.ts b/packages/heartware/src/meta.ts index 9d05005..11cfc86 100644 --- a/packages/heartware/src/meta.ts +++ b/packages/heartware/src/meta.ts @@ -11,10 +11,10 @@ * Default URL: https://markdown.new/github.com/warengonzaga */ +import { existsSync } from 'node:fs'; +import { readFile, stat, writeFile } from 'node:fs/promises'; +import { join } from 'node:path'; import { logger } from '@tinyclaw/logger'; -import { existsSync } from 'fs'; -import { readFile, stat, writeFile } from 'fs/promises'; -import { join } from 'path'; /** Default remote URL for creator metadata */ export const DEFAULT_META_URL = 'https://markdown.new/github.com/warengonzaga'; @@ -80,7 +80,7 @@ async function fetchRemoteContent(url: string, timeout: number): Promise // Enforce size limit if (content.length > MAX_META_SIZE) { - return content.slice(0, MAX_META_SIZE) + '\n\n[Content truncated]'; + return `${content.slice(0, MAX_META_SIZE)}\n\n[Content truncated]`; } return content; diff --git a/packages/heartware/src/sandbox.ts b/packages/heartware/src/sandbox.ts index a5e845f..4669a5e 100644 --- a/packages/heartware/src/sandbox.ts +++ b/packages/heartware/src/sandbox.ts @@ -12,7 +12,7 @@ * - Validates file sizes */ -import { normalize, relative, resolve } from 'path'; +import { normalize, relative, resolve } from 'node:path'; import { HeartwareSecurityError } from './errors.js'; import type { ContentValidationResult, diff --git a/packages/heartware/src/soul-generator.ts b/packages/heartware/src/soul-generator.ts index 47d4929..ff4a271 100644 --- a/packages/heartware/src/soul-generator.ts +++ b/packages/heartware/src/soul-generator.ts @@ -14,7 +14,7 @@ * Additional hash rounds with domain separation provide discrete selections. */ -import { createHash } from 'crypto'; +import { createHash } from 'node:crypto'; import { AMBIGUITY_STYLES, AWAKENING_EVENTS, diff --git a/packages/heartware/src/soul-traits.ts b/packages/heartware/src/soul-traits.ts index a365e87..0475411 100644 --- a/packages/heartware/src/soul-traits.ts +++ b/packages/heartware/src/soul-traits.ts @@ -9,7 +9,7 @@ * character flavor, values, quirks, interaction modifiers). */ -import type { HumorType, InteractionStyle } from './types.js'; +import type { HumorType } from './types.js'; // ============================================ // Big Five Descriptors diff --git a/packages/heartware/src/tools.ts b/packages/heartware/src/tools.ts index 4408500..df757ba 100644 --- a/packages/heartware/src/tools.ts +++ b/packages/heartware/src/tools.ts @@ -178,7 +178,7 @@ export function createHeartwareTools(manager: HeartwareManager): Tool[] { let memory = ''; try { memory = await manager.read('MEMORY.md'); - } catch (err) { + } catch (_err) { // File might not exist yet } @@ -233,7 +233,7 @@ export function createHeartwareTools(manager: HeartwareManager): Tool[] { let log = ''; try { log = await manager.read(filename); - } catch (err) { + } catch (_err) { // File doesn't exist, create header log = `# Daily Memory Log - ${today}\n\n`; } @@ -281,7 +281,7 @@ export function createHeartwareTools(manager: HeartwareManager): Tool[] { try { const content = await manager.read(filename); output += `\n=== ${dateStr} ===\n${content}\n`; - } catch (err) {} + } catch (_err) {} } if (!output) { diff --git a/packages/heartware/tests/meta.test.ts b/packages/heartware/tests/meta.test.ts index e37e348..72e2eae 100644 --- a/packages/heartware/tests/meta.test.ts +++ b/packages/heartware/tests/meta.test.ts @@ -1,7 +1,7 @@ -import { afterEach, describe, expect, it } from 'bun:test'; -import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'fs'; -import { tmpdir } from 'os'; -import { join } from 'path'; +import { describe, expect, it } from 'bun:test'; +import { mkdtempSync, rmSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; import { DEFAULT_META_URL, fetchCreatorMeta, @@ -83,7 +83,7 @@ describe('Creator Meta — Fetch', () => { writeFileSync(cachePath, content, 'utf-8'); // Backdate the file modification time to make it stale - const { utimesSync } = require('fs'); + const { utimesSync } = require('node:fs'); const staleTime = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000); // 30 days ago utimesSync(cachePath, staleTime, staleTime); diff --git a/packages/heartware/tests/soul-generator.test.ts b/packages/heartware/tests/soul-generator.test.ts index fa75b1c..32d8183 100644 --- a/packages/heartware/tests/soul-generator.test.ts +++ b/packages/heartware/tests/soul-generator.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from 'bun:test'; -import { mkdirSync, mkdtempSync, writeFileSync } from 'fs'; -import { tmpdir } from 'os'; -import { join } from 'path'; +import { mkdirSync, mkdtempSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; import { HeartwareSecurityError } from '../src/errors.js'; import { validatePath } from '../src/sandbox.js'; import { @@ -11,7 +11,6 @@ import { parseSeed, renderSoulMarkdown, } from '../src/soul-generator.js'; -import type { SoulTraits } from '../src/types.js'; // --------------------------------------------------------------------------- // Helpers @@ -399,7 +398,9 @@ describe('Soul Generator — Diversity', () => { allColors.add(traits.preferences.favoriteColor); allHumors.add(traits.humor); allCreatures.add(traits.character.creatureType); - traits.values.forEach((v) => allValues.add(v)); + traits.values.forEach((v) => { + allValues.add(v); + }); } // Should hit most of the pools diff --git a/packages/intercom/src/index.ts b/packages/intercom/src/index.ts index 3a13536..7098e0e 100644 --- a/packages/intercom/src/index.ts +++ b/packages/intercom/src/index.ts @@ -118,7 +118,7 @@ export function createIntercom(historyLimit = DEFAULT_HISTORY_LIMIT): Intercom { userId, data, }; - const seq = sequence++; + const _seq = sequence++; // Store in per-topic history (ring buffer — drop oldest if over limit) const list = getOrCreateHistory(topic); diff --git a/packages/intercom/tests/intercom.test.ts b/packages/intercom/tests/intercom.test.ts index 26959a7..af3524f 100644 --- a/packages/intercom/tests/intercom.test.ts +++ b/packages/intercom/tests/intercom.test.ts @@ -214,8 +214,8 @@ describe('Intercom', () => { const after = Date.now(); expect(received).not.toBeNull(); - expect(received!.timestamp).toBeGreaterThanOrEqual(before); - expect(received!.timestamp).toBeLessThanOrEqual(after); + expect(received?.timestamp).toBeGreaterThanOrEqual(before); + expect(received?.timestamp).toBeLessThanOrEqual(after); }); it('emit with no data defaults to empty object', () => { @@ -227,7 +227,7 @@ describe('Intercom', () => { }); intercom.emit('task:completed', 'user1'); - expect(received!.data).toEqual({}); + expect(received?.data).toEqual({}); }); // ----------------------------------------------------------------------- diff --git a/packages/learning/src/index.ts b/packages/learning/src/index.ts index 2d4af5b..7e6e184 100644 --- a/packages/learning/src/index.ts +++ b/packages/learning/src/index.ts @@ -1,6 +1,6 @@ +import { mkdirSync, readFileSync, writeFileSync } from 'node:fs'; +import { dirname, join } from 'node:path'; import type { LearnedContext, Message, Pattern, Signal } from '@tinyclaw/types'; -import { mkdirSync, readFileSync, writeFileSync } from 'fs'; -import { dirname, join } from 'path'; import { detectSignals } from './detector.js'; export interface LearningEngineConfig { @@ -29,7 +29,7 @@ export function createLearningEngine(config: LearningEngineConfig) { function savePatterns(): void { try { writeFileSync(patternsPath, JSON.stringify(patterns, null, 2)); - } catch (err) { + } catch (_err) { // Silent fail - could be logged if logger is available } } @@ -65,7 +65,7 @@ export function createLearningEngine(config: LearningEngineConfig) { } return { - analyze(userMessage: string, assistantMessage: string, history: Message[]): void { + analyze(userMessage: string, assistantMessage: string, _history: Message[]): void { const signals = detectSignals(userMessage, assistantMessage); for (const signal of signals) { storeSignal(signal); diff --git a/packages/matcher/tests/matcher.test.ts b/packages/matcher/tests/matcher.test.ts index 8bfeb32..cde45f3 100644 --- a/packages/matcher/tests/matcher.test.ts +++ b/packages/matcher/tests/matcher.test.ts @@ -114,8 +114,8 @@ describe('HybridMatcher', () => { const result = matcher.findBest('research data analyst', candidates); expect(result).not.toBeNull(); - expect(result!.id).toBe('2'); - expect(result!.result.score).toBeGreaterThan(0.3); + expect(result?.id).toBe('2'); + expect(result?.result.score).toBeGreaterThan(0.3); }); it('findBest returns null when no candidate exceeds minScore', () => { @@ -146,7 +146,7 @@ describe('HybridMatcher', () => { const result = matcher.findBest('data analysis python', candidates); expect(result).not.toBeNull(); // Should pick candidate 1 (most overlap) or 2 (close match) - expect(['1', '2']).toContain(result!.id); + expect(['1', '2']).toContain(result?.id); }); // ----------------------------------------------------------------------- diff --git a/packages/memory/tests/memory-engine.test.ts b/packages/memory/tests/memory-engine.test.ts index be6b710..05a96b2 100644 --- a/packages/memory/tests/memory-engine.test.ts +++ b/packages/memory/tests/memory-engine.test.ts @@ -1,9 +1,9 @@ import { afterEach, beforeEach, describe, expect, it } from 'bun:test'; +import { existsSync, unlinkSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; import { createDatabase } from '@tinyclaw/core'; import type { Database, MemoryEngine } from '@tinyclaw/types'; -import { existsSync, unlinkSync } from 'fs'; -import { tmpdir } from 'os'; -import { join } from 'path'; import { createMemoryEngine } from '../src/index.js'; // --------------------------------------------------------------------------- @@ -68,12 +68,12 @@ describe('MemoryEngine', () => { const event = engine.getEvent(id); expect(event).not.toBeNull(); - expect(event!.userId).toBe('user1'); - expect(event!.eventType).toBe('fact_stored'); - expect(event!.content).toBe('User prefers dark mode'); - expect(event!.outcome).toBeNull(); - expect(event!.importance).toBe(0.6); // Default for fact_stored - expect(event!.accessCount).toBe(0); + expect(event?.userId).toBe('user1'); + expect(event?.eventType).toBe('fact_stored'); + expect(event?.content).toBe('User prefers dark mode'); + expect(event?.outcome).toBeNull(); + expect(event?.importance).toBe(0.6); // Default for fact_stored + expect(event?.accessCount).toBe(0); }); it('stores event with custom importance', () => { @@ -85,8 +85,8 @@ describe('MemoryEngine', () => { }); const event = engine.getEvent(id); - expect(event!.importance).toBe(0.85); - expect(event!.outcome).toBe('TensorFlow recommended'); + expect(event?.importance).toBe(0.85); + expect(event?.outcome).toBe('TensorFlow recommended'); }); it('uses correct default importance per event type', () => { @@ -104,7 +104,7 @@ describe('MemoryEngine', () => { for (const { type, expected } of types) { const id = engine.recordEvent('user1', { type, content: `Test ${type}` }); const event = engine.getEvent(id); - expect(event!.importance).toBe(expected); + expect(event?.importance).toBe(expected); } }); @@ -199,7 +199,7 @@ describe('MemoryEngine', () => { expect(recentResult).toBeDefined(); expect(oldResult).toBeDefined(); - expect(recentResult!.relevanceScore).toBeGreaterThan(oldResult!.relevanceScore); + expect(recentResult?.relevanceScore).toBeGreaterThan(oldResult?.relevanceScore); }); it('reinforce: bumped memories score higher', () => { @@ -230,7 +230,7 @@ describe('MemoryEngine', () => { // Reinforced memory should have higher temporal score component // (access count boosts temporal score) const event2 = engine.getEvent(id2); - expect(event2!.accessCount).toBe(3); + expect(event2?.accessCount).toBe(3); }); it('returns empty results for no matches', () => { @@ -309,7 +309,7 @@ describe('MemoryEngine', () => { // Check importance was reduced const event = engine.getEvent(id); - expect(event!.importance).toBeLessThan(0.6); + expect(event?.importance).toBeLessThan(0.6); }); it('prunes low-importance old entries', () => { @@ -472,13 +472,13 @@ describe('MemoryEngine', () => { }); const before = engine.getEvent(id); - expect(before!.accessCount).toBe(0); + expect(before?.accessCount).toBe(0); engine.reinforce(id); const after = engine.getEvent(id); - expect(after!.accessCount).toBe(1); - expect(after!.lastAccessedAt).toBeGreaterThanOrEqual(before!.lastAccessedAt); + expect(after?.accessCount).toBe(1); + expect(after?.lastAccessedAt).toBeGreaterThanOrEqual(before?.lastAccessedAt); }); it('handles non-existent id gracefully', () => { @@ -499,7 +499,7 @@ describe('MemoryEngine', () => { engine.reinforce(id); const event = engine.getEvent(id); - expect(event!.accessCount).toBe(5); + expect(event?.accessCount).toBe(5); }); }); @@ -606,7 +606,7 @@ describe('MemoryEngine', () => { }); const event = engine.getEvent(id); - expect(event!.content).toBe(longContent); + expect(event?.content).toBe(longContent); }); it('handles unicode content', () => { @@ -616,8 +616,8 @@ describe('MemoryEngine', () => { }); const event = engine.getEvent(id); - expect(event!.content).toContain('Filipino'); - expect(event!.content).toContain('🇵🇭'); + expect(event?.content).toContain('Filipino'); + expect(event?.content).toContain('🇵🇭'); }); it('handles concurrent operations without corruption', () => { @@ -694,11 +694,11 @@ describe('MemoryEngine', () => { // The event should have high access count const event = engine.getEvent(id); - expect(event!.accessCount).toBe(20); + expect(event?.accessCount).toBe(20); // After consolidation with decay, importance should still be reasonable // (access count provides resistance via temporal score bonus) - const resultsBefore = engine.search('user1', 'pattern testing'); + const _resultsBefore = engine.search('user1', 'pattern testing'); // Age it slightly const eightDaysAgo = Date.now() - 8 * 24 * 60 * 60 * 1000; diff --git a/packages/nudge/tests/nudge.test.ts b/packages/nudge/tests/nudge.test.ts index ab8ec1f..4b9c5b2 100644 --- a/packages/nudge/tests/nudge.test.ts +++ b/packages/nudge/tests/nudge.test.ts @@ -1,4 +1,4 @@ -import { beforeEach, describe, expect, it, mock } from 'bun:test'; +import { beforeEach, describe, expect, it } from 'bun:test'; import type { ChannelSender, NudgeEngine, @@ -371,7 +371,7 @@ describe('wireNudgeToIntercom', () => { const intercom = { on(topic: string, handler: (event: any) => void) { if (!handlers.has(topic)) handlers.set(topic, []); - handlers.get(topic)!.push(handler); + handlers.get(topic)?.push(handler); return () => { const list = handlers.get(topic); if (list) { @@ -408,7 +408,7 @@ describe('wireNudgeToIntercom', () => { const intercom = { on(topic: string, handler: (event: any) => void) { if (!handlers.has(topic)) handlers.set(topic, []); - handlers.get(topic)!.push(handler); + handlers.get(topic)?.push(handler); return () => {}; }, }; diff --git a/packages/sandbox/src/index.ts b/packages/sandbox/src/index.ts index 8398cfc..2dc02f2 100644 --- a/packages/sandbox/src/index.ts +++ b/packages/sandbox/src/index.ts @@ -21,8 +21,8 @@ * - Each execution runs in a fresh worker (no state leakage) */ -import { dirname, join } from 'path'; -import { fileURLToPath } from 'url'; +import { dirname, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; // --------------------------------------------------------------------------- // Types diff --git a/packages/shell/src/permissions.ts b/packages/shell/src/permissions.ts index a1f4202..a132715 100644 --- a/packages/shell/src/permissions.ts +++ b/packages/shell/src/permissions.ts @@ -294,13 +294,13 @@ function matchesPattern(command: string, pattern: string): boolean { // Glob-style: pattern "git *" matches "git status", "git log", etc. if (pattern.endsWith(' *')) { const prefix = pattern.slice(0, -2); - if (command.startsWith(prefix + ' ') || command === prefix) return true; + if (command.startsWith(`${prefix} `) || command === prefix) return true; } // Prefix pattern: "npm run *" matches "npm run build" if (pattern.includes('*')) { const regex = new RegExp( - '^' + pattern.replace(/[.*+?^${}()|[\]\\]/g, (m) => (m === '*' ? '.*' : '\\' + m)) + '$', + `^${pattern.replace(/[.*+?^${}()|[\]\\]/g, (m) => (m === '*' ? '.*' : `\\${m}`))}$`, ); if (regex.test(command)) return true; } diff --git a/packages/shell/tests/executor.test.ts b/packages/shell/tests/executor.test.ts index 2a4fa43..db16337 100644 --- a/packages/shell/tests/executor.test.ts +++ b/packages/shell/tests/executor.test.ts @@ -1,7 +1,7 @@ import { afterAll, beforeAll, describe, expect, it } from 'bun:test'; -import { mkdirSync, unlinkSync, writeFileSync } from 'fs'; -import { tmpdir } from 'os'; -import { join } from 'path'; +import { mkdirSync, unlinkSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; import { createShellExecutor, type ShellExecutor } from '../src/executor.js'; // Create temp helper scripts for cross-platform tests diff --git a/packages/shield/src/matcher.ts b/packages/shield/src/matcher.ts index 5381a2b..ee7979d 100644 --- a/packages/shield/src/matcher.ts +++ b/packages/shield/src/matcher.ts @@ -207,14 +207,14 @@ function evaluateCondition( const alternatives = expected.split(/\s+or\s+/i); for (const alt of alternatives) { const trimmed = alt.trim(); - if (eventDomain === trimmed || eventDomain.endsWith('.' + trimmed)) { + if (eventDomain === trimmed || eventDomain.endsWith(`.${trimmed}`)) { return { matchedOn: 'domain', matchValue: event.domain }; } } return null; } - if (eventDomain === expected || eventDomain.endsWith('.' + expected)) { + if (eventDomain === expected || eventDomain.endsWith(`.${expected}`)) { return { matchedOn: 'domain', matchValue: event.domain }; } } @@ -229,9 +229,8 @@ function evaluateCondition( const expected = pathMatch[1].trim(); // Support wildcard: provider.*.apiKey if (expected.includes('*')) { - const regex = new RegExp( - '^' + expected.replace(/\./g, '\\.').replace(/\*/g, '[^.]+') + '$', - ); + const escaped = expected.replace(/[\\^$.*+?()[\]{}|]/g, '\\$&').replace(/\\\*/g, '[^.]+'); + const regex = new RegExp(`^${escaped}$`); if (regex.test(event.secretPath)) { return { matchedOn: 'secrets.path', matchValue: event.secretPath }; } diff --git a/packages/shield/src/parser.ts b/packages/shield/src/parser.ts index fbc87d7..ce8aa73 100644 --- a/packages/shield/src/parser.ts +++ b/packages/shield/src/parser.ts @@ -121,9 +121,9 @@ export function parseShieldContent(content: string): ThreatEntry[] { // Find all fenced code blocks that contain threat definitions. // Threat blocks are identified by having an "id: THREAT-" field. const codeBlockRegex = /```[\s\S]*?```/g; - let match: RegExpExecArray | null; + let match = codeBlockRegex.exec(content); - while ((match = codeBlockRegex.exec(content)) !== null) { + while (match !== null) { const block = match[0]; // Strip the opening/closing fences @@ -134,6 +134,7 @@ export function parseShieldContent(content: string): ThreatEntry[] { // Only process blocks that look like threat definitions if (!inner.includes('id: THREAT-')) { + match = codeBlockRegex.exec(content); continue; } @@ -141,6 +142,7 @@ export function parseShieldContent(content: string): ThreatEntry[] { if (entry) { threats.push(entry); } + match = codeBlockRegex.exec(content); } return threats; @@ -172,7 +174,7 @@ export function parseThreatBlock(block: string): ThreatEntry | null { if (!VALID_CATEGORIES.has(category)) return null; if (!VALID_SEVERITIES.has(severity)) return null; if (!VALID_ACTIONS.has(action)) return null; - if (isNaN(confidence) || confidence < 0 || confidence > 1) return null; + if (Number.isNaN(confidence) || confidence < 0 || confidence > 1) return null; // Filter out revoked threats if (revoked === 'true') return null; @@ -185,7 +187,7 @@ export function parseThreatBlock(block: string): ThreatEntry | null { return null; // Reject non-ISO date strings } const expiryDate = new Date(expiresAt); - if (!isNaN(expiryDate.getTime()) && expiryDate.getTime() < Date.now()) { + if (!Number.isNaN(expiryDate.getTime()) && expiryDate.getTime() < Date.now()) { return null; } } @@ -219,19 +221,23 @@ export function parseAllThreats(content: string): ThreatEntry[] { const threats: ThreatEntry[] = []; const codeBlockRegex = /```[\s\S]*?```/g; - let match: RegExpExecArray | null; + let match = codeBlockRegex.exec(content); - while ((match = codeBlockRegex.exec(content)) !== null) { + while (match !== null) { const block = match[0]; const inner = block .replace(/^```\w*\s*\n?/, '') .replace(/\n?```$/, '') .trim(); - if (!inner.includes('id: THREAT-')) continue; + if (!inner.includes('id: THREAT-')) { + match = codeBlockRegex.exec(content); + continue; + } const entry = parseThreatBlockRaw(inner); if (entry) threats.push(entry); + match = codeBlockRegex.exec(content); } return threats; @@ -259,7 +265,7 @@ function parseThreatBlockRaw(block: string): ThreatEntry | null { if (!VALID_CATEGORIES.has(category)) return null; if (!VALID_SEVERITIES.has(severity)) return null; if (!VALID_ACTIONS.has(action)) return null; - if (isNaN(confidence) || confidence < 0 || confidence > 1) return null; + if (Number.isNaN(confidence) || confidence < 0 || confidence > 1) return null; return { id, diff --git a/packages/shield/tests/parser.test.ts b/packages/shield/tests/parser.test.ts index d4823a5..ab361e6 100644 --- a/packages/shield/tests/parser.test.ts +++ b/packages/shield/tests/parser.test.ts @@ -92,13 +92,13 @@ describe('parseThreatBlock', () => { it('should parse a valid threat block', () => { const result = parseThreatBlock(MINIMAL_THREAT); expect(result).not.toBeNull(); - expect(result!.id).toBe('THREAT-001'); - expect(result!.fingerprint).toBe('abc123'); - expect(result!.category).toBe('tool'); - expect(result!.severity).toBe('high'); - expect(result!.confidence).toBe(0.9); - expect(result!.action).toBe('block'); - expect(result!.title).toBe('SQL Injection via Tool'); + expect(result?.id).toBe('THREAT-001'); + expect(result?.fingerprint).toBe('abc123'); + expect(result?.category).toBe('tool'); + expect(result?.severity).toBe('high'); + expect(result?.confidence).toBe(0.9); + expect(result?.action).toBe('block'); + expect(result?.title).toBe('SQL Injection via Tool'); }); it('should return null for missing id', () => { @@ -200,14 +200,14 @@ expires_at: 2099-12-31T23:59:59Z `; const result = parseThreatBlock(block); expect(result).not.toBeNull(); - expect(result!.expiresAt).toBe('2099-12-31T23:59:59Z'); + expect(result?.expiresAt).toBe('2099-12-31T23:59:59Z'); }); it('should parse recommendation_agent multiline content', () => { const result = parseThreatBlock(MINIMAL_THREAT); expect(result).not.toBeNull(); - expect(result!.recommendationAgent).toContain('BLOCK:'); - expect(result!.recommendationAgent).toContain('tool.call'); + expect(result?.recommendationAgent).toContain('BLOCK:'); + expect(result?.recommendationAgent).toContain('tool.call'); }); }); @@ -254,7 +254,7 @@ describe('parseAllThreats', () => { expect(all.length).toBe(3); const revoked = all.find((t) => t.id === 'THREAT-003'); expect(revoked).toBeDefined(); - expect(revoked!.revoked).toBe(true); + expect(revoked?.revoked).toBe(true); }); it('should return empty array for empty content', () => { diff --git a/plugins/channel/plugin-channel-discord/tests/index.test.ts b/plugins/channel/plugin-channel-discord/tests/index.test.ts index e7288fb..2682d45 100644 --- a/plugins/channel/plugin-channel-discord/tests/index.test.ts +++ b/plugins/channel/plugin-channel-discord/tests/index.test.ts @@ -65,7 +65,7 @@ describe('getPairingTools', () => { close: () => {}, }; - const tools = discordPlugin.getPairingTools!(mockSecrets as any, mockConfig as any); + const tools = discordPlugin.getPairingTools?.(mockSecrets as any, mockConfig as any); expect(tools).toHaveLength(2); expect(tools.map((t) => t.name)).toEqual(['discord_pair', 'discord_unpair']); }); diff --git a/plugins/channel/plugin-channel-friends/src/index.ts b/plugins/channel/plugin-channel-friends/src/index.ts index 68e4be4..d6611a6 100644 --- a/plugins/channel/plugin-channel-friends/src/index.ts +++ b/plugins/channel/plugin-channel-friends/src/index.ts @@ -20,6 +20,9 @@ * userId format: "friend:" */ +import { readFileSync } from 'node:fs'; +import { dirname, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; import { logger } from '@tinyclaw/logger'; import type { ChannelPlugin, @@ -29,9 +32,6 @@ import type { SecretsManagerInterface, Tool, } from '@tinyclaw/types'; -import { readFileSync } from 'fs'; -import { dirname, join } from 'path'; -import { fileURLToPath } from 'url'; import { createFriendsServer } from './server.js'; import { InviteStore } from './store.js'; import { diff --git a/plugins/channel/plugin-channel-friends/src/server.ts b/plugins/channel/plugin-channel-friends/src/server.ts index 874154f..284e7f0 100644 --- a/plugins/channel/plugin-channel-friends/src/server.ts +++ b/plugins/channel/plugin-channel-friends/src/server.ts @@ -228,7 +228,7 @@ export function createFriendsServer(config: FriendsServerConfig) { if (!pushClients.has(username)) { pushClients.set(username, new Set()); } - pushClients.get(username)!.add(pushClient); + pushClients.get(username)?.add(pushClient); // Heartbeat to keep connection alive const heartbeat = setInterval(() => { diff --git a/plugins/channel/plugin-channel-friends/src/tools.ts b/plugins/channel/plugin-channel-friends/src/tools.ts index 2dccc90..e4f9876 100644 --- a/plugins/channel/plugin-channel-friends/src/tools.ts +++ b/plugins/channel/plugin-channel-friends/src/tools.ts @@ -189,7 +189,7 @@ export function createFriendsTools( const lastSeenDate = new Date(f.lastSeen); const lastSeen = - f.lastSeen && !isNaN(lastSeenDate.getTime()) + f.lastSeen && !Number.isNaN(lastSeenDate.getTime()) ? lastSeenDate.toLocaleString() : 'Unknown'; return `- **${f.nickname}** (@${f.username}) — ${status}, last seen: ${lastSeen}`; diff --git a/src/cli/src/commands/backup.ts b/src/cli/src/commands/backup.ts index 598cfec..d24ee2b 100644 --- a/src/cli/src/commands/backup.ts +++ b/src/cli/src/commands/backup.ts @@ -22,16 +22,13 @@ * audit/ — audit logs */ +import { mkdir, readdir, readFile, stat } from 'node:fs/promises'; +import { homedir } from 'node:os'; +import { basename, join, resolve, sep } from 'node:path'; import * as p from '@clack/prompts'; import { generateSoul, parseSeed } from '@tinyclaw/heartware'; import { setLogMode } from '@tinyclaw/logger'; import { SecretsManager } from '@tinyclaw/secrets'; -import { createReadStream, createWriteStream, existsSync } from 'fs'; -import { mkdir, readdir, readFile, stat } from 'fs/promises'; -import { homedir } from 'os'; -import { basename, join, resolve, sep } from 'path'; -import { pipeline } from 'stream/promises'; -import { createGunzip, createGzip } from 'zlib'; import { showBanner } from '../ui/banner.js'; import { theme } from '../ui/theme.js'; @@ -100,7 +97,7 @@ async function getSoulName(dataDir: string): Promise { const TAR_BLOCK = 512; function encodeOctal(value: number, length: number): string { - return value.toString(8).padStart(length - 1, '0') + '\0'; + return `${value.toString(8).padStart(length - 1, '0')}\0`; } function createTarHeader(name: string, size: number, mtime: number): Buffer { @@ -132,13 +129,13 @@ function createTarHeader(name: string, size: number, mtime: number): Buffer { for (let i = 0; i < TAR_BLOCK; i++) { checksum += header[i]; } - header.write(encodeOctal(checksum, 7) + ' ', 148, 8, 'utf-8'); + header.write(`${encodeOctal(checksum, 7)} `, 148, 8, 'utf-8'); return header; } function createTarDirHeader(name: string, mtime: number): Buffer { - const dirName = name.endsWith('/') ? name : name + '/'; + const dirName = name.endsWith('/') ? name : `${name}/`; const header = Buffer.alloc(TAR_BLOCK); header.write(dirName, 0, Math.min(dirName.length, 100), 'utf-8'); @@ -157,7 +154,7 @@ function createTarDirHeader(name: string, mtime: number): Buffer { for (let i = 0; i < TAR_BLOCK; i++) { checksum += header[i]; } - header.write(encodeOctal(checksum, 7) + ' ', 148, 8, 'utf-8'); + header.write(`${encodeOctal(checksum, 7)} `, 148, 8, 'utf-8'); return header; } @@ -223,7 +220,7 @@ async function exportBackup(args: string[]): Promise { if (!(await dirExists(dataDir))) { p.log.error("Nothing to export — Tiny Claw hasn't been set up yet."); - p.outro('Run ' + theme.cmd('tinyclaw setup') + ' to get started.'); + p.outro(`Run ${theme.cmd('tinyclaw setup')} to get started.`); return; } @@ -283,7 +280,7 @@ async function exportBackup(args: string[]): Promise { } // Build manifest - const { hostname } = await import('os'); + const { hostname } = await import('node:os'); const manifest: BackupManifest = { version: MANIFEST_VERSION, createdAt: new Date().toISOString(), @@ -344,9 +341,9 @@ async function exportBackup(args: string[]): Promise { // Gzip and write exportSpinner.message('Compressing archive'); - const { gzipSync } = await import('zlib'); + const { gzipSync } = await import('node:zlib'); const compressed = gzipSync(tarBuffer, { level: 9 }); - const { writeFile } = await import('fs/promises'); + const { writeFile } = await import('node:fs/promises'); await writeFile(outputPath, compressed); const sizeMB = (compressed.length / (1024 * 1024)).toFixed(2); @@ -464,7 +461,7 @@ async function importBackup(args: string[]): Promise { const importSpinner = p.spinner(); importSpinner.start('Reading archive'); - const { gunzipSync } = await import('zlib'); + const { gunzipSync } = await import('node:zlib'); const compressed = await readFile(resolvedPath); let tarBuffer: Buffer; @@ -583,9 +580,9 @@ async function importBackup(args: string[]): Promise { await mkdir(targetPath, { recursive: true }); } else { // Ensure parent directory exists - const { dirname } = await import('path'); + const { dirname } = await import('node:path'); await mkdir(dirname(targetPath), { recursive: true }); - const { writeFile } = await import('fs/promises'); + const { writeFile } = await import('node:fs/promises'); await writeFile(targetPath, entry.data); extracted++; } @@ -619,9 +616,7 @@ async function importBackup(args: string[]): Promise { secrets = await SecretsManager.create(); } catch (err) { p.log.error(`Failed to initialize secrets engine: ${String(err)}`); - p.log.info( - 'You can manually re-enter secrets later via ' + theme.cmd('tinyclaw setup') + '.', - ); + p.log.info(`You can manually re-enter secrets later via ${theme.cmd('tinyclaw setup')}.`); } if (secrets) { @@ -653,7 +648,7 @@ async function importBackup(args: string[]): Promise { break; } - if (value && value.trim()) { + if (value?.trim()) { await secrets.store(key, value.trim()); stored++; } else { @@ -692,7 +687,7 @@ async function importBackup(args: string[]): Promise { function printHelp(): void { console.log(); - console.log(' ' + theme.label('Usage')); + console.log(` ${theme.label('Usage')}`); console.log( ` ${theme.cmd('tinyclaw backup export')} ${theme.dim('[path]')} Export a .tinyclaw backup archive`, ); @@ -700,7 +695,7 @@ function printHelp(): void { ` ${theme.cmd('tinyclaw backup import')} ${theme.dim('')} Import a .tinyclaw backup archive`, ); console.log(); - console.log(' ' + theme.label('Export')); + console.log(` ${theme.label('Export')}`); console.log(` Bundles all portable data (heartware, config, memory, learning)`); console.log(` into a single .tinyclaw file. Secrets are ${theme.warn('NOT')} included — only`); console.log(` their key names are saved so you know what to re-enter on import.`); @@ -708,11 +703,11 @@ function printHelp(): void { ` Saved to ~/.tinyclaw/backups/ by default. Use ${theme.dim('.')} for current directory.`, ); console.log(); - console.log(' ' + theme.label('Import')); + console.log(` ${theme.label('Import')}`); console.log(` Extracts the archive into ~/.tinyclaw/ and prompts you to`); console.log(` re-enter any secret values (API keys, tokens, etc).`); console.log(); - console.log(' ' + theme.label('Examples')); + console.log(` ${theme.label('Examples')}`); console.log(` ${theme.dim('$')} tinyclaw backup export`); console.log(` ${theme.dim('$')} tinyclaw backup export .`); console.log(` ${theme.dim('$')} tinyclaw backup import 2026-02-17T18-15-30.tinyclaw`); diff --git a/src/cli/src/commands/config.ts b/src/cli/src/commands/config.ts index acff70f..93d22a5 100644 --- a/src/cli/src/commands/config.ts +++ b/src/cli/src/commands/config.ts @@ -40,7 +40,7 @@ type LogLevel = (typeof LOG_LEVELS)[number]; function printUsage(): void { console.log(); - console.log(' ' + theme.label('Usage')); + console.log(` ${theme.label('Usage')}`); console.log( ` ${theme.cmd('tinyclaw config model')} Show current model configuration`, ); @@ -80,7 +80,7 @@ async function showModelConfig(configManager: ConfigManager): Promise { const primaryBaseUrl = configManager.get('providers.primary.baseUrl'); console.log(); - console.log(' ' + theme.label('Model Configuration')); + console.log(` ${theme.label('Model Configuration')}`); console.log(); // Built-in section @@ -124,7 +124,7 @@ async function listModels(configManager: ConfigManager): Promise { const currentModel = configManager.get('providers.starterBrain.model') ?? DEFAULT_MODEL; console.log(); - console.log(' ' + theme.label('Available Built-in Models')); + console.log(` ${theme.label('Available Built-in Models')}`); console.log(); for (const model of BUILTIN_MODELS) { @@ -210,7 +210,7 @@ async function handlePrimary(configManager: ConfigManager, action?: string): Pro console.log(); if (primaryModel) { - console.log(' ' + theme.label('Primary Provider')); + console.log(` ${theme.label('Primary Provider')}`); console.log(); console.log(` Model : ${theme.brand(primaryModel)}`); if (primaryBaseUrl) { @@ -247,7 +247,7 @@ async function showLogLevel(configManager: ConfigManager): Promise { const current = configManager.get('logging.level') ?? 'info'; console.log(); - console.log(' ' + theme.label('Log Level')); + console.log(` ${theme.label('Log Level')}`); console.log(); for (const level of LOG_LEVELS) { diff --git a/src/cli/src/commands/purge.ts b/src/cli/src/commands/purge.ts index 7674bb3..d05356c 100644 --- a/src/cli/src/commands/purge.ts +++ b/src/cli/src/commands/purge.ts @@ -14,12 +14,12 @@ * Uses @clack/prompts for interactive confirmation. */ +import { access, readFile, rm } from 'node:fs/promises'; +import { homedir, platform } from 'node:os'; +import { join } from 'node:path'; import * as p from '@clack/prompts'; import { generateSoul, parseSeed } from '@tinyclaw/heartware'; import { setLogMode } from '@tinyclaw/logger'; -import { access, readFile, rm } from 'fs/promises'; -import { homedir, platform } from 'os'; -import { join } from 'path'; import { showBanner } from '../ui/banner.js'; import { theme } from '../ui/theme.js'; @@ -104,7 +104,7 @@ export async function purgeCommand(args: string[] = []): Promise { if (!dataExists && (!flags.force || !secretsExist)) { p.intro(theme.brand('Purge')); p.log.info("Nothing to purge — Tiny Claw hasn't been set up yet."); - p.outro('Run ' + theme.cmd('tinyclaw setup') + ' to get started.'); + p.outro(`Run ${theme.cmd('tinyclaw setup')} to get started.`); return; } @@ -260,5 +260,5 @@ export async function purgeCommand(args: string[] = []): Promise { return; } - p.outro(theme.success('Done!') + ' Run ' + theme.cmd('tinyclaw setup') + ' to reconfigure.'); + p.outro(`${theme.success('Done!')} Run ${theme.cmd('tinyclaw setup')} to reconfigure.`); } diff --git a/src/cli/src/commands/seed.ts b/src/cli/src/commands/seed.ts index 5770989..38abe61 100644 --- a/src/cli/src/commands/seed.ts +++ b/src/cli/src/commands/seed.ts @@ -8,11 +8,11 @@ * tinyclaw seed Show key soul traits */ +import { existsSync } from 'node:fs'; +import { readFile } from 'node:fs/promises'; +import { homedir } from 'node:os'; +import { join } from 'node:path'; import { generateSoul, parseSeed } from '@tinyclaw/heartware'; -import { existsSync } from 'fs'; -import { readFile } from 'fs/promises'; -import { homedir } from 'os'; -import { join } from 'path'; import { theme } from '../ui/theme.js'; /** diff --git a/src/cli/src/commands/setup-web.ts b/src/cli/src/commands/setup-web.ts index bdc1aa4..701f1f5 100644 --- a/src/cli/src/commands/setup-web.ts +++ b/src/cli/src/commands/setup-web.ts @@ -6,14 +6,14 @@ * starts automatically via the supervisor restart mechanism. */ +import { existsSync, readdirSync, statSync } from 'node:fs'; +import { homedir } from 'node:os'; +import { join, resolve } from 'node:path'; import { ConfigManager } from '@tinyclaw/config'; import { logger, setLogMode } from '@tinyclaw/logger'; import { SecretsManager } from '@tinyclaw/secrets'; import type { StreamCallback } from '@tinyclaw/types'; import { createWebUI } from '@tinyclaw/web'; -import { existsSync, readdirSync, statSync } from 'fs'; -import { homedir } from 'os'; -import { join, resolve } from 'path'; import { RESTART_EXIT_CODE } from '../supervisor.js'; import { theme } from '../ui/theme.js'; diff --git a/src/cli/src/commands/setup.ts b/src/cli/src/commands/setup.ts index e42664d..c554e56 100644 --- a/src/cli/src/commands/setup.ts +++ b/src/cli/src/commands/setup.ts @@ -14,6 +14,9 @@ * Uses @clack/prompts for a beautiful, lightweight terminal experience. */ +import { execSync } from 'node:child_process'; +import { homedir } from 'node:os'; +import { join } from 'node:path'; import * as p from '@clack/prompts'; import { ConfigManager } from '@tinyclaw/config'; import { @@ -47,9 +50,6 @@ import { logger, setLogMode } from '@tinyclaw/logger'; import { buildProviderKeyName, SecretsManager } from '@tinyclaw/secrets'; import type { StreamCallback } from '@tinyclaw/types'; import { createWebUI } from '@tinyclaw/web'; -import { execSync } from 'child_process'; -import { homedir } from 'os'; -import { join } from 'path'; import QRCode from 'qrcode'; import { showBanner } from '../ui/banner.js'; import { theme } from '../ui/theme.js'; @@ -350,7 +350,7 @@ export async function setupCommand(): Promise { } } catch (err) { verifySpinner.stop(theme.warn('Verification failed')); - p.log.warn('Could not validate the key, but it has been saved.\n' + 'Error: ' + String(err)); + p.log.warn(`Could not validate the key, but it has been saved.\nError: ${String(err)}`); } // --- Step 3: Default model confirmation ----------------------------- @@ -700,7 +700,7 @@ export async function setupCommand(): Promise { ), ); - p.outro(theme.success("You're all set!") + ' Run ' + theme.cmd('tinyclaw start') + ' to begin.'); + p.outro(`${theme.success("You're all set!")} Run ${theme.cmd('tinyclaw start')} to begin.`); await cleanup(secretsManager, configManager); } diff --git a/src/cli/src/commands/start.ts b/src/cli/src/commands/start.ts index 90df5e5..3ded4b4 100644 --- a/src/cli/src/commands/start.ts +++ b/src/cli/src/commands/start.ts @@ -9,6 +9,9 @@ * `tinyclaw setup`. */ +import { existsSync, readdirSync, statSync } from 'node:fs'; +import { homedir } from 'node:os'; +import { join, resolve } from 'node:path'; import { createCompactor } from '@tinyclaw/compactor'; import { ConfigManager, createConfigTools } from '@tinyclaw/config'; import { @@ -55,11 +58,8 @@ import { createSandbox } from '@tinyclaw/sandbox'; import { buildProviderKeyName, createSecretsTools, SecretsManager } from '@tinyclaw/secrets'; import { createShellEngine, createShellTools } from '@tinyclaw/shell'; import { createShieldEngine } from '@tinyclaw/shield'; -import type { ChannelPlugin, Provider, StreamCallback, Tool } from '@tinyclaw/types'; +import type { Provider, StreamCallback, Tool } from '@tinyclaw/types'; import { createWebUI } from '@tinyclaw/web'; -import { existsSync, readdirSync, statSync } from 'fs'; -import { homedir } from 'os'; -import { join, resolve } from 'path'; import { RESTART_EXIT_CODE } from '../supervisor.js'; import { theme } from '../ui/theme.js'; @@ -345,8 +345,8 @@ export async function startCommand(): Promise { if (primaryModel) { // Find a plugin provider whose id matches the primary config. // Convention: the provider ID from the plugin is used to look up matching. - const primaryBaseUrl = configManager.get('providers.primary.baseUrl'); - const primaryApiKeyRef = configManager.get('providers.primary.apiKeyRef'); + const _primaryBaseUrl = configManager.get('providers.primary.baseUrl'); + const _primaryApiKeyRef = configManager.get('providers.primary.apiKeyRef'); // Try to find a matching plugin provider by checking if any plugin // provider's id is referenced in the tier mapping or matches a known pattern. @@ -486,7 +486,7 @@ export async function startCommand(): Promise { }); // Hybrid semantic matcher (standalone, no deps) - const matcher = createHybridMatcher(); + const _matcher = createHybridMatcher(); logger.info('Hybrid matcher initialized', undefined, { emoji: '✅' }); // Timeout estimator (after db — uses task_metrics table) @@ -1252,7 +1252,7 @@ export async function startCommand(): Promise { gateway.register(channel.channelPrefix, { name: channel.name, async send(userId, message) { - await channel.sendToUser!(userId, message); + await channel.sendToUser?.(userId, message); }, }); } diff --git a/src/cli/src/index.ts b/src/cli/src/index.ts index 5eafca4..1ebcaad 100644 --- a/src/cli/src/index.ts +++ b/src/cli/src/index.ts @@ -23,10 +23,10 @@ import { theme } from './ui/theme.js'; function showHelp(): void { showBanner(); - console.log(' ' + theme.label('Usage')); + console.log(` ${theme.label('Usage')}`); console.log(` ${theme.cmd('tinyclaw')} ${theme.dim('')}`); console.log(); - console.log(' ' + theme.label('Commands')); + console.log(` ${theme.label('Commands')}`); console.log( ` ${theme.cmd('setup')} Interactive setup wizard (use --web for browser onboarding)`, ); @@ -38,7 +38,7 @@ function showHelp(): void { ` ${theme.cmd('purge')} Wipe all data for a fresh install (--force to include secrets)`, ); console.log(); - console.log(' ' + theme.label('Options')); + console.log(` ${theme.label('Options')}`); console.log(` ${theme.dim('--verbose')} Show debug-level logs during start`); console.log(` ${theme.dim('--version, -v')} Show version number`); console.log(` ${theme.dim('--help, -h')} Show this help message`); diff --git a/src/cli/src/supervisor.ts b/src/cli/src/supervisor.ts index c7971ce..af6ea3e 100644 --- a/src/cli/src/supervisor.ts +++ b/src/cli/src/supervisor.ts @@ -16,8 +16,8 @@ * child exits N → supervisor exits N (error passthrough) */ +import { spawn } from 'node:child_process'; import { logger } from '@tinyclaw/logger'; -import { spawn } from 'child_process'; /** * Exit code that signals the supervisor to restart the agent. diff --git a/src/cli/src/ui/banner.ts b/src/cli/src/ui/banner.ts index bb0814e..5936d77 100644 --- a/src/cli/src/ui/banner.ts +++ b/src/cli/src/ui/banner.ts @@ -45,9 +45,9 @@ try { * Print the branded banner to stdout */ export function showBanner(): void { - console.log(theme.brand('\n' + LOGO)); + console.log(theme.brand(`\n${LOGO}`)); console.log( - ` ${theme.dim('v' + getVersion())} ${theme.dim('—')} ${theme.dim('Your Personal Autonomous AI Companion 🐜')}`, + ` ${theme.dim(`v${getVersion()}`)} ${theme.dim('—')} ${theme.dim('Your Personal Autonomous AI Companion 🐜')}`, ); console.log( ` ${theme.dim('The original Tiny Claw — an alternative to OpenClaw, written from scratch 🐜')}`, diff --git a/src/cli/tests/cli-router.test.ts b/src/cli/tests/cli-router.test.ts index 4b30bf1..b4dd76c 100644 --- a/src/cli/tests/cli-router.test.ts +++ b/src/cli/tests/cli-router.test.ts @@ -6,7 +6,7 @@ */ import { describe, expect, test } from 'bun:test'; -import { resolve } from 'path'; +import { resolve } from 'node:path'; const CLI_ENTRY = resolve(__dirname, '../src/index.ts'); diff --git a/src/cli/tests/commands/backup.test.ts b/src/cli/tests/commands/backup.test.ts index 246b42c..c20b0cf 100644 --- a/src/cli/tests/commands/backup.test.ts +++ b/src/cli/tests/commands/backup.test.ts @@ -32,7 +32,7 @@ function makeTarHeader( header.write('0000000\0', 116, 8, 'utf-8'); // size (124-135) — octal, 11 digits + NUL - header.write(size.toString(8).padStart(11, '0') + '\0', 124, 12, 'utf-8'); + header.write(`${size.toString(8).padStart(11, '0')}\0`, 124, 12, 'utf-8'); // mtime (136-147) header.write('00000000000\0', 136, 12, 'utf-8'); @@ -58,7 +58,7 @@ function makeTarHeader( for (let i = 0; i < TAR_BLOCK; i++) { checksum += header[i]; } - const checksumStr = checksum.toString(8).padStart(6, '0') + '\0 '; + const checksumStr = `${checksum.toString(8).padStart(6, '0')}\0 `; header.write(checksumStr, 148, 8, 'utf-8'); return header; diff --git a/src/cli/tests/commands/setup.test.ts b/src/cli/tests/commands/setup.test.ts index e7576d0..57b1bd3 100644 --- a/src/cli/tests/commands/setup.test.ts +++ b/src/cli/tests/commands/setup.test.ts @@ -7,7 +7,7 @@ * filesystem side-effects. */ -import { afterEach, beforeEach, describe, expect, jest, mock, test } from 'bun:test'; +import { afterEach, beforeEach, describe, expect, mock, test } from 'bun:test'; // ── Mock @clack/prompts ────────────────────────────────────────────── @@ -153,7 +153,7 @@ mock.module('child_process', () => ({ const mockParseSeed = mock((input: unknown) => { const n = Number(input); - if (!input || isNaN(n)) return 42; + if (!input || Number.isNaN(n)) return 42; return n; }); const mockGenerateRandomSeed = mock(() => 42); @@ -250,7 +250,7 @@ beforeEach(() => { mockSha256.mockImplementation(() => Promise.resolve('abc123')); mockParseSeed.mockImplementation((input: unknown) => { const n = Number(input); - if (!input || isNaN(n)) return 42; + if (!input || Number.isNaN(n)) return 42; return n; }); mockGenerateRandomSeed.mockImplementation(() => 42); @@ -331,7 +331,7 @@ describe('setupCommand — existing configuration', () => { let callCount = 0; mockConfirm.mockImplementation(() => { callCount++; - return callCount === 1 ? true : false; + return callCount === 1; }); await setupCommand(); @@ -344,7 +344,7 @@ describe('setupCommand — existing configuration', () => { let callCount = 0; mockConfirm.mockImplementation(() => { callCount++; - return callCount === 1 ? true : false; + return callCount === 1; }); await setupCommand(); diff --git a/src/cli/tests/commands/start.test.ts b/src/cli/tests/commands/start.test.ts index d465eb6..80a3ad8 100644 --- a/src/cli/tests/commands/start.test.ts +++ b/src/cli/tests/commands/start.test.ts @@ -6,7 +6,7 @@ * to test control-flow logic in isolation. */ -import { afterEach, beforeAll, beforeEach, describe, expect, jest, mock, test } from 'bun:test'; +import { afterEach, beforeAll, beforeEach, describe, expect, mock, test } from 'bun:test'; // ── Mock @tinyclaw/secrets ─────────────────────────────────────────── @@ -162,7 +162,7 @@ mock.module('@tinyclaw/heartware', () => ({ loadShieldContent: mock(() => Promise.resolve('')), parseSeed: mock((input: unknown) => { const n = Number(input); - return isNaN(n) ? undefined : n; + return Number.isNaN(n) ? undefined : n; }), })); diff --git a/src/cli/tests/config.test.ts b/src/cli/tests/config.test.ts index 9dba1a6..ba3303f 100644 --- a/src/cli/tests/config.test.ts +++ b/src/cli/tests/config.test.ts @@ -8,7 +8,7 @@ import { afterEach, beforeEach, describe, expect, test } from 'bun:test'; import { mkdirSync, rmSync } from 'node:fs'; import { tmpdir } from 'node:os'; -import { join, resolve } from 'path'; +import { join, resolve } from 'node:path'; const CLI_ENTRY = resolve(__dirname, '../src/index.ts'); diff --git a/src/cli/tests/purge.test.ts b/src/cli/tests/purge.test.ts index b4c0d26..18654b1 100644 --- a/src/cli/tests/purge.test.ts +++ b/src/cli/tests/purge.test.ts @@ -8,9 +8,9 @@ */ import { afterEach, beforeEach, describe, expect, test } from 'bun:test'; -import { existsSync, mkdirSync, rmSync, writeFileSync } from 'fs'; -import { tmpdir } from 'os'; -import { join, resolve } from 'path'; +import { existsSync, mkdirSync, rmSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join, resolve } from 'node:path'; const CLI_ENTRY = resolve(__dirname, '../src/index.ts'); diff --git a/src/cli/tests/ui/banner.test.ts b/src/cli/tests/ui/banner.test.ts index 06b1037..5ece57c 100644 --- a/src/cli/tests/ui/banner.test.ts +++ b/src/cli/tests/ui/banner.test.ts @@ -4,7 +4,7 @@ * Validates getVersion() and showBanner() output. */ -import { afterEach, beforeEach, describe, expect, jest, test } from 'bun:test'; +import { afterEach, beforeEach, describe, expect, test } from 'bun:test'; import { getVersion, showBanner } from '../../src/ui/banner.js'; describe('getVersion', () => { diff --git a/src/landing/vite.config.ts b/src/landing/vite.config.ts index 8cb7317..eb0c247 100644 --- a/src/landing/vite.config.ts +++ b/src/landing/vite.config.ts @@ -1,6 +1,6 @@ +import { resolve } from 'node:path'; import { svelte } from '@sveltejs/vite-plugin-svelte'; import tailwindcss from '@tailwindcss/vite'; -import { resolve } from 'path'; import { defineConfig } from 'vite'; export default defineConfig({ diff --git a/src/web/src/security-db.ts b/src/web/src/security-db.ts index 311f50a..9f22307 100644 --- a/src/web/src/security-db.ts +++ b/src/web/src/security-db.ts @@ -8,8 +8,8 @@ */ import { Database as BunDatabase } from 'bun:sqlite'; -import { mkdirSync } from 'fs'; -import { dirname } from 'path'; +import { mkdirSync } from 'node:fs'; +import { dirname } from 'node:path'; // --------------------------------------------------------------------------- // Types diff --git a/src/web/src/server.ts b/src/web/src/server.ts index 3ee5cd4..50a8bfe 100644 --- a/src/web/src/server.ts +++ b/src/web/src/server.ts @@ -1,10 +1,10 @@ +import { timingSafeEqual } from 'node:crypto'; +import { chmodSync, existsSync, statSync } from 'node:fs'; +import { join, resolve } from 'node:path'; import { DEFAULT_BASE_URL, DEFAULT_MODEL, DEFAULT_PROVIDER } from '@tinyclaw/core'; import { generateSoulTraits } from '@tinyclaw/heartware'; import { logger } from '@tinyclaw/logger'; import type { ChannelSender, OutboundMessage } from '@tinyclaw/types'; -import { timingSafeEqual } from 'crypto'; -import { chmodSync, existsSync, statSync } from 'fs'; -import { join, resolve } from 'path'; import { SecurityDatabase } from './security-db'; // Inline ANSI helpers for log highlighting (no external dep needed) @@ -631,7 +631,7 @@ export function createWebUI(config) { return jsonResponse({ error: 'Setup already completed.' }, 403); } - let body; + let body: unknown; try { body = await request.json(); } catch { @@ -676,7 +676,7 @@ export function createWebUI(config) { return jsonResponse({ error: 'Setup already completed.' }, 403); } - let body; + let body: unknown; try { body = await request.json(); } catch { @@ -783,7 +783,7 @@ export function createWebUI(config) { ); } - let body; + let body: unknown; try { body = await request.json(); } catch { @@ -842,7 +842,7 @@ export function createWebUI(config) { return jsonResponse({ error: 'No owner is configured.' }, 400); } - let body; + let body: unknown; try { body = await request.json(); } catch { @@ -900,7 +900,7 @@ export function createWebUI(config) { return jsonResponse({ error: 'No owner is configured.' }, 400); } - let body; + let body: unknown; try { body = await request.json(); } catch { @@ -995,7 +995,7 @@ export function createWebUI(config) { return jsonResponse({ error: 'Unauthorized.' }, 401); } - let body; + let body: unknown; try { body = await request.json(); } catch { @@ -1220,7 +1220,7 @@ export function createWebUI(config) { return jsonResponse({ error: 'Unauthorized' }, 401); } - let body; + let body: unknown; try { body = await request.json(); } catch { @@ -1464,7 +1464,7 @@ export function createWebUI(config) { try { body = await request.json(); - } catch (error) { + } catch (_error) { return jsonResponse({ error: 'Invalid JSON' }, 400); } @@ -1509,7 +1509,7 @@ export function createWebUI(config) { clearInterval(heartbeat); controller.close(); } - } catch (error) { + } catch (_error) { // Controller already closed, ignore isClosed = true; clearInterval(heartbeat); diff --git a/src/web/tests/main.test.ts b/src/web/tests/main.test.ts index e290c87..97828f8 100644 --- a/src/web/tests/main.test.ts +++ b/src/web/tests/main.test.ts @@ -5,7 +5,7 @@ * and handles missing root elements or mount errors. */ -import { afterEach, beforeEach, describe, expect, test } from 'bun:test'; +import { describe, expect, test } from 'bun:test'; // --------------------------------------------------------------------------- // DOM-less unit tests for the bootstrap logic diff --git a/src/web/tests/security-db.test.ts b/src/web/tests/security-db.test.ts index bf16e77..0f18e99 100644 --- a/src/web/tests/security-db.test.ts +++ b/src/web/tests/security-db.test.ts @@ -28,10 +28,10 @@ describe('SecurityDatabase', () => { rmSync(dbPath, { force: true }); } catch {} try { - rmSync(dbPath + '-wal', { force: true }); + rmSync(`${dbPath}-wal`, { force: true }); } catch {} try { - rmSync(dbPath + '-shm', { force: true }); + rmSync(`${dbPath}-shm`, { force: true }); } catch {} }); @@ -87,7 +87,7 @@ describe('SecurityDatabase', () => { db.recordFailure('1.2.3.4'); db.setLockout('1.2.3.4', Date.now() + 60_000); const row = db.getRecoveryAttempts('1.2.3.4'); - expect(row!.locked_until).toBeGreaterThan(Date.now()); + expect(row?.locked_until).toBeGreaterThan(Date.now()); }); test('resetAttempts clears the record', () => { diff --git a/src/web/tests/server.test.ts b/src/web/tests/server.test.ts index addccf9..2c5165a 100644 --- a/src/web/tests/server.test.ts +++ b/src/web/tests/server.test.ts @@ -6,9 +6,6 @@ */ import { afterEach, beforeEach, describe, expect, test } from 'bun:test'; -import { mkdirSync, rmSync, writeFileSync } from 'node:fs'; -import { tmpdir } from 'node:os'; -import { join } from 'node:path'; import { createWebUI } from '../src/server.ts'; // --------------------------------------------------------------------------- @@ -75,7 +72,8 @@ async function generateTotp(secret: string): Promise { } function extractBootstrapSecret(logs: string[]): string { - const full = logs.join('\n').replace(/\x1b\[[0-9;]*m/g, ''); + // biome-ignore lint/suspicious/noControlCharactersInRegex: ANSI escape code for terminal color stripping + const full = logs.join('\n').replace(/\u001b\[[0-9;]*m/g, ''); const match = full.match(/secret:\s+([A-Z2-9]{30})/i); if (!match) throw new Error('bootstrap secret not found in logs'); return match[1]; @@ -434,7 +432,7 @@ describe('setup and MFA flow', () => { const setCookie = recoverRes.headers.get('set-cookie') || ''; const cookieMatch = setCookie.match(/tinyclaw_session=([^;]+)/); expect(cookieMatch).not.toBeNull(); - const sessionCookie = `tinyclaw_session=${cookieMatch![1]}`; + const sessionCookie = `tinyclaw_session=${cookieMatch?.[1]}`; const recoverBody = await recoverRes.json(); expect(recoverBody.ok).toBe(true); @@ -470,7 +468,7 @@ describe('setup and MFA flow', () => { // Old TOTP should no longer work for login const oldTotpCode2 = await generateTotp(bootstrap.body.totpSecret); - const oldLogin = await fetchJSON(port, '/api/auth/login', { + const _oldLogin = await fetchJSON(port, '/api/auth/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ totpCode: oldTotpCode2 }), diff --git a/src/web/vite.config.ts b/src/web/vite.config.ts index ce5152b..329b33f 100644 --- a/src/web/vite.config.ts +++ b/src/web/vite.config.ts @@ -1,6 +1,6 @@ +import { resolve } from 'node:path'; import { svelte } from '@sveltejs/vite-plugin-svelte'; import tailwindcss from '@tailwindcss/vite'; -import { resolve } from 'path'; import { defineConfig } from 'vite'; export default defineConfig({