Alvin 2 months ago
parent
commit
cba629cac8

+ 3 - 0
.github/workflows/ci.yml

@@ -26,6 +26,9 @@ jobs:
       - name: Install dependencies
         run: bun install --frozen-lockfile
 
+      - name: Run lint
+        run: bun run lint
+
       - name: Run typecheck
         run: bun run typecheck
 

+ 16 - 1
biome.json

@@ -9,9 +9,24 @@
   "linter": {
     "enabled": true,
     "rules": {
-      "recommended": true
+      "recommended": true,
+      "suspicious": {
+        "noExplicitAny": "warn"
+      }
     }
   },
+  "overrides": [
+    {
+      "includes": ["**/*.test.ts", "**/*.test.tsx"],
+      "linter": {
+        "rules": {
+          "suspicious": {
+            "noExplicitAny": "off"
+          }
+        }
+      }
+    }
+  ],
   "formatter": {
     "enabled": true,
     "formatWithErrors": false,

+ 4 - 3
src/cli/config-io.ts

@@ -297,9 +297,9 @@ export function detectCurrentConfig(): DetectedConfig {
   // Try to detect from lite config
   const { config: liteConfig } = parseConfig(getLiteConfig());
   if (liteConfig && typeof liteConfig === 'object') {
-    const configObj = liteConfig as Record<string, any>;
+    const configObj = liteConfig as Record<string, unknown>;
     const presetName = configObj.preset as string;
-    const presets = configObj.presets as Record<string, any>;
+    const presets = configObj.presets as Record<string, unknown>;
     const agents = presets?.[presetName] as
       | Record<string, { model?: string }>
       | undefined;
@@ -313,7 +313,8 @@ export function detectCurrentConfig(): DetectedConfig {
     }
 
     if (configObj.tmux && typeof configObj.tmux === 'object') {
-      result.hasTmux = configObj.tmux.enabled === true;
+      const tmuxConfig = configObj.tmux as { enabled?: boolean };
+      result.hasTmux = tmuxConfig.enabled === true;
     }
   }
 

+ 1 - 1
src/cli/install.ts

@@ -122,7 +122,7 @@ function formatConfigSummary(config: InstallConfig): string {
 function printAgentModels(config: InstallConfig): void {
   const liteConfig = generateLiteConfig(config);
   const presetName = (liteConfig.preset as string) || 'unknown';
-  const presets = liteConfig.presets as Record<string, any>;
+  const presets = liteConfig.presets as Record<string, unknown>;
   const agents = presets?.[presetName] as Record<
     string,
     { model: string; skills: string[] }

+ 11 - 4
src/index.ts

@@ -21,9 +21,9 @@ import {
   lsp_rename,
   SkillMcpManager,
 } from './tools';
+import { parseList } from './tools/skill/builtin';
 import { startTmuxCheck } from './utils';
 import { log } from './utils/logger';
-import { parseList } from './tools/skill/builtin';
 
 const OhMyOpenCodeLite: Plugin = async (ctx) => {
   const config = loadPluginConfig(ctx.directory);
@@ -103,7 +103,7 @@ const OhMyOpenCodeLite: Plugin = async (ctx) => {
       } else {
         Object.assign(opencodeConfig.agent, agents);
       }
-      const configAgent = opencodeConfig.agent as Record<string, any>;
+      const configAgent = opencodeConfig.agent as Record<string, unknown>;
 
       // Merge MCP configs
       const configMcp = opencodeConfig.mcp as
@@ -127,7 +127,14 @@ const OhMyOpenCodeLite: Plugin = async (ctx) => {
         if (!configAgent[agentName]) {
           configAgent[agentName] = { ...agentConfig };
         }
-        const agentPermission = (configAgent[agentName].permission ?? {}) as Record<string, unknown>;
+        const agentConfigEntry = configAgent[agentName] as Record<
+          string,
+          unknown
+        >;
+        const agentPermission = (agentConfigEntry.permission ?? {}) as Record<
+          string,
+          unknown
+        >;
 
         // Parse mcps list with wildcard and exclusion support
         const allowedMcps = parseList(agentMcps, allMcpNames);
@@ -146,7 +153,7 @@ const OhMyOpenCodeLite: Plugin = async (ctx) => {
         }
 
         // Update agent config with permissions
-        configAgent[agentName].permission = agentPermission;
+        agentConfigEntry.permission = agentPermission;
       }
     },
 

+ 44 - 24
src/tools/grep/grep.test.ts

@@ -1,10 +1,10 @@
 import { afterAll, beforeAll, describe, expect, test } from 'bun:test';
-import { join } from 'node:path';
 import { mkdir, rm, writeFile } from 'node:fs/promises';
 import { tmpdir } from 'node:os';
+import { join } from 'node:path';
 import { runRg, runRgCount } from './cli';
-import { formatGrepResult } from './utils';
 import { grep } from './tools';
+import { formatGrepResult } from './utils';
 
 describe('grep tool', () => {
   const testDir = join(tmpdir(), `grep-test-${Date.now()}`);
@@ -13,8 +13,14 @@ describe('grep tool', () => {
 
   beforeAll(async () => {
     await mkdir(testDir, { recursive: true });
-    await writeFile(testFile1, 'Hello world\nThis is a test file\nAnother line with match');
-    await writeFile(testFile2, 'const x = \'Hello world\';\nconsole.log(\'test\');');
+    await writeFile(
+      testFile1,
+      'Hello world\nThis is a test file\nAnother line with match',
+    );
+    await writeFile(
+      testFile2,
+      "const x = 'Hello world';\nconsole.log('test');",
+    );
   });
 
   afterAll(async () => {
@@ -46,9 +52,9 @@ describe('grep tool', () => {
     test('formats matches correctly', () => {
       const result = {
         matches: [
-          { file: 'file1.ts', line: 10, text: 'const foo = \'bar\'' },
+          { file: 'file1.ts', line: 10, text: "const foo = 'bar'" },
           { file: 'file1.ts', line: 15, text: 'console.log(foo)' },
-          { file: 'file2.ts', line: 5, text: 'import { foo } from \'./file1\'' },
+          { file: 'file2.ts', line: 5, text: "import { foo } from './file1'" },
         ],
         totalMatches: 3,
         filesSearched: 2,
@@ -57,10 +63,10 @@ describe('grep tool', () => {
 
       const output = formatGrepResult(result);
       expect(output).toContain('file1.ts:');
-      expect(output).toContain('  10: const foo = \'bar\'');
+      expect(output).toContain("  10: const foo = 'bar'");
       expect(output).toContain('  15: console.log(foo)');
       expect(output).toContain('file2.ts:');
-      expect(output).toContain('  5: import { foo } from \'./file1\'');
+      expect(output).toContain("  5: import { foo } from './file1'");
       expect(output).toContain('Found 3 matches in 2 files');
     });
 
@@ -83,8 +89,16 @@ describe('grep tool', () => {
       });
 
       expect(result.totalMatches).toBeGreaterThanOrEqual(2);
-      expect(result.matches.some((m) => m.file.includes('test1.txt') && m.text.includes('Hello'))).toBe(true);
-      expect(result.matches.some((m) => m.file.includes('test2.ts') && m.text.includes('Hello'))).toBe(true);
+      expect(
+        result.matches.some(
+          (m) => m.file.includes('test1.txt') && m.text.includes('Hello'),
+        ),
+      ).toBe(true);
+      expect(
+        result.matches.some(
+          (m) => m.file.includes('test2.ts') && m.text.includes('Hello'),
+        ),
+      ).toBe(true);
     });
 
     test('respects file inclusion patterns', async () => {
@@ -94,8 +108,12 @@ describe('grep tool', () => {
         globs: ['*.txt'],
       });
 
-      expect(result.matches.some((m) => m.file.includes('test1.txt'))).toBe(true);
-      expect(result.matches.some((m) => m.file.includes('test2.ts'))).toBe(false);
+      expect(result.matches.some((m) => m.file.includes('test1.txt'))).toBe(
+        true,
+      );
+      expect(result.matches.some((m) => m.file.includes('test2.ts'))).toBe(
+        false,
+      );
     });
 
     test('handles no matches', async () => {
@@ -116,14 +134,14 @@ describe('grep tool', () => {
       const resultInsensitive = await runRg({
         pattern: 'hello',
         paths: [testDir],
-        caseSensitive: false
+        caseSensitive: false,
       });
       expect(resultInsensitive.totalMatches).toBeGreaterThan(0);
 
       const resultSensitive = await runRg({
         pattern: 'hello', // File has "Hello"
         paths: [testDir],
-        caseSensitive: true
+        caseSensitive: true,
       });
       expect(resultSensitive.totalMatches).toBe(0);
     });
@@ -132,14 +150,14 @@ describe('grep tool', () => {
       const resultPartial = await runRg({
         pattern: 'Hell',
         paths: [testDir],
-        wholeWord: false
+        wholeWord: false,
       });
       expect(resultPartial.totalMatches).toBeGreaterThan(0);
 
       const resultWhole = await runRg({
         pattern: 'Hell',
         paths: [testDir],
-        wholeWord: true
+        wholeWord: true,
       });
       expect(resultWhole.totalMatches).toBe(0);
     });
@@ -148,10 +166,12 @@ describe('grep tool', () => {
       const result = await runRg({
         pattern: 'Hello',
         paths: [testDir],
-        maxCount: 1
+        maxCount: 1,
       });
       // maxCount is per file
-      expect(result.matches.filter(m => m.file.includes('test1.txt')).length).toBeLessThanOrEqual(1);
+      expect(
+        result.matches.filter((m) => m.file.includes('test1.txt')).length,
+      ).toBeLessThanOrEqual(1);
     });
   });
 
@@ -159,11 +179,11 @@ describe('grep tool', () => {
     test('counts matches correctly', async () => {
       const results = await runRgCount({
         pattern: 'Hello',
-        paths: [testDir]
+        paths: [testDir],
       });
 
       expect(results.length).toBeGreaterThan(0);
-      const file1Result = results.find(r => r.file.includes('test1.txt'));
+      const file1Result = results.find((r) => r.file.includes('test1.txt'));
       expect(file1Result).toBeDefined();
       expect(file1Result?.count).toBe(1);
     });
@@ -171,7 +191,7 @@ describe('grep tool', () => {
 
   describe('grep tool execute', () => {
     test('executes successfully', async () => {
-      // @ts-ignore
+      // @ts-expect-error
       const result = await grep.execute({
         pattern: 'Hello',
         path: testDir,
@@ -183,7 +203,7 @@ describe('grep tool', () => {
     });
 
     test('handles errors gracefully', async () => {
-      // @ts-ignore
+      // @ts-expect-error
       const result = await grep.execute({
         pattern: 'Hello',
         path: '/non/existent/path/12345',
@@ -195,11 +215,11 @@ describe('grep tool', () => {
     });
 
     test('respects include pattern in execute', async () => {
-      // @ts-ignore
+      // @ts-expect-error
       const result = await grep.execute({
         pattern: 'Hello',
         path: testDir,
-        include: '*.txt'
+        include: '*.txt',
       });
 
       expect(result).toContain('test1.txt');

+ 13 - 8
src/tools/lsp/client.ts

@@ -241,20 +241,25 @@ export class LSPClient {
 
     this.connection.onNotification(
       'textDocument/publishDiagnostics',
-      (params: any) => {
+      (params: { uri?: string; diagnostics?: Diagnostic[] }) => {
         if (params.uri) {
           this.diagnosticsStore.set(params.uri, params.diagnostics ?? []);
         }
       },
     );
 
-    this.connection.onRequest('workspace/configuration', (params: any) => {
-      const items = params.items ?? [];
-      return items.map((item: any) => {
-        if (item.section === 'json') return { validate: { enable: true } };
-        return {};
-      });
-    });
+    this.connection.onRequest(
+      'workspace/configuration',
+      (params: { items?: unknown[] }) => {
+        const items = params.items ?? [];
+        return items.map((item: unknown) => {
+          const configItem = item as { section?: string };
+          if (configItem.section === 'json')
+            return { validate: { enable: true } };
+          return {};
+        });
+      },
+    );
 
     this.connection.onRequest('client/registerCapability', () => null);
     this.connection.onRequest('window/workDoneProgress/create', () => null);

+ 1 - 1
src/utils/zip-extractor.ts

@@ -96,7 +96,7 @@ export async function extractZip(
   const exitCode = await proc.exited;
 
   if (exitCode !== 0) {
-    const stderr = await new Response(proc.stderr as any).text();
+    const stderr = await new Response(proc.stderr as ReadableStream).text();
     throw new Error(`zip extraction failed (exit ${exitCode}): ${stderr}`);
   }
 }