@aigne/doc-smith 0.8.11-beta.5 → 0.8.11-beta.7
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 +3 -1
- package/.aigne/doc-smith/output/structure-plan.json +2 -2
- package/.aigne/doc-smith/preferences.yml +28 -20
- package/.aigne/doc-smith/upload-cache.yaml +702 -0
- package/.release-please-manifest.json +1 -1
- package/CHANGELOG.md +22 -2
- package/README.md +2 -2
- package/agents/clear/choose-contents.mjs +2 -2
- package/agents/clear/clear-document-structure.mjs +8 -8
- package/agents/clear/index.yaml +1 -1
- package/agents/evaluate/document-structure.yaml +1 -1
- package/agents/generate/check-document-structure.yaml +3 -3
- package/agents/generate/check-need-generate-structure.mjs +8 -8
- package/agents/generate/document-structure-tools/add-document.mjs +38 -13
- package/agents/generate/document-structure-tools/delete-document.mjs +37 -14
- package/agents/generate/document-structure-tools/move-document.mjs +46 -19
- package/agents/generate/document-structure-tools/update-document.mjs +39 -12
- package/agents/generate/generate-structure.yaml +1 -1
- package/agents/generate/update-document-structure.yaml +3 -9
- package/agents/generate/user-review-document-structure.mjs +15 -14
- package/agents/translate/translate-document.yaml +1 -9
- package/agents/update/batch-generate-document.yaml +1 -1
- package/agents/update/check-document.mjs +2 -2
- package/agents/update/check-update-is-single.mjs +2 -1
- package/agents/update/document-tools/update-document-content.mjs +24 -14
- package/agents/update/fs-tools/glob.mjs +184 -0
- package/agents/update/fs-tools/grep.mjs +317 -0
- package/agents/update/fs-tools/read-file.mjs +307 -0
- package/agents/update/generate-document.yaml +4 -7
- package/agents/update/update-document-detail.yaml +6 -10
- package/agents/update/user-review-document.mjs +13 -13
- package/agents/utils/check-feedback-refiner.mjs +1 -1
- package/agents/utils/choose-docs.mjs +1 -1
- package/agents/utils/load-document-all-content.mjs +3 -3
- package/agents/utils/load-sources.mjs +1 -1
- package/agents/utils/save-docs.mjs +7 -28
- package/aigne.yaml +2 -2
- package/assets/screenshots/doc-complete-setup.png +0 -0
- package/assets/screenshots/doc-generate-docs.png +0 -0
- package/assets/screenshots/doc-generate.png +0 -0
- package/assets/screenshots/doc-generated-successfully.png +0 -0
- package/assets/screenshots/doc-publish.png +0 -0
- package/assets/screenshots/doc-regenerate.png +0 -0
- package/assets/screenshots/doc-translate-langs.png +0 -0
- package/assets/screenshots/doc-translate.png +0 -0
- package/assets/screenshots/doc-update.png +0 -0
- package/docs/_sidebar.md +1 -1
- package/docs/advanced-how-it-works.ja.md +31 -31
- package/docs/advanced-how-it-works.md +10 -10
- package/docs/advanced-how-it-works.zh-TW.md +24 -24
- package/docs/advanced-how-it-works.zh.md +20 -20
- package/docs/advanced-quality-assurance.ja.md +57 -61
- package/docs/advanced-quality-assurance.md +57 -61
- package/docs/advanced-quality-assurance.zh-TW.md +57 -61
- package/docs/advanced-quality-assurance.zh.md +57 -61
- package/docs/advanced.ja.md +8 -4
- package/docs/advanced.md +7 -3
- package/docs/advanced.zh-TW.md +9 -5
- package/docs/advanced.zh.md +9 -5
- package/docs/changelog.ja.md +206 -29
- package/docs/changelog.md +177 -0
- package/docs/changelog.zh-TW.md +229 -52
- package/docs/changelog.zh.md +204 -27
- package/docs/cli-reference.ja.md +181 -80
- package/docs/cli-reference.md +168 -67
- package/docs/cli-reference.zh-TW.md +177 -76
- package/docs/cli-reference.zh.md +172 -71
- package/docs/configuration-interactive-setup.ja.md +45 -42
- package/docs/configuration-interactive-setup.md +9 -6
- package/docs/configuration-interactive-setup.zh-TW.md +26 -23
- package/docs/configuration-interactive-setup.zh.md +25 -22
- package/docs/configuration-language-support.ja.md +33 -63
- package/docs/configuration-language-support.md +32 -62
- package/docs/configuration-language-support.zh-TW.md +35 -65
- package/docs/configuration-language-support.zh.md +32 -62
- package/docs/configuration-llm-setup.ja.md +25 -23
- package/docs/configuration-llm-setup.md +20 -18
- package/docs/configuration-llm-setup.zh-TW.md +21 -19
- package/docs/configuration-llm-setup.zh.md +20 -18
- package/docs/configuration-preferences.ja.md +67 -52
- package/docs/configuration-preferences.md +55 -40
- package/docs/configuration-preferences.zh-TW.md +69 -54
- package/docs/configuration-preferences.zh.md +68 -53
- package/docs/configuration.ja.md +98 -58
- package/docs/configuration.md +42 -2
- package/docs/configuration.zh-TW.md +86 -46
- package/docs/configuration.zh.md +76 -36
- package/docs/features-generate-documentation.ja.md +49 -55
- package/docs/features-generate-documentation.md +49 -55
- package/docs/features-generate-documentation.zh-TW.md +54 -60
- package/docs/features-generate-documentation.zh.md +48 -54
- package/docs/features-publish-your-docs.ja.md +68 -45
- package/docs/features-publish-your-docs.md +65 -42
- package/docs/features-publish-your-docs.zh-TW.md +74 -51
- package/docs/features-publish-your-docs.zh.md +67 -44
- package/docs/features-translate-documentation.ja.md +46 -35
- package/docs/features-translate-documentation.md +39 -28
- package/docs/features-translate-documentation.zh-TW.md +45 -34
- package/docs/features-translate-documentation.zh.md +39 -28
- package/docs/features-update-and-refine.ja.md +75 -71
- package/docs/features-update-and-refine.md +67 -63
- package/docs/features-update-and-refine.zh-TW.md +72 -67
- package/docs/features-update-and-refine.zh.md +71 -67
- package/docs/features.ja.md +29 -19
- package/docs/features.md +25 -15
- package/docs/features.zh-TW.md +28 -18
- package/docs/features.zh.md +31 -21
- package/docs/getting-started.ja.md +43 -46
- package/docs/getting-started.md +36 -39
- package/docs/getting-started.zh-TW.md +41 -44
- package/docs/getting-started.zh.md +39 -42
- package/docs/overview.ja.md +63 -11
- package/docs/overview.md +62 -10
- package/docs/overview.zh-TW.md +67 -15
- package/docs/overview.zh.md +62 -10
- package/docs-mcp/analyze-docs-relevance.yaml +6 -6
- package/docs-mcp/docs-search.yaml +1 -1
- package/media.md +9 -9
- package/package.json +2 -2
- package/prompts/common/document-structure/conflict-resolution-guidance.md +3 -3
- package/prompts/common/document-structure/document-structure-rules.md +2 -2
- package/prompts/detail/custom/custom-components.md +304 -188
- package/prompts/detail/document-rules.md +5 -5
- package/prompts/detail/generate-document.md +21 -8
- package/prompts/detail/update-document.md +8 -12
- package/prompts/evaluate/document-structure.md +6 -6
- package/prompts/structure/check-document-structure.md +10 -10
- package/prompts/structure/document-rules.md +2 -2
- package/prompts/structure/generate-structure-system.md +3 -3
- package/prompts/structure/structure-example.md +1 -1
- package/prompts/structure/structure-getting-started.md +1 -1
- package/prompts/structure/update-document-structure.md +18 -14
- package/prompts/utils/feedback-refiner.md +3 -3
- package/tests/agents/clear/choose-contents.test.mjs +1 -1
- package/tests/agents/clear/clear-document-structure.test.mjs +36 -30
- package/tests/agents/evaluate/generate-report.test.mjs +1 -1
- package/tests/agents/generate/check-need-generate-structure.test.mjs +1 -1
- package/tests/agents/generate/document-structure-tools/add-document.test.mjs +2 -2
- package/tests/agents/generate/document-structure-tools/delete-document.test.mjs +4 -4
- package/tests/agents/generate/document-structure-tools/move-document.test.mjs +12 -12
- package/tests/agents/generate/document-structure-tools/update-document.test.mjs +3 -3
- package/tests/agents/generate/user-review-document-structure.test.mjs +36 -13
- package/tests/agents/update/check-document.test.mjs +1 -1
- package/tests/agents/update/document-tools/update-document-content.test.mjs +115 -112
- package/tests/agents/update/fs-tools/glob.test.mjs +438 -0
- package/tests/agents/update/fs-tools/grep.test.mjs +279 -0
- package/tests/agents/update/fs-tools/read-file.test.mjs +553 -0
- package/tests/agents/update/user-review-document.test.mjs +48 -27
- package/tests/agents/utils/format-document-structure.test.mjs +5 -5
- package/tests/agents/utils/load-sources.test.mjs +4 -4
- package/tests/agents/utils/save-docs.test.mjs +1 -1
- package/tests/utils/conflict-detector.test.mjs +1 -1
- package/tests/utils/docs-finder-utils.test.mjs +8 -8
- package/types/document-schema.mjs +5 -6
- package/types/document-structure-schema.mjs +25 -13
- package/utils/conflict-detector.mjs +1 -1
- package/utils/constants/index.mjs +4 -4
- package/utils/docs-finder-utils.mjs +11 -11
- package/utils/markdown-checker.mjs +1 -1
|
@@ -0,0 +1,317 @@
|
|
|
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
|
+
};
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import fsPromises from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Detects if a file is likely binary by checking for null bytes
|
|
7
|
+
*/
|
|
8
|
+
function isBinaryFile(buffer) {
|
|
9
|
+
// Check first 8KB for null bytes
|
|
10
|
+
const checkLength = Math.min(buffer.length, 8192);
|
|
11
|
+
for (let i = 0; i < checkLength; i++) {
|
|
12
|
+
if (buffer[i] === 0) {
|
|
13
|
+
return true;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Gets MIME type based on file extension
|
|
21
|
+
*/
|
|
22
|
+
function getMimeType(filePath) {
|
|
23
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
24
|
+
const mimeTypes = {
|
|
25
|
+
// Text files
|
|
26
|
+
".txt": "text/plain",
|
|
27
|
+
".md": "text/markdown",
|
|
28
|
+
".json": "application/json",
|
|
29
|
+
".js": "text/javascript",
|
|
30
|
+
".mjs": "text/javascript",
|
|
31
|
+
".ts": "text/typescript",
|
|
32
|
+
".tsx": "text/typescript",
|
|
33
|
+
".jsx": "text/javascript",
|
|
34
|
+
".py": "text/x-python",
|
|
35
|
+
".java": "text/x-java",
|
|
36
|
+
".cpp": "text/x-c",
|
|
37
|
+
".c": "text/x-c",
|
|
38
|
+
".h": "text/x-c",
|
|
39
|
+
".css": "text/css",
|
|
40
|
+
".html": "text/html",
|
|
41
|
+
".xml": "text/xml",
|
|
42
|
+
".yaml": "text/yaml",
|
|
43
|
+
".yml": "text/yaml",
|
|
44
|
+
".toml": "text/plain",
|
|
45
|
+
".ini": "text/plain",
|
|
46
|
+
".cfg": "text/plain",
|
|
47
|
+
".conf": "text/plain",
|
|
48
|
+
".sh": "text/x-shellscript",
|
|
49
|
+
".bash": "text/x-shellscript",
|
|
50
|
+
".zsh": "text/x-shellscript",
|
|
51
|
+
".fish": "text/x-shellscript",
|
|
52
|
+
|
|
53
|
+
// Image files
|
|
54
|
+
".jpg": "image/jpeg",
|
|
55
|
+
".jpeg": "image/jpeg",
|
|
56
|
+
".png": "image/png",
|
|
57
|
+
".gif": "image/gif",
|
|
58
|
+
".webp": "image/webp",
|
|
59
|
+
".svg": "image/svg+xml",
|
|
60
|
+
".bmp": "image/bmp",
|
|
61
|
+
".ico": "image/x-icon",
|
|
62
|
+
|
|
63
|
+
// Document files
|
|
64
|
+
".pdf": "application/pdf",
|
|
65
|
+
".doc": "application/msword",
|
|
66
|
+
".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
67
|
+
|
|
68
|
+
// Other
|
|
69
|
+
".zip": "application/zip",
|
|
70
|
+
".tar": "application/x-tar",
|
|
71
|
+
".gz": "application/gzip",
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
return mimeTypes[ext] || "application/octet-stream";
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Validates file path and checks accessibility
|
|
79
|
+
*/
|
|
80
|
+
function validateFilePath(filePath) {
|
|
81
|
+
if (!filePath || typeof filePath !== "string" || filePath.trim() === "") {
|
|
82
|
+
return "File path parameter is required and cannot be empty";
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (!path.isAbsolute(filePath)) {
|
|
86
|
+
return `File path must be absolute, but was relative: ${filePath}`;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
const stats = fs.statSync(filePath);
|
|
91
|
+
if (stats.isDirectory()) {
|
|
92
|
+
return `Path is a directory, not a file: ${filePath}`;
|
|
93
|
+
}
|
|
94
|
+
return null;
|
|
95
|
+
} catch (error) {
|
|
96
|
+
if (error.code === "ENOENT") {
|
|
97
|
+
return `File does not exist: ${filePath}`;
|
|
98
|
+
} else if (error.code === "EACCES") {
|
|
99
|
+
return `Permission denied: ${filePath}`;
|
|
100
|
+
}
|
|
101
|
+
return `Cannot access file: ${filePath} (${error.message})`;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Reads file content with proper handling for different file types
|
|
107
|
+
*/
|
|
108
|
+
async function readFileContent(filePath, offset = 0, limit = null, encoding = "utf8") {
|
|
109
|
+
const stats = await fsPromises.stat(filePath);
|
|
110
|
+
const fileSize = stats.size;
|
|
111
|
+
const mimeType = getMimeType(filePath);
|
|
112
|
+
|
|
113
|
+
// Handle binary files differently
|
|
114
|
+
if (
|
|
115
|
+
mimeType.startsWith("image/") ||
|
|
116
|
+
mimeType === "application/pdf" ||
|
|
117
|
+
fileSize > 10 * 1024 * 1024
|
|
118
|
+
) {
|
|
119
|
+
return {
|
|
120
|
+
content: `[Binary file: ${path.basename(filePath)}]`,
|
|
121
|
+
mimeType,
|
|
122
|
+
fileSize,
|
|
123
|
+
isBinary: true,
|
|
124
|
+
encoding: null,
|
|
125
|
+
lineCount: null,
|
|
126
|
+
isTruncated: false,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Read file as buffer first to check if it's binary
|
|
131
|
+
const buffer = await fsPromises.readFile(filePath);
|
|
132
|
+
|
|
133
|
+
if (isBinaryFile(buffer)) {
|
|
134
|
+
return {
|
|
135
|
+
content: `[Binary file: ${path.basename(filePath)}]`,
|
|
136
|
+
mimeType,
|
|
137
|
+
fileSize,
|
|
138
|
+
isBinary: true,
|
|
139
|
+
encoding: null,
|
|
140
|
+
lineCount: null,
|
|
141
|
+
isTruncated: false,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Convert buffer to text
|
|
146
|
+
let content = buffer.toString(encoding);
|
|
147
|
+
const lines = content.split(/\r?\n/);
|
|
148
|
+
const totalLines = lines.length;
|
|
149
|
+
let isTruncated = false;
|
|
150
|
+
let linesShown = [1, totalLines];
|
|
151
|
+
|
|
152
|
+
// Handle offset and limit for text files
|
|
153
|
+
if (offset > 0 || limit !== null) {
|
|
154
|
+
const startLine = Math.max(0, offset);
|
|
155
|
+
const endLine = limit !== null ? Math.min(startLine + limit, totalLines) : totalLines;
|
|
156
|
+
|
|
157
|
+
if (startLine >= totalLines) {
|
|
158
|
+
return {
|
|
159
|
+
content: "",
|
|
160
|
+
mimeType,
|
|
161
|
+
fileSize,
|
|
162
|
+
isBinary: false,
|
|
163
|
+
encoding,
|
|
164
|
+
lineCount: totalLines,
|
|
165
|
+
isTruncated: false,
|
|
166
|
+
linesShown: [startLine + 1, startLine + 1],
|
|
167
|
+
message: `Offset ${offset} is beyond file end (file has ${totalLines} lines)`,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const selectedLines = lines.slice(startLine, endLine);
|
|
172
|
+
content = selectedLines.join("\n");
|
|
173
|
+
isTruncated = startLine > 0 || endLine < totalLines;
|
|
174
|
+
linesShown = [startLine + 1, endLine];
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Auto-truncate very large files (more than 10000 lines)
|
|
178
|
+
const maxLines = 10000;
|
|
179
|
+
if (limit === null && totalLines > maxLines) {
|
|
180
|
+
const selectedLines = lines.slice(0, maxLines);
|
|
181
|
+
content = selectedLines.join("\n");
|
|
182
|
+
isTruncated = true;
|
|
183
|
+
linesShown = [1, maxLines];
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return {
|
|
187
|
+
content,
|
|
188
|
+
mimeType,
|
|
189
|
+
fileSize,
|
|
190
|
+
isBinary: false,
|
|
191
|
+
encoding,
|
|
192
|
+
lineCount: totalLines,
|
|
193
|
+
isTruncated,
|
|
194
|
+
linesShown,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export default async function readFile({ path: filePath, offset, limit, encoding = "utf8" }) {
|
|
199
|
+
let result = {};
|
|
200
|
+
let error = null;
|
|
201
|
+
|
|
202
|
+
try {
|
|
203
|
+
// Validate file path first (this checks if it's absolute)
|
|
204
|
+
const pathError = validateFilePath(filePath);
|
|
205
|
+
if (pathError) {
|
|
206
|
+
throw new Error(pathError);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Now we know filePath is valid and absolute
|
|
210
|
+
|
|
211
|
+
// Validate numeric parameters
|
|
212
|
+
if (offset !== undefined && (typeof offset !== "number" || offset < 0)) {
|
|
213
|
+
throw new Error("Offset must be a non-negative number");
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (limit !== undefined && (typeof limit !== "number" || limit <= 0)) {
|
|
217
|
+
throw new Error("Limit must be a positive number");
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Read file content
|
|
221
|
+
const fileResult = await readFileContent(filePath, offset, limit, encoding);
|
|
222
|
+
|
|
223
|
+
// Build success result
|
|
224
|
+
result = {
|
|
225
|
+
content: fileResult.content,
|
|
226
|
+
metadata: {
|
|
227
|
+
path: filePath,
|
|
228
|
+
mimeType: fileResult.mimeType,
|
|
229
|
+
fileSize: fileResult.fileSize,
|
|
230
|
+
isBinary: fileResult.isBinary,
|
|
231
|
+
encoding: fileResult.encoding,
|
|
232
|
+
lineCount: fileResult.lineCount,
|
|
233
|
+
},
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
// Add truncation info if applicable
|
|
237
|
+
if (fileResult.isTruncated) {
|
|
238
|
+
result.truncated = {
|
|
239
|
+
isTruncated: true,
|
|
240
|
+
linesShown: fileResult.linesShown,
|
|
241
|
+
totalLines: fileResult.lineCount,
|
|
242
|
+
nextOffset: fileResult.linesShown[1],
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
// Add helpful message for truncated content
|
|
246
|
+
const [start, end] = fileResult.linesShown;
|
|
247
|
+
const nextOffset = end;
|
|
248
|
+
result.message =
|
|
249
|
+
`Content truncated. Showing lines ${start}-${end} of ${fileResult.lineCount} total lines. ` +
|
|
250
|
+
`To read more, use offset: ${nextOffset}.`;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Add message if provided
|
|
254
|
+
if (fileResult.message) {
|
|
255
|
+
result.message = fileResult.message;
|
|
256
|
+
}
|
|
257
|
+
} catch (err) {
|
|
258
|
+
error = err;
|
|
259
|
+
result = {
|
|
260
|
+
content: null,
|
|
261
|
+
metadata: {
|
|
262
|
+
path: filePath,
|
|
263
|
+
mimeType: null,
|
|
264
|
+
fileSize: null,
|
|
265
|
+
isBinary: null,
|
|
266
|
+
encoding: null,
|
|
267
|
+
lineCount: null,
|
|
268
|
+
},
|
|
269
|
+
message: `Error reading file: ${err.message}`,
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return {
|
|
274
|
+
command: "read_file",
|
|
275
|
+
arguments: {
|
|
276
|
+
path: filePath,
|
|
277
|
+
offset,
|
|
278
|
+
limit,
|
|
279
|
+
encoding,
|
|
280
|
+
},
|
|
281
|
+
result,
|
|
282
|
+
error: error && { message: error.message },
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
readFile.input_schema = {
|
|
287
|
+
type: "object",
|
|
288
|
+
properties: {
|
|
289
|
+
path: {
|
|
290
|
+
type: "string",
|
|
291
|
+
description: "The path to the file to read (for backwards compatibility)",
|
|
292
|
+
},
|
|
293
|
+
offset: {
|
|
294
|
+
type: "number",
|
|
295
|
+
description: "Optional: The 0-based line number to start reading from",
|
|
296
|
+
},
|
|
297
|
+
limit: {
|
|
298
|
+
type: "number",
|
|
299
|
+
description: "Optional: Maximum number of lines to read",
|
|
300
|
+
},
|
|
301
|
+
encoding: {
|
|
302
|
+
type: "string",
|
|
303
|
+
description: "Optional: File encoding (defaults to utf8)",
|
|
304
|
+
},
|
|
305
|
+
},
|
|
306
|
+
required: ["path"],
|
|
307
|
+
};
|
|
@@ -41,14 +41,11 @@ input_schema:
|
|
|
41
41
|
- rules
|
|
42
42
|
- datasources
|
|
43
43
|
- originalDocumentStructure
|
|
44
|
-
|
|
45
|
-
type: object
|
|
46
|
-
properties:
|
|
47
|
-
content:
|
|
48
|
-
type: string
|
|
49
|
-
required:
|
|
50
|
-
- content
|
|
44
|
+
output_key: content
|
|
51
45
|
skills:
|
|
46
|
+
- fs-tools/glob.mjs
|
|
47
|
+
- fs-tools/grep.mjs
|
|
48
|
+
- fs-tools/read-file.mjs
|
|
52
49
|
- type: team
|
|
53
50
|
task_render_mode: collapse
|
|
54
51
|
name: generateD2DiagramContent
|
|
@@ -39,14 +39,10 @@ input_schema:
|
|
|
39
39
|
required:
|
|
40
40
|
- originalContent
|
|
41
41
|
- feedback
|
|
42
|
-
|
|
43
|
-
type: object
|
|
44
|
-
properties:
|
|
45
|
-
updatedContent:
|
|
46
|
-
type: string
|
|
47
|
-
description: Final updated markdown content after applying modifications
|
|
48
|
-
operationSummary:
|
|
49
|
-
type: string
|
|
50
|
-
description: Summary of the operations performed on the document content
|
|
42
|
+
output_key: message
|
|
51
43
|
skills:
|
|
52
|
-
- ./document-tools/update-document-content.mjs
|
|
44
|
+
- ./document-tools/update-document-content.mjs
|
|
45
|
+
- ./fs-tools/glob.mjs
|
|
46
|
+
- ./fs-tools/grep.mjs
|
|
47
|
+
- ./fs-tools/read-file.mjs
|
|
48
|
+
task_render_mode: collapse
|