sherpa-mcp 0.1.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 (40) hide show
  1. package/LICENSE +21 -0
  2. package/dist/adapters/ollama.js +110 -0
  3. package/dist/adapters/ollama.js.map +1 -0
  4. package/dist/adapters/openai-compatible.js +88 -0
  5. package/dist/adapters/openai-compatible.js.map +1 -0
  6. package/dist/adapters/types.js +13 -0
  7. package/dist/adapters/types.js.map +1 -0
  8. package/dist/backend-factory.js +15 -0
  9. package/dist/backend-factory.js.map +1 -0
  10. package/dist/chunking.js +28 -0
  11. package/dist/chunking.js.map +1 -0
  12. package/dist/config.js +62 -0
  13. package/dist/config.js.map +1 -0
  14. package/dist/file-enumeration.js +70 -0
  15. package/dist/file-enumeration.js.map +1 -0
  16. package/dist/hash.js +5 -0
  17. package/dist/hash.js.map +1 -0
  18. package/dist/index.js +48 -0
  19. package/dist/index.js.map +1 -0
  20. package/dist/map-reduce.js +48 -0
  21. package/dist/map-reduce.js.map +1 -0
  22. package/dist/paths.js +51 -0
  23. package/dist/paths.js.map +1 -0
  24. package/dist/server.js +60 -0
  25. package/dist/server.js.map +1 -0
  26. package/dist/summary-truncate.js +20 -0
  27. package/dist/summary-truncate.js.map +1 -0
  28. package/dist/syntax-validators.js +49 -0
  29. package/dist/syntax-validators.js.map +1 -0
  30. package/dist/tools/apply-transform.js +59 -0
  31. package/dist/tools/apply-transform.js.map +1 -0
  32. package/dist/tools/delegate-exploration.js +70 -0
  33. package/dist/tools/delegate-exploration.js.map +1 -0
  34. package/dist/tools/delegate-search.js +117 -0
  35. package/dist/tools/delegate-search.js.map +1 -0
  36. package/dist/tools/delegate-transform.js +81 -0
  37. package/dist/tools/delegate-transform.js.map +1 -0
  38. package/dist/tools/health-check.js +14 -0
  39. package/dist/tools/health-check.js.map +1 -0
  40. package/package.json +46 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Gastón Parravicini
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,110 @@
1
+ import { BackendTimeoutError, ContextExceededError } from './types.js';
2
+ export class OllamaBackend {
3
+ opts;
4
+ cachedCapabilities = null;
5
+ cachedModel = null;
6
+ constructor(opts) {
7
+ this.opts = opts;
8
+ }
9
+ async fetchWithTimeout(url, init, timeoutMs) {
10
+ const controller = new AbortController();
11
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
12
+ try {
13
+ return await fetch(url, { ...init, signal: controller.signal });
14
+ }
15
+ finally {
16
+ clearTimeout(timer);
17
+ }
18
+ }
19
+ async generate(prompt, opts) {
20
+ const start = Date.now();
21
+ const timeoutMs = this.opts.generateTimeoutMs ?? 300_000;
22
+ let res;
23
+ try {
24
+ res = await this.fetchWithTimeout(`${this.opts.baseUrl}/api/generate`, {
25
+ method: 'POST',
26
+ headers: { 'content-type': 'application/json' },
27
+ body: JSON.stringify({
28
+ model: this.opts.model,
29
+ prompt,
30
+ stream: false,
31
+ options: { num_predict: opts.maxOutputTokens, temperature: opts.temperature }
32
+ })
33
+ }, timeoutMs);
34
+ }
35
+ catch (err) {
36
+ if (err?.name === 'AbortError') {
37
+ throw new BackendTimeoutError(`Ollama didn't respond in ${timeoutMs}ms`);
38
+ }
39
+ throw err;
40
+ }
41
+ if (!res.ok) {
42
+ const body = await res.text();
43
+ if (res.status === 400 && /context/i.test(body)) {
44
+ throw new ContextExceededError(body);
45
+ }
46
+ throw new Error(`Ollama responded ${res.status}: ${body}`);
47
+ }
48
+ const data = (await res.json());
49
+ return {
50
+ text: data.response,
51
+ usage: {
52
+ tokensIn: data.prompt_eval_count ?? 0,
53
+ tokensOut: data.eval_count ?? 0,
54
+ elapsedMs: Date.now() - start
55
+ }
56
+ };
57
+ }
58
+ async getCapabilities() {
59
+ const health = await this.checkHealth();
60
+ if (health.status === 'ok' && this.cachedCapabilities && this.cachedModel === health.model) {
61
+ return this.cachedCapabilities;
62
+ }
63
+ const res = await this.fetchWithTimeout(`${this.opts.baseUrl}/api/show`, {
64
+ method: 'POST',
65
+ headers: { 'content-type': 'application/json' },
66
+ body: JSON.stringify({ name: this.opts.model })
67
+ }, this.opts.healthTimeoutMs ?? 10_000);
68
+ if (!res.ok) {
69
+ // Do not cache this fallback: /api/show may be transiently failing,
70
+ // and caching would permanently pin the conservative default for the
71
+ // process lifetime even after the failure resolves.
72
+ const fallback = { contextWindow: 4096, maxOutputTokens: 2048 };
73
+ return fallback;
74
+ }
75
+ const data = (await res.json());
76
+ const contextKey = Object.keys(data.model_info ?? {}).find((k) => k.endsWith('.context_length'));
77
+ const contextWindow = contextKey ? data.model_info[contextKey] : 4096;
78
+ const capabilities = {
79
+ contextWindow,
80
+ maxOutputTokens: Math.min(4096, Math.floor(contextWindow / 2))
81
+ };
82
+ this.cachedCapabilities = capabilities;
83
+ this.cachedModel = this.opts.model;
84
+ return capabilities;
85
+ }
86
+ async checkHealth() {
87
+ const timeoutMs = this.opts.healthTimeoutMs ?? 10_000;
88
+ let tagsRes;
89
+ try {
90
+ tagsRes = await this.fetchWithTimeout(`${this.opts.baseUrl}/api/tags`, { method: 'GET' }, timeoutMs);
91
+ }
92
+ catch {
93
+ return { status: 'unreachable', detail: `Could not connect to ${this.opts.baseUrl}` };
94
+ }
95
+ if (!tagsRes.ok) {
96
+ return { status: 'unreachable', detail: `Ollama responded ${tagsRes.status} at ${this.opts.baseUrl}` };
97
+ }
98
+ const data = (await tagsRes.json());
99
+ const availableModels = data.models.map((m) => m.name);
100
+ if (!this.opts.model || !availableModels.includes(this.opts.model)) {
101
+ return {
102
+ status: 'model_not_loaded',
103
+ detail: `Model "${this.opts.model || '(not set)'}" is not available in Ollama`,
104
+ availableModels
105
+ };
106
+ }
107
+ return { status: 'ok', model: this.opts.model };
108
+ }
109
+ }
110
+ //# sourceMappingURL=ollama.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ollama.js","sourceRoot":"","sources":["../../src/adapters/ollama.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,88 @@
1
+ import { BackendTimeoutError, ContextExceededError } from './types.js';
2
+ const FALLBACK_CONTEXT_WINDOW = 4096;
3
+ const FALLBACK_MAX_OUTPUT_TOKENS = 2048;
4
+ export class OpenAICompatibleBackend {
5
+ opts;
6
+ constructor(opts) {
7
+ this.opts = opts;
8
+ }
9
+ async fetchWithTimeout(url, init, timeoutMs) {
10
+ const controller = new AbortController();
11
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
12
+ try {
13
+ return await fetch(url, { ...init, signal: controller.signal });
14
+ }
15
+ finally {
16
+ clearTimeout(timer);
17
+ }
18
+ }
19
+ async generate(prompt, opts) {
20
+ const start = Date.now();
21
+ const timeoutMs = this.opts.generateTimeoutMs ?? 300_000;
22
+ let res;
23
+ try {
24
+ res = await this.fetchWithTimeout(`${this.opts.baseUrl}/v1/chat/completions`, {
25
+ method: 'POST',
26
+ headers: { 'content-type': 'application/json' },
27
+ body: JSON.stringify({
28
+ model: this.opts.model,
29
+ messages: [{ role: 'user', content: prompt }],
30
+ max_tokens: opts.maxOutputTokens,
31
+ temperature: opts.temperature
32
+ })
33
+ }, timeoutMs);
34
+ }
35
+ catch (err) {
36
+ if (err?.name === 'AbortError') {
37
+ throw new BackendTimeoutError(`OpenAI-compatible backend didn't respond in ${timeoutMs}ms`);
38
+ }
39
+ throw err;
40
+ }
41
+ if (!res.ok) {
42
+ const body = await res.text();
43
+ if (res.status === 400 && /context|too long/i.test(body)) {
44
+ throw new ContextExceededError(body);
45
+ }
46
+ throw new Error(`Backend responded ${res.status}: ${body}`);
47
+ }
48
+ const data = (await res.json());
49
+ return {
50
+ text: data.choices[0].message.content,
51
+ usage: {
52
+ tokensIn: data.usage?.prompt_tokens ?? 0,
53
+ tokensOut: data.usage?.completion_tokens ?? 0,
54
+ elapsedMs: Date.now() - start
55
+ }
56
+ };
57
+ }
58
+ async getCapabilities() {
59
+ return {
60
+ contextWindow: this.opts.contextWindowOverride ?? FALLBACK_CONTEXT_WINDOW,
61
+ maxOutputTokens: this.opts.maxOutputTokensOverride ?? FALLBACK_MAX_OUTPUT_TOKENS
62
+ };
63
+ }
64
+ async checkHealth() {
65
+ const timeoutMs = this.opts.healthTimeoutMs ?? 10_000;
66
+ let res;
67
+ try {
68
+ res = await this.fetchWithTimeout(`${this.opts.baseUrl}/v1/models`, { method: 'GET' }, timeoutMs);
69
+ }
70
+ catch {
71
+ return { status: 'unreachable', detail: `Could not connect to ${this.opts.baseUrl}` };
72
+ }
73
+ if (!res.ok) {
74
+ return { status: 'unreachable', detail: `Backend responded ${res.status} at ${this.opts.baseUrl}` };
75
+ }
76
+ const data = (await res.json());
77
+ const availableModels = data.data.map((m) => m.id);
78
+ if (!this.opts.model || !availableModels.includes(this.opts.model)) {
79
+ return {
80
+ status: 'model_not_loaded',
81
+ detail: `Model "${this.opts.model || '(not set)'}" is not available`,
82
+ availableModels
83
+ };
84
+ }
85
+ return { status: 'ok', model: this.opts.model };
86
+ }
87
+ }
88
+ //# sourceMappingURL=openai-compatible.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openai-compatible.js","sourceRoot":"","sources":["../../src/adapters/openai-compatible.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ export class BackendTimeoutError extends Error {
2
+ constructor(detail) {
3
+ super(detail);
4
+ this.name = 'BackendTimeoutError';
5
+ }
6
+ }
7
+ export class ContextExceededError extends Error {
8
+ constructor(detail) {
9
+ super(detail);
10
+ this.name = 'ContextExceededError';
11
+ }
12
+ }
13
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/adapters/types.ts"],"names":[],"mappings":"AAqBA,MAAM,OAAO,mBAAoB,SAAQ,KAAK;IAC5C,YAAY,MAAc;QACxB,KAAK,CAAC,MAAM,CAAC,CAAC;QACd,IAAI,CAAC,IAAI,GAAG,qBAAqB,CAAC;IACpC,CAAC;CACF;AAED,MAAM,OAAO,oBAAqB,SAAQ,KAAK;IAC7C,YAAY,MAAc;QACxB,KAAK,CAAC,MAAM,CAAC,CAAC;QACd,IAAI,CAAC,IAAI,GAAG,sBAAsB,CAAC;IACrC,CAAC;CACF"}
@@ -0,0 +1,15 @@
1
+ import { OllamaBackend } from './adapters/ollama.js';
2
+ import { OpenAICompatibleBackend } from './adapters/openai-compatible.js';
3
+ export function createBackend(config, modelOverride) {
4
+ const model = modelOverride ?? config.model ?? '';
5
+ if (config.backend === 'ollama') {
6
+ return new OllamaBackend({ baseUrl: config.baseUrl, model });
7
+ }
8
+ return new OpenAICompatibleBackend({
9
+ baseUrl: config.baseUrl,
10
+ model,
11
+ contextWindowOverride: config.contextWindowOverride,
12
+ maxOutputTokensOverride: config.maxOutputTokensOverride
13
+ });
14
+ }
15
+ //# sourceMappingURL=backend-factory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"backend-factory.js","sourceRoot":"","sources":["../src/backend-factory.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,uBAAuB,EAAE,MAAM,iCAAiC,CAAC;AAE1E,MAAM,UAAU,aAAa,CAAC,MAAoB,EAAE,aAAsB;IACxE,MAAM,KAAK,GAAG,aAAa,IAAI,MAAM,CAAC,KAAK,IAAI,EAAE,CAAC;IAClD,IAAI,MAAM,CAAC,OAAO,KAAK,QAAQ,EAAE,CAAC;QAChC,OAAO,IAAI,aAAa,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC;IAC/D,CAAC;IACD,OAAO,IAAI,uBAAuB,CAAC;QACjC,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,KAAK;QACL,qBAAqB,EAAE,MAAM,CAAC,qBAAqB;QACnD,uBAAuB,EAAE,MAAM,CAAC,uBAAuB;KACxD,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,28 @@
1
+ export function estimateTokens(text) {
2
+ return Math.ceil(text.length / 4);
3
+ }
4
+ export function planChunks(files, contextWindow, reserveTokens) {
5
+ const budget = contextWindow - reserveTokens;
6
+ const skipped = [];
7
+ const chunks = [];
8
+ let current = [];
9
+ let currentTokens = 0;
10
+ for (const file of files) {
11
+ const fileTokens = estimateTokens(file.content);
12
+ if (fileTokens > budget) {
13
+ skipped.push({ path: file.path, reason: 'exceeds_context_window' });
14
+ continue;
15
+ }
16
+ if (currentTokens + fileTokens > budget && current.length > 0) {
17
+ chunks.push(current);
18
+ current = [];
19
+ currentTokens = 0;
20
+ }
21
+ current.push(file);
22
+ currentTokens += fileTokens;
23
+ }
24
+ if (current.length > 0)
25
+ chunks.push(current);
26
+ return { chunks, skipped };
27
+ }
28
+ //# sourceMappingURL=chunking.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chunking.js","sourceRoot":"","sources":["../src/chunking.ts"],"names":[],"mappings":"AAUA,MAAM,UAAU,cAAc,CAAC,IAAY;IACzC,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;AACpC,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,KAAoB,EAAE,aAAqB,EAAE,aAAqB;IAC3F,MAAM,MAAM,GAAG,aAAa,GAAG,aAAa,CAAC;IAC7C,MAAM,OAAO,GAAuC,EAAE,CAAC;IACvD,MAAM,MAAM,GAAoB,EAAE,CAAC;IACnC,IAAI,OAAO,GAAkB,EAAE,CAAC;IAChC,IAAI,aAAa,GAAG,CAAC,CAAC;IAEtB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,UAAU,GAAG,cAAc,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAChD,IAAI,UAAU,GAAG,MAAM,EAAE,CAAC;YACxB,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,wBAAwB,EAAE,CAAC,CAAC;YACpE,SAAS;QACX,CAAC;QACD,IAAI,aAAa,GAAG,UAAU,GAAG,MAAM,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC9D,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YACrB,OAAO,GAAG,EAAE,CAAC;YACb,aAAa,GAAG,CAAC,CAAC;QACpB,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnB,aAAa,IAAI,UAAU,CAAC;IAC9B,CAAC;IACD,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC;QAAE,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAE7C,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;AAC7B,CAAC"}
package/dist/config.js ADDED
@@ -0,0 +1,62 @@
1
+ import fs from 'node:fs';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ const DEFAULTS = {
5
+ backend: 'ollama',
6
+ baseUrl: 'http://localhost:11434',
7
+ model: undefined,
8
+ maxFiles: 100,
9
+ maxChunks: 20,
10
+ resultsDir: '.sherpa',
11
+ truncationThreshold: 0.75,
12
+ contextWindowOverride: undefined,
13
+ maxOutputTokensOverride: undefined
14
+ };
15
+ function readJsonIfExists(filePath) {
16
+ if (!fs.existsSync(filePath))
17
+ return {};
18
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'));
19
+ }
20
+ function parseNumericEnv(envVarName, rawValue) {
21
+ const value = Number(rawValue);
22
+ if (!Number.isFinite(value)) {
23
+ throw new Error(`sherpa: invalid value for ${envVarName}: "${rawValue}" is not a finite number.`);
24
+ }
25
+ return value;
26
+ }
27
+ export function loadConfig(projectRoot, opts = {}) {
28
+ const env = opts.env ?? process.env;
29
+ const userConfigPath = opts.userConfigPath ?? path.join(os.homedir(), '.claude', 'sherpa', 'config.json');
30
+ const projectConfigPath = path.join(projectRoot, 'sherpa.config.json');
31
+ const userConfig = readJsonIfExists(userConfigPath);
32
+ const projectConfig = readJsonIfExists(projectConfigPath);
33
+ const merged = { ...DEFAULTS, ...userConfig, ...projectConfig };
34
+ if (env.SHERPA_BACKEND) {
35
+ if (env.SHERPA_BACKEND !== 'ollama' && env.SHERPA_BACKEND !== 'openai-compatible') {
36
+ throw new Error(`sherpa: invalid value for SHERPA_BACKEND: "${env.SHERPA_BACKEND}". ` +
37
+ `Valid values are "ollama" or "openai-compatible".`);
38
+ }
39
+ merged.backend = env.SHERPA_BACKEND;
40
+ }
41
+ if (env.SHERPA_BASE_URL)
42
+ merged.baseUrl = env.SHERPA_BASE_URL;
43
+ if (env.SHERPA_MODEL)
44
+ merged.model = env.SHERPA_MODEL;
45
+ if (env.SHERPA_MAX_FILES)
46
+ merged.maxFiles = parseNumericEnv('SHERPA_MAX_FILES', env.SHERPA_MAX_FILES);
47
+ if (env.SHERPA_MAX_CHUNKS)
48
+ merged.maxChunks = parseNumericEnv('SHERPA_MAX_CHUNKS', env.SHERPA_MAX_CHUNKS);
49
+ if (env.SHERPA_RESULTS_DIR)
50
+ merged.resultsDir = env.SHERPA_RESULTS_DIR;
51
+ if (env.SHERPA_TRUNCATION_THRESHOLD) {
52
+ merged.truncationThreshold = parseNumericEnv('SHERPA_TRUNCATION_THRESHOLD', env.SHERPA_TRUNCATION_THRESHOLD);
53
+ }
54
+ if (env.SHERPA_CONTEXT_WINDOW) {
55
+ merged.contextWindowOverride = parseNumericEnv('SHERPA_CONTEXT_WINDOW', env.SHERPA_CONTEXT_WINDOW);
56
+ }
57
+ if (env.SHERPA_MAX_OUTPUT_TOKENS) {
58
+ merged.maxOutputTokensOverride = parseNumericEnv('SHERPA_MAX_OUTPUT_TOKENS', env.SHERPA_MAX_OUTPUT_TOKENS);
59
+ }
60
+ return merged;
61
+ }
62
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAc7B,MAAM,QAAQ,GAAiB;IAC7B,OAAO,EAAE,QAAQ;IACjB,OAAO,EAAE,wBAAwB;IACjC,KAAK,EAAE,SAAS;IAChB,QAAQ,EAAE,GAAG;IACb,SAAS,EAAE,EAAE;IACb,UAAU,EAAE,SAAS;IACrB,mBAAmB,EAAE,IAAI;IACzB,qBAAqB,EAAE,SAAS;IAChC,uBAAuB,EAAE,SAAS;CACnC,CAAC;AAEF,SAAS,gBAAgB,CAAC,QAAgB;IACxC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC;QAAE,OAAO,EAAE,CAAC;IACxC,OAAO,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,MAAM,CAAC,CAA0B,CAAC;AAChF,CAAC;AAED,SAAS,eAAe,CAAC,UAAkB,EAAE,QAAgB;IAC3D,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC;IAC/B,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QAC5B,MAAM,IAAI,KAAK,CACb,6BAA6B,UAAU,MAAM,QAAQ,2BAA2B,CACjF,CAAC;IACJ,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU,UAAU,CACxB,WAAmB,EACnB,OAA6D,EAAE;IAE/D,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC;IACpC,MAAM,cAAc,GAClB,IAAI,CAAC,cAAc,IAAI,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,QAAQ,EAAE,aAAa,CAAC,CAAC;IACrF,MAAM,iBAAiB,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,oBAAoB,CAAC,CAAC;IAEvE,MAAM,UAAU,GAAG,gBAAgB,CAAC,cAAc,CAAC,CAAC;IACpD,MAAM,aAAa,GAAG,gBAAgB,CAAC,iBAAiB,CAAC,CAAC;IAE1D,MAAM,MAAM,GAAiB,EAAE,GAAG,QAAQ,EAAE,GAAG,UAAU,EAAE,GAAG,aAAa,EAAE,CAAC;IAE9E,IAAI,GAAG,CAAC,cAAc,EAAE,CAAC;QACvB,IAAI,GAAG,CAAC,cAAc,KAAK,QAAQ,IAAI,GAAG,CAAC,cAAc,KAAK,mBAAmB,EAAE,CAAC;YAClF,MAAM,IAAI,KAAK,CACb,8CAA8C,GAAG,CAAC,cAAc,KAAK;gBACrE,mDAAmD,CACpD,CAAC;QACJ,CAAC;QACD,MAAM,CAAC,OAAO,GAAG,GAAG,CAAC,cAAc,CAAC;IACtC,CAAC;IACD,IAAI,GAAG,CAAC,eAAe;QAAE,MAAM,CAAC,OAAO,GAAG,GAAG,CAAC,eAAe,CAAC;IAC9D,IAAI,GAAG,CAAC,YAAY;QAAE,MAAM,CAAC,KAAK,GAAG,GAAG,CAAC,YAAY,CAAC;IACtD,IAAI,GAAG,CAAC,gBAAgB;QAAE,MAAM,CAAC,QAAQ,GAAG,eAAe,CAAC,kBAAkB,EAAE,GAAG,CAAC,gBAAgB,CAAC,CAAC;IACtG,IAAI,GAAG,CAAC,iBAAiB;QAAE,MAAM,CAAC,SAAS,GAAG,eAAe,CAAC,mBAAmB,EAAE,GAAG,CAAC,iBAAiB,CAAC,CAAC;IAC1G,IAAI,GAAG,CAAC,kBAAkB;QAAE,MAAM,CAAC,UAAU,GAAG,GAAG,CAAC,kBAAkB,CAAC;IACvE,IAAI,GAAG,CAAC,2BAA2B,EAAE,CAAC;QACpC,MAAM,CAAC,mBAAmB,GAAG,eAAe,CAC1C,6BAA6B,EAC7B,GAAG,CAAC,2BAA2B,CAChC,CAAC;IACJ,CAAC;IACD,IAAI,GAAG,CAAC,qBAAqB,EAAE,CAAC;QAC9B,MAAM,CAAC,qBAAqB,GAAG,eAAe,CAAC,uBAAuB,EAAE,GAAG,CAAC,qBAAqB,CAAC,CAAC;IACrG,CAAC;IACD,IAAI,GAAG,CAAC,wBAAwB,EAAE,CAAC;QACjC,MAAM,CAAC,uBAAuB,GAAG,eAAe,CAC9C,0BAA0B,EAC1B,GAAG,CAAC,wBAAwB,CAC7B,CAAC;IACJ,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,70 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import * as ignoreModule from 'ignore';
4
+ import { resolveWithinRoot } from './paths.js';
5
+ // Cast works around a TypeScript CJS/ESM interop issue in ignore@5.3.2
6
+ // (CommonJS runtime, but its .d.ts declares an ESM `export default`).
7
+ const ignore = ignoreModule.default;
8
+ function loadIgnoreRules(root, resultsDir) {
9
+ const ig = ignore();
10
+ const gitignorePath = path.join(root, '.gitignore');
11
+ if (fs.existsSync(gitignorePath)) {
12
+ ig.add(fs.readFileSync(gitignorePath, 'utf8'));
13
+ }
14
+ // Always excluded, regardless of .gitignore: sherpa's own results
15
+ // directory. Without this, a project that hasn't yet added resultsDir
16
+ // to .gitignore (the README tells you to, but nothing enforces it) will
17
+ // have delegate_exploration read back its own prior output on the next
18
+ // run over the same paths — wasted local tokens, and it compounds since
19
+ // each run adds another file to resultsDir.
20
+ ig.add(['.git', 'node_modules', resultsDir]);
21
+ return ig;
22
+ }
23
+ function walk(dir, root, ig, out) {
24
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
25
+ for (const entry of entries) {
26
+ const full = path.join(dir, entry.name);
27
+ const rel = path.relative(root, full);
28
+ if (ig.ignores(rel))
29
+ continue;
30
+ if (entry.isDirectory()) {
31
+ walk(full, root, ig, out);
32
+ }
33
+ else if (entry.isFile()) {
34
+ out.push(full);
35
+ }
36
+ }
37
+ }
38
+ export function enumerateFiles(root, inputPaths, maxFiles, resultsDir) {
39
+ const ig = loadIgnoreRules(root, resultsDir);
40
+ const filesSkipped = [];
41
+ const collected = [];
42
+ for (const inputPath of inputPaths) {
43
+ const resolved = resolveWithinRoot(root, inputPath);
44
+ if (!resolved) {
45
+ filesSkipped.push({ path: inputPath, reason: 'outside_project_root' });
46
+ continue;
47
+ }
48
+ let stat;
49
+ try {
50
+ stat = fs.statSync(resolved);
51
+ }
52
+ catch {
53
+ filesSkipped.push({ path: inputPath, reason: 'not_found' });
54
+ continue;
55
+ }
56
+ if (stat.isDirectory()) {
57
+ walk(resolved, root, ig, collected);
58
+ }
59
+ else if (stat.isFile()) {
60
+ const rel = path.relative(root, resolved);
61
+ if (!ig.ignores(rel))
62
+ collected.push(resolved);
63
+ }
64
+ }
65
+ const unique = Array.from(new Set(collected));
66
+ const truncatedByBudget = unique.length > maxFiles;
67
+ const files = truncatedByBudget ? unique.slice(0, maxFiles) : unique;
68
+ return { files, filesSkipped, truncatedByBudget };
69
+ }
70
+ //# sourceMappingURL=file-enumeration.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-enumeration.js","sourceRoot":"","sources":["../src/file-enumeration.ts"],"names":[],"mappings":"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"}
package/dist/hash.js ADDED
@@ -0,0 +1,5 @@
1
+ import crypto from 'node:crypto';
2
+ export function sha256(content) {
3
+ return crypto.createHash('sha256').update(content, 'utf8').digest('hex');
4
+ }
5
+ //# sourceMappingURL=hash.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hash.js","sourceRoot":"","sources":["../src/hash.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,aAAa,CAAC;AAEjC,MAAM,UAAU,MAAM,CAAC,OAAe;IACpC,OAAO,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAC3E,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,48 @@
1
+ #!/usr/bin/env node
2
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
+ import path from 'node:path';
4
+ import { resolveProjectRoot } from './paths.js';
5
+ import { loadConfig } from './config.js';
6
+ import { createServer } from './server.js';
7
+ /**
8
+ * Validates that resultsDir stays confined within root, applying the same
9
+ * path-confinement rule used everywhere else. Throws a clear error if
10
+ * resultsDir escapes the project root (e.g. via "../../somewhere" in a
11
+ * committed sherpa.config.json) instead of silently writing outside the
12
+ * project.
13
+ *
14
+ * Unlike resolveWithinRoot, this check is purely lexical (path.resolve/
15
+ * path.relative on the strings, no fs.realpathSync). That's deliberate:
16
+ * SHERPA_RESULTS_DIR is validated at startup, before any tool has run and
17
+ * created the directory tree (which happens via
18
+ * mkdirSync(..., {recursive: true})), so requiring the path (or its
19
+ * parent) to already exist on disk would be too strict — a nested
20
+ * resultsDir like "nested/.sherpa" is legitimate even though "nested/"
21
+ * doesn't exist yet. This check offers no symlink-escape protection, but
22
+ * that's acceptable here: at config-validation time there's nothing yet
23
+ * to symlink-escape, and once tools write inside resultsDir they do so
24
+ * via path.join(root, resultsDir, <uuid>) with filenames generated by
25
+ * crypto.randomUUID() (not user-controlled), so there's no relevant
26
+ * symlink attack surface.
27
+ */
28
+ export function validateResultsDir(root, resultsDir) {
29
+ const resolved = path.resolve(root, resultsDir);
30
+ const rel = path.relative(root, resolved);
31
+ if (rel.startsWith(`..${path.sep}`) || rel === '..' || path.isAbsolute(rel)) {
32
+ throw new Error(`sherpa cannot start: SHERPA_RESULTS_DIR ("${resultsDir}") escapes the project root ` +
33
+ `(${root}). It must resolve to a path inside the root.`);
34
+ }
35
+ }
36
+ async function main() {
37
+ const root = resolveProjectRoot(process.cwd());
38
+ const config = loadConfig(root);
39
+ validateResultsDir(root, config.resultsDir);
40
+ const server = createServer(root, config);
41
+ const transport = new StdioServerTransport();
42
+ await server.connect(transport);
43
+ }
44
+ main().catch((err) => {
45
+ console.error(err);
46
+ process.exit(1);
47
+ });
48
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAChD,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,UAAU,kBAAkB,CAAC,IAAY,EAAE,UAAkB;IACjE,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;IAChD,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;IAC1C,IAAI,GAAG,CAAC,UAAU,CAAC,KAAK,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,GAAG,KAAK,IAAI,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QAC5E,MAAM,IAAI,KAAK,CACb,6CAA6C,UAAU,8BAA8B;YACrF,IAAI,IAAI,+CAA+C,CACxD,CAAC;IACJ,CAAC;AACH,CAAC;AAED,KAAK,UAAU,IAAI;IACjB,MAAM,IAAI,GAAG,kBAAkB,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;IAC/C,MAAM,MAAM,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC;IAChC,kBAAkB,CAAC,IAAI,EAAE,MAAM,CAAC,UAAU,CAAC,CAAC;IAC5C,MAAM,MAAM,GAAG,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IAC1C,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;AAClC,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACnB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Shared map-reduce runner used by delegate_exploration and delegate_search:
3
+ * slices `chunks` down to `budgetConfig.maxChunks`, sends one map call per
4
+ * used chunk, and — only if more than one chunk was used — a final reduce
5
+ * call over the partial results. Usage (calls/tokens/elapsed) is summed
6
+ * across every call made.
7
+ */
8
+ export async function runMapReduce(backend, chunks, budgetConfig, buildMapPrompt, buildReducePrompt, generateOpts, emptyText) {
9
+ const usedChunks = chunks.slice(0, budgetConfig.maxChunks);
10
+ const droppedChunks = chunks.slice(budgetConfig.maxChunks);
11
+ const truncatedByChunkBudget = chunks.length > budgetConfig.maxChunks;
12
+ let calls = 0;
13
+ let tokensIn = 0;
14
+ let tokensOut = 0;
15
+ let elapsedMs = 0;
16
+ const partials = [];
17
+ for (const chunk of usedChunks) {
18
+ const { text, usage } = await backend.generate(buildMapPrompt(chunk), generateOpts);
19
+ calls++;
20
+ tokensIn += usage.tokensIn;
21
+ tokensOut += usage.tokensOut;
22
+ elapsedMs += usage.elapsedMs;
23
+ partials.push(text);
24
+ }
25
+ let finalText;
26
+ if (partials.length === 0) {
27
+ finalText = emptyText;
28
+ }
29
+ else if (partials.length === 1) {
30
+ finalText = partials[0];
31
+ }
32
+ else {
33
+ const { text, usage } = await backend.generate(buildReducePrompt(partials), generateOpts);
34
+ calls++;
35
+ tokensIn += usage.tokensIn;
36
+ tokensOut += usage.tokensOut;
37
+ elapsedMs += usage.elapsedMs;
38
+ finalText = text;
39
+ }
40
+ return {
41
+ finalText,
42
+ usage: { calls, tokensIn, tokensOut, elapsedMs },
43
+ usedChunkCount: usedChunks.length,
44
+ truncatedByChunkBudget,
45
+ droppedChunks
46
+ };
47
+ }
48
+ //# sourceMappingURL=map-reduce.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"map-reduce.js","sourceRoot":"","sources":["../src/map-reduce.ts"],"names":[],"mappings":"AAkBA;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,OAAgB,EAChB,MAAuB,EACvB,YAAmC,EACnC,cAAgD,EAChD,iBAAiD,EACjD,YAAyC,EACzC,SAAiB;IAEjB,MAAM,UAAU,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,YAAY,CAAC,SAAS,CAAC,CAAC;IAC3D,MAAM,aAAa,GAAG,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,SAAS,CAAC,CAAC;IAC3D,MAAM,sBAAsB,GAAG,MAAM,CAAC,MAAM,GAAG,YAAY,CAAC,SAAS,CAAC;IAEtE,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,IAAI,QAAQ,GAAG,CAAC,CAAC;IACjB,IAAI,SAAS,GAAG,CAAC,CAAC;IAClB,IAAI,SAAS,GAAG,CAAC,CAAC;IAClB,MAAM,QAAQ,GAAa,EAAE,CAAC;IAE9B,KAAK,MAAM,KAAK,IAAI,UAAU,EAAE,CAAC;QAC/B,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,MAAM,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAC,KAAK,CAAC,EAAE,YAAY,CAAC,CAAC;QACpF,KAAK,EAAE,CAAC;QACR,QAAQ,IAAI,KAAK,CAAC,QAAQ,CAAC;QAC3B,SAAS,IAAI,KAAK,CAAC,SAAS,CAAC;QAC7B,SAAS,IAAI,KAAK,CAAC,SAAS,CAAC;QAC7B,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACtB,CAAC;IAED,IAAI,SAAiB,CAAC;IACtB,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1B,SAAS,GAAG,SAAS,CAAC;IACxB,CAAC;SAAM,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACjC,SAAS,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;IAC1B,CAAC;SAAM,CAAC;QACN,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,MAAM,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAC,QAAQ,CAAC,EAAE,YAAY,CAAC,CAAC;QAC1F,KAAK,EAAE,CAAC;QACR,QAAQ,IAAI,KAAK,CAAC,QAAQ,CAAC;QAC3B,SAAS,IAAI,KAAK,CAAC,SAAS,CAAC;QAC7B,SAAS,IAAI,KAAK,CAAC,SAAS,CAAC;QAC7B,SAAS,GAAG,IAAI,CAAC;IACnB,CAAC;IAED,OAAO;QACL,SAAS;QACT,KAAK,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,SAAS,EAAE;QAChD,cAAc,EAAE,UAAU,CAAC,MAAM;QACjC,sBAAsB;QACtB,aAAa;KACd,CAAC;AACJ,CAAC"}
package/dist/paths.js ADDED
@@ -0,0 +1,51 @@
1
+ import fs from 'node:fs';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ export function resolveProjectRoot(cwd) {
5
+ const root = fs.realpathSync(cwd);
6
+ const home = fs.realpathSync(os.homedir());
7
+ if (root === '/' || root === home) {
8
+ throw new Error(`sherpa cannot start with ${root} as the project root — too broad to confine. ` +
9
+ `Start the server from the project directory.`);
10
+ }
11
+ return root;
12
+ }
13
+ export function resolveWithinRoot(root, target) {
14
+ const candidate = path.resolve(root, target);
15
+ let real;
16
+ try {
17
+ real = fs.realpathSync(candidate);
18
+ }
19
+ catch {
20
+ // The file may not exist yet (a new write target). But it could also
21
+ // be a dangling symlink (target doesn't exist) — in that case it's
22
+ // NOT a legitimate new file, it's a symlink that would write through
23
+ // it to a destination potentially outside the root. We reject it.
24
+ try {
25
+ const stat = fs.lstatSync(candidate);
26
+ if (stat.isSymbolicLink()) {
27
+ return null;
28
+ }
29
+ }
30
+ catch {
31
+ // Nothing exists at that path at all (not even a dangling symlink)
32
+ // — it's a legitimate new write target, keep going.
33
+ }
34
+ // Resolve the parent directory and rebuild the final path.
35
+ const parent = path.dirname(candidate);
36
+ let realParent;
37
+ try {
38
+ realParent = fs.realpathSync(parent);
39
+ }
40
+ catch {
41
+ return null;
42
+ }
43
+ real = path.join(realParent, path.basename(candidate));
44
+ }
45
+ const rel = path.relative(root, real);
46
+ if (rel.startsWith(`..${path.sep}`) || rel === '..' || path.isAbsolute(rel)) {
47
+ return null;
48
+ }
49
+ return real;
50
+ }
51
+ //# sourceMappingURL=paths.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paths.js","sourceRoot":"","sources":["../src/paths.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,MAAM,UAAU,kBAAkB,CAAC,GAAW;IAC5C,MAAM,IAAI,GAAG,EAAE,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC;IAClC,MAAM,IAAI,GAAG,EAAE,CAAC,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC;IAC3C,IAAI,IAAI,KAAK,GAAG,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;QAClC,MAAM,IAAI,KAAK,CACb,4BAA4B,IAAI,+CAA+C;YAC/E,8CAA8C,CAC/C,CAAC;IACJ,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,IAAY,EAAE,MAAc;IAC5D,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IAE7C,IAAI,IAAY,CAAC;IACjB,IAAI,CAAC;QACH,IAAI,GAAG,EAAE,CAAC,YAAY,CAAC,SAAS,CAAC,CAAC;IACpC,CAAC;IAAC,MAAM,CAAC;QACP,qEAAqE;QACrE,mEAAmE;QACnE,qEAAqE;QACrE,kEAAkE;QAClE,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,EAAE,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;YACrC,IAAI,IAAI,CAAC,cAAc,EAAE,EAAE,CAAC;gBAC1B,OAAO,IAAI,CAAC;YACd,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,mEAAmE;YACnE,oDAAoD;QACtD,CAAC;QACD,2DAA2D;QAC3D,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;QACvC,IAAI,UAAkB,CAAC;QACvB,IAAI,CAAC;YACH,UAAU,GAAG,EAAE,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;QACvC,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAC;QACd,CAAC;QACD,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC;IACzD,CAAC;IAED,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IACtC,IAAI,GAAG,CAAC,UAAU,CAAC,KAAK,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,GAAG,KAAK,IAAI,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QAC5E,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
package/dist/server.js ADDED
@@ -0,0 +1,60 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { z } from 'zod';
3
+ import { createBackend } from './backend-factory.js';
4
+ import { healthCheck } from './tools/health-check.js';
5
+ import { delegateExploration } from './tools/delegate-exploration.js';
6
+ import { delegateSearch } from './tools/delegate-search.js';
7
+ import { delegateTransform } from './tools/delegate-transform.js';
8
+ import { applyTransform } from './tools/apply-transform.js';
9
+ export function createServer(root, config) {
10
+ const server = new McpServer({ name: 'sherpa', version: '0.1.0' });
11
+ // One Backend instance per distinct model override, reused across tool
12
+ // calls for the life of this process. This is what makes OllamaBackend's
13
+ // getCapabilities() cache (keyed on the model it last saw) actually do
14
+ // something — a fresh instance per call would always start with an empty
15
+ // cache and redo the /api/show round trip every time. Backend instances
16
+ // hold no other call-scoped state, so reusing them across calls/tools is
17
+ // safe: checkHealth()/getCapabilities() still hit the network live on
18
+ // every call, this only skips the now-redundant capability lookup.
19
+ const backends = new Map();
20
+ function getBackend(modelOverride) {
21
+ const key = modelOverride ?? '';
22
+ let backend = backends.get(key);
23
+ if (!backend) {
24
+ backend = createBackend(config, modelOverride);
25
+ backends.set(key, backend);
26
+ }
27
+ return backend;
28
+ }
29
+ server.tool('health_check', 'Checks whether the local backend (Ollama/llama.cpp/LM Studio) is available and which model is loaded. ' +
30
+ "Use it before delegating if you're not sure sherpa is available.", { model: z.string().optional() }, async ({ model }) => {
31
+ const backend = getBackend(model);
32
+ const result = await healthCheck(backend);
33
+ return { content: [{ type: 'text', text: JSON.stringify(result) }] };
34
+ });
35
+ server.tool('delegate_exploration', 'Delegates to a local model the reading and synthesis of findings over a set of project files/directories, ' +
36
+ "without that content entering the orchestrator's context.", { paths: z.array(z.string()), instruction: z.string(), model: z.string().optional() }, async ({ paths, instruction, model }) => {
37
+ const backend = getBackend(model);
38
+ const result = await delegateExploration(backend, root, config.resultsDir, { maxFiles: config.maxFiles, maxChunks: config.maxChunks }, { paths, instruction });
39
+ return { content: [{ type: 'text', text: JSON.stringify(result) }] };
40
+ });
41
+ server.tool('delegate_search', 'Runs ripgrep over project paths and delegates to a local model the synthesis of the matches ' +
42
+ 'according to the given instruction.', { pattern: z.string(), paths: z.array(z.string()), instruction: z.string(), model: z.string().optional() }, async ({ pattern, paths, instruction, model }) => {
43
+ const backend = getBackend(model);
44
+ const result = await delegateSearch(backend, root, config.resultsDir, { maxFiles: config.maxFiles, maxChunks: config.maxChunks }, { pattern, paths, instruction });
45
+ return { content: [{ type: 'text', text: JSON.stringify(result) }] };
46
+ });
47
+ server.tool('delegate_transform', 'Generates, via a local model, a proposed batch transformation over the given files. ' +
48
+ "Doesn't write to disk — the result is reviewed and applied with apply_transform.", { paths: z.array(z.string()), instruction: z.string(), model: z.string().optional() }, async ({ paths, instruction, model }) => {
49
+ const backend = getBackend(model);
50
+ const result = await delegateTransform(backend, root, config.resultsDir, config.truncationThreshold, { paths, instruction });
51
+ return { content: [{ type: 'text', text: JSON.stringify(result) }] };
52
+ });
53
+ server.tool('apply_transform', 'Applies to the filesystem the changes proposed by a prior delegate_transform, exactly as they were ' +
54
+ 'reviewed — without regenerating anything.', { resultPath: z.string(), paths: z.array(z.string()).optional() }, async ({ resultPath, paths }) => {
55
+ const result = applyTransform(root, resultPath, paths);
56
+ return { content: [{ type: 'text', text: JSON.stringify(result) }] };
57
+ });
58
+ return server;
59
+ }
60
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ const DEFAULT_CHAR_BUDGET = 4000;
2
+ export function structuredTruncate(text, resultPath, charBudget = DEFAULT_CHAR_BUDGET) {
3
+ if (text.length <= charBudget) {
4
+ return { summary: text };
5
+ }
6
+ const items = text.split('\n').filter((line) => line.trim().length > 0);
7
+ let acc = '';
8
+ let count = 0;
9
+ for (const item of items) {
10
+ const candidate = acc ? `${acc}\n${item}` : item;
11
+ if (candidate.length > charBudget)
12
+ break;
13
+ acc = candidate;
14
+ count++;
15
+ }
16
+ return {
17
+ summary: `${acc}\n\n(truncated: ${count} of ${items.length} findings, rest in ${resultPath})`
18
+ };
19
+ }
20
+ //# sourceMappingURL=summary-truncate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"summary-truncate.js","sourceRoot":"","sources":["../src/summary-truncate.ts"],"names":[],"mappings":"AAAA,MAAM,mBAAmB,GAAG,IAAI,CAAC;AAEjC,MAAM,UAAU,kBAAkB,CAChC,IAAY,EACZ,UAAkB,EAClB,aAAqB,mBAAmB;IAExC,IAAI,IAAI,CAAC,MAAM,IAAI,UAAU,EAAE,CAAC;QAC9B,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IAC3B,CAAC;IAED,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACxE,IAAI,GAAG,GAAG,EAAE,CAAC;IACb,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,SAAS,GAAG,GAAG,CAAC,CAAC,CAAC,GAAG,GAAG,KAAK,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;QACjD,IAAI,SAAS,CAAC,MAAM,GAAG,UAAU;YAAE,MAAM;QACzC,GAAG,GAAG,SAAS,CAAC;QAChB,KAAK,EAAE,CAAC;IACV,CAAC;IAED,OAAO;QACL,OAAO,EAAE,GAAG,GAAG,mBAAmB,KAAK,OAAO,KAAK,CAAC,MAAM,sBAAsB,UAAU,GAAG;KAC9F,CAAC;AACJ,CAAC"}
@@ -0,0 +1,49 @@
1
+ import vm from 'node:vm';
2
+ import path from 'node:path';
3
+ export const SYNTAX_VALIDATORS = {
4
+ '.json': (content) => {
5
+ try {
6
+ JSON.parse(content);
7
+ return true;
8
+ }
9
+ catch {
10
+ return false;
11
+ }
12
+ },
13
+ '.js': (content) => {
14
+ try {
15
+ new vm.Script(content);
16
+ return true;
17
+ }
18
+ catch {
19
+ return false;
20
+ }
21
+ },
22
+ '.mjs': (content) => {
23
+ try {
24
+ new vm.Script(content);
25
+ return true;
26
+ }
27
+ catch {
28
+ return false;
29
+ }
30
+ },
31
+ '.cjs': (content) => {
32
+ try {
33
+ new vm.Script(content);
34
+ return true;
35
+ }
36
+ catch {
37
+ return false;
38
+ }
39
+ }
40
+ };
41
+ /** null = no validator available for this extension; the guard doesn't apply. */
42
+ export function validateSyntax(filePath, content) {
43
+ const ext = path.extname(filePath);
44
+ const validator = SYNTAX_VALIDATORS[ext];
45
+ if (!validator)
46
+ return null;
47
+ return validator(content);
48
+ }
49
+ //# sourceMappingURL=syntax-validators.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"syntax-validators.js","sourceRoot":"","sources":["../src/syntax-validators.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAI7B,MAAM,CAAC,MAAM,iBAAiB,GAAoC;IAChE,OAAO,EAAE,CAAC,OAAO,EAAE,EAAE;QACnB,IAAI,CAAC;YACH,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;YACpB,OAAO,IAAI,CAAC;QACd,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;IACD,KAAK,EAAE,CAAC,OAAO,EAAE,EAAE;QACjB,IAAI,CAAC;YACH,IAAI,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;YACvB,OAAO,IAAI,CAAC;QACd,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;IACD,MAAM,EAAE,CAAC,OAAO,EAAE,EAAE;QAClB,IAAI,CAAC;YACH,IAAI,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;YACvB,OAAO,IAAI,CAAC;QACd,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;IACD,MAAM,EAAE,CAAC,OAAO,EAAE,EAAE;QAClB,IAAI,CAAC;YACH,IAAI,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;YACvB,OAAO,IAAI,CAAC;QACd,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;CACF,CAAC;AAEF,iFAAiF;AACjF,MAAM,UAAU,cAAc,CAAC,QAAgB,EAAE,OAAe;IAC9D,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACnC,MAAM,SAAS,GAAG,iBAAiB,CAAC,GAAG,CAAC,CAAC;IACzC,IAAI,CAAC,SAAS;QAAE,OAAO,IAAI,CAAC;IAC5B,OAAO,SAAS,CAAC,OAAO,CAAC,CAAC;AAC5B,CAAC"}
@@ -0,0 +1,59 @@
1
+ import fs from 'node:fs';
2
+ import { resolveWithinRoot } from '../paths.js';
3
+ import { sha256 } from '../hash.js';
4
+ import { RESULT_SCHEMA_VERSION } from './delegate-transform.js';
5
+ export class UnsupportedSchemaVersionError extends Error {
6
+ constructor(found, expected) {
7
+ super(`resultPath has schemaVersion ${found}, this version of sherpa supports ${expected}. ` +
8
+ `Re-run delegate_transform.`);
9
+ this.name = 'UnsupportedSchemaVersionError';
10
+ }
11
+ }
12
+ export function applyTransform(root, resultPath, paths) {
13
+ const resolvedResultPath = resolveWithinRoot(root, resultPath);
14
+ if (!resolvedResultPath) {
15
+ throw new Error(`resultPath outside the project root: ${resultPath}`);
16
+ }
17
+ const raw = fs.readFileSync(resolvedResultPath, 'utf8');
18
+ const data = JSON.parse(raw);
19
+ if (data.schemaVersion !== RESULT_SCHEMA_VERSION) {
20
+ throw new UnsupportedSchemaVersionError(data.schemaVersion, RESULT_SCHEMA_VERSION);
21
+ }
22
+ const filter = paths ? new Set(paths) : null;
23
+ const applied = [];
24
+ const stale = [];
25
+ const skipped = [];
26
+ const failed = [];
27
+ for (const entry of data.files) {
28
+ if (filter && !filter.has(entry.path)) {
29
+ skipped.push(entry.path);
30
+ continue;
31
+ }
32
+ const resolvedTarget = resolveWithinRoot(root, entry.path);
33
+ if (!resolvedTarget) {
34
+ failed.push({ path: entry.path, reason: 'outside_project_root' });
35
+ continue;
36
+ }
37
+ let currentContent;
38
+ try {
39
+ currentContent = fs.readFileSync(resolvedTarget, 'utf8');
40
+ }
41
+ catch {
42
+ failed.push({ path: entry.path, reason: 'read_error' });
43
+ continue;
44
+ }
45
+ if (sha256(currentContent) !== entry.originalHash) {
46
+ stale.push(entry.path);
47
+ continue;
48
+ }
49
+ try {
50
+ fs.writeFileSync(resolvedTarget, entry.proposedContent, 'utf8');
51
+ applied.push(entry.path);
52
+ }
53
+ catch {
54
+ failed.push({ path: entry.path, reason: 'write_error' });
55
+ }
56
+ }
57
+ return { applied, stale, skipped, failed };
58
+ }
59
+ //# sourceMappingURL=apply-transform.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"apply-transform.js","sourceRoot":"","sources":["../../src/tools/apply-transform.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,70 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import crypto from 'node:crypto';
4
+ import { enumerateFiles } from '../file-enumeration.js';
5
+ import { planChunks } from '../chunking.js';
6
+ import { structuredTruncate } from '../summary-truncate.js';
7
+ import { runMapReduce } from '../map-reduce.js';
8
+ function buildMapPrompt(instruction, chunk) {
9
+ const filesBlock = chunk.map((f) => `--- ${f.path} ---\n${f.content}`).join('\n\n');
10
+ return (`Instruction: ${instruction}\n\n` +
11
+ `Analyze the following files and report findings relevant to the instruction, ` +
12
+ `one per line, format "- <file>: <finding>".\n\n${filesBlock}`);
13
+ }
14
+ function buildReducePrompt(instruction, partials) {
15
+ return (`Original instruction: ${instruction}\n\n` +
16
+ `Below are partial findings from different groups of files. Synthesize them into ` +
17
+ `a final list of unique, relevant findings, same format "- <file>: <finding>", ` +
18
+ `no duplicates.\n\n${partials.join('\n\n')}`);
19
+ }
20
+ export async function delegateExploration(backend, root, resultsDir, budgetConfig, params) {
21
+ const health = await backend.checkHealth();
22
+ if (health.status !== 'ok') {
23
+ return {
24
+ status: health.status,
25
+ detail: health.detail,
26
+ ...(health.status === 'model_not_loaded' ? { availableModels: health.availableModels } : {})
27
+ };
28
+ }
29
+ const enumeration = enumerateFiles(root, params.paths, budgetConfig.maxFiles, resultsDir);
30
+ const capabilities = await backend.getCapabilities();
31
+ const reserve = capabilities.maxOutputTokens + 300;
32
+ const fileContents = [];
33
+ const filesSkipped = [...enumeration.filesSkipped];
34
+ for (const filePath of enumeration.files) {
35
+ try {
36
+ const content = fs.readFileSync(filePath, 'utf8');
37
+ fileContents.push({ path: path.relative(root, filePath), content });
38
+ }
39
+ catch {
40
+ filesSkipped.push({ path: path.relative(root, filePath), reason: 'read_error' });
41
+ }
42
+ }
43
+ const { chunks, skipped: chunkSkipped } = planChunks(fileContents, capabilities.contextWindow, reserve);
44
+ filesSkipped.push(...chunkSkipped);
45
+ const { finalText, usage, usedChunkCount, truncatedByChunkBudget, droppedChunks } = await runMapReduce(backend, chunks, budgetConfig, (chunk) => buildMapPrompt(params.instruction, chunk), (partials) => buildReducePrompt(params.instruction, partials), { maxOutputTokens: capabilities.maxOutputTokens }, '(no findings: no processable files)');
46
+ const truncatedByBudget = enumeration.truncatedByBudget || truncatedByChunkBudget;
47
+ for (const droppedChunk of droppedChunks) {
48
+ for (const file of droppedChunk) {
49
+ filesSkipped.push({ path: file.path, reason: 'chunk_budget_exceeded' });
50
+ }
51
+ }
52
+ fs.mkdirSync(path.join(root, resultsDir), { recursive: true });
53
+ const resultPath = path.join(resultsDir, `exploration-${crypto.randomUUID()}.md`);
54
+ fs.writeFileSync(path.join(root, resultPath), finalText, 'utf8');
55
+ const { summary } = structuredTruncate(finalText, resultPath);
56
+ return {
57
+ summary,
58
+ resultPath,
59
+ filesProcessed: chunks.slice(0, usedChunkCount).reduce((sum, c) => sum + c.length, 0),
60
+ filesSkipped,
61
+ budget: {
62
+ maxFiles: budgetConfig.maxFiles,
63
+ maxChunks: budgetConfig.maxChunks,
64
+ filesConsidered: enumeration.files.length,
65
+ truncatedByBudget
66
+ },
67
+ usage
68
+ };
69
+ }
70
+ //# sourceMappingURL=delegate-exploration.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"delegate-exploration.js","sourceRoot":"","sources":["../../src/tools/delegate-exploration.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,117 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import crypto from 'node:crypto';
5
+ import { resolveWithinRoot } from '../paths.js';
6
+ import { planChunks } from '../chunking.js';
7
+ import { structuredTruncate } from '../summary-truncate.js';
8
+ import { runMapReduce } from '../map-reduce.js';
9
+ export class RipgrepNotFoundError extends Error {
10
+ constructor() {
11
+ super('ripgrep (rg) is not installed or not on PATH. Install it: ' +
12
+ 'macOS: brew install ripgrep · Debian/Ubuntu: apt install ripgrep · ' +
13
+ 'https://github.com/BurntSushi/ripgrep#installation');
14
+ this.name = 'RipgrepNotFoundError';
15
+ }
16
+ }
17
+ const RIPGREP_MAX_BUFFER = 64 * 1024 * 1024;
18
+ const defaultSpawnFn = (command, args, options) => spawnSync(command, args, { ...options, maxBuffer: RIPGREP_MAX_BUFFER });
19
+ export function runRipgrep(root, pattern, resolvedPaths, resultsDir, spawnFn = defaultSpawnFn) {
20
+ const result = spawnFn('rg', ['--json', '-C', '2', '-g', `!/${resultsDir}/**`, '--', pattern, ...resolvedPaths], { cwd: root, encoding: 'utf8' });
21
+ if (result.error) {
22
+ const code = result.error.code;
23
+ if (code === 'ENOENT') {
24
+ throw new RipgrepNotFoundError();
25
+ }
26
+ throw new Error(`ripgrep failed to run (${code ?? 'unknown error'}): ${result.error.message}`);
27
+ }
28
+ if (typeof result.status === 'number' && result.status >= 2) {
29
+ throw new Error(`ripgrep failed (exit ${result.status}): ${result.stderr}`);
30
+ }
31
+ const matches = [];
32
+ const lines = (result.stdout ?? '').split('\n').filter(Boolean);
33
+ for (const line of lines) {
34
+ let parsed;
35
+ try {
36
+ parsed = JSON.parse(line);
37
+ }
38
+ catch {
39
+ continue;
40
+ }
41
+ if (parsed.type === 'match') {
42
+ const pathText = parsed.data?.path?.text;
43
+ if (typeof pathText !== 'string') {
44
+ continue;
45
+ }
46
+ matches.push({
47
+ path: path.relative(root, pathText),
48
+ lineNumber: parsed.data.line_number,
49
+ line: parsed.data.lines.text,
50
+ context: []
51
+ });
52
+ }
53
+ }
54
+ return matches;
55
+ }
56
+ function buildMapPrompt(instruction, chunk) {
57
+ const block = chunk.map((c) => `${c.path}: ${c.content}`).join('\n');
58
+ return (`Instruction: ${instruction}\n\nMatches found:\n${block}\n\n` +
59
+ `Synthesize according to the instruction, format "- <file>: <finding>".`);
60
+ }
61
+ function buildReducePrompt(instruction, partials) {
62
+ return (`Original instruction: ${instruction}\n\n` +
63
+ `Synthesize these partial findings into a single list:\n\n${partials.join('\n\n')}`);
64
+ }
65
+ export async function delegateSearch(backend, root, resultsDir, budgetConfig, params, deps = {}) {
66
+ const health = await backend.checkHealth();
67
+ if (health.status !== 'ok') {
68
+ return {
69
+ status: health.status,
70
+ detail: health.detail,
71
+ ...(health.status === 'model_not_loaded' ? { availableModels: health.availableModels } : {})
72
+ };
73
+ }
74
+ const filesSkipped = [];
75
+ const resolvedPaths = [];
76
+ for (const p of params.paths) {
77
+ const resolved = resolveWithinRoot(root, p);
78
+ if (!resolved) {
79
+ filesSkipped.push({ path: p, reason: 'outside_project_root' });
80
+ continue;
81
+ }
82
+ resolvedPaths.push(resolved);
83
+ }
84
+ const matches = resolvedPaths.length > 0 ? runRipgrep(root, params.pattern, resolvedPaths, resultsDir, deps.spawnFn) : [];
85
+ const capabilities = await backend.getCapabilities();
86
+ const reserve = capabilities.maxOutputTokens + 300;
87
+ const matchBlocks = matches.map((m) => ({
88
+ path: `${m.path}:${m.lineNumber}`,
89
+ content: m.line
90
+ }));
91
+ const { chunks } = planChunks(matchBlocks, capabilities.contextWindow, reserve);
92
+ const { finalText, usage, usedChunkCount, truncatedByChunkBudget, droppedChunks } = await runMapReduce(backend, chunks, budgetConfig, (chunk) => buildMapPrompt(params.instruction, chunk), (partials) => buildReducePrompt(params.instruction, partials), { maxOutputTokens: capabilities.maxOutputTokens }, '(no matches)');
93
+ for (const droppedChunk of droppedChunks) {
94
+ for (const match of droppedChunk) {
95
+ filesSkipped.push({ path: match.path, reason: 'chunk_budget_exceeded' });
96
+ }
97
+ }
98
+ fs.mkdirSync(path.join(root, resultsDir), { recursive: true });
99
+ const resultPath = path.join(resultsDir, `search-${crypto.randomUUID()}.md`);
100
+ fs.writeFileSync(path.join(root, resultPath), finalText, 'utf8');
101
+ const { summary } = structuredTruncate(finalText, resultPath);
102
+ return {
103
+ summary,
104
+ resultPath,
105
+ matchCount: matches.length,
106
+ matchesProcessed: chunks.slice(0, usedChunkCount).reduce((sum, c) => sum + c.length, 0),
107
+ filesSkipped,
108
+ budget: {
109
+ maxFiles: budgetConfig.maxFiles,
110
+ maxChunks: budgetConfig.maxChunks,
111
+ filesConsidered: matches.length,
112
+ truncatedByBudget: truncatedByChunkBudget
113
+ },
114
+ usage
115
+ };
116
+ }
117
+ //# sourceMappingURL=delegate-search.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"delegate-search.js","sourceRoot":"","sources":["../../src/tools/delegate-search.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,81 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import crypto from 'node:crypto';
4
+ import { createTwoFilesPatch } from 'diff';
5
+ import { resolveWithinRoot } from '../paths.js';
6
+ import { sha256 } from '../hash.js';
7
+ import { validateSyntax } from '../syntax-validators.js';
8
+ export const RESULT_SCHEMA_VERSION = 1;
9
+ const PREVIEW_LINES = 15;
10
+ export async function delegateTransform(backend, root, resultsDir, truncationThreshold, params) {
11
+ const health = await backend.checkHealth();
12
+ if (health.status !== 'ok') {
13
+ return {
14
+ status: health.status,
15
+ detail: health.detail,
16
+ ...(health.status === 'model_not_loaded' ? { availableModels: health.availableModels } : {})
17
+ };
18
+ }
19
+ const capabilities = await backend.getCapabilities();
20
+ const results = [];
21
+ const proposals = [];
22
+ let calls = 0;
23
+ let tokensIn = 0;
24
+ let tokensOut = 0;
25
+ let elapsedMs = 0;
26
+ for (const inputPath of params.paths) {
27
+ const resolved = resolveWithinRoot(root, inputPath);
28
+ if (!resolved) {
29
+ results.push({ path: inputPath, status: 'rejected', reason: 'outside_project_root' });
30
+ continue;
31
+ }
32
+ const relPath = path.relative(root, resolved);
33
+ let original;
34
+ try {
35
+ original = fs.readFileSync(resolved, 'utf8');
36
+ }
37
+ catch {
38
+ results.push({ path: relPath, status: 'skipped', reason: 'read_error' });
39
+ continue;
40
+ }
41
+ const estimatedInputTokens = Math.ceil(original.length / 4);
42
+ const marginTokens = 200;
43
+ if (estimatedInputTokens + marginTokens > capabilities.maxOutputTokens) {
44
+ results.push({ path: relPath, status: 'skipped', reason: 'exceeds_model_output_budget' });
45
+ continue;
46
+ }
47
+ const prompt = `Instruction: ${params.instruction}\n\nFile (${relPath}):\n${original}\n\n` +
48
+ `Return the complete transformed file according to the instruction, with no extra explanation.`;
49
+ const { text: proposedContent, usage } = await backend.generate(prompt, {
50
+ maxOutputTokens: capabilities.maxOutputTokens
51
+ });
52
+ calls++;
53
+ tokensIn += usage.tokensIn;
54
+ tokensOut += usage.tokensOut;
55
+ elapsedMs += usage.elapsedMs;
56
+ const originalLines = original.split('\n').length;
57
+ const proposedLines = proposedContent.split('\n').length;
58
+ if (proposedLines < truncationThreshold * originalLines) {
59
+ results.push({ path: relPath, status: 'rejected', reason: 'output_possibly_truncated' });
60
+ continue;
61
+ }
62
+ const syntaxOk = validateSyntax(relPath, proposedContent);
63
+ if (syntaxOk === false) {
64
+ results.push({ path: relPath, status: 'rejected', reason: 'syntax_validation_failed' });
65
+ continue;
66
+ }
67
+ if (proposedContent === original) {
68
+ results.push({ path: relPath, status: 'unchanged' });
69
+ continue;
70
+ }
71
+ const diff = createTwoFilesPatch(relPath, relPath, original, proposedContent);
72
+ proposals.push({ path: relPath, originalHash: sha256(original), proposedContent, diff });
73
+ const diffPreview = diff.split('\n').slice(0, PREVIEW_LINES).join('\n');
74
+ results.push({ path: relPath, status: 'would_change', diffPreview });
75
+ }
76
+ fs.mkdirSync(path.join(root, resultsDir), { recursive: true });
77
+ const resultPath = path.join(resultsDir, `transform-${crypto.randomUUID()}.json`);
78
+ fs.writeFileSync(path.join(root, resultPath), JSON.stringify({ schemaVersion: RESULT_SCHEMA_VERSION, files: proposals }, null, 2), 'utf8');
79
+ return { results, resultPath, usage: { calls, tokensIn, tokensOut, elapsedMs } };
80
+ }
81
+ //# sourceMappingURL=delegate-transform.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"delegate-transform.js","sourceRoot":"","sources":["../../src/tools/delegate-transform.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ export async function healthCheck(backend) {
2
+ const health = await backend.checkHealth();
3
+ if (health.status !== 'ok') {
4
+ return health;
5
+ }
6
+ const capabilities = await backend.getCapabilities();
7
+ return {
8
+ status: 'ok',
9
+ model: health.model,
10
+ contextWindow: capabilities.contextWindow,
11
+ maxOutputTokens: capabilities.maxOutputTokens
12
+ };
13
+ }
14
+ //# sourceMappingURL=health-check.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"health-check.js","sourceRoot":"","sources":["../../src/tools/health-check.ts"],"names":[],"mappings":"AAWA,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,OAAgB;IAChD,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,WAAW,EAAE,CAAC;IAC3C,IAAI,MAAM,CAAC,MAAM,KAAK,IAAI,EAAE,CAAC;QAC3B,OAAO,MAAM,CAAC;IAChB,CAAC;IACD,MAAM,YAAY,GAAG,MAAM,OAAO,CAAC,eAAe,EAAE,CAAC;IACrD,OAAO;QACL,MAAM,EAAE,IAAI;QACZ,KAAK,EAAE,MAAM,CAAC,KAAK;QACnB,aAAa,EAAE,YAAY,CAAC,aAAa;QACzC,eAAe,EAAE,YAAY,CAAC,eAAe;KAC9C,CAAC;AACJ,CAAC"}
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "sherpa-mcp",
3
+ "version": "0.1.0",
4
+ "description": "MCP server that delegates high-volume file exploration, search, and transformation to a local LLM backend",
5
+ "author": "Gastón Parravicini",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/Tongas/sherpa-mcp.git",
10
+ "directory": "mcp-server"
11
+ },
12
+ "homepage": "https://github.com/Tongas/sherpa-mcp#readme",
13
+ "bugs": {
14
+ "url": "https://github.com/Tongas/sherpa-mcp/issues"
15
+ },
16
+ "type": "module",
17
+ "bin": {
18
+ "sherpa-mcp": "./dist/index.js"
19
+ },
20
+ "main": "./dist/index.js",
21
+ "files": [
22
+ "dist"
23
+ ],
24
+ "scripts": {
25
+ "build": "tsc -p tsconfig.json",
26
+ "prepare": "npm run build",
27
+ "prepublishOnly": "npm run build",
28
+ "test": "vitest run",
29
+ "start": "node dist/index.js"
30
+ },
31
+ "dependencies": {
32
+ "@modelcontextprotocol/sdk": "^1.0.0",
33
+ "zod": "^3.23.0",
34
+ "diff": "^5.2.0",
35
+ "ignore": "^5.3.0"
36
+ },
37
+ "devDependencies": {
38
+ "typescript": "^5.5.0",
39
+ "vitest": "^2.0.0",
40
+ "@types/node": "^20.14.0",
41
+ "@types/diff": "^5.2.1"
42
+ },
43
+ "engines": {
44
+ "node": ">=18.0.0"
45
+ }
46
+ }