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.
- package/dist/bin/repowise.js +317 -237
- package/package.json +1 -1
package/dist/bin/repowise.js
CHANGED
|
@@ -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
|
-
|
|
1146
|
-
|
|
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((
|
|
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 (
|
|
4894
|
+
switch (toolName) {
|
|
4872
4895
|
case "find_symbol":
|
|
4873
|
-
return {
|
|
4896
|
+
return {
|
|
4897
|
+
ok: true,
|
|
4898
|
+
tool: toolName,
|
|
4899
|
+
result: findSymbol(graph, args)
|
|
4900
|
+
};
|
|
4874
4901
|
case "get_symbol":
|
|
4875
|
-
return { ok: true, tool:
|
|
4902
|
+
return { ok: true, tool: toolName, result: getSymbol(graph, args) };
|
|
4876
4903
|
case "get_impact":
|
|
4877
|
-
return { ok: true, tool:
|
|
4904
|
+
return { ok: true, tool: toolName, result: getImpact(graph, args) };
|
|
4878
4905
|
case "list_edges":
|
|
4879
|
-
return { ok: true, tool:
|
|
4906
|
+
return { ok: true, tool: toolName, result: listEdges(graph, args) };
|
|
4880
4907
|
case "search_pattern":
|
|
4881
|
-
return {
|
|
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:
|
|
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) =>
|
|
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:
|
|
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 (
|
|
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
|
|
10628
|
-
|
|
10629
|
-
|
|
10630
|
-
"
|
|
10631
|
-
"
|
|
10632
|
-
|
|
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
|
-
|
|
10694
|
+
async function connect() {
|
|
10653
10695
|
const connectResp = await postJson(`${endpoint}/mcp/connect`, { repoId }, headers);
|
|
10654
|
-
|
|
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 =
|
|
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
|
|
10676
|
-
|
|
10677
|
-
|
|
10678
|
-
|
|
10679
|
-
)
|
|
10680
|
-
|
|
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";
|