@aigne/doc-smith 0.8.12-beta.6 → 0.8.12-beta.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.aigne/doc-smith/config.yaml +1 -1
- package/.aigne/doc-smith/history.yaml +37 -0
- package/.aigne/doc-smith/media-description.yaml +91 -0
- package/.aigne/doc-smith/preferences.yml +12 -0
- package/.aigne/doc-smith/upload-cache.yaml +36 -69
- package/.release-please-manifest.json +1 -1
- package/CHANGELOG.md +24 -0
- package/agents/clear/choose-contents.mjs +14 -1
- package/agents/clear/clear-media-description.mjs +129 -0
- package/agents/clear/index.yaml +3 -1
- package/agents/evaluate/code-snippet.mjs +28 -24
- package/agents/evaluate/document-structure.yaml +0 -4
- package/agents/evaluate/document.yaml +1 -5
- package/agents/generate/index.yaml +1 -0
- package/agents/generate/update-document-structure.yaml +9 -3
- package/agents/history/view.mjs +5 -2
- package/agents/init/index.mjs +10 -0
- package/agents/media/batch-generate-media-description.yaml +44 -0
- package/agents/media/generate-media-description.yaml +47 -0
- package/agents/media/load-media-description.mjs +238 -0
- package/agents/update/generate-document.yaml +10 -4
- package/agents/update/index.yaml +1 -0
- package/agents/update/update-document-detail.yaml +9 -3
- package/agents/update/user-review-document.mjs +2 -1
- package/agents/utils/load-sources.mjs +103 -53
- package/aigne.yaml +6 -0
- package/assets/report-template/report.html +34 -34
- package/docs/configuration-initial-setup.md +74 -55
- package/docs/configuration.ja.md +59 -86
- package/docs/configuration.md +59 -86
- package/docs/configuration.zh-TW.md +59 -86
- package/docs/configuration.zh.md +59 -86
- package/docs/getting-started.ja.md +43 -24
- package/docs/getting-started.md +29 -10
- package/docs/getting-started.zh-TW.md +42 -23
- package/docs/getting-started.zh.md +39 -20
- package/docs/guides-cleaning-up.ja.md +16 -15
- package/docs/guides-cleaning-up.md +19 -17
- package/docs/guides-cleaning-up.zh-TW.md +16 -15
- package/docs/guides-cleaning-up.zh.md +12 -11
- package/docs/guides-evaluating-documents.md +70 -29
- package/docs/guides-generating-documentation.ja.md +34 -32
- package/docs/guides-generating-documentation.md +59 -119
- package/docs/guides-generating-documentation.zh-TW.md +34 -32
- package/docs/guides-generating-documentation.zh.md +30 -28
- package/docs/guides-interactive-chat.md +34 -26
- package/docs/guides-managing-history.ja.md +17 -20
- package/docs/guides-managing-history.md +19 -17
- package/docs/guides-managing-history.zh-TW.md +18 -21
- package/docs/guides-managing-history.zh.md +13 -16
- package/docs/guides-publishing-your-docs.md +40 -35
- package/docs/guides-translating-documentation.ja.md +17 -17
- package/docs/guides-translating-documentation.md +39 -34
- package/docs/guides-translating-documentation.zh-TW.md +21 -21
- package/docs/guides-translating-documentation.zh.md +18 -18
- package/docs/guides-updating-documentation.ja.md +35 -35
- package/docs/guides-updating-documentation.md +11 -9
- package/docs/guides-updating-documentation.zh-TW.md +27 -27
- package/docs/guides-updating-documentation.zh.md +26 -26
- package/docs/overview.ja.md +13 -13
- package/docs/overview.md +2 -2
- package/docs/overview.zh-TW.md +19 -19
- package/docs/overview.zh.md +16 -16
- package/docs/release-notes.md +60 -27
- package/package.json +2 -1
- package/prompts/common/afs/afs-tools-usage.md +5 -0
- package/prompts/common/afs/use-afs-instruction.md +1 -0
- package/prompts/detail/generate/system-prompt.md +0 -13
- package/prompts/detail/generate/user-prompt.md +7 -0
- package/prompts/detail/update/system-prompt.md +1 -2
- package/prompts/detail/update/user-prompt.md +7 -0
- package/prompts/evaluate/document-structure.md +6 -7
- package/prompts/evaluate/document.md +16 -25
- package/prompts/media/media-description/system-prompt.md +35 -0
- package/prompts/media/media-description/user-prompt.md +8 -0
- package/prompts/structure/generate/system-prompt.md +0 -19
- package/prompts/structure/generate/user-prompt.md +22 -1
- package/prompts/structure/update/system-prompt.md +0 -17
- package/prompts/structure/update/user-prompt.md +24 -0
- package/tests/agents/history/view.test.mjs +97 -0
- package/tests/utils/history-utils.test.mjs +125 -97
- package/utils/constants/index.mjs +0 -107
- package/utils/file-utils.mjs +42 -1
- package/utils/history-utils.mjs +3 -3
- package/agents/update/fs-tools/glob.mjs +0 -184
- package/agents/update/fs-tools/grep.mjs +0 -317
- package/agents/update/fs-tools/read-file.mjs +0 -309
- package/media.md +0 -19
- package/tests/agents/update/fs-tools/glob.test.mjs +0 -438
- package/tests/agents/update/fs-tools/grep.test.mjs +0 -279
- package/tests/agents/update/fs-tools/read-file.test.mjs +0 -549
|
@@ -1,184 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
|
-
import fsPromises from "node:fs/promises";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Sorts files by modification time (newest first), then alphabetically
|
|
7
|
-
*/
|
|
8
|
-
function sortFilesByModTime(files, basePath) {
|
|
9
|
-
return files
|
|
10
|
-
.map((file) => {
|
|
11
|
-
const fullPath = path.isAbsolute(file) ? file : path.resolve(basePath, file);
|
|
12
|
-
try {
|
|
13
|
-
const stats = fs.statSync(fullPath);
|
|
14
|
-
return {
|
|
15
|
-
path: file,
|
|
16
|
-
fullPath,
|
|
17
|
-
mtime: stats.mtimeMs || 0,
|
|
18
|
-
};
|
|
19
|
-
} catch {
|
|
20
|
-
return {
|
|
21
|
-
path: file,
|
|
22
|
-
fullPath,
|
|
23
|
-
mtime: 0,
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
})
|
|
27
|
-
.sort((a, b) => {
|
|
28
|
-
// Sort by modification time (newest first), then alphabetically
|
|
29
|
-
if (b.mtime !== a.mtime) {
|
|
30
|
-
return b.mtime - a.mtime;
|
|
31
|
-
}
|
|
32
|
-
return a.path.localeCompare(b.path);
|
|
33
|
-
})
|
|
34
|
-
.map((item) => item.path);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Filters out common ignore patterns
|
|
39
|
-
*/
|
|
40
|
-
function shouldIgnoreFile(filePath) {
|
|
41
|
-
const ignorePatterns = [
|
|
42
|
-
"node_modules",
|
|
43
|
-
".git",
|
|
44
|
-
".DS_Store",
|
|
45
|
-
".vscode",
|
|
46
|
-
".idea",
|
|
47
|
-
"dist",
|
|
48
|
-
"build",
|
|
49
|
-
"*.log",
|
|
50
|
-
"coverage",
|
|
51
|
-
".nyc_output",
|
|
52
|
-
".cache",
|
|
53
|
-
];
|
|
54
|
-
|
|
55
|
-
const normalizedPath = filePath.replace(/\\/g, "/");
|
|
56
|
-
|
|
57
|
-
return ignorePatterns.some((pattern) => {
|
|
58
|
-
if (pattern.includes("*")) {
|
|
59
|
-
// Simple wildcard matching for patterns like "*.log"
|
|
60
|
-
const regex = new RegExp(pattern.replace(/\*/g, ".*"));
|
|
61
|
-
return regex.test(path.basename(normalizedPath));
|
|
62
|
-
} else {
|
|
63
|
-
// Directory or file name matching
|
|
64
|
-
return (
|
|
65
|
-
normalizedPath.includes(`/${pattern}/`) ||
|
|
66
|
-
normalizedPath.endsWith(`/${pattern}`) ||
|
|
67
|
-
normalizedPath.startsWith(`${pattern}/`) ||
|
|
68
|
-
normalizedPath === pattern
|
|
69
|
-
);
|
|
70
|
-
}
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
export default async function glob({
|
|
75
|
-
pattern,
|
|
76
|
-
case_sensitive = false,
|
|
77
|
-
respect_git_ignore = true,
|
|
78
|
-
limit = 100,
|
|
79
|
-
}) {
|
|
80
|
-
let result = [];
|
|
81
|
-
let error = null;
|
|
82
|
-
const searchDir = process.cwd();
|
|
83
|
-
|
|
84
|
-
try {
|
|
85
|
-
// Validate required parameters
|
|
86
|
-
if (!pattern || typeof pattern !== "string" || pattern.trim() === "") {
|
|
87
|
-
throw new Error("Pattern parameter is required and cannot be empty");
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// Use Node.js built-in glob
|
|
91
|
-
const globOptions = {
|
|
92
|
-
cwd: searchDir,
|
|
93
|
-
nodir: true, // Only return files, not directories
|
|
94
|
-
dot: true, // Include hidden files
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
// Note: Node.js fs.glob doesn't support case_sensitive option directly
|
|
98
|
-
// We'll handle case sensitivity in post-processing if needed
|
|
99
|
-
const iter = fsPromises.glob(pattern, globOptions);
|
|
100
|
-
const files = [];
|
|
101
|
-
|
|
102
|
-
for await (const file of iter) {
|
|
103
|
-
if (files.length >= limit) break;
|
|
104
|
-
|
|
105
|
-
// Apply ignore filters
|
|
106
|
-
if (shouldIgnoreFile(file)) {
|
|
107
|
-
continue;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// Handle case sensitivity if needed
|
|
111
|
-
if (case_sensitive === false) {
|
|
112
|
-
// Node.js glob is case-sensitive by default on most systems
|
|
113
|
-
// For case-insensitive matching, we rely on the pattern itself
|
|
114
|
-
// or the filesystem behavior
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
files.push(file);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// Sort files by modification time (newest first)
|
|
121
|
-
const sortedFiles = sortFilesByModTime(files, searchDir);
|
|
122
|
-
|
|
123
|
-
// Build result message
|
|
124
|
-
let message;
|
|
125
|
-
if (sortedFiles.length === 0) {
|
|
126
|
-
message = `No files found matching pattern "${pattern}"`;
|
|
127
|
-
} else {
|
|
128
|
-
const fileCount = sortedFiles.length;
|
|
129
|
-
const truncated = files.length >= limit;
|
|
130
|
-
message = `Found ${fileCount}${truncated ? "+" : ""} file(s) matching "${pattern}"`;
|
|
131
|
-
message += ", sorted by modification time (newest first):";
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
result = {
|
|
135
|
-
files: sortedFiles,
|
|
136
|
-
count: sortedFiles.length,
|
|
137
|
-
message,
|
|
138
|
-
truncated: files.length >= limit,
|
|
139
|
-
};
|
|
140
|
-
} catch (err) {
|
|
141
|
-
error = err;
|
|
142
|
-
result = {
|
|
143
|
-
files: [],
|
|
144
|
-
count: 0,
|
|
145
|
-
message: `Error during glob search: ${err.message}`,
|
|
146
|
-
truncated: false,
|
|
147
|
-
};
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
return {
|
|
151
|
-
command: "glob",
|
|
152
|
-
arguments: {
|
|
153
|
-
pattern,
|
|
154
|
-
case_sensitive,
|
|
155
|
-
respect_git_ignore,
|
|
156
|
-
limit,
|
|
157
|
-
},
|
|
158
|
-
result,
|
|
159
|
-
error: error && { message: error.message },
|
|
160
|
-
};
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
glob.input_schema = {
|
|
164
|
-
type: "object",
|
|
165
|
-
properties: {
|
|
166
|
-
pattern: {
|
|
167
|
-
type: "string",
|
|
168
|
-
description: 'The glob pattern to match files against (e.g., "**/*.js", "src/**/*.ts")',
|
|
169
|
-
},
|
|
170
|
-
case_sensitive: {
|
|
171
|
-
type: "boolean",
|
|
172
|
-
description: "Optional: Whether the search should be case-sensitive (defaults to false)",
|
|
173
|
-
},
|
|
174
|
-
respect_git_ignore: {
|
|
175
|
-
type: "boolean",
|
|
176
|
-
description: "Optional: Whether to respect .gitignore patterns (defaults to true)",
|
|
177
|
-
},
|
|
178
|
-
limit: {
|
|
179
|
-
type: "number",
|
|
180
|
-
description: "Optional: Maximum number of files to return (defaults to 100)",
|
|
181
|
-
},
|
|
182
|
-
},
|
|
183
|
-
required: ["pattern"],
|
|
184
|
-
};
|
|
@@ -1,317 +0,0 @@
|
|
|
1
|
-
import childProcess from "node:child_process";
|
|
2
|
-
import fs from "node:fs";
|
|
3
|
-
import fsPromises from "node:fs/promises";
|
|
4
|
-
import { EOL } from "node:os";
|
|
5
|
-
import path from "node:path";
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Checks if a command is available in the system's PATH
|
|
9
|
-
*/
|
|
10
|
-
function isCommandAvailable(command) {
|
|
11
|
-
return new Promise((resolve) => {
|
|
12
|
-
const checkCommand = process.platform === "win32" ? "where" : "command";
|
|
13
|
-
const checkArgs = process.platform === "win32" ? [command] : ["-v", command];
|
|
14
|
-
|
|
15
|
-
try {
|
|
16
|
-
const child = childProcess.spawn(checkCommand, checkArgs, {
|
|
17
|
-
stdio: "ignore",
|
|
18
|
-
shell: process.platform === "win32",
|
|
19
|
-
});
|
|
20
|
-
child.on("close", (code) => resolve(code === 0));
|
|
21
|
-
child.on("error", () => resolve(false));
|
|
22
|
-
} catch {
|
|
23
|
-
resolve(false);
|
|
24
|
-
}
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Checks if a directory is a git repository
|
|
30
|
-
*/
|
|
31
|
-
function isGitRepository(dir) {
|
|
32
|
-
try {
|
|
33
|
-
const gitDir = path.join(dir, ".git");
|
|
34
|
-
return fs.existsSync(gitDir);
|
|
35
|
-
} catch {
|
|
36
|
-
return false;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Parses grep output in format: filePath:lineNumber:lineContent
|
|
42
|
-
*/
|
|
43
|
-
function parseGrepOutput(output, basePath) {
|
|
44
|
-
const results = [];
|
|
45
|
-
if (!output) return results;
|
|
46
|
-
|
|
47
|
-
const lines = output.split(EOL);
|
|
48
|
-
|
|
49
|
-
for (const line of lines) {
|
|
50
|
-
if (!line.trim()) continue;
|
|
51
|
-
|
|
52
|
-
const firstColonIndex = line.indexOf(":");
|
|
53
|
-
if (firstColonIndex === -1) continue;
|
|
54
|
-
|
|
55
|
-
const secondColonIndex = line.indexOf(":", firstColonIndex + 1);
|
|
56
|
-
if (secondColonIndex === -1) continue;
|
|
57
|
-
|
|
58
|
-
const filePathRaw = line.substring(0, firstColonIndex);
|
|
59
|
-
const lineNumberStr = line.substring(firstColonIndex + 1, secondColonIndex);
|
|
60
|
-
const lineContent = line.substring(secondColonIndex + 1);
|
|
61
|
-
|
|
62
|
-
const lineNumber = parseInt(lineNumberStr, 10);
|
|
63
|
-
if (!Number.isNaN(lineNumber)) {
|
|
64
|
-
const relativePath = path.relative(basePath, path.resolve(basePath, filePathRaw));
|
|
65
|
-
results.push({
|
|
66
|
-
filePath: relativePath || path.basename(filePathRaw),
|
|
67
|
-
lineNumber,
|
|
68
|
-
line: lineContent,
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
return results;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Performs grep search using multiple strategies
|
|
77
|
-
*/
|
|
78
|
-
async function performGrepSearch({ pattern, include }) {
|
|
79
|
-
const absolutePath = path.resolve(".");
|
|
80
|
-
|
|
81
|
-
// Strategy 1: git grep
|
|
82
|
-
const isGit = isGitRepository(absolutePath);
|
|
83
|
-
const gitAvailable = isGit && (await isCommandAvailable("git"));
|
|
84
|
-
|
|
85
|
-
if (gitAvailable) {
|
|
86
|
-
try {
|
|
87
|
-
const gitArgs = ["grep", "--untracked", "-n", "-E", "--ignore-case", pattern];
|
|
88
|
-
if (include) {
|
|
89
|
-
gitArgs.push("--", include);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const output = await new Promise((resolve, reject) => {
|
|
93
|
-
const child = childProcess.spawn("git", gitArgs, {
|
|
94
|
-
cwd: absolutePath,
|
|
95
|
-
windowsHide: true,
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
let stdout = "";
|
|
99
|
-
let stderr = "";
|
|
100
|
-
|
|
101
|
-
child.stdout.on("data", (chunk) => {
|
|
102
|
-
stdout += chunk;
|
|
103
|
-
});
|
|
104
|
-
child.stderr.on("data", (chunk) => {
|
|
105
|
-
stderr += chunk;
|
|
106
|
-
});
|
|
107
|
-
child.on("error", (err) => reject(new Error(`Failed to start git grep: ${err.message}`)));
|
|
108
|
-
child.on("close", (code) => {
|
|
109
|
-
if (code === 0) resolve(stdout);
|
|
110
|
-
else if (code === 1)
|
|
111
|
-
resolve(""); // No matches
|
|
112
|
-
else reject(new Error(`git grep exited with code ${code}: ${stderr}`));
|
|
113
|
-
});
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
return parseGrepOutput(output, absolutePath);
|
|
117
|
-
} catch (gitError) {
|
|
118
|
-
console.debug(`git grep failed: ${gitError.message}. Falling back...`);
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// Strategy 2: System grep
|
|
123
|
-
const grepAvailable = await isCommandAvailable("grep");
|
|
124
|
-
if (grepAvailable) {
|
|
125
|
-
try {
|
|
126
|
-
const grepArgs = [
|
|
127
|
-
"-r",
|
|
128
|
-
"-n",
|
|
129
|
-
"-H",
|
|
130
|
-
"-E",
|
|
131
|
-
"-i",
|
|
132
|
-
"--exclude-dir=node_modules",
|
|
133
|
-
"--exclude-dir=.git",
|
|
134
|
-
];
|
|
135
|
-
if (include) {
|
|
136
|
-
grepArgs.push(`--include=${include}`);
|
|
137
|
-
}
|
|
138
|
-
grepArgs.push(pattern, ".");
|
|
139
|
-
|
|
140
|
-
const output = await new Promise((resolve, reject) => {
|
|
141
|
-
const child = childProcess.spawn("grep", grepArgs, {
|
|
142
|
-
cwd: absolutePath,
|
|
143
|
-
windowsHide: true,
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
let stdout = "";
|
|
147
|
-
let stderr = "";
|
|
148
|
-
|
|
149
|
-
child.stdout.on("data", (chunk) => {
|
|
150
|
-
stdout += chunk;
|
|
151
|
-
});
|
|
152
|
-
child.stderr.on("data", (chunk) => {
|
|
153
|
-
const stderrStr = chunk.toString();
|
|
154
|
-
// Suppress common harmless stderr messages
|
|
155
|
-
if (
|
|
156
|
-
!stderrStr.includes("Permission denied") &&
|
|
157
|
-
!/grep:.*: Is a directory/i.test(stderrStr)
|
|
158
|
-
) {
|
|
159
|
-
stderr += chunk;
|
|
160
|
-
}
|
|
161
|
-
});
|
|
162
|
-
child.on("error", (err) =>
|
|
163
|
-
reject(new Error(`Failed to start system grep: ${err.message}`)),
|
|
164
|
-
);
|
|
165
|
-
child.on("close", (code) => {
|
|
166
|
-
if (code === 0) resolve(stdout);
|
|
167
|
-
else if (code === 1)
|
|
168
|
-
resolve(""); // No matches
|
|
169
|
-
else {
|
|
170
|
-
if (stderr.trim()) reject(new Error(`System grep exited with code ${code}: ${stderr}`));
|
|
171
|
-
else resolve(""); // Exit code > 1 but no stderr, likely just suppressed errors
|
|
172
|
-
}
|
|
173
|
-
});
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
return parseGrepOutput(output, absolutePath);
|
|
177
|
-
} catch (grepError) {
|
|
178
|
-
console.debug(`System grep failed: ${grepError.message}. Falling back...`);
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
// Strategy 3: JavaScript fallback
|
|
183
|
-
console.debug("Falling back to JavaScript grep implementation.");
|
|
184
|
-
const matches = [];
|
|
185
|
-
|
|
186
|
-
// Escape regex special characters in a string
|
|
187
|
-
function escapeRegExp(str) {
|
|
188
|
-
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
try {
|
|
192
|
-
const regex = new RegExp(pattern, "i");
|
|
193
|
-
|
|
194
|
-
async function searchDirectory(dir) {
|
|
195
|
-
const entries = await fsPromises.readdir(dir, { withFileTypes: true });
|
|
196
|
-
|
|
197
|
-
for (const entry of entries) {
|
|
198
|
-
if (entry.name.startsWith(".") && entry.name !== ".") continue;
|
|
199
|
-
if (entry.name === "node_modules") continue;
|
|
200
|
-
|
|
201
|
-
const fullPath = path.join(dir, entry.name);
|
|
202
|
-
|
|
203
|
-
if (entry.isDirectory()) {
|
|
204
|
-
await searchDirectory(fullPath);
|
|
205
|
-
} else if (entry.isFile()) {
|
|
206
|
-
// Apply include filter if specified
|
|
207
|
-
if (include) {
|
|
208
|
-
let patternStr = escapeRegExp(include);
|
|
209
|
-
patternStr = patternStr.replace(/\\\*/g, ".*").replace(/\\\?/g, ".");
|
|
210
|
-
const includePattern = `^${patternStr}$`;
|
|
211
|
-
if (!new RegExp(includePattern).test(entry.name)) {
|
|
212
|
-
continue;
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
try {
|
|
217
|
-
const content = await fsPromises.readFile(fullPath, "utf8");
|
|
218
|
-
const lines = content.split(/\r?\n/);
|
|
219
|
-
|
|
220
|
-
lines.forEach((line, index) => {
|
|
221
|
-
if (regex.test(line)) {
|
|
222
|
-
matches.push({
|
|
223
|
-
filePath: path.relative(absolutePath, fullPath),
|
|
224
|
-
lineNumber: index + 1,
|
|
225
|
-
line,
|
|
226
|
-
});
|
|
227
|
-
}
|
|
228
|
-
});
|
|
229
|
-
} catch (_readError) {
|
|
230
|
-
// Ignore read errors (binary files, permissions, etc.)
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
await searchDirectory(absolutePath);
|
|
237
|
-
return matches;
|
|
238
|
-
} catch (error) {
|
|
239
|
-
throw new Error(`JavaScript fallback failed: ${error.message}`);
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
export default async function grep({ pattern, include }) {
|
|
244
|
-
let result = "";
|
|
245
|
-
let error = null;
|
|
246
|
-
|
|
247
|
-
try {
|
|
248
|
-
// Validate pattern parameter (allow empty string but not undefined/null)
|
|
249
|
-
if (pattern === undefined || pattern === null) {
|
|
250
|
-
throw new Error("Pattern parameter is required");
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
// Validate regex pattern
|
|
254
|
-
try {
|
|
255
|
-
new RegExp(pattern);
|
|
256
|
-
} catch (regexError) {
|
|
257
|
-
throw new Error(`Invalid regular expression pattern: ${pattern}. ${regexError.message}`);
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
const matches = await performGrepSearch({ pattern, include });
|
|
261
|
-
|
|
262
|
-
if (matches.length === 0) {
|
|
263
|
-
result = `No matches found for pattern "${pattern}"${include ? ` (filter: "${include}")` : ""}.`;
|
|
264
|
-
} else {
|
|
265
|
-
// Group matches by file
|
|
266
|
-
const matchesByFile = matches.reduce((acc, match) => {
|
|
267
|
-
if (!acc[match.filePath]) {
|
|
268
|
-
acc[match.filePath] = [];
|
|
269
|
-
}
|
|
270
|
-
acc[match.filePath].push(match);
|
|
271
|
-
acc[match.filePath].sort((a, b) => a.lineNumber - b.lineNumber);
|
|
272
|
-
return acc;
|
|
273
|
-
}, {});
|
|
274
|
-
|
|
275
|
-
const matchCount = matches.length;
|
|
276
|
-
const matchTerm = matchCount === 1 ? "match" : "matches";
|
|
277
|
-
|
|
278
|
-
result = `Found ${matchCount} ${matchTerm} for pattern "${pattern}"${include ? ` (filter: "${include}")` : ""}:\n---\n`;
|
|
279
|
-
|
|
280
|
-
for (const filePath in matchesByFile) {
|
|
281
|
-
result += `File: ${filePath}\n`;
|
|
282
|
-
matchesByFile[filePath].forEach((match) => {
|
|
283
|
-
const trimmedLine = match.line.trim();
|
|
284
|
-
result += `L${match.lineNumber}: ${trimmedLine}\n`;
|
|
285
|
-
});
|
|
286
|
-
result += "---\n";
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
result = result.trim();
|
|
290
|
-
}
|
|
291
|
-
} catch (e) {
|
|
292
|
-
error = e;
|
|
293
|
-
result = `Error during grep search: ${e.message}`;
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
return {
|
|
297
|
-
command: "grep",
|
|
298
|
-
arguments: { pattern, include },
|
|
299
|
-
result,
|
|
300
|
-
error: error && { message: error.message },
|
|
301
|
-
};
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
grep.input_schema = {
|
|
305
|
-
type: "object",
|
|
306
|
-
properties: {
|
|
307
|
-
pattern: {
|
|
308
|
-
type: "string",
|
|
309
|
-
description: "The regular expression pattern to search for in file contents",
|
|
310
|
-
},
|
|
311
|
-
include: {
|
|
312
|
-
type: "string",
|
|
313
|
-
description: 'Optional: File pattern to include in search (e.g. "*.js", "*.{ts,tsx}")',
|
|
314
|
-
},
|
|
315
|
-
},
|
|
316
|
-
required: ["pattern"],
|
|
317
|
-
};
|