repowisestage 0.0.46 → 0.0.47

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 (2) hide show
  1. package/dist/bin/repowise.js +317 -237
  2. package/package.json +1 -1
@@ -721,6 +721,200 @@ var init_registry = __esm({
721
721
  }
722
722
  });
723
723
 
724
+ // ../listener/dist/lsp/installer.js
725
+ import { promises as fs } from "fs";
726
+ import { join as join14, dirname as dirname5 } from "path";
727
+ import { spawn as spawn3 } from "child_process";
728
+ function getLspInstallDir() {
729
+ return join14(getConfigDir(), "lsp-servers");
730
+ }
731
+ function getLspBinDir() {
732
+ return join14(getLspInstallDir(), "node_modules", ".bin");
733
+ }
734
+ async function ensureNpmLspInstalled(config2) {
735
+ if (!config2.npmPackage)
736
+ return { installed: false, skipped: "no-npm-package" };
737
+ const installDir = getLspInstallDir();
738
+ const binPath = join14(installDir, "node_modules", ".bin", config2.command);
739
+ if (await pathExists(binPath)) {
740
+ return { installed: false, skipped: "already-present" };
741
+ }
742
+ try {
743
+ await fs.mkdir(installDir, { recursive: true });
744
+ const pkgJsonPath = join14(installDir, "package.json");
745
+ if (!await pathExists(pkgJsonPath)) {
746
+ await fs.writeFile(pkgJsonPath, JSON.stringify({ name: "repowise-lsp-servers", private: true, version: "0.0.0" }, null, 2), "utf-8");
747
+ }
748
+ await runNpmInstall(installDir, config2.npmPackage);
749
+ if (!await pathExists(binPath)) {
750
+ return {
751
+ installed: false,
752
+ error: `npm install completed but ${config2.command} still not at ${binPath}`
753
+ };
754
+ }
755
+ return { installed: true };
756
+ } catch (err) {
757
+ return {
758
+ installed: false,
759
+ error: err instanceof Error ? err.message : String(err)
760
+ };
761
+ }
762
+ }
763
+ async function resolveNpmCommand() {
764
+ const nodeDir = dirname5(process.execPath);
765
+ for (const candidate of [join14(nodeDir, "npm"), join14(nodeDir, "npm.cmd")]) {
766
+ try {
767
+ await fs.access(candidate);
768
+ return candidate;
769
+ } catch {
770
+ }
771
+ }
772
+ for (const candidate of ["/opt/homebrew/bin/npm", "/usr/local/bin/npm", "/usr/bin/npm"]) {
773
+ try {
774
+ await fs.access(candidate);
775
+ return candidate;
776
+ } catch {
777
+ }
778
+ }
779
+ return "npm";
780
+ }
781
+ async function runNpmInstall(cwd, pkg2) {
782
+ const npmCmd = await resolveNpmCommand();
783
+ const nodeDir = dirname5(process.execPath);
784
+ const augmentedPath = process.env.PATH ? `${nodeDir}:${process.env.PATH}` : nodeDir;
785
+ return new Promise((resolve4, reject) => {
786
+ const child = spawn3(npmCmd, ["install", "--no-audit", "--no-fund", "--silent", pkg2], {
787
+ cwd,
788
+ stdio: ["ignore", "pipe", "pipe"],
789
+ env: { ...process.env, PATH: augmentedPath }
790
+ });
791
+ let stderr = "";
792
+ child.stderr.on("data", (chunk) => {
793
+ stderr += chunk.toString();
794
+ });
795
+ child.on("error", (err) => reject(err));
796
+ child.on("close", (code) => {
797
+ if (code === 0) {
798
+ resolve4();
799
+ } else {
800
+ reject(new Error(`npm install ${pkg2} exited ${(code ?? -1).toString()}: ${sanitizeNpmStderr(stderr)}`));
801
+ }
802
+ });
803
+ });
804
+ }
805
+ function sanitizeNpmStderr(raw) {
806
+ const truncated = raw.split("\n").slice(0, 3).join(" ").trim();
807
+ return truncated.replace(/(https?:\/\/)[^@\s/]*@/g, (_match, scheme) => `${scheme}<redacted>@`);
808
+ }
809
+ async function pathExists(p) {
810
+ try {
811
+ await fs.access(p);
812
+ return true;
813
+ } catch {
814
+ return false;
815
+ }
816
+ }
817
+ async function detectRepoLanguages(repoRoot) {
818
+ const found = /* @__PURE__ */ new Set();
819
+ let inspected = 0;
820
+ const MAX_INSPECT = 200;
821
+ async function inspect(dir) {
822
+ if (inspected >= MAX_INSPECT)
823
+ return;
824
+ let entries;
825
+ try {
826
+ entries = await fs.readdir(dir);
827
+ } catch {
828
+ return;
829
+ }
830
+ for (const name of entries) {
831
+ if (inspected >= MAX_INSPECT)
832
+ return;
833
+ if (name.startsWith("."))
834
+ continue;
835
+ if (name === "node_modules" || name === "dist" || name === "build")
836
+ continue;
837
+ const lang = detectLanguage(name);
838
+ if (lang) {
839
+ found.add(lang);
840
+ inspected += 1;
841
+ }
842
+ }
843
+ }
844
+ await inspect(repoRoot);
845
+ let topEntries = [];
846
+ try {
847
+ topEntries = await fs.readdir(repoRoot);
848
+ } catch {
849
+ return found;
850
+ }
851
+ for (const name of topEntries) {
852
+ if (name.startsWith("."))
853
+ continue;
854
+ if (name === "node_modules" || name === "dist" || name === "build")
855
+ continue;
856
+ const sub = join14(repoRoot, name);
857
+ try {
858
+ const stat7 = await fs.stat(sub);
859
+ if (stat7.isDirectory())
860
+ await inspect(sub);
861
+ } catch {
862
+ }
863
+ }
864
+ return found;
865
+ }
866
+ async function prepareLspServersForRepos(repos) {
867
+ const detected = /* @__PURE__ */ new Set();
868
+ for (const r of repos) {
869
+ if (!r.localPath)
870
+ continue;
871
+ try {
872
+ const stat7 = await fs.stat(r.localPath);
873
+ if (!stat7.isDirectory())
874
+ continue;
875
+ } catch {
876
+ continue;
877
+ }
878
+ try {
879
+ const langs = await detectRepoLanguages(r.localPath);
880
+ for (const l of langs)
881
+ detected.add(l);
882
+ } catch {
883
+ }
884
+ }
885
+ const results = [];
886
+ for (const language of detected) {
887
+ const configs = LSP_REGISTRY[language];
888
+ const npmConfig = configs.find((c) => c.npmPackage);
889
+ if (!npmConfig) {
890
+ results.push({
891
+ language,
892
+ installed: false,
893
+ alreadyPresent: false,
894
+ skippedNoNpmPackage: true,
895
+ hint: configs[0]?.installHint
896
+ });
897
+ continue;
898
+ }
899
+ const outcome = await ensureNpmLspInstalled(npmConfig);
900
+ results.push({
901
+ language,
902
+ installed: outcome.installed,
903
+ alreadyPresent: outcome.skipped === "already-present",
904
+ skippedNoNpmPackage: false,
905
+ error: outcome.error
906
+ });
907
+ }
908
+ return results;
909
+ }
910
+ var init_installer = __esm({
911
+ "../listener/dist/lsp/installer.js"() {
912
+ "use strict";
913
+ init_config_dir();
914
+ init_registry();
915
+ }
916
+ });
917
+
724
918
  // ../../packages/shared/src/types/typed-resolution.ts
