Files
Claude-Code-Workflow/ccw/src/tools/read-file.ts
catlog22 964292ebdb feat: Add comprehensive tests for contentPattern and glob pattern matching
- Implemented final verification tests for contentPattern to validate behavior with empty strings, dangerous patterns, and normal patterns.
- Created glob pattern matching tests to verify regex conversion and matching functionality.
- Developed infinite loop risk tests using Worker threads to isolate potential blocking operations.
- Introduced optimized contentPattern tests to validate improvements in the findMatches function.
- Added verification tests to assess the effectiveness of contentPattern optimizations.
- Conducted safety tests for contentPattern to identify edge cases and potential vulnerabilities.
- Implemented unrestricted loop tests to analyze infinite loop risks without match limits.
- Developed tests for zero-width pattern detection logic to ensure proper handling of dangerous regex patterns.
2026-02-09 11:13:01 +08:00

110 lines
2.9 KiB
TypeScript

/**
* Read File Tool - Single file precise reading with optional line pagination
*
* Features:
* - Read a single file with full content
* - Line-based pagination with offset/limit
* - Binary file detection
*/
import { z } from 'zod';
import type { ToolSchema, ToolResult } from '../types/tool.js';
import { existsSync, statSync } from 'fs';
import { relative } from 'path';
import { validatePath, getProjectRoot } from '../utils/path-validator.js';
import {
MAX_CONTENT_LENGTH,
readFileContent,
type FileEntry,
type ReadResult,
} from '../utils/file-reader.js';
const ParamsSchema = z.object({
path: z.string().describe('Single file path to read'),
offset: z.number().min(0).optional().describe('Line offset to start reading from (0-based)'),
limit: z.number().min(1).optional().describe('Number of lines to read'),
});
type Params = z.infer<typeof ParamsSchema>;
export const schema: ToolSchema = {
name: 'read_file',
description: `Read a single file with optional line-based pagination.
Usage:
read_file(path="file.ts") # Full content
read_file(path="file.ts", offset=100, limit=50) # Lines 100-149 (0-based)
Supports both absolute and relative paths. Relative paths are resolved from project root.
Use offset/limit for large file pagination.`,
inputSchema: {
type: 'object',
properties: {
path: {
type: 'string',
description: 'Single file path to read',
},
offset: {
type: 'number',
description: 'Line offset to start reading from (0-based)',
minimum: 0,
},
limit: {
type: 'number',
description: 'Number of lines to read',
minimum: 1,
},
},
required: ['path'],
},
};
export async function handler(params: Record<string, unknown>): Promise<ToolResult<ReadResult>> {
const parsed = ParamsSchema.safeParse(params);
if (!parsed.success) {
return { success: false, error: `Invalid params: ${parsed.error.message}` };
}
const { path: filePath, offset, limit } = parsed.data;
const cwd = getProjectRoot();
const resolvedPath = await validatePath(filePath);
if (!existsSync(resolvedPath)) {
return { success: false, error: `File not found: ${filePath}` };
}
const stat = statSync(resolvedPath);
if (!stat.isFile()) {
return { success: false, error: `Not a file: ${filePath}. Use read_many_files for directories.` };
}
const { content, truncated, totalLines, lineRange } = readFileContent(resolvedPath, {
maxLength: MAX_CONTENT_LENGTH,
offset,
limit,
});
const entry: FileEntry = {
path: relative(cwd, resolvedPath) || filePath,
size: stat.size,
content,
truncated,
totalLines,
lineRange,
};
let message = `Read 1 file`;
if (lineRange) {
message += ` [lines ${lineRange.start}-${lineRange.end} of ${totalLines}]`;
}
return {
success: true,
result: {
files: [entry],
totalFiles: 1,
message,
},
};
}