@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.
Files changed (44) hide show
  1. package/dist/lib/file-operations/common.d.ts +42 -0
  2. package/dist/lib/file-operations/common.js +87 -0
  3. package/dist/lib/file-operations/file-info.js +13 -19
  4. package/dist/lib/file-operations/glob-engine.d.ts +1 -6
  5. package/dist/lib/file-operations/glob-engine.js +0 -9
  6. package/dist/lib/file-operations/glob-helpers.d.ts +18 -0
  7. package/dist/lib/file-operations/glob-helpers.js +23 -0
  8. package/dist/lib/file-operations/list-directory.js +22 -46
  9. package/dist/lib/file-operations/read-multiple-files.js +11 -19
  10. package/dist/lib/file-operations/search-content.d.ts +1 -8
  11. package/dist/lib/file-operations/search-content.js +126 -204
  12. package/dist/lib/file-operations/search-files.js +48 -94
  13. package/dist/lib/file-operations/search-matcher.d.ts +10 -0
  14. package/dist/lib/file-operations/search-matcher.js +72 -0
  15. package/dist/lib/file-operations/search-worker.js +3 -1
  16. package/dist/lib/file-operations/tree.d.ts +2 -2
  17. package/dist/lib/file-operations/tree.js +26 -42
  18. package/dist/lib/fs-helpers.d.ts +1 -0
  19. package/dist/lib/fs-helpers.js +9 -0
  20. package/dist/lib/option-utils.d.ts +3 -0
  21. package/dist/lib/option-utils.js +15 -0
  22. package/dist/lib/path-validation.d.ts +1 -0
  23. package/dist/lib/path-validation.js +7 -0
  24. package/dist/lib/progress-reporting.d.ts +11 -0
  25. package/dist/lib/progress-reporting.js +13 -0
  26. package/dist/prompts.js +3 -3
  27. package/dist/resources/generated-instructions.js +14 -14
  28. package/dist/resources/tool-catalog.js +9 -9
  29. package/dist/resources/tool-info.js +5 -5
  30. package/dist/resources/workflows.js +17 -17
  31. package/dist/schemas.js +21 -90
  32. package/dist/server/bootstrap.js +2 -2
  33. package/dist/tools/apply-patch.js +3 -0
  34. package/dist/tools/calculate-hash.js +12 -12
  35. package/dist/tools/delete-file.js +5 -2
  36. package/dist/tools/list-directory.js +3 -21
  37. package/dist/tools/read-multiple.js +11 -21
  38. package/dist/tools/replace-in-files.js +10 -11
  39. package/dist/tools/search-content.js +2 -2
  40. package/dist/tools/search-files.js +3 -21
  41. package/dist/tools/shared.d.ts +19 -0
  42. package/dist/tools/shared.js +64 -18
  43. package/dist/tools/stat-many.js +11 -22
  44. 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 { buildMatcher, scanFileInWorker } from './search-content.js';
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,7 +1,7 @@
1
- type TreeEntryType = 'file' | 'directory' | 'symlink' | 'other';
1
+ import type { EntryType } from './common.js';
2
2
  interface TreeEntry {
3
3
  name: string;
4
- type: TreeEntryType;
4
+ type: EntryType;
5
5
  relativePath: string;
6
6
  children?: TreeEntry[];
7
7
  }
@@ -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 { createTimedAbortSignal } from '../fs-helpers.js';
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, resolveEntryType } from './glob-engine.js';
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 getStopReason(signal, totalEntries, maxEntries) {
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
- if (type !== 'symlink') {
105
- const normalized = normalizePath(entry.path);
106
- if (!isPathWithinDirectories(normalized, rootDirectories)) {
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
- const { signal, cleanup } = createTimedAbortSignal(options.signal, normalized.timeoutMs);
222
- const root = await validateExistingDirectory(dirPath, signal);
223
- const rootNormalized = normalizePath(root);
224
- const rootDirectories = [rootNormalized];
225
- try {
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 = getStopReason(signal, totalEntries, normalized.maxEntries);
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
  }
@@ -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: {
@@ -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 = 'Retrieve the full filesystem-mcp XML usage guide.';
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 sections: ${available}\n\n${instructions}`;
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('Section heading prefix to filter (e.g. "error handling strategy"). Omit for full instructions.'),
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
- Expert filesystem agent. Operate ONLY within allowed roots. Always discover before acting never guess paths.
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 server usage guide.
19
- - \`internal://tool-catalog\`: Tool routing and cross-tool data-flow guide.
20
- - \`internal://workflows\`: Standard operating sequences (explore/search/edit/patch).
21
- - \`internal://tool-info/{name}\`: Per-tool details (nuances/gotchas), e.g. \`internal://tool-info/read\`.
22
- - \`filesystem-mcp://result/{id}\`: Large output cache. Call \`resources/read\` immediately if \`resourceUri\` is returned.
23
- - \`filesystem-mcp://metrics\`: Live per-tool stats.
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: provide \`_meta.progressToken\` in \`tools/call\`, poll \`tasks/get\`, call \`tasks/result\`.
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\` Call \`roots\`; use allowed path.
39
- - \`E_NOT_FOUND\` Call \`ls\`/\`find\`; verify spelling.
40
- - \`E_TOO_LARGE\` Use range/head or \`read_many\`.
41
- - \`E_TIMEOUT\` Reduce scope or result limits.
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) => ${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) => `⚠ ${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-based file discovery.
13
- - Use \`grep\` for content-based searches.
14
- - Use \`search_and_replace\` ONLY for bulk replacements, not for discovery.
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, single-occurrence string replacements in existing files.
19
- - Use \`write\` to create new files or completely overwrite existing content.
20
- - Use \`search_and_replace\` for bulk regex replacements across multiple files.
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
- - Always generate a patch with \`diff_files\` first.
25
- - Always use \`dryRun: true\` with \`apply_patch\` to verify changes.
26
- - \`apply_patch\` works on unified diff format.
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
- 'Allowed roots only (negotiated via CLI).',
41
- 'Sensitive files denylisted by default.',
42
- `Max file size (${Math.floor(MAX_TEXT_FILE_SIZE / 1024 / 1024)}MB) & search results (${MAX_SEARCH_RESULTS} files, ${DEFAULT_SEARCH_CONTENT_RESULTS} lines) enforced.`,
43
- 'If a response includes `resourceUri`, call `resources/read` immediately results expire on process restart.',
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('', `**Annotations:** ${entry.annotations.join(', ')}`);
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: navigating an unfamiliar directory or reading file content.
5
- 1. \`roots\` (List allowed paths).
6
- 2. \`ls\` (files) | \`tree\` (structure).
7
- 3. \`stat\` | \`stat_many\` (size/type check).
8
- 4. \`read\` | \`read_many\` (content).
9
- > **Strict:** Never guess paths. Resolve first.
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: locating files by name pattern or by content match.
12
+ Use when: you need files by pattern or content.
13
13
  1. \`find\` (glob candidates).
14
- 2. \`grep\` (content search).
15
- 3. \`read\` (verify context).
16
- > **Strict:** Use \`grep\` for content search, not \`find\`.
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: modifying existing files or reorganizing the filesystem.
20
- 1. \`edit\` (precise string match).
21
- 2. \`search_and_replace\` (bulk regex/glob).
22
- 3. \`mv\` | \`rm\` (file layout).
23
- 4. \`mkdir\` (create dirs).
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 structured diffs produced by \`diff_files\`.
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:** Pass \`diff_files\` output directly into \`apply_patch\`.
31
+ > **Tip:** Feed \`diff_files\` output directly to \`apply_patch\`.
32
32
  </workflows>`;
33
33
  }