725
919
  function typedResolutionKey(filePath, propertyName, line, column) {
726
920
  return `${filePath}\0${propertyName}\0${line.toString()}\0${column.toString()}`;
@@ -1132,7 +1326,8 @@ __export(lsp_tools_exports, {
1132
1326
  lspWorkspaceSymbol: () => lspWorkspaceSymbol
1133
1327
  });
1134
1328
  import { fileURLToPath as fileURLToPath2 } from "url";
1135
- import { relative as pathRelative, resolve as pathResolve3 } from "path";
1329
+ import { relative as pathRelative, resolve as pathResolve3, join as pathJoin } from "path";
1330
+ import { existsSync } from "fs";
1136
1331
  import { execSync } from "child_process";
1137
1332
  function probeBinary(command) {
1138
1333
  if (process.env["VITEST"])
@@ -1142,8 +1337,12 @@ function probeBinary(command) {
1142
1337
  return cached;
1143
1338
  let found = false;
1144
1339
  try {
1145
- execSync(`which ${command}`, { stdio: "ignore", timeout: 200 });
1146
- found = true;
1340
+ if (existsSync(pathJoin(getLspBinDir(), command))) {
1341
+ found = true;
1342
+ } else {
1343
+ execSync(`which ${command}`, { stdio: "ignore", timeout: 200 });
1344
+ found = true;
1345
+ }
1147
1346
  } catch {
1148
1347
  found = false;
1149
1348
  }
@@ -1465,6 +1664,7 @@ var init_lsp_tools = __esm({
1465
1664
  "../listener/dist/lsp/lsp-tools.js"() {
1466
1665
  "use strict";
1467
1666
  init_registry();
1667
+ init_installer();
1468
1668
  binaryProbeCache = /* @__PURE__ */ new Map();
1469
1669
  }
1470
1670
  });
@@ -3042,196 +3242,8 @@ var LspClient = class extends EventEmitter {
3042
3242
  }
3043
3243
  };
3044
3244
 
3045
- // ../listener/dist/lsp/installer.js
3046
- init_config_dir();
3047
- init_registry();
3048
- import { promises as fs } from "fs";
3049
- import { join as join14, dirname as dirname5 } from "path";
3050
- import { spawn as spawn3 } from "child_process";
3051
- function getLspInstallDir() {
3052
- return join14(getConfigDir(), "lsp-servers");
3053
- }
3054
- function getLspBinDir() {
3055
- return join14(getLspInstallDir(), "node_modules", ".bin");
3056
- }
3057
- async function ensureNpmLspInstalled(config2) {
3058
- if (!config2.npmPackage)
3059
- return { installed: false, skipped: "no-npm-package" };
3060
- const installDir = getLspInstallDir();
3061
- const binPath = join14(installDir, "node_modules", ".bin", config2.command);
3062
- if (await pathExists(binPath)) {
3063
- return { installed: false, skipped: "already-present" };
3064
- }
3065
- try {
3066
- await fs.mkdir(installDir, { recursive: true });
3067
- const pkgJsonPath = join14(installDir, "package.json");
3068
- if (!await pathExists(pkgJsonPath)) {
3069
- await fs.writeFile(pkgJsonPath, JSON.stringify({ name: "repowise-lsp-servers", private: true, version: "0.0.0" }, null, 2), "utf-8");
3070
- }
3071
- await runNpmInstall(installDir, config2.npmPackage);
3072
- if (!await pathExists(binPath)) {
3073
- return {
3074
- installed: false,
3075
- error: `npm install completed but ${config2.command} still not at ${binPath}`
3076
- };
3077
- }
3078
- return { installed: true };
3079
- } catch (err) {
3080
- return {
3081
- installed: false,
3082
- error: err instanceof Error ? err.message : String(err)
3083
- };
3084
- }
3085
- }
3086
- async function resolveNpmCommand() {
3087
- const nodeDir = dirname5(process.execPath);
3088
- for (const candidate of [join14(nodeDir, "npm"), join14(nodeDir, "npm.cmd")]) {
3089
- try {
3090
- await fs.access(candidate);
3091
- return candidate;
3092
- } catch {
3093
- }
3094
- }
3095
- for (const candidate of ["/opt/homebrew/bin/npm", "/usr/local/bin/npm", "/usr/bin/npm"]) {
3096
- try {
3097
- await fs.access(candidate);
3098
- return candidate;
3099
- } catch {
3100
- }
3101
- }
3102
- return "npm";
3103
- }
3104
- async function runNpmInstall(cwd, pkg2) {
3105
- const npmCmd = await resolveNpmCommand();
3106
- const nodeDir = dirname5(process.execPath);
3107
- const augmentedPath = process.env.PATH ? `${nodeDir}:${process.env.PATH}` : nodeDir;
3108
- return new Promise((resolve4, reject) => {
3109
- const child = spawn3(npmCmd, ["install", "--no-audit", "--no-fund", "--silent", pkg2], {
3110
- cwd,
3111
- stdio: ["ignore", "pipe", "pipe"],
3112
- env: { ...process.env, PATH: augmentedPath }
3113
- });
3114
- let stderr = "";
3115
- child.stderr.on("data", (chunk) => {
3116
- stderr += chunk.toString();
3117
- });
3118
- child.on("error", (err) => reject(err));
3119
- child.on("close", (code) => {
3120
- if (code === 0) {
3121
- resolve4();
3122
- } else {
3123
- reject(new Error(`npm install ${pkg2} exited ${(code ?? -1).toString()}: ${sanitizeNpmStderr(stderr)}`));
3124
- }
3125
- });
3126
- });
3127
- }
3128
- function sanitizeNpmStderr(raw) {
3129
- const truncated = raw.split("\n").slice(0, 3).join(" ").trim();
3130
- return truncated.replace(/(https?:\/\/)[^@\s/]*@/g, (_match, scheme) => `${scheme}<redacted>@`);
3131
- }
3132
- async function pathExists(p) {
3133
- try {
3134
- await fs.access(p);
3135
- return true;
3136
- } catch {
3137
- return false;
3138
- }
3139
- }
3140
- async function detectRepoLanguages(repoRoot) {
3141
- const found = /* @__PURE__ */ new Set();
3142
- let inspected = 0;
3143
- const MAX_INSPECT = 200;
3144
- async function inspect(dir) {
3145
- if (inspected >= MAX_INSPECT)
3146
- return;
3147
- let entries;
3148
- try {
3149
- entries = await fs.readdir(dir);
3150
- } catch {
3151
- return;
3152
- }
3153
- for (const name of entries) {
3154
- if (inspected >= MAX_INSPECT)
3155
- return;
3156
- if (name.startsWith("."))
3157
- continue;
3158
- if (name === "node_modules" || name === "dist" || name === "build")
3159
- continue;
3160
- const lang = detectLanguage(name);
3161
- if (lang) {
3162
- found.add(lang);
3163
- inspected += 1;
3164
- }
3165
- }
3166
- }
3167
- await inspect(repoRoot);
3168
- let topEntries = [];
3169
- try {
3170
- topEntries = await fs.readdir(repoRoot);
3171
- } catch {
3172
- return found;
3173
- }
3174
- for (const name of topEntries) {
3175
- if (name.startsWith("."))
3176
- continue;
3177
- if (name === "node_modules" || name === "dist" || name === "build")
3178
- continue;
3179
- const sub = join14(repoRoot, name);
3180
- try {
3181
- const stat7 = await fs.stat(sub);
3182
- if (stat7.isDirectory())
3183
- await inspect(sub);
3184
- } catch {
3185
- }
3186
- }
3187
- return found;
3188
- }
3189
- async function prepareLspServersForRepos(repos) {
3190
- const detected = /* @__PURE__ */ new Set();
3191
- for (const r of repos) {
3192
- if (!r.localPath)
3193
- continue;
3194
- try {
3195
- const stat7 = await fs.stat(r.localPath);
3196
- if (!stat7.isDirectory())
3197
- continue;
3198
- } catch {
3199
- continue;
3200
- }
3201
- try {
3202
- const langs = await detectRepoLanguages(r.localPath);
3203
- for (const l of langs)
3204
- detected.add(l);
3205
- } catch {
3206
- }
3207
- }
3208
- const results = [];
3209
- for (const language of detected) {
3210
- const configs = LSP_REGISTRY[language];
3211
- const npmConfig = configs.find((c) => c.npmPackage);
3212
- if (!npmConfig) {
3213
- results.push({
3214
- language,
3215
- installed: false,
3216
- alreadyPresent: false,
3217
- skippedNoNpmPackage: true,
3218
- hint: configs[0]?.installHint
3219
- });
3220
- continue;
3221
- }
3222
- const outcome = await ensureNpmLspInstalled(npmConfig);
3223
- results.push({
3224
- language,
3225
- installed: outcome.installed,
3226
- alreadyPresent: outcome.skipped === "already-present",
3227
- skippedNoNpmPackage: false,
3228
- error: outcome.error
3229
- });
3230
- }
3231
- return results;
3232
- }
3233
-
3234
3245
  // ../listener/dist/lsp/workspace-session.js
3246
+ init_installer();
3235
3247
  function keyOf(args) {
3236
3248
  return `${args.repoRoot}\0${args.language}`;
3237
3249
  }
@@ -4866,24 +4878,49 @@ function batchQuery(graphJson, req) {
4866
4878
  const allQueries = req.queries ?? [];
4867
4879
  const queries = allQueries.slice(0, BATCH_CAP);
4868
4880
  const droppedCount = Math.max(0, allQueries.length - queries.length);
4869
- const results = queries.map((q) => {
4881
+ const results = queries.map((rawQ) => {
4882
+ const q = rawQ;
4883
+ const toolName = q.tool ?? "";
4884
+ const params = q.params ?? q.arguments;
4885
+ if (!params || typeof params !== "object" || Array.isArray(params)) {
4886
+ return {
4887
+ ok: false,
4888
+ tool: toolName,
4889
+ error: "params required (object) \u2014 pass `params` or `arguments` per sub-query"
4890
+ };
4891
+ }
4892
+ const args = params;
4870
4893
  try {
4871
- switch (q.tool) {
4894
+ switch (toolName) {
4872
4895
  case "find_symbol":
4873
- return { ok: true, tool: q.tool, result: findSymbol(graph, q.params) };
4896
+ return {
4897
+ ok: true,
4898
+ tool: toolName,
4899
+ result: findSymbol(graph, args)
4900
+ };
4874
4901
  case "get_symbol":
4875
- return { ok: true, tool: q.tool, result: getSymbol(graph, q.params) };
4902
+ return { ok: true, tool: toolName, result: getSymbol(graph, args) };
4876
4903
  case "get_impact":
4877
- return { ok: true, tool: q.tool, result: getImpact(graph, q.params) };
4904
+ return { ok: true, tool: toolName, result: getImpact(graph, args) };
4878
4905
  case "list_edges":
4879
- return { ok: true, tool: q.tool, result: listEdges(graph, q.params) };
4906
+ return { ok: true, tool: toolName, result: listEdges(graph, args) };
4880
4907
  case "search_pattern":
4881
- return { ok: true, tool: q.tool, result: searchPattern(graph, q.params) };
4908
+ return {
4909
+ ok: true,
4910
+ tool: toolName,
4911
+ result: searchPattern(graph, args)
4912
+ };
4913
+ default:
4914
+ return {
4915
+ ok: false,
4916
+ tool: toolName,
4917
+ error: `unsupported sub-query tool: ${String(toolName)}`
4918
+ };
4882
4919
  }
4883
4920
  } catch (err) {
4884
4921
  return {
4885
4922
  ok: false,
4886
- tool: q.tool,
4923
+ tool: toolName,
4887
4924
  error: err instanceof Error ? err.message : String(err)
4888
4925
  };
4889
4926
  }
@@ -5121,7 +5158,14 @@ function handleRequest(req, res, options, sessions, secretCtx) {
5121
5158
  sessions.set(sessionId, { repoId: body.repoId });
5122
5159
  return sendJson(res, 200, { sessionId });
5123
5160
  });
5124
- }).catch((err) => sendJson(res, 500, { error: err instanceof Error ? err.message : String(err) }));
5161
+ }).catch((err) => {
5162
+ const rawMsg = err instanceof Error ? err.message : String(err);
5163
+ const safeMsg = stripAbsolutePaths(rawMsg);
5164
+ if (err?.code === "ENOENT") {
5165
+ return sendJson(res, 404, { error: "unknown_repo" });
5166
+ }
5167
+ sendJson(res, 500, { error: safeMsg });
5168
+ });
5125
5169
  return;
5126
5170
  }
5127
5171
  if (method === "POST" && url === "/mcp/disconnect") {
@@ -5305,6 +5349,7 @@ function dispatchSessionTool(req, res, url, sessions, logger, handler) {
5305
5349
  latencyMs: Date.now() - startedAt,
5306
5350
  error: "unknown_session"
5307
5351
  });
5352
+ logToStdout("rejected", tool, null, Date.now() - startedAt, "unknown_session");
5308
5353
  return sendJson(res, 404, { error: "unknown_session" });
5309
5354
  }
5310
5355
  try {
@@ -5319,8 +5364,10 @@ function dispatchSessionTool(req, res, url, sessions, logger, handler) {
5319
5364
  status: "ok",
5320
5365
  latencyMs: Date.now() - startedAt
5321
5366
  });
5367
+ logToStdout("ok", tool, session.repoId, Date.now() - startedAt);
5322
5368
  return sendJson(res, 200, result.value);
5323
5369
  }
