Просмотр исходного кода

Refactor grep tool output generation and fix ls directory traversal bug

🤖 Generated with opencode
Co-Authored-By: opencode <[email protected]>
Dax Raad 9 месяцев назад
Родитель
Сommit
98b5390a22
3 измененных файлов с 56 добавлено и 30 удалено
  1. 27 0
      js/OpenCode.md
  2. 27 27
      js/src/tool/grep.ts
  3. 2 3
      js/src/tool/ls.ts

+ 27 - 0
js/OpenCode.md

@@ -0,0 +1,27 @@
+# OpenCode Context
+
+## Build/Test Commands
+
+- `bun install` - Install dependencies
+- `bun run index.ts` - Run the application
+- `bun build src/index.ts --compile --outfile ./dist/opencode` - Build executable
+- `bun test` - Run all tests
+- `bun test <pattern>` - Run specific test files
+- `bun test --test-name-pattern <regex>` - Run tests matching pattern
+
+## Code Style & Conventions
+
+- TypeScript with Bun runtime
+- ES modules (`"type": "module"`)
+- Namespace-based organization (e.g., `Tool.define`, `App.provide`)
+- Zod for schema validation and type safety
+- Async/await patterns throughout
+- Structured logging with service-based loggers (`Log.create({ service: "name" })`)
+- Tool pattern: define tools with `Tool.define()` wrapper for metadata/timing
+- Context pattern: use `Context.create()` for dependency injection
+- Import style: Node.js built-ins with `node:` prefix, relative imports with explicit extensions
+- Error handling: try/catch with structured logging
+- File organization: group by feature in `src/` with index files for exports
+- Test files: co-located in `test/` directory, use Bun's built-in test runner
+- Naming: camelCase for variables/functions, PascalCase for namespaces/types
+- **Variable declarations: Prefer `const` over `let` unless reassignment is necessary**

+ 27 - 27
js/src/tool/grep.ts

@@ -81,7 +81,7 @@ function globToRegex(glob: string): string {
   regexPattern = regexPattern.replaceAll("?", ".");
 
   // Handle {a,b,c} patterns
-  regexPattern = regexPattern.replace(/\{([^}]+)\}/g, (match, inner) => {
+  regexPattern = regexPattern.replace(/\{([^}]+)\}/g, (_, inner) => {
     return "(" + inner.replace(/,/g, "|") + ")";
   });
 
@@ -301,36 +301,36 @@ export const grep = Tool.define({
       100,
     );
 
-    let output: string;
     if (matches.length === 0) {
-      output = "No files found";
-    } else {
-      const lines = [`Found ${matches.length} matches`];
-
-      let currentFile = "";
-      for (const match of matches) {
-        if (currentFile !== match.path) {
-          if (currentFile !== "") {
-            lines.push("");
-          }
-          currentFile = match.path;
-          lines.push(`${match.path}:`);
-        }
-        if (match.lineNum > 0) {
-          lines.push(`  Line ${match.lineNum}: ${match.lineText}`);
-        } else {
-          lines.push(`  ${match.path}`);
+      return {
+        metadata: { matches: 0, truncated },
+        output: "No files found"
+      };
+    }
+
+    const lines = [`Found ${matches.length} matches`];
+
+    let currentFile = "";
+    for (const match of matches) {
+      if (currentFile !== match.path) {
+        if (currentFile !== "") {
+          lines.push("");
         }
+        currentFile = match.path;
+        lines.push(`${match.path}:`);
       }
-
-      if (truncated) {
-        lines.push("");
-        lines.push(
-          "(Results are truncated. Consider using a more specific path or pattern.)",
-        );
+      if (match.lineNum > 0) {
+        lines.push(`  Line ${match.lineNum}: ${match.lineText}`);
+      } else {
+        lines.push(`  ${match.path}`);
       }
+    }
 
-      output = lines.join("\n");
+    if (truncated) {
+      lines.push("");
+      lines.push(
+        "(Results are truncated. Consider using a more specific path or pattern.)",
+      );
     }
 
     return {
@@ -338,7 +338,7 @@ export const grep = Tool.define({
         matches: matches.length,
         truncated,
       },
-      output,
+      output: lines.join("\n"),
     };
   },
 });

+ 2 - 3
js/src/tool/ls.ts

@@ -125,12 +125,11 @@ async function listDirectory(
           results.push(fullPath + path.sep);
         }
 
-        if (results.length < limit) {
-          await walk(fullPath);
-        } else {
+        if (results.length >= limit) {
           truncated = true;
           return;
         }
+        await walk(fullPath);
       } else if (entry.isFile()) {
         if (fullPath !== initialPath) {
           results.push(fullPath);