@j0hanz/filesystem-mcp 1.7.2 → 1.8.0
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/dist/lib/file-operations/common.d.ts +42 -0
- package/dist/lib/file-operations/common.js +87 -0
- package/dist/lib/file-operations/file-info.js +13 -19
- package/dist/lib/file-operations/glob-engine.d.ts +1 -6
- package/dist/lib/file-operations/glob-engine.js +0 -9
- package/dist/lib/file-operations/glob-helpers.d.ts +18 -0
- package/dist/lib/file-operations/glob-helpers.js +23 -0
- package/dist/lib/file-operations/list-directory.js +22 -46
- package/dist/lib/file-operations/read-multiple-files.js +11 -19
- package/dist/lib/file-operations/search-content.d.ts +1 -8
- package/dist/lib/file-operations/search-content.js +126 -204
- package/dist/lib/file-operations/search-files.js +48 -94
- package/dist/lib/file-operations/search-matcher.d.ts +10 -0
- package/dist/lib/file-operations/search-matcher.js +72 -0
- package/dist/lib/file-operations/search-worker.js +3 -1
- package/dist/lib/file-operations/tree.d.ts +2 -2
- package/dist/lib/file-operations/tree.js +26 -42
- package/dist/lib/fs-helpers.d.ts +1 -0
- package/dist/lib/fs-helpers.js +9 -0
- package/dist/lib/option-utils.d.ts +3 -0
- package/dist/lib/option-utils.js +15 -0
- package/dist/lib/path-validation.d.ts +1 -0
- package/dist/lib/path-validation.js +7 -0
- package/dist/lib/progress-reporting.d.ts +11 -0
- package/dist/lib/progress-reporting.js +13 -0
- package/dist/prompts.js +3 -3
- package/dist/resources/generated-instructions.js +14 -14
- package/dist/resources/tool-catalog.js +9 -9
- package/dist/resources/tool-info.js +5 -5
- package/dist/resources/workflows.js +17 -17
- package/dist/schemas.js +21 -90
- package/dist/server/bootstrap.js +2 -2
- package/dist/tools/apply-patch.js +3 -0
- package/dist/tools/calculate-hash.js +12 -12
- package/dist/tools/delete-file.js +5 -2
- package/dist/tools/list-directory.js +3 -21
- package/dist/tools/read-multiple.js +11 -21
- package/dist/tools/replace-in-files.js +10 -11
- package/dist/tools/search-content.js +2 -2
- package/dist/tools/search-files.js +3 -21
- package/dist/tools/shared.d.ts +19 -0
- package/dist/tools/shared.js +64 -18
- package/dist/tools/stat-many.js +11 -22
- package/package.json +6 -2
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const MatcherOptionsSchema: z.ZodObject<{
|
|
3
|
+
caseSensitive: z.ZodBoolean;
|
|
4
|
+
wholeWord: z.ZodBoolean;
|
|
5
|
+
isLiteral: z.ZodBoolean;
|
|
6
|
+
}, z.core.$strict>;
|
|
7
|
+
export type MatcherOptions = z.infer<typeof MatcherOptionsSchema>;
|
|
8
|
+
export type Matcher = (line: string) => number;
|
|
9
|
+
export declare function validatePattern(pattern: string, options: MatcherOptions): void;
|
|
10
|
+
export declare function buildMatcher(pattern: string, options: MatcherOptions): Matcher;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import RE2 from 're2';
|
|
3
|
+
import safeRegex from 'safe-regex2';
|
|
4
|
+
export const MatcherOptionsSchema = z.strictObject({
|
|
5
|
+
caseSensitive: z.boolean(),
|
|
6
|
+
wholeWord: z.boolean(),
|
|
7
|
+
isLiteral: z.boolean(),
|
|
8
|
+
});
|
|
9
|
+
function countRegexLineMatches(regex, line) {
|
|
10
|
+
regex.lastIndex = 0;
|
|
11
|
+
let count = 0;
|
|
12
|
+
while (regex.exec(line) !== null) {
|
|
13
|
+
count++;
|
|
14
|
+
if (regex.lastIndex === 0)
|
|
15
|
+
regex.lastIndex++;
|
|
16
|
+
}
|
|
17
|
+
return count;
|
|
18
|
+
}
|
|
19
|
+
function escapeLiteral(pattern) {
|
|
20
|
+
return pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
21
|
+
}
|
|
22
|
+
function buildRegexPattern(pattern, options) {
|
|
23
|
+
const escaped = options.isLiteral ? escapeLiteral(pattern) : pattern;
|
|
24
|
+
return options.wholeWord ? `\\b${escaped}\\b` : escaped;
|
|
25
|
+
}
|
|
26
|
+
export function validatePattern(pattern, options) {
|
|
27
|
+
if (options.isLiteral && pattern.length === 0)
|
|
28
|
+
return;
|
|
29
|
+
if (options.isLiteral && !options.wholeWord)
|
|
30
|
+
return;
|
|
31
|
+
const final = buildRegexPattern(pattern, options);
|
|
32
|
+
if (!safeRegex(final)) {
|
|
33
|
+
throw new Error(`Potentially unsafe regular expression (ReDoS risk): ${pattern}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function buildLiteralMatcher(pattern, options) {
|
|
37
|
+
if (!options.caseSensitive) {
|
|
38
|
+
const final = escapeLiteral(pattern);
|
|
39
|
+
const regex = new RegExp(final, 'gi');
|
|
40
|
+
return (line) => countRegexLineMatches(regex, line);
|
|
41
|
+
}
|
|
42
|
+
// Fast path for case-sensitive literal
|
|
43
|
+
const needle = pattern;
|
|
44
|
+
if (needle.length === 0)
|
|
45
|
+
return () => 0;
|
|
46
|
+
return (line) => {
|
|
47
|
+
if (line.length === 0)
|
|
48
|
+
return 0;
|
|
49
|
+
let count = 0;
|
|
50
|
+
let pos = line.indexOf(needle);
|
|
51
|
+
while (pos !== -1) {
|
|
52
|
+
count++;
|
|
53
|
+
pos = line.indexOf(needle, pos + needle.length);
|
|
54
|
+
}
|
|
55
|
+
return count;
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function buildRegexMatcher(final, caseSensitive) {
|
|
59
|
+
const regex = new RE2(final, caseSensitive ? 'g' : 'gi');
|
|
60
|
+
return (line) => countRegexLineMatches(regex, line);
|
|
61
|
+
}
|
|
62
|
+
export function buildMatcher(pattern, options) {
|
|
63
|
+
if (options.isLiteral && pattern.length === 0)
|
|
64
|
+
return () => 0;
|
|
65
|
+
if (options.isLiteral && !options.wholeWord) {
|
|
66
|
+
// fast path for simple literal search
|
|
67
|
+
return buildLiteralMatcher(pattern, options);
|
|
68
|
+
}
|
|
69
|
+
const final = buildRegexPattern(pattern, options);
|
|
70
|
+
validatePattern(pattern, options); // Re-validate to be safe
|
|
71
|
+
return buildRegexMatcher(final, options.caseSensitive);
|
|
72
|
+
}
|
|
@@ -2,7 +2,9 @@ import { parentPort, threadId, workerData } from 'node:worker_threads';
|
|
|
2
2
|
import { formatUnknownErrorMessage } from '../errors.js';
|
|
3
3
|
import { isProbablyBinary } from '../fs-helpers.js';
|
|
4
4
|
import { startPerfMeasure } from '../observability.js';
|
|
5
|
-
import {
|
|
5
|
+
import { scanFileInWorker } from './search-content.js';
|
|
6
|
+
import { buildMatcher } from './search-matcher.js';
|
|
7
|
+
import {} from './search-matcher.js';
|
|
6
8
|
const matcherCache = new Map();
|
|
7
9
|
const MAX_MATCHER_CACHE_SIZE = 100;
|
|
8
10
|
function getMatcherCacheKey(pattern, options) {
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import * as path from 'node:path';
|
|
2
2
|
import { DEFAULT_EXCLUDE_PATTERNS, DEFAULT_SEARCH_TIMEOUT_MS, } from '../constants.js';
|
|
3
|
-
import {
|
|
3
|
+
import { withTimedAbortSignal } from '../fs-helpers.js';
|
|
4
4
|
import { toPosixPath } from '../path-format.js';
|
|
5
5
|
import { isSensitivePath } from '../path-policy.js';
|
|
6
6
|
import { isPathWithinDirectories, normalizePath, validateExistingDirectory, validateExistingPathDetailed, } from '../path-validation.js';
|
|
7
|
+
import { isEntryAccessibleByType, resolveEntryType, resolveStopReason, } from './common.js';
|
|
7
8
|
import { isIgnoredByGitignore, loadRootGitignore } from './gitignore.js';
|
|
8
|
-
import { globEntries
|
|
9
|
+
import { globEntries } from './glob-engine.js';
|
|
9
10
|
function toSafeNonNegativeInt(value, fallback) {
|
|
10
11
|
if (typeof value !== 'number' || !Number.isFinite(value))
|
|
11
12
|
return fallback;
|
|
@@ -90,36 +91,11 @@ function getTreeTypeRank(type) {
|
|
|
90
91
|
return 1;
|
|
91
92
|
return 2;
|
|
92
93
|
}
|
|
93
|
-
function
|
|
94
|
-
if (signal.aborted) {
|
|
95
|
-
return 'aborted';
|
|
96
|
-
}
|
|
97
|
-
if (totalEntries >= maxEntries) {
|
|
98
|
-
return 'maxEntries';
|
|
99
|
-
}
|
|
100
|
-
return undefined;
|
|
101
|
-
}
|
|
102
|
-
async function resolveTreeEntry(entry, root, rootDirectories, gitignoreMatcher, signal) {
|
|
94
|
+
async function resolveTreeEntry(entry, root, rootDirectories, gitignoreMatcher, signal, accessDeps) {
|
|
103
95
|
const type = resolveEntryType(entry.dirent);
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
return null;
|
|
108
|
-
}
|
|
109
|
-
if (isSensitivePath(entry.path, normalized)) {
|
|
110
|
-
return null;
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
else {
|
|
114
|
-
try {
|
|
115
|
-
const validated = await validateExistingPathDetailed(entry.path, signal);
|
|
116
|
-
if (isSensitivePath(validated.requestedPath, validated.resolvedPath)) {
|
|
117
|
-
return null;
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
catch {
|
|
121
|
-
return null;
|
|
122
|
-
}
|
|
96
|
+
const isAccessible = await isEntryAccessibleByType(entry.path, type, rootDirectories, signal, accessDeps);
|
|
97
|
+
if (!isAccessible) {
|
|
98
|
+
return null;
|
|
123
99
|
}
|
|
124
100
|
if (gitignoreMatcher &&
|
|
125
101
|
isIgnoredByGitignore(gitignoreMatcher, root, entry.path, {
|
|
@@ -218,11 +194,16 @@ export function formatTreeAscii(tree) {
|
|
|
218
194
|
}
|
|
219
195
|
export async function treeDirectory(dirPath, options = {}) {
|
|
220
196
|
const normalized = normalizeOptions(options);
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
197
|
+
return withTimedAbortSignal(options.signal, normalized.timeoutMs, async (signal) => {
|
|
198
|
+
const root = await validateExistingDirectory(dirPath, signal);
|
|
199
|
+
const rootNormalized = normalizePath(root);
|
|
200
|
+
const rootDirectories = [rootNormalized];
|
|
201
|
+
const accessDeps = {
|
|
202
|
+
normalizePath,
|
|
203
|
+
isPathWithinDirectories,
|
|
204
|
+
isSensitivePath,
|
|
205
|
+
validateSymlinkPath: validateExistingPathDetailed,
|
|
206
|
+
};
|
|
226
207
|
const excludePatterns = normalized.includeIgnored
|
|
227
208
|
? []
|
|
228
209
|
: DEFAULT_EXCLUDE_PATTERNS;
|
|
@@ -253,12 +234,18 @@ export async function treeDirectory(dirPath, options = {}) {
|
|
|
253
234
|
suppressErrors: true,
|
|
254
235
|
});
|
|
255
236
|
for await (const entry of stream) {
|
|
256
|
-
const stopReason =
|
|
237
|
+
const stopReason = resolveStopReason({
|
|
238
|
+
signal,
|
|
239
|
+
current: totalEntries,
|
|
240
|
+
max: normalized.maxEntries,
|
|
241
|
+
abortedReason: 'aborted',
|
|
242
|
+
maxReason: 'maxEntries',
|
|
243
|
+
});
|
|
257
244
|
if (stopReason) {
|
|
258
245
|
truncated = true;
|
|
259
246
|
break;
|
|
260
247
|
}
|
|
261
|
-
const resolved = await resolveTreeEntry(entry, root, rootDirectories, gitignoreMatcher, signal);
|
|
248
|
+
const resolved = await resolveTreeEntry(entry, root, rootDirectories, gitignoreMatcher, signal, accessDeps);
|
|
262
249
|
if (!resolved) {
|
|
263
250
|
continue;
|
|
264
251
|
}
|
|
@@ -274,8 +261,5 @@ export async function treeDirectory(dirPath, options = {}) {
|
|
|
274
261
|
truncated,
|
|
275
262
|
totalEntries,
|
|
276
263
|
};
|
|
277
|
-
}
|
|
278
|
-
finally {
|
|
279
|
-
cleanup();
|
|
280
|
-
}
|
|
264
|
+
});
|
|
281
265
|
}
|
package/dist/lib/fs-helpers.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ export declare function createTimedAbortSignal(baseSignal: AbortSignal | undefin
|
|
|
7
7
|
signal: AbortSignal;
|
|
8
8
|
cleanup: () => void;
|
|
9
9
|
};
|
|
10
|
+
export declare function withTimedAbortSignal<T>(baseSignal: AbortSignal | undefined, timeoutMs: number | undefined, run: (signal: AbortSignal) => Promise<T>): Promise<T>;
|
|
10
11
|
interface ParallelResult<R> {
|
|
11
12
|
results: R[];
|
|
12
13
|
errors: {
|
package/dist/lib/fs-helpers.js
CHANGED
|
@@ -110,6 +110,15 @@ export function createTimedAbortSignal(baseSignal, timeoutMs) {
|
|
|
110
110
|
}
|
|
111
111
|
return createNoopSignal();
|
|
112
112
|
}
|
|
113
|
+
export async function withTimedAbortSignal(baseSignal, timeoutMs, run) {
|
|
114
|
+
const { signal, cleanup } = createTimedAbortSignal(baseSignal, timeoutMs);
|
|
115
|
+
try {
|
|
116
|
+
return await run(signal);
|
|
117
|
+
}
|
|
118
|
+
finally {
|
|
119
|
+
cleanup();
|
|
120
|
+
}
|
|
121
|
+
}
|
|
113
122
|
function createNoopSignal() {
|
|
114
123
|
return { signal: SHARED_NOOP_SIGNAL, cleanup: () => { } };
|
|
115
124
|
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export declare function mergeOptions<T extends object>(defaults: T, overrides: Partial<T>): T;
|
|
2
|
+
export declare function omitOptionKeys<T extends object, K extends keyof T>(input: T, keys: readonly K[]): Omit<T, K>;
|
|
3
|
+
export declare function setIfDefined<T extends object, K extends keyof T>(target: T, key: K, value: T[K] | undefined): void;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export function mergeOptions(defaults, overrides) {
|
|
2
|
+
return { ...defaults, ...overrides };
|
|
3
|
+
}
|
|
4
|
+
export function omitOptionKeys(input, keys) {
|
|
5
|
+
const output = { ...input };
|
|
6
|
+
for (const key of keys) {
|
|
7
|
+
Reflect.deleteProperty(output, key);
|
|
8
|
+
}
|
|
9
|
+
return output;
|
|
10
|
+
}
|
|
11
|
+
export function setIfDefined(target, key, value) {
|
|
12
|
+
if (value !== undefined) {
|
|
13
|
+
target[key] = value;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -8,6 +8,7 @@ import { McpError } from './errors.js';
|
|
|
8
8
|
*/
|
|
9
9
|
export declare function normalizePath(p: string): string;
|
|
10
10
|
export declare function getAllowedDirectories(): string[];
|
|
11
|
+
export declare function isAllowedDirectoryRoot(normalizedPath: string): boolean;
|
|
11
12
|
export declare function isPathWithinDirectories(normalizedPath: string, allowedDirs: readonly string[]): boolean;
|
|
12
13
|
export declare function setAllowedDirectoriesResolved(dirs: readonly string[], signal?: AbortSignal): Promise<void>;
|
|
13
14
|
export declare function getReservedDeviceNameForPath(requestedPath: string): string | undefined;
|
|
@@ -124,6 +124,13 @@ function setAllowedDirectoriesState(primary, expanded) {
|
|
|
124
124
|
export function getAllowedDirectories() {
|
|
125
125
|
return [...allowedDirectoriesExpanded];
|
|
126
126
|
}
|
|
127
|
+
export function isAllowedDirectoryRoot(normalizedPath) {
|
|
128
|
+
for (const dir of allowedDirectoriesExpanded) {
|
|
129
|
+
if (isSamePath(normalizedPath, dir))
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
127
134
|
function getAllowedDirectoriesForRelativeResolution() {
|
|
128
135
|
return allowedDirectoriesPrimary.length > 0
|
|
129
136
|
? allowedDirectoriesPrimary
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface ProgressPayload {
|
|
2
|
+
current: number;
|
|
3
|
+
total?: number;
|
|
4
|
+
}
|
|
5
|
+
export type ProgressCallback = ((progress: ProgressPayload) => void) | undefined;
|
|
6
|
+
export interface PeriodicProgressOptions {
|
|
7
|
+
total?: number;
|
|
8
|
+
throttleModulo?: number;
|
|
9
|
+
force?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export declare function reportPeriodicProgress(onProgress: ProgressCallback, current: number, options?: PeriodicProgressOptions): void;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export function reportPeriodicProgress(onProgress, current, options = {}) {
|
|
2
|
+
if (!onProgress || current === 0)
|
|
3
|
+
return;
|
|
4
|
+
const throttleModulo = options.throttleModulo ?? 1;
|
|
5
|
+
const force = options.force ?? false;
|
|
6
|
+
if (!force && throttleModulo > 1 && current % throttleModulo !== 0) {
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
onProgress({
|
|
10
|
+
current,
|
|
11
|
+
...(options.total !== undefined ? { total: options.total } : {}),
|
|
12
|
+
});
|
|
13
|
+
}
|
package/dist/prompts.js
CHANGED
|
@@ -2,7 +2,7 @@ import { z } from 'zod';
|
|
|
2
2
|
import { withDefaultIcons } from './tools/shared.js';
|
|
3
3
|
const HELP_PROMPT_NAME = 'get-help';
|
|
4
4
|
const HELP_PROMPT_TITLE = 'Get Help';
|
|
5
|
-
const HELP_PROMPT_DESCRIPTION = '
|
|
5
|
+
const HELP_PROMPT_DESCRIPTION = 'Return filesystem-mcp usage instructions.';
|
|
6
6
|
function filterInstructionsByTopic(instructions, topic) {
|
|
7
7
|
const normalized = topic.trim().toLowerCase();
|
|
8
8
|
if (!normalized)
|
|
@@ -16,7 +16,7 @@ function filterInstructionsByTopic(instructions, topic) {
|
|
|
16
16
|
.map((sec) => sec.split('\n')[0]?.replace(/^##\s*/u, '') ?? '')
|
|
17
17
|
.filter(Boolean)
|
|
18
18
|
.join(', ');
|
|
19
|
-
return `Section '${topic}' not found. Available
|
|
19
|
+
return `Section '${topic}' not found. Available: ${available}\n\n${instructions}`;
|
|
20
20
|
}
|
|
21
21
|
export function registerGetHelpPrompt(server, instructions, iconInfo) {
|
|
22
22
|
const baseConfig = withDefaultIcons({ title: HELP_PROMPT_TITLE, description: HELP_PROMPT_DESCRIPTION }, iconInfo);
|
|
@@ -26,7 +26,7 @@ export function registerGetHelpPrompt(server, instructions, iconInfo) {
|
|
|
26
26
|
topic: z
|
|
27
27
|
.string()
|
|
28
28
|
.optional()
|
|
29
|
-
.describe('
|
|
29
|
+
.describe('Optional section heading prefix (example: "error handling"). Omit to return full instructions.'),
|
|
30
30
|
},
|
|
31
31
|
}, ({ topic }) => {
|
|
32
32
|
const text = topic
|
|
@@ -2,7 +2,7 @@ import { buildToolCatalogDetailsOnly } from './tool-catalog.js';
|
|
|
2
2
|
import { buildCoreContextPack, getSharedConstraints, getToolContracts, } from './tool-info.js';
|
|
3
3
|
import { buildWorkflowGuide } from './workflows.js';
|
|
4
4
|
const INSTRUCTIONS_HEADER = `<role>
|
|
5
|
-
|
|
5
|
+
Filesystem agent for local paths only. Operate inside allowed roots. Discover before action. Never guess paths.
|
|
6
6
|
</role>
|
|
7
7
|
|
|
8
8
|
<tools_overview>
|
|
@@ -15,16 +15,16 @@ Expert filesystem agent. Operate ONLY within allowed roots. Always discover befo
|
|
|
15
15
|
</tools_overview>
|
|
16
16
|
|
|
17
17
|
<resources>
|
|
18
|
-
- \`internal://instructions\`: Full
|
|
19
|
-
- \`internal://tool-catalog\`: Tool routing and
|
|
20
|
-
- \`internal://workflows\`: Standard
|
|
21
|
-
- \`internal://tool-info/{name}\`: Per-tool
|
|
22
|
-
- \`filesystem-mcp://result/{id}\`:
|
|
23
|
-
- \`filesystem-mcp://metrics\`:
|
|
18
|
+
- \`internal://instructions\`: Full usage reference.
|
|
19
|
+
- \`internal://tool-catalog\`: Tool routing and data-flow rules.
|
|
20
|
+
- \`internal://workflows\`: Standard execution sequences.
|
|
21
|
+
- \`internal://tool-info/{name}\`: Per-tool nuances and gotchas (example: \`internal://tool-info/read\`).
|
|
22
|
+
- \`filesystem-mcp://result/{id}\`: Cached large output. If \`resourceUri\` is returned, call \`resources/read\` immediately.
|
|
23
|
+
- \`filesystem-mcp://metrics\`: Per-tool runtime metrics.
|
|
24
24
|
</resources>
|
|
25
25
|
|
|
26
26
|
<task_protocol>
|
|
27
|
-
Async execution:
|
|
27
|
+
Async execution: pass \`_meta.progressToken\` in \`tools/call\`, poll \`tasks/get\`, then call \`tasks/result\`.
|
|
28
28
|
Task-capable: \`find\`, \`tree\`, \`read\`, \`read_many\`, \`stat_many\`, \`grep\`, \`mkdir\`, \`write\`, \`mv\`, \`rm\`, \`calculate_hash\`, \`apply_patch\`, \`search_and_replace\`.
|
|
29
29
|
</task_protocol>
|
|
30
30
|
`;
|
|
@@ -35,19 +35,19 @@ ${getSharedConstraints()
|
|
|
35
35
|
</constraints>
|
|
36
36
|
|
|
37
37
|
<error_handling>
|
|
38
|
-
- \`E_ACCESS_DENIED\`
|
|
39
|
-
- \`E_NOT_FOUND\`
|
|
40
|
-
- \`E_TOO_LARGE\`
|
|
41
|
-
- \`E_TIMEOUT\`
|
|
38
|
+
- \`E_ACCESS_DENIED\` => call \`roots\`, then use an allowed path.
|
|
39
|
+
- \`E_NOT_FOUND\` => call \`ls\` or \`find\`, then verify spelling.
|
|
40
|
+
- \`E_TOO_LARGE\` => use \`head\`, line ranges, or \`read_many\`.
|
|
41
|
+
- \`E_TIMEOUT\` => reduce scope or result limits.
|
|
42
42
|
</error_handling>
|
|
43
43
|
`;
|
|
44
44
|
function formatToolSection(tool) {
|
|
45
45
|
const parts = [`### ${tool.name}\n${tool.description}`];
|
|
46
46
|
if (tool.nuances && tool.nuances.length > 0) {
|
|
47
|
-
parts.push(...tool.nuances.map((n) =>
|
|
47
|
+
parts.push(...tool.nuances.map((n) => `- Nuance: ${n}`));
|
|
48
48
|
}
|
|
49
49
|
if (tool.gotchas && tool.gotchas.length > 0) {
|
|
50
|
-
parts.push(...tool.gotchas.map((g) =>
|
|
50
|
+
parts.push(...tool.gotchas.map((g) => `- Gotcha: ${g}`));
|
|
51
51
|
}
|
|
52
52
|
return parts.join('\n');
|
|
53
53
|
}
|
|
@@ -9,21 +9,21 @@ diff_files (patch text) -> apply_patch.patch
|
|
|
9
9
|
|
|
10
10
|
## Search Strategy
|
|
11
11
|
|
|
12
|
-
- Use \`find\` for glob
|
|
13
|
-
- Use \`grep\` for
|
|
14
|
-
- Use \`search_and_replace\`
|
|
12
|
+
- Use \`find\` for glob file discovery.
|
|
13
|
+
- Use \`grep\` for text search.
|
|
14
|
+
- Use \`search_and_replace\` only for replacement, never discovery.
|
|
15
15
|
|
|
16
16
|
## Write Strategy
|
|
17
17
|
|
|
18
|
-
- Use \`edit\` for precise,
|
|
19
|
-
- Use \`write\` to create
|
|
20
|
-
- Use \`search_and_replace\` for bulk
|
|
18
|
+
- Use \`edit\` for precise, first-occurrence replacements in existing files.
|
|
19
|
+
- Use \`write\` to create files or overwrite full contents.
|
|
20
|
+
- Use \`search_and_replace\` for bulk multi-file replacements.
|
|
21
21
|
|
|
22
22
|
## Patch Management
|
|
23
23
|
|
|
24
|
-
-
|
|
25
|
-
-
|
|
26
|
-
- \`apply_patch\`
|
|
24
|
+
- Generate patches with \`diff_files\` first.
|
|
25
|
+
- Run \`apply_patch\` with \`dryRun: true\` before writing.
|
|
26
|
+
- \`apply_patch\` accepts unified diffs only.
|
|
27
27
|
</tool_selection_guide>
|
|
28
28
|
`;
|
|
29
29
|
export function buildToolCatalog() {
|
|
@@ -37,10 +37,10 @@ export function buildCoreContextPack() {
|
|
|
37
37
|
}
|
|
38
38
|
export function getSharedConstraints() {
|
|
39
39
|
return [
|
|
40
|
-
'
|
|
41
|
-
'Sensitive
|
|
42
|
-
`
|
|
43
|
-
'If a response includes `resourceUri`, call `resources/read` immediately
|
|
40
|
+
'Use allowed roots only (provided by CLI negotiation).',
|
|
41
|
+
'Sensitive paths are denylisted by default.',
|
|
42
|
+
`Limits are enforced: max file size ${Math.floor(MAX_TEXT_FILE_SIZE / 1024 / 1024)}MB; search caps ${MAX_SEARCH_RESULTS} files and ${DEFAULT_SEARCH_CONTENT_RESULTS} lines.`,
|
|
43
|
+
'If a response includes `resourceUri`, call `resources/read` immediately; cached results expire on process restart.',
|
|
44
44
|
];
|
|
45
45
|
}
|
|
46
46
|
export function buildToolInfo(name) {
|
|
@@ -49,7 +49,7 @@ export function buildToolInfo(name) {
|
|
|
49
49
|
return undefined;
|
|
50
50
|
const lines = [`## ${entry.name}`, '', entry.description];
|
|
51
51
|
if (entry.annotations && entry.annotations.length > 0) {
|
|
52
|
-
lines.push('', `**
|
|
52
|
+
lines.push('', `**Hints:** ${entry.annotations.join(', ')}`);
|
|
53
53
|
}
|
|
54
54
|
if (entry.nuances && entry.nuances.length > 0) {
|
|
55
55
|
lines.push('', '**Nuances:**');
|
|
@@ -1,33 +1,33 @@
|
|
|
1
1
|
export function buildWorkflowGuide() {
|
|
2
2
|
return `<workflows>
|
|
3
3
|
### A: EXPLORE
|
|
4
|
-
Use when:
|
|
5
|
-
1. \`roots\` (
|
|
6
|
-
2. \`ls\` (
|
|
7
|
-
3. \`stat\`
|
|
8
|
-
4. \`read\`
|
|
9
|
-
> **Strict:**
|
|
4
|
+
Use when: you need directory layout or file content.
|
|
5
|
+
1. \`roots\` (list allowed paths).
|
|
6
|
+
2. \`ls\` (flat view) or \`tree\` (recursive view).
|
|
7
|
+
3. \`stat\` or \`stat_many\` (type and size checks).
|
|
8
|
+
4. \`read\` or \`read_many\` (read content).
|
|
9
|
+
> **Strict:** Resolve paths first. Never guess.
|
|
10
10
|
|
|
11
11
|
### B: SEARCH
|
|
12
|
-
Use when:
|
|
12
|
+
Use when: you need files by pattern or content.
|
|
13
13
|
1. \`find\` (glob candidates).
|
|
14
|
-
2. \`grep\` (content
|
|
15
|
-
3. \`read\` (verify context).
|
|
16
|
-
> **Strict:**
|
|
14
|
+
2. \`grep\` (content matches).
|
|
15
|
+
3. \`read\` (verify matched context).
|
|
16
|
+
> **Strict:** Do content search with \`grep\`, not \`find\`.
|
|
17
17
|
|
|
18
18
|
### C: EDIT
|
|
19
|
-
Use when:
|
|
20
|
-
1. \`edit\` (
|
|
21
|
-
2. \`search_and_replace\` (bulk
|
|
22
|
-
3. \`mv\`
|
|
23
|
-
4. \`mkdir\` (
|
|
19
|
+
Use when: you need to modify files or layout.
|
|
20
|
+
1. \`edit\` (targeted string replacement).
|
|
21
|
+
2. \`search_and_replace\` (bulk replacements).
|
|
22
|
+
3. \`mv\` or \`rm\` (layout changes).
|
|
23
|
+
4. \`mkdir\` (directory creation).
|
|
24
24
|
> **Strict:** Confirm destructive ops (\`write\`, \`mv\`, \`rm\`, bulk replace).
|
|
25
25
|
|
|
26
26
|
### D: PATCH
|
|
27
|
-
Use when: applying
|
|
27
|
+
Use when: applying unified diffs from \`diff_files\`.
|
|
28
28
|
1. \`diff_files\` (generate).
|
|
29
29
|
2. \`apply_patch\` (dryRun: true).
|
|
30
30
|
3. \`apply_patch\` (dryRun: false).
|
|
31
|
-
> **Tip:**
|
|
31
|
+
> **Tip:** Feed \`diff_files\` output directly to \`apply_patch\`.
|
|
32
32
|
</workflows>`;
|
|
33
33
|
}
|