5370
+ const rejectErr = typeof result.body["error"] === "string" ? result.body["error"] : "tool_rejected";
5324
5371
  recordLog(logger, {
5325
5372
  ts: new Date(startedAt).toISOString(),
5326
5373
  sessionId: body.sessionId,
@@ -5329,8 +5376,9 @@ function dispatchSessionTool(req, res, url, sessions, logger, handler) {
5329
5376
  args: redactArgs(body),
5330
5377
  status: "rejected",
5331
5378
  latencyMs: Date.now() - startedAt,
5332
- error: typeof result.body["error"] === "string" ? result.body["error"] : "tool_rejected"
5379
+ error: rejectErr
5333
5380
  });
5381
+ logToStdout("rejected", tool, session.repoId, Date.now() - startedAt, rejectErr);
5334
5382
  return sendJson(res, result.status, result.body);
5335
5383
  } catch (err) {
5336
5384
  const isTimeout = err instanceof ToolTimeoutError;
@@ -5346,6 +5394,7 @@ function dispatchSessionTool(req, res, url, sessions, logger, handler) {
5346
5394
  latencyMs: Date.now() - startedAt,
5347
5395
  error: safeMsg
5348
5396
  });
5397
+ logToStdout("error", tool, session.repoId, Date.now() - startedAt, safeMsg);
5349
5398
  if (isTimeout) {
5350
5399
  return sendJson(res, 504, { error: "tool-timeout", tool });
5351
5400
  }
@@ -5365,6 +5414,11 @@ function recordLog(logger, entry) {
5365
5414
  return;
5366
5415
  void logger.append(entry);
5367
5416
  }
5417
+ function logToStdout(status2, tool, repoId, latencyMs, error) {
5418
+ const repoFrag = repoId ? `repo=${repoId}` : "repo=\u2014";
5419
+ const errFrag = error ? ` error=${error}` : "";
5420
+ console.log(`[mcp] ${status2} ${tool} ${repoFrag} ${latencyMs}ms${errFrag}`);
5421
+ }
5368
5422
  var REDACT_FIELDS = /* @__PURE__ */ new Set([
5369
5423
  "query",
5370
5424
  "pattern",
@@ -6044,6 +6098,9 @@ async function runAutoConfig(opts) {
6044
6098
  return results;
6045
6099
  }
6046
6100
 
6101
+ // ../listener/dist/main.js
6102
+ init_installer();
6103
+
6047
6104
  // ../listener/dist/typed-resolution/resolver-loop.js
6048
6105
  init_src();
6049
6106
  init_registry();
@@ -7727,11 +7784,11 @@ async function writeClaudeSubagentHook(repoRoot, contextFolder) {
7727
7784
  }
7728
7785
 
7729
7786
  // src/lib/gitignore.ts
7730
- import { readFileSync as readFileSync2, writeFileSync, existsSync } from "fs";
7787
+ import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync2 } from "fs";
7731
7788
  import { join as join32 } from "path";
7732
7789
  function ensureGitignore(repoRoot, entry) {
7733
7790
  const gitignorePath = join32(repoRoot, ".gitignore");
7734
- if (existsSync(gitignorePath)) {
7791
+ if (existsSync2(gitignorePath)) {
7735
7792
  const content = readFileSync2(gitignorePath, "utf-8");
7736
7793
  const lines = content.split("\n").map((l) => l.trim());
7737
7794
  if (lines.includes(entry) || lines.includes(entry + "/")) {
@@ -10521,6 +10578,7 @@ async function mcpShim(opts) {
10521
10578
  const stderr = opts.stderr ?? process.stderr;
10522
10579
  const maxBytes = opts.maxRequestBytes ?? DEFAULT_MAX;
10523
10580
  const postJson = opts.postJson ?? defaultPostJson;
10581
+ const getJson = opts.getJson ?? defaultGetJson;
10524
10582
  const sessionState = { sessionId: null };
10525
10583
  const rl = createInterface2({ input: stdin, crlfDelay: Infinity });
10526
10584
  for await (const rawLine of rl) {
@@ -10566,6 +10624,7 @@ async function mcpShim(opts) {
10566
10624
  opts.repoId,
10567
10625
  parsed,
10568
10626
  postJson,
10627
+ getJson,
10569
10628
  headers,
10570
10629
  sessionState
10571
10630
  );
@@ -10608,7 +10667,7 @@ function writeJson(stream, value) {
10608
10667
  stream.write(`${JSON.stringify(value)}
10609
10668
  `);
10610
10669
  }
10611
- async function routeMessage(endpoint, repoId, message, postJson, headers, sessionState) {
10670
+ async function routeMessage(endpoint, repoId, message, postJson, getJson, headers, sessionState) {
10612
10671
  const msg = message;
10613
10672
  const rpcId = msg.id;
10614
10673
  const method = msg.method;
@@ -10624,41 +10683,28 @@ async function routeMessage(endpoint, repoId, message, postJson, headers, sessio
10624
10683
  };
10625
10684
  }
10626
10685
  if (method === "tools/list") {
10627
- const tools = [
10628
- "find_symbol",
10629
- "get_symbol",
10630
- "get_impact",
10631
- "list_edges",
10632
- "search_pattern",
10633
- "batch_query",
10634
- "find_callers",
10635
- "find_references",
10636
- "get_deps",
10637
- "get_call_graph",
10638
- "find_tests_for_symbol",
10639
- "get_todos",
10640
- "get_freshness",
10641
- "lsp_definition",
10642
- "lsp_references",
10643
- "lsp_hover",
10644
- "lsp_workspace_symbol",
10645
- "lsp_document_symbol",
10646
- "lsp_call_hierarchy",
10647
- "lsp_implementation",
10648
- "lsp_type_hierarchy"
10649
- ].map((name) => ({ name, description: "", inputSchema: { type: "object" } }));
10686
+ const catalog = await getJson(`${endpoint}/mcp/tools`, headers);
10687
+ const tools = (catalog.tools ?? []).map((t) => ({
10688
+ name: t.name,
10689
+ description: [t.description, t.whenToUse].filter((s) => s && s.length > 0).join("\n\n"),
10690
+ inputSchema: t.inputSchema ?? { type: "object" }
10691
+ }));
10650
10692
  return { jsonrpc: "2.0", id: rpcId, result: { tools } };
10651
10693
  }
10652
- if (!sessionState.sessionId) {
10694
+ async function connect() {
10653
10695
  const connectResp = await postJson(`${endpoint}/mcp/connect`, { repoId }, headers);
10654
- if (!connectResp.sessionId) {
10696
+ return connectResp.sessionId ?? null;
10697
+ }
10698
+ if (!sessionState.sessionId) {
10699
+ const sid = await connect();
10700
+ if (!sid) {
10655
10701
  return {
10656
10702
  jsonrpc: "2.0",
10657
10703
  id: rpcId,
10658
10704
  error: { code: -32002, message: "failed to establish MCP session" }
10659
10705
  };
10660
10706
  }
10661
- sessionState.sessionId = connectResp.sessionId;
10707
+ sessionState.sessionId = sid;
10662
10708
  }
10663
10709
  if (method === "tools/call") {
10664
10710
  const params = msg.params ?? {};
@@ -10672,12 +10718,33 @@ async function routeMessage(endpoint, repoId, message, postJson, headers, sessio
10672
10718
  error: { code: -32602, message: "tools/call missing name" }
10673
10719
  };
10674
10720
  }
10675
- const result = await postJson(
10676
- `${endpoint}/mcp/tools/${name}`,
10677
- { sessionId: sessionState.sessionId, ...args },
10678
- headers
10679
- );
10680
- return { jsonrpc: "2.0", id: rpcId, result };
10721
+ const callTool = (sid) => postJson(`${endpoint}/mcp/tools/${name}`, { sessionId: sid, ...args }, headers);
10722
+ let result;
10723
+ try {
10724
+ result = await callTool(sessionState.sessionId);
10725
+ } catch (err) {
10726
+ const detail = err.message;
10727
+ const isStaleSession = /HTTP (401|404)/.test(detail) || /session/i.test(detail) || /unknown_session/i.test(detail);
10728
+ if (!isStaleSession) throw err;
10729
+ sessionState.sessionId = null;
10730
+ const fresh = await connect();
10731
+ if (!fresh) {
10732
+ return {
10733
+ jsonrpc: "2.0",
10734
+ id: rpcId,
10735
+ error: { code: -32002, message: "failed to establish MCP session (after retry)" }
10736
+ };
10737
+ }
10738
+ sessionState.sessionId = fresh;
10739
+ result = await callTool(fresh);
10740
+ }
10741
+ return {
10742
+ jsonrpc: "2.0",
10743
+ id: rpcId,
10744
+ result: {
10745
+ content: [{ type: "text", text: JSON.stringify(result) }]
10746
+ }
10747
+ };
10681
10748
  }
10682
10749
  return {
10683
10750
  jsonrpc: "2.0",
@@ -10702,6 +10769,19 @@ async function defaultPostJson(url, body, headers = { "content-type": "applicati
10702
10769
  }
10703
10770
  return await res.json();
10704
10771
  }
10772
+ async function defaultGetJson(url, headers = {}) {
10773
+ const res = await fetch(url, { method: "GET", headers });
10774
+ if (!res.ok) {
10775
+ let detail = "";
10776
+ try {
10777
+ const errBody = await res.json();
10778
+ detail = errBody.message ?? errBody.error ?? "";
10779
+ } catch {
10780
+ }
10781
+ throw new Error(`HTTP ${res.status.toString()}${detail ? `: ${detail}` : ""}`);
10782
+ }
10783
+ return await res.json();
10784
+ }
10705
10785
 
10706
10786
  // src/commands/mcp-serve.ts
10707
10787
  import { createInterface as createInterface3 } from "readline";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "repowisestage",
3
- "version": "0.0.46",
3
+ "version": "0.0.47",
4
4
  "type": "module",
5
5
  "description": "AI-optimized codebase context generator",
6
6
  "bin": {