repowisestage 0.0.71 → 0.0.73
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 +1182 -454
- package/package.json +1 -1
package/dist/bin/repowise.js
CHANGED
|
@@ -508,19 +508,19 @@ var init_src = __esm({
|
|
|
508
508
|
// ../listener/dist/process-manager.js
|
|
509
509
|
import { spawn } from "child_process";
|
|
510
510
|
import { openSync, closeSync } from "fs";
|
|
511
|
-
import { readFile as
|
|
511
|
+
import { readFile as readFile7, writeFile as writeFile8, mkdir as mkdir7, unlink as unlink6 } from "fs/promises";
|
|
512
512
|
import { homedir as homedir2 } from "os";
|
|
513
|
-
import { join as
|
|
513
|
+
import { join as join11 } from "path";
|
|
514
514
|
import { createRequire } from "module";
|
|
515
515
|
import { fileURLToPath } from "url";
|
|
516
516
|
function repowiseDir() {
|
|
517
517
|
return getConfigDir();
|
|
518
518
|
}
|
|
519
519
|
function pidPath() {
|
|
520
|
-
return
|
|
520
|
+
return join11(repowiseDir(), "listener.pid");
|
|
521
521
|
}
|
|
522
522
|
function logDirPath() {
|
|
523
|
-
return
|
|
523
|
+
return join11(repowiseDir(), "logs");
|
|
524
524
|
}
|
|
525
525
|
function resolveListenerCommand() {
|
|
526
526
|
try {
|
|
@@ -534,7 +534,7 @@ function resolveListenerCommand() {
|
|
|
534
534
|
}
|
|
535
535
|
async function readPid() {
|
|
536
536
|
try {
|
|
537
|
-
const content = await
|
|
537
|
+
const content = await readFile7(pidPath(), "utf-8");
|
|
538
538
|
const pid = parseInt(content.trim(), 10);
|
|
539
539
|
return Number.isNaN(pid) ? null : pid;
|
|
540
540
|
} catch (err) {
|
|
@@ -560,8 +560,8 @@ async function startBackground() {
|
|
|
560
560
|
const logDir2 = logDirPath();
|
|
561
561
|
await mkdir7(logDir2, { recursive: true });
|
|
562
562
|
const cmd = resolveListenerCommand();
|
|
563
|
-
const stdoutFd = openSync(
|
|
564
|
-
const stderrFd = openSync(
|
|
563
|
+
const stdoutFd = openSync(join11(logDir2, "listener-stdout.log"), "a");
|
|
564
|
+
const stderrFd = openSync(join11(logDir2, "listener-stderr.log"), "a");
|
|
565
565
|
const child = spawn(process.execPath, [cmd.script, ...cmd.args], {
|
|
566
566
|
detached: true,
|
|
567
567
|
stdio: ["ignore", stdoutFd, stderrFd],
|
|
@@ -574,7 +574,7 @@ async function startBackground() {
|
|
|
574
574
|
const pid = child.pid;
|
|
575
575
|
if (!pid)
|
|
576
576
|
throw new Error("Failed to spawn listener process");
|
|
577
|
-
await
|
|
577
|
+
await writeFile8(pidPath(), String(pid));
|
|
578
578
|
return pid;
|
|
579
579
|
}
|
|
580
580
|
async function stopProcess() {
|
|
@@ -609,7 +609,7 @@ async function isRunning() {
|
|
|
609
609
|
}
|
|
610
610
|
async function removePidFile() {
|
|
611
611
|
try {
|
|
612
|
-
await
|
|
612
|
+
await unlink6(pidPath());
|
|
613
613
|
} catch {
|
|
614
614
|
}
|
|
615
615
|
}
|
|
@@ -1170,12 +1170,12 @@ import { createGunzip } from "zlib";
|
|
|
1170
1170
|
import { spawn as spawn3 } from "child_process";
|
|
1171
1171
|
import { pipeline } from "stream/promises";
|
|
1172
1172
|
import { createHash as createHash3 } from "crypto";
|
|
1173
|
-
import { join as
|
|
1173
|
+
import { join as join14, dirname as dirname5 } from "path";
|
|
1174
1174
|
function getNativeLspDir() {
|
|
1175
|
-
return
|
|
1175
|
+
return join14(getLspInstallDir(), "native");
|
|
1176
1176
|
}
|
|
1177
1177
|
function getNativeLspBinDir() {
|
|
1178
|
-
return
|
|
1178
|
+
return join14(getNativeLspDir(), "bin");
|
|
1179
1179
|
}
|
|
1180
1180
|
async function ensureNativeLspInstalled(lspKey) {
|
|
1181
1181
|
const entry = NATIVE_LSP_VERSIONS[lspKey];
|
|
@@ -1201,15 +1201,15 @@ async function ensureNativeLspInstalled(lspKey) {
|
|
|
1201
1201
|
}
|
|
1202
1202
|
}
|
|
1203
1203
|
const usesWrapper = Boolean(asset.launcherJarGlob && asset.wrapperBinaryName);
|
|
1204
|
-
const versionDir =
|
|
1204
|
+
const versionDir = join14(getNativeLspDir(), lspKey, entry.version);
|
|
1205
1205
|
if (usesWrapper && asset.launcherJarGlob && asset.wrapperBinaryName) {
|
|
1206
1206
|
const jarPath = await resolveGlobInsideDir(versionDir, asset.launcherJarGlob);
|
|
1207
|
-
const shimPath =
|
|
1207
|
+
const shimPath = join14(getNativeLspBinDir(), asset.wrapperBinaryName);
|
|
1208
1208
|
if (jarPath && await pathExists(shimPath)) {
|
|
1209
1209
|
return { installed: false, alreadyPresent: true, binaryPath: shimPath };
|
|
1210
1210
|
}
|
|
1211
1211
|
} else {
|
|
1212
|
-
const installedBinary =
|
|
1212
|
+
const installedBinary = join14(versionDir, asset.binaryPath);
|
|
1213
1213
|
if (await pathExists(installedBinary)) {
|
|
1214
1214
|
await ensureSymlinkInBin(lspKey, installedBinary, asset.binaryPath);
|
|
1215
1215
|
return { installed: false, alreadyPresent: true, binaryPath: installedBinary };
|
|
@@ -1217,7 +1217,7 @@ async function ensureNativeLspInstalled(lspKey) {
|
|
|
1217
1217
|
}
|
|
1218
1218
|
try {
|
|
1219
1219
|
await fs.mkdir(versionDir, { recursive: true });
|
|
1220
|
-
const tmpDownload =
|
|
1220
|
+
const tmpDownload = join14(versionDir, `.${asset.filename}.download`);
|
|
1221
1221
|
const downloadUrl = entry.source.kind === "github-release" ? `https://github.com/${entry.source.repo}/releases/download/${entry.version}/${asset.filename}` : asset.url ?? "";
|
|
1222
1222
|
if (!downloadUrl) {
|
|
1223
1223
|
return {
|
|
@@ -1259,7 +1259,7 @@ async function ensureNativeLspInstalled(lspKey) {
|
|
|
1259
1259
|
await pruneStaleVersions(lspKey, entry.version);
|
|
1260
1260
|
return { installed: true, alreadyPresent: false, binaryPath: shimPath };
|
|
1261
1261
|
}
|
|
1262
|
-
const installedBinary =
|
|
1262
|
+
const installedBinary = join14(versionDir, asset.binaryPath);
|
|
1263
1263
|
if (!await pathExists(installedBinary)) {
|
|
1264
1264
|
return {
|
|
1265
1265
|
installed: false,
|
|
@@ -1283,7 +1283,7 @@ async function resolveGlobInsideDir(versionDir, glob) {
|
|
|
1283
1283
|
const parts = glob.split("/");
|
|
1284
1284
|
let dir = versionDir;
|
|
1285
1285
|
for (let i = 0; i < parts.length - 1; i += 1) {
|
|
1286
|
-
dir =
|
|
1286
|
+
dir = join14(dir, parts[i]);
|
|
1287
1287
|
}
|
|
1288
1288
|
const pattern = parts[parts.length - 1];
|
|
1289
1289
|
const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
|
|
@@ -1296,10 +1296,10 @@ async function resolveGlobInsideDir(versionDir, glob) {
|
|
|
1296
1296
|
}
|
|
1297
1297
|
const matches = entries.filter((e) => re.test(e)).sort();
|
|
1298
1298
|
const last = matches[matches.length - 1];
|
|
1299
|
-
return last ?
|
|
1299
|
+
return last ? join14(dir, last) : null;
|
|
1300
1300
|
}
|
|
1301
1301
|
async function pruneStaleVersions(lspKey, currentVersion) {
|
|
1302
|
-
const lspDir =
|
|
1302
|
+
const lspDir = join14(getNativeLspDir(), lspKey);
|
|
1303
1303
|
let entries;
|
|
1304
1304
|
try {
|
|
1305
1305
|
entries = await fs.readdir(lspDir);
|
|
@@ -1308,7 +1308,7 @@ async function pruneStaleVersions(lspKey, currentVersion) {
|
|
|
1308
1308
|
}
|
|
1309
1309
|
await Promise.all(entries.filter((name) => name !== currentVersion).map(async (name) => {
|
|
1310
1310
|
try {
|
|
1311
|
-
await fs.rm(
|
|
1311
|
+
await fs.rm(join14(lspDir, name), { recursive: true, force: true });
|
|
1312
1312
|
} catch {
|
|
1313
1313
|
}
|
|
1314
1314
|
}));
|
|
@@ -1322,9 +1322,9 @@ function cmdQuote(value) {
|
|
|
1322
1322
|
async function writeJdtlsWrapper(input) {
|
|
1323
1323
|
const binDir = getNativeLspBinDir();
|
|
1324
1324
|
await fs.mkdir(binDir, { recursive: true });
|
|
1325
|
-
const shimPath =
|
|
1325
|
+
const shimPath = join14(binDir, input.binaryName);
|
|
1326
1326
|
const platformCfgDir = process.platform === "darwin" ? "config_mac" : process.platform === "win32" ? "config_win" : "config_linux";
|
|
1327
|
-
const workspaceBase =
|
|
1327
|
+
const workspaceBase = join14(input.versionDir, "workspaces");
|
|
1328
1328
|
const qLauncher = shQuote(input.launcherJarPath);
|
|
1329
1329
|
const qConfig = shQuote(`${input.versionDir}/${platformCfgDir}`);
|
|
1330
1330
|
const qWorkspaceBase = shQuote(workspaceBase);
|
|
@@ -1384,7 +1384,7 @@ async function ensureSymlinkInBin(lspKey, target, sourceAssetPath) {
|
|
|
1384
1384
|
await fs.mkdir(binDir, { recursive: true });
|
|
1385
1385
|
const winExtMatch = process.platform === "win32" && sourceAssetPath ? /\.(exe|bat|cmd|ps1)$/i.exec(sourceAssetPath) : null;
|
|
1386
1386
|
const linkName = winExtMatch ? `${lspKey}${winExtMatch[0].toLowerCase()}` : lspKey;
|
|
1387
|
-
const linkPath =
|
|
1387
|
+
const linkPath = join14(binDir, linkName);
|
|
1388
1388
|
try {
|
|
1389
1389
|
await fs.unlink(linkPath);
|
|
1390
1390
|
} catch {
|
|
@@ -1433,12 +1433,12 @@ async function extractAsset(source, destDir, asset) {
|
|
|
1433
1433
|
return;
|
|
1434
1434
|
}
|
|
1435
1435
|
if (filename.endsWith(".gz")) {
|
|
1436
|
-
const outPath =
|
|
1436
|
+
const outPath = join14(destDir, asset.binaryPath);
|
|
1437
1437
|
await fs.mkdir(dirname5(outPath), { recursive: true });
|
|
1438
1438
|
await pipeline((await fs.open(source)).createReadStream(), createGunzip(), createWriteStream(outPath));
|
|
1439
1439
|
return;
|
|
1440
1440
|
}
|
|
1441
|
-
await fs.copyFile(source,
|
|
1441
|
+
await fs.copyFile(source, join14(destDir, asset.binaryPath));
|
|
1442
1442
|
}
|
|
1443
1443
|
async function runExternal(cmd, args) {
|
|
1444
1444
|
return new Promise((resolve5, reject) => {
|
|
@@ -1476,9 +1476,9 @@ var init_native_installer = __esm({
|
|
|
1476
1476
|
// ../listener/dist/lsp/coursier-installer.js
|
|
1477
1477
|
import { promises as fs2 } from "fs";
|
|
1478
1478
|
import { spawn as spawn4 } from "child_process";
|
|
1479
|
-
import { join as
|
|
1479
|
+
import { join as join15 } from "path";
|
|
1480
1480
|
function getCoursierBinDir() {
|
|
1481
|
-
return
|
|
1481
|
+
return join15(getLspInstallDir(), "coursier-bin");
|
|
1482
1482
|
}
|
|
1483
1483
|
async function ensureCoursierLspInstalled(key) {
|
|
1484
1484
|
const entry = COURSIER_LSPS[key];
|
|
@@ -1486,8 +1486,8 @@ async function ensureCoursierLspInstalled(key) {
|
|
|
1486
1486
|
return { installed: false, alreadyPresent: false, skipped: "no-coursier-entry" };
|
|
1487
1487
|
}
|
|
1488
1488
|
const binDir = getCoursierBinDir();
|
|
1489
|
-
const binaryPath =
|
|
1490
|
-
const binaryPathExe =
|
|
1489
|
+
const binaryPath = join15(binDir, entry.appName);
|
|
1490
|
+
const binaryPathExe = join15(binDir, `${entry.appName}.bat`);
|
|
1491
1491
|
if (await pathExists2(binaryPath) || await pathExists2(binaryPathExe)) {
|
|
1492
1492
|
return { installed: false, alreadyPresent: true, binaryPath };
|
|
1493
1493
|
}
|
|
@@ -1500,8 +1500,8 @@ async function ensureCoursierLspInstalled(key) {
|
|
|
1500
1500
|
error: csBootstrap.error ?? csBootstrap.skipped
|
|
1501
1501
|
};
|
|
1502
1502
|
}
|
|
1503
|
-
const csPath =
|
|
1504
|
-
const csPathExe =
|
|
1503
|
+
const csPath = join15(getNativeLspBinDir(), "coursier");
|
|
1504
|
+
const csPathExe = join15(getNativeLspBinDir(), "coursier.exe");
|
|
1505
1505
|
const cs = await pathExists2(csPath) ? csPath : await pathExists2(csPathExe) ? csPathExe : null;
|
|
1506
1506
|
if (!cs) {
|
|
1507
1507
|
return {
|
|
@@ -1567,7 +1567,7 @@ var init_coursier_installer = __esm({
|
|
|
1567
1567
|
import { spawn as spawn5 } from "child_process";
|
|
1568
1568
|
import { promises as fs3 } from "fs";
|
|
1569
1569
|
import { homedir as homedir3 } from "os";
|
|
1570
|
-
import { join as
|
|
1570
|
+
import { join as join16 } from "path";
|
|
1571
1571
|
async function ensureToolchainLspInstalled(key) {
|
|
1572
1572
|
const entry = TOOLCHAIN_LSPS[key];
|
|
1573
1573
|
if (!entry) {
|
|
@@ -1615,7 +1615,7 @@ async function ensureToolchainLspInstalled(key) {
|
|
|
1615
1615
|
error: err instanceof Error ? err.message : String(err)
|
|
1616
1616
|
};
|
|
1617
1617
|
}
|
|
1618
|
-
const binaryPath =
|
|
1618
|
+
const binaryPath = join16(binDir, entry.binaryName);
|
|
1619
1619
|
if (!await pathExists3(binaryPath)) {
|
|
1620
1620
|
return {
|
|
1621
1621
|
installed: false,
|
|
@@ -1632,14 +1632,14 @@ async function resolveToolchainBinDir(entry) {
|
|
|
1632
1632
|
}
|
|
1633
1633
|
if (entry.toolchainBinDir) {
|
|
1634
1634
|
if (entry.toolchain === "go") {
|
|
1635
|
-
const gopath = process.env["GOPATH"] ??
|
|
1636
|
-
return
|
|
1635
|
+
const gopath = process.env["GOPATH"] ?? join16(homedir3(), "go");
|
|
1636
|
+
return join16(gopath, "bin");
|
|
1637
1637
|
}
|
|
1638
|
-
return
|
|
1638
|
+
return join16(homedir3(), entry.toolchainBinDir);
|
|
1639
1639
|
}
|
|
1640
1640
|
if (entry.toolchain === "gem") {
|
|
1641
1641
|
const userdir = await captureStdout("gem", ["env", "userdir"]);
|
|
1642
|
-
return
|
|
1642
|
+
return join16(userdir.trim(), "bin");
|
|
1643
1643
|
}
|
|
1644
1644
|
throw new Error(`no bin-dir resolution for toolchain ${entry.toolchain}`);
|
|
1645
1645
|
}
|
|
@@ -1649,10 +1649,10 @@ function getToolchainBinDirs() {
|
|
|
1649
1649
|
if (entry.bundled || !entry.toolchain || !entry.toolchainBinDir)
|
|
1650
1650
|
continue;
|
|
1651
1651
|
if (entry.toolchain === "go") {
|
|
1652
|
-
const gopath = process.env["GOPATH"] ??
|
|
1653
|
-
dirs.add(
|
|
1652
|
+
const gopath = process.env["GOPATH"] ?? join16(homedir3(), "go");
|
|
1653
|
+
dirs.add(join16(gopath, "bin"));
|
|
1654
1654
|
} else {
|
|
1655
|
-
dirs.add(
|
|
1655
|
+
dirs.add(join16(homedir3(), entry.toolchainBinDir));
|
|
1656
1656
|
}
|
|
1657
1657
|
}
|
|
1658
1658
|
return [...dirs];
|
|
@@ -1720,20 +1720,20 @@ var init_toolchain_installer = __esm({
|
|
|
1720
1720
|
|
|
1721
1721
|
// ../listener/dist/lsp/installer.js
|
|
1722
1722
|
import { promises as fs4 } from "fs";
|
|
1723
|
-
import { join as
|
|
1723
|
+
import { join as join17, dirname as dirname6 } from "path";
|
|
1724
1724
|
import { spawn as spawn6 } from "child_process";
|
|
1725
|
-
import
|
|
1725
|
+
import lockfile4 from "proper-lockfile";
|
|
1726
1726
|
function getLspInstallDir() {
|
|
1727
|
-
return
|
|
1727
|
+
return join17(getConfigDir(), "lsp-servers");
|
|
1728
1728
|
}
|
|
1729
1729
|
function getLspBinDir() {
|
|
1730
|
-
return
|
|
1730
|
+
return join17(getLspInstallDir(), "node_modules", ".bin");
|
|
1731
1731
|
}
|
|
1732
1732
|
async function ensureNpmLspInstalled(config2) {
|
|
1733
1733
|
if (!config2.npmPackage)
|
|
1734
1734
|
return { installed: false, skipped: "no-npm-package" };
|
|
1735
1735
|
const installDir = getLspInstallDir();
|
|
1736
|
-
const binPath =
|
|
1736
|
+
const binPath = join17(installDir, "node_modules", ".bin", config2.command);
|
|
1737
1737
|
if (await pathExists4(binPath)) {
|
|
1738
1738
|
return { installed: false, skipped: "already-present" };
|
|
1739
1739
|
}
|
|
@@ -1741,7 +1741,7 @@ async function ensureNpmLspInstalled(config2) {
|
|
|
1741
1741
|
await fs4.mkdir(installDir, { recursive: true });
|
|
1742
1742
|
let release = null;
|
|
1743
1743
|
try {
|
|
1744
|
-
release = await
|
|
1744
|
+
release = await lockfile4.lock(installDir, {
|
|
1745
1745
|
stale: 18e4,
|
|
1746
1746
|
retries: { retries: 20, factor: 1.5, minTimeout: 500, maxTimeout: 5e3 },
|
|
1747
1747
|
realpath: false
|
|
@@ -1755,7 +1755,7 @@ async function ensureNpmLspInstalled(config2) {
|
|
|
1755
1755
|
if (await pathExists4(binPath)) {
|
|
1756
1756
|
return { installed: false, skipped: "already-present" };
|
|
1757
1757
|
}
|
|
1758
|
-
const pkgJsonPath =
|
|
1758
|
+
const pkgJsonPath = join17(installDir, "package.json");
|
|
1759
1759
|
if (!await pathExists4(pkgJsonPath)) {
|
|
1760
1760
|
await fs4.writeFile(pkgJsonPath, JSON.stringify({ name: "repowise-lsp-servers", private: true, version: "0.0.0" }, null, 2), "utf-8");
|
|
1761
1761
|
}
|
|
@@ -1784,7 +1784,7 @@ async function ensureNpmLspInstalled(config2) {
|
|
|
1784
1784
|
}
|
|
1785
1785
|
async function resolveNpmCommand() {
|
|
1786
1786
|
const nodeDir = dirname6(process.execPath);
|
|
1787
|
-
for (const candidate of [
|
|
1787
|
+
for (const candidate of [join17(nodeDir, "npm"), join17(nodeDir, "npm.cmd")]) {
|
|
1788
1788
|
try {
|
|
1789
1789
|
await fs4.access(candidate);
|
|
1790
1790
|
return candidate;
|
|
@@ -1875,7 +1875,7 @@ async function detectRepoLanguages(repoRoot) {
|
|
|
1875
1875
|
continue;
|
|
1876
1876
|
if (name === "node_modules" || name === "dist" || name === "build")
|
|
1877
1877
|
continue;
|
|
1878
|
-
const sub =
|
|
1878
|
+
const sub = join17(repoRoot, name);
|
|
1879
1879
|
try {
|
|
1880
1880
|
const stat8 = await fs4.stat(sub);
|
|
1881
1881
|
if (stat8.isDirectory())
|
|
@@ -1991,7 +1991,7 @@ var init_installer = __esm({
|
|
|
1991
1991
|
|
|
1992
1992
|
// ../listener/dist/lsp/daemon-path.js
|
|
1993
1993
|
import { homedir as homedir4 } from "os";
|
|
1994
|
-
import { delimiter, dirname as dirname7, join as
|
|
1994
|
+
import { delimiter, dirname as dirname7, join as join18 } from "path";
|
|
1995
1995
|
function commonBinDirs() {
|
|
1996
1996
|
const home = homedir4();
|
|
1997
1997
|
return [
|
|
@@ -2004,13 +2004,13 @@ function commonBinDirs() {
|
|
|
2004
2004
|
"/bin",
|
|
2005
2005
|
"/usr/sbin",
|
|
2006
2006
|
"/sbin",
|
|
2007
|
-
|
|
2007
|
+
join18(home, ".local", "bin"),
|
|
2008
2008
|
// pipx / pip --user / uv
|
|
2009
|
-
|
|
2009
|
+
join18(home, ".npm-global", "bin"),
|
|
2010
2010
|
// common npm global prefix
|
|
2011
|
-
|
|
2011
|
+
join18(home, "go", "bin"),
|
|
2012
2012
|
// gopls
|
|
2013
|
-
|
|
2013
|
+
join18(home, ".cargo", "bin")
|
|
2014
2014
|
// rust-analyzer via cargo
|
|
2015
2015
|
];
|
|
2016
2016
|
}
|
|
@@ -2046,9 +2046,9 @@ __export(service_installer_exports, {
|
|
|
2046
2046
|
uninstall: () => uninstall
|
|
2047
2047
|
});
|
|
2048
2048
|
import { execFile as execFile4 } from "child_process";
|
|
2049
|
-
import { writeFile as
|
|
2049
|
+
import { writeFile as writeFile9, mkdir as mkdir8, unlink as unlink7 } from "fs/promises";
|
|
2050
2050
|
import { homedir as homedir5 } from "os";
|
|
2051
|
-
import { join as
|
|
2051
|
+
import { join as join19 } from "path";
|
|
2052
2052
|
function exec(cmd, args) {
|
|
2053
2053
|
return new Promise((resolve5, reject) => {
|
|
2054
2054
|
execFile4(cmd, args, (err, stdout) => {
|
|
@@ -2067,10 +2067,10 @@ function sanitizeEnvValue(value) {
|
|
|
2067
2067
|
return Array.from(value).filter((ch) => ch.charCodeAt(0) >= 32).join("");
|
|
2068
2068
|
}
|
|
2069
2069
|
function plistPath() {
|
|
2070
|
-
return
|
|
2070
|
+
return join19(homedir5(), "Library", "LaunchAgents", `${PLIST_LABEL}.plist`);
|
|
2071
2071
|
}
|
|
2072
2072
|
function logDir() {
|
|
2073
|
-
return
|
|
2073
|
+
return join19(getConfigDir(), "logs");
|
|
2074
2074
|
}
|
|
2075
2075
|
function buildPlist() {
|
|
2076
2076
|
const cmd = resolveListenerCommand();
|
|
@@ -2104,9 +2104,9 @@ ${programArgs}
|
|
|
2104
2104
|
<string>${xmlEscape(buildDaemonPath())}</string>
|
|
2105
2105
|
</dict>
|
|
2106
2106
|
<key>StandardOutPath</key>
|
|
2107
|
-
<string>${xmlEscape(
|
|
2107
|
+
<string>${xmlEscape(join19(logs, "listener-stdout.log"))}</string>
|
|
2108
2108
|
<key>StandardErrorPath</key>
|
|
2109
|
-
<string>${xmlEscape(
|
|
2109
|
+
<string>${xmlEscape(join19(logs, "listener-stderr.log"))}</string>
|
|
2110
2110
|
<key>ProcessType</key>
|
|
2111
2111
|
<string>Background</string>
|
|
2112
2112
|
<!--
|
|
@@ -2135,12 +2135,12 @@ ${programArgs}
|
|
|
2135
2135
|
}
|
|
2136
2136
|
async function darwinInstall() {
|
|
2137
2137
|
await mkdir8(logDir(), { recursive: true });
|
|
2138
|
-
await mkdir8(
|
|
2138
|
+
await mkdir8(join19(homedir5(), "Library", "LaunchAgents"), { recursive: true });
|
|
2139
2139
|
try {
|
|
2140
2140
|
await exec("launchctl", ["unload", plistPath()]);
|
|
2141
2141
|
} catch {
|
|
2142
2142
|
}
|
|
2143
|
-
await
|
|
2143
|
+
await writeFile9(plistPath(), buildPlist());
|
|
2144
2144
|
await exec("launchctl", ["load", plistPath()]);
|
|
2145
2145
|
}
|
|
2146
2146
|
async function darwinUninstall() {
|
|
@@ -2149,7 +2149,7 @@ async function darwinUninstall() {
|
|
|
2149
2149
|
} catch {
|
|
2150
2150
|
}
|
|
2151
2151
|
try {
|
|
2152
|
-
await
|
|
2152
|
+
await unlink7(plistPath());
|
|
2153
2153
|
} catch {
|
|
2154
2154
|
}
|
|
2155
2155
|
}
|
|
@@ -2162,7 +2162,7 @@ async function darwinIsInstalled() {
|
|
|
2162
2162
|
}
|
|
2163
2163
|
}
|
|
2164
2164
|
function unitPath() {
|
|
2165
|
-
return
|
|
2165
|
+
return join19(homedir5(), ".config", "systemd", "user", `${SYSTEMD_SERVICE}.service`);
|
|
2166
2166
|
}
|
|
2167
2167
|
function buildUnit() {
|
|
2168
2168
|
const cmd = resolveListenerCommand();
|
|
@@ -2182,16 +2182,16 @@ Environment=PATH=${sanitizeEnvValue(buildDaemonPath())}
|
|
|
2182
2182
|
ExecStart=${execStart}
|
|
2183
2183
|
Restart=always
|
|
2184
2184
|
RestartSec=10
|
|
2185
|
-
StandardOutput=append:${
|
|
2186
|
-
StandardError=append:${
|
|
2185
|
+
StandardOutput=append:${join19(logs, "listener-stdout.log")}
|
|
2186
|
+
StandardError=append:${join19(logs, "listener-stderr.log")}
|
|
2187
2187
|
|
|
2188
2188
|
[Install]
|
|
2189
2189
|
WantedBy=default.target`;
|
|
2190
2190
|
}
|
|
2191
2191
|
async function linuxInstall() {
|
|
2192
2192
|
await mkdir8(logDir(), { recursive: true });
|
|
2193
|
-
await mkdir8(
|
|
2194
|
-
await
|
|
2193
|
+
await mkdir8(join19(homedir5(), ".config", "systemd", "user"), { recursive: true });
|
|
2194
|
+
await writeFile9(unitPath(), buildUnit());
|
|
2195
2195
|
await exec("systemctl", ["--user", "daemon-reload"]);
|
|
2196
2196
|
await exec("systemctl", ["--user", "enable", SYSTEMD_SERVICE]);
|
|
2197
2197
|
await exec("systemctl", ["--user", "start", SYSTEMD_SERVICE]);
|
|
@@ -2206,7 +2206,7 @@ async function linuxUninstall() {
|
|
|
2206
2206
|
} catch {
|
|
2207
2207
|
}
|
|
2208
2208
|
try {
|
|
2209
|
-
await
|
|
2209
|
+
await unlink7(unitPath());
|
|
2210
2210
|
} catch {
|
|
2211
2211
|
}
|
|
2212
2212
|
try {
|
|
@@ -2385,8 +2385,8 @@ __export(sidecar_cache_exports, {
|
|
|
2385
2385
|
persistMergedSidecar: () => persistMergedSidecar,
|
|
2386
2386
|
sidecarCachePaths: () => sidecarCachePaths
|
|
2387
2387
|
});
|
|
2388
|
-
import { mkdir as mkdir9, readFile as
|
|
2389
|
-
import { dirname as dirname9, join as
|
|
2388
|
+
import { mkdir as mkdir9, readFile as readFile9, readdir as readdir3, stat as stat2, unlink as unlink9, writeFile as writeFile10 } from "fs/promises";
|
|
2389
|
+
import { dirname as dirname9, join as join21 } from "path";
|
|
2390
2390
|
function repoIdSafe(repoId) {
|
|
2391
2391
|
return /^[A-Za-z0-9_.-]{1,128}$/.test(repoId) && !repoId.startsWith(".");
|
|
2392
2392
|
}
|
|
@@ -2400,17 +2400,17 @@ function sidecarCachePaths(repoId, commitSha) {
|
|
|
2400
2400
|
if (!commitShaSafe(commitSha)) {
|
|
2401
2401
|
throw new Error(`unsafe commitSha: ${commitSha}`);
|
|
2402
2402
|
}
|
|
2403
|
-
const repoDir =
|
|
2404
|
-
const fullPath =
|
|
2403
|
+
const repoDir = join21(getConfigDir(), "typed-resolution", repoId);
|
|
2404
|
+
const fullPath = join21(repoDir, `${commitSha}.jsonl`);
|
|
2405
2405
|
return { fullPath, repoDir };
|
|
2406
2406
|
}
|
|
2407
2407
|
async function persistMergedSidecar(repoId, commitSha, sidecar) {
|
|
2408
2408
|
const { fullPath, repoDir } = sidecarCachePaths(repoId, commitSha);
|
|
2409
2409
|
await mkdir9(repoDir, { recursive: true });
|
|
2410
2410
|
const tmpPath = `${fullPath}.tmp`;
|
|
2411
|
-
await
|
|
2412
|
-
const { rename:
|
|
2413
|
-
await
|
|
2411
|
+
await writeFile10(tmpPath, JSON.stringify(sidecar), "utf-8");
|
|
2412
|
+
const { rename: rename8 } = await import("fs/promises");
|
|
2413
|
+
await rename8(tmpPath, fullPath);
|
|
2414
2414
|
try {
|
|
2415
2415
|
await sweepSidecarDir(repoDir);
|
|
2416
2416
|
} catch {
|
|
@@ -2424,7 +2424,7 @@ async function sweepSidecarDir(repoDir) {
|
|
|
2424
2424
|
const withMtime = [];
|
|
2425
2425
|
for (const name of jsonlFiles) {
|
|
2426
2426
|
try {
|
|
2427
|
-
const s = await stat2(
|
|
2427
|
+
const s = await stat2(join21(repoDir, name));
|
|
2428
2428
|
withMtime.push({ name, mtimeMs: s.mtimeMs });
|
|
2429
2429
|
} catch {
|
|
2430
2430
|
}
|
|
@@ -2435,7 +2435,7 @@ async function sweepSidecarDir(repoDir) {
|
|
|
2435
2435
|
for (const { name, mtimeMs } of candidates) {
|
|
2436
2436
|
if (now - mtimeMs >= SWEEP_MAX_AGE_MS) {
|
|
2437
2437
|
try {
|
|
2438
|
-
await
|
|
2438
|
+
await unlink9(join21(repoDir, name));
|
|
2439
2439
|
} catch {
|
|
2440
2440
|
}
|
|
2441
2441
|
}
|
|
@@ -2461,7 +2461,7 @@ async function loadMergedSidecar(repoId, commitSha) {
|
|
|
2461
2461
|
let body;
|
|
2462
2462
|
try {
|
|
2463
2463
|
const { fullPath } = sidecarCachePaths(repoId, commitSha);
|
|
2464
|
-
body = await
|
|
2464
|
+
body = await readFile9(fullPath, "utf-8");
|
|
2465
2465
|
} catch {
|
|
2466
2466
|
return null;
|
|
2467
2467
|
}
|
|
@@ -3133,15 +3133,15 @@ var init_telemetry = __esm({
|
|
|
3133
3133
|
// bin/repowise.ts
|
|
3134
3134
|
import { readFileSync as readFileSync3 } from "fs";
|
|
3135
3135
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
3136
|
-
import { dirname as dirname23, join as
|
|
3136
|
+
import { dirname as dirname23, join as join63 } from "path";
|
|
3137
3137
|
import { Command } from "commander";
|
|
3138
3138
|
|
|
3139
3139
|
// ../listener/dist/main.js
|
|
3140
3140
|
init_config_dir();
|
|
3141
|
-
import { readFile as
|
|
3142
|
-
import { join as
|
|
3141
|
+
import { readFile as readFile14, writeFile as writeFile16, mkdir as mkdir16, stat as fsStat } from "fs/promises";
|
|
3142
|
+
import { join as join42, dirname as dirname17 } from "path";
|
|
3143
3143
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
3144
|
-
import
|
|
3144
|
+
import lockfile5 from "proper-lockfile";
|
|
3145
3145
|
|
|
3146
3146
|
// ../../packages/shared/dist/lib/ai-tools.js
|
|
3147
3147
|
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2, readdir, stat, unlink as unlink2 } from "fs/promises";
|
|
@@ -4391,8 +4391,53 @@ function migrateState(state, oldId, newId) {
|
|
|
4391
4391
|
|
|
4392
4392
|
// ../listener/dist/lib/auth.js
|
|
4393
4393
|
init_config_dir();
|
|
4394
|
-
import { readFile as readFile5, writeFile as writeFile5, mkdir as mkdir5, chmod as chmod3 } from "fs/promises";
|
|
4394
|
+
import { readFile as readFile5, writeFile as writeFile5, mkdir as mkdir5, chmod as chmod3, rename as rename3, open as open3, unlink as unlink5 } from "fs/promises";
|
|
4395
4395
|
import { join as join7 } from "path";
|
|
4396
|
+
|
|
4397
|
+
// ../../packages/shared/dist/lib/creds.js
|
|
4398
|
+
function isCredsHardExpired(creds) {
|
|
4399
|
+
if (!creds || typeof creds.expiresAt !== "number")
|
|
4400
|
+
return true;
|
|
4401
|
+
return Date.now() >= creds.expiresAt;
|
|
4402
|
+
}
|
|
4403
|
+
function classifyRefreshFailure(status2, body) {
|
|
4404
|
+
if (status2 === 400 && body != null && /invalid_grant/i.test(body))
|
|
4405
|
+
return "terminal";
|
|
4406
|
+
return "transient";
|
|
4407
|
+
}
|
|
4408
|
+
var RefreshTokenError = class extends Error {
|
|
4409
|
+
kind;
|
|
4410
|
+
status;
|
|
4411
|
+
constructor(kind, message, status2 = null) {
|
|
4412
|
+
super(message);
|
|
4413
|
+
this.name = "RefreshTokenError";
|
|
4414
|
+
this.kind = kind;
|
|
4415
|
+
this.status = status2;
|
|
4416
|
+
}
|
|
4417
|
+
};
|
|
4418
|
+
|
|
4419
|
+
// ../listener/dist/lib/auth.js
|
|
4420
|
+
import lockfile3 from "proper-lockfile";
|
|
4421
|
+
|
|
4422
|
+
// ../listener/dist/lib/http.js
|
|
4423
|
+
async function fetchWithTimeout(url, init, timeoutMs) {
|
|
4424
|
+
const controller = new AbortController();
|
|
4425
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
4426
|
+
try {
|
|
4427
|
+
return await fetch(url, { ...init, signal: controller.signal });
|
|
4428
|
+
} finally {
|
|
4429
|
+
clearTimeout(timer);
|
|
4430
|
+
}
|
|
4431
|
+
}
|
|
4432
|
+
|
|
4433
|
+
// ../listener/dist/lib/auth.js
|
|
4434
|
+
var TOKEN_ENDPOINT_TIMEOUT_MS = 3e4;
|
|
4435
|
+
var REFRESH_MAX_ATTEMPTS = 3;
|
|
4436
|
+
var lastRefreshOutcome = "none";
|
|
4437
|
+
function getLastRefreshOutcome() {
|
|
4438
|
+
return lastRefreshOutcome;
|
|
4439
|
+
}
|
|
4440
|
+
var sleepMs = (ms) => new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
4396
4441
|
function getTokenUrl(creds) {
|
|
4397
4442
|
const cognito = creds?.cognito;
|
|
4398
4443
|
const domain = process.env["REPOWISE_COGNITO_DOMAIN"] ?? cognito?.domain ?? "auth-repowise-dev";
|
|
@@ -4406,24 +4451,33 @@ function getClientId(creds) {
|
|
|
4406
4451
|
async function refreshTokens(refreshToken, creds) {
|
|
4407
4452
|
const clientId = getClientId(creds);
|
|
4408
4453
|
if (!clientId) {
|
|
4409
|
-
throw new
|
|
4454
|
+
throw new RefreshTokenError("terminal", "No Cognito client ID available. Run `repowise login` first.", null);
|
|
4455
|
+
}
|
|
4456
|
+
let response;
|
|
4457
|
+
try {
|
|
4458
|
+
response = await fetchWithTimeout(getTokenUrl(creds), {
|
|
4459
|
+
method: "POST",
|
|
4460
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
4461
|
+
body: new URLSearchParams({
|
|
4462
|
+
grant_type: "refresh_token",
|
|
4463
|
+
client_id: clientId,
|
|
4464
|
+
refresh_token: refreshToken
|
|
4465
|
+
})
|
|
4466
|
+
}, TOKEN_ENDPOINT_TIMEOUT_MS);
|
|
4467
|
+
} catch (err) {
|
|
4468
|
+
throw new RefreshTokenError("transient", `Token refresh network error: ${err instanceof Error ? err.message : String(err)}`, null);
|
|
4410
4469
|
}
|
|
4411
|
-
const response = await fetch(getTokenUrl(creds), {
|
|
4412
|
-
method: "POST",
|
|
4413
|
-
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
4414
|
-
body: new URLSearchParams({
|
|
4415
|
-
grant_type: "refresh_token",
|
|
4416
|
-
client_id: clientId,
|
|
4417
|
-
refresh_token: refreshToken
|
|
4418
|
-
})
|
|
4419
|
-
});
|
|
4420
4470
|
if (!response.ok) {
|
|
4421
|
-
|
|
4471
|
+
const body = await response.text().catch(() => "");
|
|
4472
|
+
throw new RefreshTokenError(classifyRefreshFailure(response.status, body), `Token refresh failed: ${response.status}`, response.status);
|
|
4422
4473
|
}
|
|
4423
4474
|
const data = await response.json();
|
|
4424
4475
|
return {
|
|
4425
4476
|
accessToken: data.access_token,
|
|
4426
|
-
|
|
4477
|
+
// Carry forward a rotated refresh token when Cognito returns one; otherwise
|
|
4478
|
+
// reuse the current token (rotation disabled today). Required before
|
|
4479
|
+
// enabling refresh-token rotation server-side.
|
|
4480
|
+
refreshToken: data.refresh_token ?? refreshToken,
|
|
4427
4481
|
idToken: data.id_token,
|
|
4428
4482
|
expiresAt: Date.now() + data.expires_in * 1e3,
|
|
4429
4483
|
cognito: creds.cognito
|
|
@@ -4445,30 +4499,89 @@ async function storeCredentials(credentials) {
|
|
|
4445
4499
|
const dir = getConfigDir();
|
|
4446
4500
|
const credPath = join7(dir, "credentials.json");
|
|
4447
4501
|
await mkdir5(dir, { recursive: true, mode: 448 });
|
|
4448
|
-
|
|
4449
|
-
|
|
4502
|
+
try {
|
|
4503
|
+
await writeFile5(credPath, "", { flag: "a", mode: 384 });
|
|
4504
|
+
} catch {
|
|
4505
|
+
}
|
|
4506
|
+
let release = null;
|
|
4507
|
+
try {
|
|
4508
|
+
release = await lockfile3.lock(credPath, { stale: 1e4, retries: 3, realpath: false });
|
|
4509
|
+
const tmpPath = credPath + ".tmp";
|
|
4510
|
+
try {
|
|
4511
|
+
await writeFile5(tmpPath, JSON.stringify(credentials, null, 2), { mode: 384 });
|
|
4512
|
+
let fh;
|
|
4513
|
+
try {
|
|
4514
|
+
fh = await open3(tmpPath, "r+");
|
|
4515
|
+
await fh.datasync();
|
|
4516
|
+
} finally {
|
|
4517
|
+
await fh?.close();
|
|
4518
|
+
}
|
|
4519
|
+
await rename3(tmpPath, credPath);
|
|
4520
|
+
await chmod3(credPath, 384);
|
|
4521
|
+
} catch (err) {
|
|
4522
|
+
try {
|
|
4523
|
+
await unlink5(tmpPath);
|
|
4524
|
+
} catch {
|
|
4525
|
+
}
|
|
4526
|
+
throw err;
|
|
4527
|
+
}
|
|
4528
|
+
} finally {
|
|
4529
|
+
if (release) {
|
|
4530
|
+
try {
|
|
4531
|
+
await release();
|
|
4532
|
+
} catch {
|
|
4533
|
+
}
|
|
4534
|
+
}
|
|
4535
|
+
}
|
|
4450
4536
|
}
|
|
4537
|
+
var refreshInFlight = null;
|
|
4451
4538
|
async function getValidCredentials(options) {
|
|
4452
4539
|
const creds = await getStoredCredentials();
|
|
4453
|
-
if (!creds)
|
|
4540
|
+
if (!creds) {
|
|
4541
|
+
lastRefreshOutcome = "no-creds";
|
|
4454
4542
|
return null;
|
|
4543
|
+
}
|
|
4455
4544
|
const now = Date.now();
|
|
4456
4545
|
const needsRefresh = options?.forceRefresh || now > creds.expiresAt - 5 * 60 * 1e3;
|
|
4457
|
-
if (needsRefresh) {
|
|
4546
|
+
if (!needsRefresh) {
|
|
4547
|
+
lastRefreshOutcome = "success";
|
|
4548
|
+
return creds;
|
|
4549
|
+
}
|
|
4550
|
+
if (!refreshInFlight) {
|
|
4551
|
+
refreshInFlight = doRefresh(creds).finally(() => {
|
|
4552
|
+
refreshInFlight = null;
|
|
4553
|
+
});
|
|
4554
|
+
}
|
|
4555
|
+
return refreshInFlight;
|
|
4556
|
+
}
|
|
4557
|
+
async function doRefresh(creds) {
|
|
4558
|
+
for (let attempt = 1; attempt <= REFRESH_MAX_ATTEMPTS; attempt++) {
|
|
4458
4559
|
try {
|
|
4459
4560
|
const refreshed = await refreshTokens(creds.refreshToken, creds);
|
|
4460
4561
|
await storeCredentials(refreshed);
|
|
4562
|
+
lastRefreshOutcome = "success";
|
|
4461
4563
|
return refreshed;
|
|
4462
4564
|
} catch (err) {
|
|
4565
|
+
const kind = err instanceof RefreshTokenError ? err.kind : "transient";
|
|
4463
4566
|
const message = err instanceof Error ? err.message : String(err);
|
|
4464
|
-
|
|
4465
|
-
|
|
4466
|
-
|
|
4567
|
+
if (kind === "terminal") {
|
|
4568
|
+
lastRefreshOutcome = "terminal";
|
|
4569
|
+
console.warn(`Token refresh rejected (terminal): ${message}`);
|
|
4570
|
+
return null;
|
|
4467
4571
|
}
|
|
4572
|
+
if (attempt < REFRESH_MAX_ATTEMPTS) {
|
|
4573
|
+
const backoffBase = Number(process.env["REPOWISE_REFRESH_BACKOFF_MS"] ?? 1e3);
|
|
4574
|
+
await sleepMs(backoffBase * attempt);
|
|
4575
|
+
continue;
|
|
4576
|
+
}
|
|
4577
|
+
lastRefreshOutcome = "transient";
|
|
4578
|
+
console.warn(`Token refresh failed (transient, ${REFRESH_MAX_ATTEMPTS} attempts): ${message}`);
|
|
4579
|
+
if (Date.now() < creds.expiresAt)
|
|
4580
|
+
return creds;
|
|
4468
4581
|
return null;
|
|
4469
4582
|
}
|
|
4470
4583
|
}
|
|
4471
|
-
return
|
|
4584
|
+
return null;
|
|
4472
4585
|
}
|
|
4473
4586
|
|
|
4474
4587
|
// ../listener/dist/poll-client.js
|
|
@@ -4593,6 +4706,17 @@ function notifyContextUpdated(repoId, fileCount) {
|
|
|
4593
4706
|
} catch {
|
|
4594
4707
|
}
|
|
4595
4708
|
}
|
|
4709
|
+
function notifyDocsReady(repoId) {
|
|
4710
|
+
try {
|
|
4711
|
+
notify({
|
|
4712
|
+
title: TITLE,
|
|
4713
|
+
// Start a NEW session (not just /mcp): an open AI session snapshots its
|
|
4714
|
+
// instructions at start, and /mcp only reloads MCP connections.
|
|
4715
|
+
message: `Full context docs are ready for ${repoId} \u2014 start a new AI session (or restart your AI tool) to use them.`
|
|
4716
|
+
});
|
|
4717
|
+
} catch {
|
|
4718
|
+
}
|
|
4719
|
+
}
|
|
4596
4720
|
|
|
4597
4721
|
// ../listener/dist/context-fetcher.js
|
|
4598
4722
|
import { execFile as execFile2 } from "child_process";
|
|
@@ -4613,6 +4737,7 @@ async function verifyContextFolder(localPath) {
|
|
|
4613
4737
|
}
|
|
4614
4738
|
|
|
4615
4739
|
// ../listener/dist/context-fetcher.js
|
|
4740
|
+
var CONTEXT_FETCH_TIMEOUT_MS = 3e4;
|
|
4616
4741
|
var execFileAsync = promisify(execFile2);
|
|
4617
4742
|
async function fetchContextUpdates(localPath) {
|
|
4618
4743
|
try {
|
|
@@ -4668,7 +4793,10 @@ async function fetchContextFromServer(repoId, localPath, apiUrl) {
|
|
|
4668
4793
|
let listRes;
|
|
4669
4794
|
for (let attempt = 1; attempt <= 3; attempt++) {
|
|
4670
4795
|
try {
|
|
4671
|
-
listRes = await fetch(`${apiUrl}/v1/repos/${repoId}/context`, {
|
|
4796
|
+
listRes = await fetch(`${apiUrl}/v1/repos/${repoId}/context`, {
|
|
4797
|
+
headers,
|
|
4798
|
+
signal: AbortSignal.timeout(CONTEXT_FETCH_TIMEOUT_MS)
|
|
4799
|
+
});
|
|
4672
4800
|
if (listRes.status === 401 || listRes.status === 403) {
|
|
4673
4801
|
console.error(`Context list auth error (${listRes.status}) for repo ${repoId}`);
|
|
4674
4802
|
return { success: false, updatedFiles: [] };
|
|
@@ -4700,7 +4828,8 @@ async function fetchContextFromServer(repoId, localPath, apiUrl) {
|
|
|
4700
4828
|
if (file.fileName.includes(".."))
|
|
4701
4829
|
continue;
|
|
4702
4830
|
const urlRes = await fetch(`${apiUrl}/v1/repos/${repoId}/context/files/${file.fileName}`, {
|
|
4703
|
-
headers
|
|
4831
|
+
headers,
|
|
4832
|
+
signal: AbortSignal.timeout(CONTEXT_FETCH_TIMEOUT_MS)
|
|
4704
4833
|
});
|
|
4705
4834
|
if (!urlRes.ok) {
|
|
4706
4835
|
console.error(`Context fetch for ${repoId}: failed to get URL for ${file.fileName} (${urlRes.status})`);
|
|
@@ -4712,7 +4841,9 @@ async function fetchContextFromServer(repoId, localPath, apiUrl) {
|
|
|
4712
4841
|
console.error(`Context fetch for ${repoId}: no presigned URL returned for ${file.fileName}`);
|
|
4713
4842
|
continue;
|
|
4714
4843
|
}
|
|
4715
|
-
const contentRes = await fetch(presignedUrl
|
|
4844
|
+
const contentRes = await fetch(presignedUrl, {
|
|
4845
|
+
signal: AbortSignal.timeout(CONTEXT_FETCH_TIMEOUT_MS)
|
|
4846
|
+
});
|
|
4716
4847
|
if (!contentRes.ok) {
|
|
4717
4848
|
console.error(`Context fetch for ${repoId}: download failed for ${file.fileName} (${contentRes.status})`);
|
|
4718
4849
|
continue;
|
|
@@ -4733,6 +4864,53 @@ async function fetchContextFromServer(repoId, localPath, apiUrl) {
|
|
|
4733
4864
|
}
|
|
4734
4865
|
}
|
|
4735
4866
|
|
|
4867
|
+
// ../listener/dist/docs-marker.js
|
|
4868
|
+
import { join as join10 } from "path";
|
|
4869
|
+
import { readFile as readFile6, writeFile as writeFile7, rename as rename4 } from "fs/promises";
|
|
4870
|
+
var DOCS_MARKER_NAME = ".repowise-meta.json";
|
|
4871
|
+
async function readDocsMarker(localPath, contextFolder) {
|
|
4872
|
+
try {
|
|
4873
|
+
const raw = await readFile6(join10(localPath, contextFolder, DOCS_MARKER_NAME), "utf-8");
|
|
4874
|
+
const m = JSON.parse(raw);
|
|
4875
|
+
if (typeof m.fileCount !== "number")
|
|
4876
|
+
return null;
|
|
4877
|
+
return {
|
|
4878
|
+
commitSha: typeof m.commitSha === "string" ? m.commitSha : null,
|
|
4879
|
+
fileCount: m.fileCount,
|
|
4880
|
+
updatedAt: typeof m.updatedAt === "string" ? m.updatedAt : ""
|
|
4881
|
+
};
|
|
4882
|
+
} catch {
|
|
4883
|
+
return null;
|
|
4884
|
+
}
|
|
4885
|
+
}
|
|
4886
|
+
async function writeDocsMarker(localPath, contextFolder, commitSha, fileCount) {
|
|
4887
|
+
const marker = {
|
|
4888
|
+
commitSha,
|
|
4889
|
+
fileCount,
|
|
4890
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4891
|
+
};
|
|
4892
|
+
const finalPath = join10(localPath, contextFolder, DOCS_MARKER_NAME);
|
|
4893
|
+
const tmpPath = `${finalPath}.tmp`;
|
|
4894
|
+
try {
|
|
4895
|
+
await writeFile7(tmpPath, JSON.stringify(marker, null, 2), "utf-8");
|
|
4896
|
+
await rename4(tmpPath, finalPath);
|
|
4897
|
+
} catch (err) {
|
|
4898
|
+
console.warn(`[docs-marker] failed to write provenance marker (${commitSha ?? "unknown commit"}): ${err instanceof Error ? err.message : String(err)}`);
|
|
4899
|
+
}
|
|
4900
|
+
}
|
|
4901
|
+
function effectiveToolVariant(graphOnly, docsPresent, marker, lastSyncCommitSha) {
|
|
4902
|
+
if (graphOnly)
|
|
4903
|
+
return "graph";
|
|
4904
|
+
if (!docsPresent)
|
|
4905
|
+
return "graph";
|
|
4906
|
+
if (!marker)
|
|
4907
|
+
return "full";
|
|
4908
|
+
if (lastSyncCommitSha && marker.commitSha && marker.commitSha !== lastSyncCommitSha) {
|
|
4909
|
+
return "graph";
|
|
4910
|
+
}
|
|
4911
|
+
return "full";
|
|
4912
|
+
}
|
|
4913
|
+
|
|
4736
4914
|
// ../listener/dist/main.js
|
|
4737
4915
|
init_process_manager();
|
|
4738
4916
|
|
|
@@ -4740,7 +4918,7 @@ init_process_manager();
|
|
|
4740
4918
|
import { execFile as execFile3 } from "child_process";
|
|
4741
4919
|
import { readFileSync } from "fs";
|
|
4742
4920
|
import { access as access2, constants, realpath } from "fs/promises";
|
|
4743
|
-
import { dirname as dirname4, join as
|
|
4921
|
+
import { dirname as dirname4, join as join12 } from "path";
|
|
4744
4922
|
import { promisify as promisify2 } from "util";
|
|
4745
4923
|
var execFileAsync2 = promisify2(execFile3);
|
|
4746
4924
|
var installInFlight = false;
|
|
@@ -4764,12 +4942,12 @@ async function installUpdate(currentVersion, packageName, targetVersion) {
|
|
|
4764
4942
|
}
|
|
4765
4943
|
installInFlight = true;
|
|
4766
4944
|
try {
|
|
4767
|
-
const npmWrapper =
|
|
4945
|
+
const npmWrapper = join12(dirname4(process.execPath), "npm");
|
|
4768
4946
|
const npmScript = await realpath(npmWrapper);
|
|
4769
4947
|
const runNpm = (args) => execFileAsync2(process.execPath, [npmScript, ...args], { timeout: 6e4 });
|
|
4770
4948
|
try {
|
|
4771
4949
|
const { stdout: prefix } = await runNpm(["prefix", "-g"]);
|
|
4772
|
-
const npmDir =
|
|
4950
|
+
const npmDir = join12(prefix.trim(), "lib", "node_modules");
|
|
4773
4951
|
const checkDir = process.platform === "win32" ? prefix.trim() : npmDir;
|
|
4774
4952
|
await access2(checkDir, constants.W_OK);
|
|
4775
4953
|
} catch (err) {
|
|
@@ -4787,7 +4965,7 @@ async function installUpdate(currentVersion, packageName, targetVersion) {
|
|
|
4787
4965
|
}
|
|
4788
4966
|
try {
|
|
4789
4967
|
const { stdout: prefix } = await runNpm(["prefix", "-g"]);
|
|
4790
|
-
const installedPkgJson =
|
|
4968
|
+
const installedPkgJson = join12(prefix.trim(), "lib", "node_modules", packageName, "package.json");
|
|
4791
4969
|
const installedVersion = JSON.parse(readFileSync(installedPkgJson, "utf-8")).version;
|
|
4792
4970
|
if (installedVersion !== targetVersion) {
|
|
4793
4971
|
const msg = `post-install check failed: expected ${targetVersion}, found ${installedVersion ?? "unknown"}`;
|
|
@@ -4856,11 +5034,11 @@ function comparePrerelease(a, b) {
|
|
|
4856
5034
|
|
|
4857
5035
|
// ../listener/dist/lib/binary-integrity.js
|
|
4858
5036
|
import { createHash as createHash2 } from "crypto";
|
|
4859
|
-
import { lstat, open as
|
|
5037
|
+
import { lstat, open as open4, readdir as readdir2 } from "fs/promises";
|
|
4860
5038
|
import { constants as fsConstants } from "fs";
|
|
4861
|
-
import { join as
|
|
5039
|
+
import { join as join13, resolve as pathResolve } from "path";
|
|
4862
5040
|
async function verifyBinary(check) {
|
|
4863
|
-
const fh = await
|
|
5041
|
+
const fh = await open4(check.path, fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW);
|
|
4864
5042
|
const hash = createHash2("sha256");
|
|
4865
5043
|
try {
|
|
4866
5044
|
const stream = fh.createReadStream();
|
|
@@ -4908,7 +5086,7 @@ async function auditNativeBindings(rootDir, expected) {
|
|
|
4908
5086
|
return;
|
|
4909
5087
|
}
|
|
4910
5088
|
for (const entryName of entries) {
|
|
4911
|
-
const full = pathResolve(
|
|
5089
|
+
const full = pathResolve(join13(dir, entryName));
|
|
4912
5090
|
const entryStat = await lstat(full).catch(() => null);
|
|
4913
5091
|
if (!entryStat)
|
|
4914
5092
|
continue;
|
|
@@ -4949,8 +5127,8 @@ async function auditNativeBindings(rootDir, expected) {
|
|
|
4949
5127
|
init_config_dir();
|
|
4950
5128
|
init_process_manager();
|
|
4951
5129
|
init_service_installer();
|
|
4952
|
-
import { unlink as
|
|
4953
|
-
import { join as
|
|
5130
|
+
import { unlink as unlink8 } from "fs/promises";
|
|
5131
|
+
import { join as join20 } from "path";
|
|
4954
5132
|
async function getListenerStatus() {
|
|
4955
5133
|
const pid = await readPid();
|
|
4956
5134
|
if (pid !== null) {
|
|
@@ -4959,7 +5137,7 @@ async function getListenerStatus() {
|
|
|
4959
5137
|
return { running: true, method: "pid", pid, serviceInstalled: serviceInstalled2 };
|
|
4960
5138
|
}
|
|
4961
5139
|
try {
|
|
4962
|
-
await
|
|
5140
|
+
await unlink8(join20(getConfigDir(), "listener.pid"));
|
|
4963
5141
|
} catch {
|
|
4964
5142
|
}
|
|
4965
5143
|
}
|
|
@@ -5004,11 +5182,11 @@ async function stopListener() {
|
|
|
5004
5182
|
|
|
5005
5183
|
// ../listener/dist/mcp/bootstrap.js
|
|
5006
5184
|
init_config_dir();
|
|
5007
|
-
import { join as
|
|
5185
|
+
import { join as join24 } from "path";
|
|
5008
5186
|
|
|
5009
5187
|
// ../listener/dist/lsp/workspace-session.js
|
|
5010
5188
|
import { pathToFileURL } from "url";
|
|
5011
|
-
import { readFile as
|
|
5189
|
+
import { readFile as readFile8 } from "fs/promises";
|
|
5012
5190
|
import { resolve as pathResolve2, sep as pathSep, dirname as dirname8, delimiter as delimiter2 } from "path";
|
|
5013
5191
|
|
|
5014
5192
|
// ../listener/dist/lsp/lsp-client.js
|
|
@@ -5445,7 +5623,7 @@ var WorkspaceManager = class {
|
|
|
5445
5623
|
}
|
|
5446
5624
|
const absolute = this.joinPath(session.repoRoot, repoRelativePath);
|
|
5447
5625
|
const p = (async () => {
|
|
5448
|
-
const text = await
|
|
5626
|
+
const text = await readFile8(absolute, "utf-8");
|
|
5449
5627
|
session.client.notify("textDocument/didOpen", {
|
|
5450
5628
|
textDocument: {
|
|
5451
5629
|
uri,
|
|
@@ -5720,8 +5898,8 @@ function buildLspSpawnPath(basePath, nodeDir, lspBinDirs) {
|
|
|
5720
5898
|
|
|
5721
5899
|
// ../listener/dist/mcp/graph-cache.js
|
|
5722
5900
|
init_config_dir();
|
|
5723
|
-
import { readFile as
|
|
5724
|
-
import { join as
|
|
5901
|
+
import { readFile as readFile10, stat as stat3 } from "fs/promises";
|
|
5902
|
+
import { join as join22 } from "path";
|
|
5725
5903
|
var EVICT_DEBOUNCE_MS = 6e4;
|
|
5726
5904
|
function assertSafeRepoId(repoId) {
|
|
5727
5905
|
if (!repoId || typeof repoId !== "string") {
|
|
@@ -5736,13 +5914,13 @@ function assertSafeRepoId(repoId) {
|
|
|
5736
5914
|
}
|
|
5737
5915
|
function createGraphCache(options = {}) {
|
|
5738
5916
|
const evictMs = options.evictDebounceMs ?? EVICT_DEBOUNCE_MS;
|
|
5739
|
-
const resolvePath = options.resolveGraphPath ?? ((repoId) =>
|
|
5917
|
+
const resolvePath = options.resolveGraphPath ?? ((repoId) => join22(defaultRepoWiseHome(), "graphs", `${repoId}.json`));
|
|
5740
5918
|
const entries = /* @__PURE__ */ new Map();
|
|
5741
5919
|
const inFlight = /* @__PURE__ */ new Map();
|
|
5742
5920
|
async function loadFromDisk(repoId) {
|
|
5743
5921
|
const graphPath = resolvePath(repoId);
|
|
5744
5922
|
const s = await stat3(graphPath);
|
|
5745
|
-
const body = await
|
|
5923
|
+
const body = await readFile10(graphPath, "utf-8");
|
|
5746
5924
|
const graph = JSON.parse(body);
|
|
5747
5925
|
if (graph.commitSha && Array.isArray(graph.edges)) {
|
|
5748
5926
|
try {
|
|
@@ -5829,8 +6007,54 @@ function defaultRepoWiseHome() {
|
|
|
5829
6007
|
}
|
|
5830
6008
|
|
|
5831
6009
|
// ../listener/dist/mcp/graph-downloader.js
|
|
5832
|
-
import { mkdir as mkdir10, rename as
|
|
6010
|
+
import { mkdir as mkdir10, rename as rename5, writeFile as writeFile11 } from "fs/promises";
|
|
5833
6011
|
import { dirname as dirname10 } from "path";
|
|
6012
|
+
var GRAPH_DOWNLOAD_TIMEOUT_MS = 45e3;
|
|
6013
|
+
var GRAPH_IDLE_TIMEOUT_MS = 6e4;
|
|
6014
|
+
async function fetchTextWithIdleTimeout(fetchFn, url, init, idleMs) {
|
|
6015
|
+
const controller = new AbortController();
|
|
6016
|
+
let timer = null;
|
|
6017
|
+
const arm = () => {
|
|
6018
|
+
if (timer)
|
|
6019
|
+
clearTimeout(timer);
|
|
6020
|
+
timer = setTimeout(() => controller.abort(), idleMs);
|
|
6021
|
+
};
|
|
6022
|
+
const disarm = () => {
|
|
6023
|
+
if (timer)
|
|
6024
|
+
clearTimeout(timer);
|
|
6025
|
+
timer = null;
|
|
6026
|
+
};
|
|
6027
|
+
arm();
|
|
6028
|
+
try {
|
|
6029
|
+
const res = await fetchFn(url, { ...init, signal: controller.signal });
|
|
6030
|
+
if (!res.ok) {
|
|
6031
|
+
disarm();
|
|
6032
|
+
return { res, text: "" };
|
|
6033
|
+
}
|
|
6034
|
+
const stream = res.body;
|
|
6035
|
+
if (!stream || typeof stream.getReader !== "function") {
|
|
6036
|
+
const text = await res.text();
|
|
6037
|
+
disarm();
|
|
6038
|
+
return { res, text };
|
|
6039
|
+
}
|
|
6040
|
+
const reader = stream.getReader();
|
|
6041
|
+
const chunks = [];
|
|
6042
|
+
for (; ; ) {
|
|
6043
|
+
const { done, value } = await reader.read();
|
|
6044
|
+
if (done)
|
|
6045
|
+
break;
|
|
6046
|
+
if (value) {
|
|
6047
|
+
chunks.push(value);
|
|
6048
|
+
arm();
|
|
6049
|
+
}
|
|
6050
|
+
}
|
|
6051
|
+
disarm();
|
|
6052
|
+
return { res, text: Buffer.concat(chunks).toString("utf-8") };
|
|
6053
|
+
} catch (err) {
|
|
6054
|
+
disarm();
|
|
6055
|
+
throw err;
|
|
6056
|
+
}
|
|
6057
|
+
}
|
|
5834
6058
|
function createGraphDownloader(options) {
|
|
5835
6059
|
const fetchFn = options.fetchImpl ?? fetch;
|
|
5836
6060
|
let lastSha = null;
|
|
@@ -5840,7 +6064,8 @@ function createGraphDownloader(options) {
|
|
|
5840
6064
|
let repoRes;
|
|
5841
6065
|
try {
|
|
5842
6066
|
repoRes = await fetchFn(`${options.apiBaseUrl}/v1/repos/${options.repoId}`, {
|
|
5843
|
-
headers: { Authorization: `Bearer ${token}` }
|
|
6067
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
6068
|
+
signal: AbortSignal.timeout(GRAPH_DOWNLOAD_TIMEOUT_MS)
|
|
5844
6069
|
});
|
|
5845
6070
|
} catch (err) {
|
|
5846
6071
|
return {
|
|
@@ -5864,15 +6089,23 @@ function createGraphDownloader(options) {
|
|
|
5864
6089
|
return { state: "no-graph" };
|
|
5865
6090
|
if (serverSha === lastSha)
|
|
5866
6091
|
return { state: "unchanged", sha: serverSha };
|
|
5867
|
-
|
|
5868
|
-
|
|
5869
|
-
|
|
6092
|
+
let graphRes;
|
|
6093
|
+
let body;
|
|
6094
|
+
try {
|
|
6095
|
+
const r = await fetchTextWithIdleTimeout(fetchFn, `${options.apiBaseUrl}/v1/repos/${options.repoId}/graph`, { headers: { Authorization: `Bearer ${token}` } }, GRAPH_IDLE_TIMEOUT_MS);
|
|
6096
|
+
graphRes = r.res;
|
|
6097
|
+
body = r.text;
|
|
6098
|
+
} catch (err) {
|
|
6099
|
+
return {
|
|
6100
|
+
state: "error",
|
|
6101
|
+
message: `graph fetch: ${err instanceof Error ? err.message : String(err)}`
|
|
6102
|
+
};
|
|
6103
|
+
}
|
|
5870
6104
|
if (graphRes.status === 404)
|
|
5871
6105
|
return { state: "no-graph" };
|
|
5872
6106
|
if (!graphRes.ok) {
|
|
5873
6107
|
return { state: "error", message: `graph status ${graphRes.status}` };
|
|
5874
6108
|
}
|
|
5875
|
-
const body = await graphRes.text();
|
|
5876
6109
|
try {
|
|
5877
6110
|
JSON.parse(body);
|
|
5878
6111
|
} catch (err) {
|
|
@@ -5883,8 +6116,8 @@ function createGraphDownloader(options) {
|
|
|
5883
6116
|
}
|
|
5884
6117
|
await mkdir10(dirname10(options.targetPath), { recursive: true });
|
|
5885
6118
|
const tmpPath = `${options.targetPath}.${Date.now()}.tmp`;
|
|
5886
|
-
await
|
|
5887
|
-
await
|
|
6119
|
+
await writeFile11(tmpPath, body, { encoding: "utf-8", mode: 384 });
|
|
6120
|
+
await rename5(tmpPath, options.targetPath);
|
|
5888
6121
|
options.graphCache.invalidate(options.repoId);
|
|
5889
6122
|
lastSha = serverSha;
|
|
5890
6123
|
return { state: "downloaded", sha: serverSha };
|
|
@@ -5904,9 +6137,112 @@ function createGraphDownloader(options) {
|
|
|
5904
6137
|
};
|
|
5905
6138
|
}
|
|
5906
6139
|
|
|
6140
|
+
// ../listener/dist/mcp/heartbeat.js
|
|
6141
|
+
import { setTimeout as delay2 } from "timers/promises";
|
|
6142
|
+
var DEFAULT_WINDOW = 60 * 60 * 1e3;
|
|
6143
|
+
function emptyStatusCounts() {
|
|
6144
|
+
return { ok: 0, error: 0, timeout: 0, rejected: 0 };
|
|
6145
|
+
}
|
|
6146
|
+
function emptyHealthCounters() {
|
|
6147
|
+
return {
|
|
6148
|
+
graphNotLoaded: 0,
|
|
6149
|
+
unknownRepo: 0,
|
|
6150
|
+
lspDisabled: 0,
|
|
6151
|
+
lspUnavailable: 0,
|
|
6152
|
+
connectFail: 0,
|
|
6153
|
+
preAuthFail: 0
|
|
6154
|
+
};
|
|
6155
|
+
}
|
|
6156
|
+
function createHeartbeatEmitter(opts) {
|
|
6157
|
+
const windowMs = opts.windowMs ?? DEFAULT_WINDOW;
|
|
6158
|
+
const now = opts.now ?? (() => Date.now());
|
|
6159
|
+
let bucketStart = Math.floor(now() / windowMs) * windowMs;
|
|
6160
|
+
let toolCounts = /* @__PURE__ */ new Map();
|
|
6161
|
+
let missCounts = /* @__PURE__ */ new Map();
|
|
6162
|
+
let statusCounts = emptyStatusCounts();
|
|
6163
|
+
let healthCounters = emptyHealthCounters();
|
|
6164
|
+
function hasData() {
|
|
6165
|
+
if (toolCounts.size > 0 || missCounts.size > 0)
|
|
6166
|
+
return true;
|
|
6167
|
+
for (const k of Object.keys(statusCounts)) {
|
|
6168
|
+
if (statusCounts[k] > 0)
|
|
6169
|
+
return true;
|
|
6170
|
+
}
|
|
6171
|
+
for (const k of Object.keys(healthCounters)) {
|
|
6172
|
+
if (healthCounters[k] > 0)
|
|
6173
|
+
return true;
|
|
6174
|
+
}
|
|
6175
|
+
return false;
|
|
6176
|
+
}
|
|
6177
|
+
function buildPayload() {
|
|
6178
|
+
return {
|
|
6179
|
+
listenerVersion: opts.listenerVersion,
|
|
6180
|
+
windowStartsAt: new Date(bucketStart).toISOString(),
|
|
6181
|
+
toolCounts: Object.fromEntries(toolCounts),
|
|
6182
|
+
statusCounts,
|
|
6183
|
+
missCounts: Object.fromEntries(missCounts),
|
|
6184
|
+
healthCounters
|
|
6185
|
+
};
|
|
6186
|
+
}
|
|
6187
|
+
function clearCounters() {
|
|
6188
|
+
toolCounts = /* @__PURE__ */ new Map();
|
|
6189
|
+
missCounts = /* @__PURE__ */ new Map();
|
|
6190
|
+
statusCounts = emptyStatusCounts();
|
|
6191
|
+
healthCounters = emptyHealthCounters();
|
|
6192
|
+
}
|
|
6193
|
+
async function flushIfDue(currentBucket) {
|
|
6194
|
+
if (currentBucket === bucketStart)
|
|
6195
|
+
return;
|
|
6196
|
+
const payload = buildPayload();
|
|
6197
|
+
bucketStart = currentBucket;
|
|
6198
|
+
clearCounters();
|
|
6199
|
+
try {
|
|
6200
|
+
await opts.sink.post(payload);
|
|
6201
|
+
} catch {
|
|
6202
|
+
}
|
|
6203
|
+
}
|
|
6204
|
+
return {
|
|
6205
|
+
record(tool, outcome) {
|
|
6206
|
+
toolCounts.set(tool, (toolCounts.get(tool) ?? 0) + 1);
|
|
6207
|
+
if ("health" in outcome) {
|
|
6208
|
+
healthCounters[outcome.health] += 1;
|
|
6209
|
+
} else {
|
|
6210
|
+
statusCounts[outcome.status] += 1;
|
|
6211
|
+
if (outcome.miss)
|
|
6212
|
+
missCounts.set(tool, (missCounts.get(tool) ?? 0) + 1);
|
|
6213
|
+
}
|
|
6214
|
+
},
|
|
6215
|
+
recordHealth(kind) {
|
|
6216
|
+
healthCounters[kind] += 1;
|
|
6217
|
+
},
|
|
6218
|
+
async tick(nowOverride) {
|
|
6219
|
+
const current = Math.floor((nowOverride ?? now()) / windowMs) * windowMs;
|
|
6220
|
+
await flushIfDue(current);
|
|
6221
|
+
},
|
|
6222
|
+
async flush() {
|
|
6223
|
+
if (!hasData())
|
|
6224
|
+
return;
|
|
6225
|
+
const payload = buildPayload();
|
|
6226
|
+
clearCounters();
|
|
6227
|
+
try {
|
|
6228
|
+
await opts.sink.post(payload);
|
|
6229
|
+
} catch {
|
|
6230
|
+
}
|
|
6231
|
+
}
|
|
6232
|
+
};
|
|
6233
|
+
}
|
|
6234
|
+
async function runHeartbeatLoop(emitter, opts = {}) {
|
|
6235
|
+
const tickMs = opts.tickMs ?? 6e4;
|
|
6236
|
+
while (!opts.signal?.aborted) {
|
|
6237
|
+
await emitter.tick();
|
|
6238
|
+
await delay2(tickMs, void 0, { signal: opts.signal }).catch(() => {
|
|
6239
|
+
});
|
|
6240
|
+
}
|
|
6241
|
+
}
|
|
6242
|
+
|
|
5907
6243
|
// ../listener/dist/mcp/log-encryption.js
|
|
5908
6244
|
import { createCipheriv, createDecipheriv, randomBytes } from "crypto";
|
|
5909
|
-
import { mkdir as mkdir11, readFile as
|
|
6245
|
+
import { mkdir as mkdir11, readFile as readFile11, stat as stat4, writeFile as writeFile12 } from "fs/promises";
|
|
5910
6246
|
import { dirname as dirname11 } from "path";
|
|
5911
6247
|
var KEY_BYTES = 32;
|
|
5912
6248
|
var IV_BYTES = 12;
|
|
@@ -5934,7 +6270,7 @@ function createFileKeyStore(keyPath) {
|
|
|
5934
6270
|
return cached;
|
|
5935
6271
|
let parsed = null;
|
|
5936
6272
|
try {
|
|
5937
|
-
const body = await
|
|
6273
|
+
const body = await readFile11(keyPath, "utf-8");
|
|
5938
6274
|
const decoded = Buffer.from(body.trim(), "base64");
|
|
5939
6275
|
if (decoded.length === KEY_BYTES)
|
|
5940
6276
|
parsed = decoded;
|
|
@@ -5956,12 +6292,12 @@ function createFileKeyStore(keyPath) {
|
|
|
5956
6292
|
const fresh = randomBytes(KEY_BYTES);
|
|
5957
6293
|
await mkdir11(dirname11(keyPath), { recursive: true });
|
|
5958
6294
|
const tmp = `${keyPath}.tmp`;
|
|
5959
|
-
await
|
|
6295
|
+
await writeFile12(tmp, fresh.toString("base64") + "\n", {
|
|
5960
6296
|
encoding: "utf-8",
|
|
5961
6297
|
mode: 384
|
|
5962
6298
|
});
|
|
5963
|
-
const { rename:
|
|
5964
|
-
await
|
|
6299
|
+
const { rename: rename8 } = await import("fs/promises");
|
|
6300
|
+
await rename8(tmp, keyPath);
|
|
5965
6301
|
cached = fresh;
|
|
5966
6302
|
try {
|
|
5967
6303
|
const s = await stat4(keyPath);
|
|
@@ -5995,7 +6331,7 @@ function decryptLine(encoded, key) {
|
|
|
5995
6331
|
}
|
|
5996
6332
|
|
|
5997
6333
|
// ../listener/dist/mcp/mcp-logger.js
|
|
5998
|
-
import { appendFile, mkdir as mkdir12, stat as stat5, unlink as
|
|
6334
|
+
import { appendFile, mkdir as mkdir12, stat as stat5, unlink as unlink10, writeFile as writeFile13, readFile as readFile12 } from "fs/promises";
|
|
5999
6335
|
import { dirname as dirname12 } from "path";
|
|
6000
6336
|
var DEFAULT_MAX_BYTES = 100 * 1024 * 1024;
|
|
6001
6337
|
function createMcpLogger(options) {
|
|
@@ -6010,7 +6346,7 @@ function createMcpLogger(options) {
|
|
|
6010
6346
|
if (legacyPath === options.filePath)
|
|
6011
6347
|
return;
|
|
6012
6348
|
try {
|
|
6013
|
-
await
|
|
6349
|
+
await unlink10(legacyPath);
|
|
6014
6350
|
console.log(`[mcp-logger] removed legacy plaintext log at ${legacyPath} (one-time migration)`);
|
|
6015
6351
|
} catch {
|
|
6016
6352
|
}
|
|
@@ -6041,19 +6377,20 @@ function createMcpLogger(options) {
|
|
|
6041
6377
|
}
|
|
6042
6378
|
async function rotate(path, size) {
|
|
6043
6379
|
const dropAt = Math.floor(size * 0.25);
|
|
6044
|
-
const body = await
|
|
6380
|
+
const body = await readFile12(path, "utf-8");
|
|
6045
6381
|
const idx = body.indexOf("\n", dropAt);
|
|
6046
6382
|
if (idx < 0) {
|
|
6047
|
-
await
|
|
6383
|
+
await writeFile13(path, "", { encoding: "utf-8", mode: 384 });
|
|
6048
6384
|
return;
|
|
6049
6385
|
}
|
|
6050
|
-
await
|
|
6386
|
+
await writeFile13(path, body.slice(idx + 1), { encoding: "utf-8", mode: 384 });
|
|
6051
6387
|
}
|
|
6052
6388
|
|
|
6053
6389
|
// ../listener/dist/mcp/mcp-log-uploader.js
|
|
6054
6390
|
import { randomUUID } from "crypto";
|
|
6055
|
-
import { readFile as
|
|
6391
|
+
import { readFile as readFile13, writeFile as writeFile14, mkdir as mkdir13 } from "fs/promises";
|
|
6056
6392
|
import { dirname as dirname13 } from "path";
|
|
6393
|
+
var MCP_LOG_UPLOAD_TIMEOUT_MS = 3e4;
|
|
6057
6394
|
var DEFAULT_MAX_ENTRIES = 5e3;
|
|
6058
6395
|
var DEFAULT_MAX_BYTES2 = 1 * 1024 * 1024;
|
|
6059
6396
|
function createMcpLogUploader(options) {
|
|
@@ -6078,7 +6415,7 @@ function createMcpLogUploader(options) {
|
|
|
6078
6415
|
}
|
|
6079
6416
|
let body;
|
|
6080
6417
|
try {
|
|
6081
|
-
body = await
|
|
6418
|
+
body = await readFile13(options.logFilePath, "utf-8");
|
|
6082
6419
|
} catch (err) {
|
|
6083
6420
|
if (err.code === "ENOENT") {
|
|
6084
6421
|
return { uploaded: 0, bytesAdvanced: 0 };
|
|
@@ -6150,7 +6487,8 @@ function createMcpLogUploader(options) {
|
|
|
6150
6487
|
"Content-Type": "application/json",
|
|
6151
6488
|
Authorization: `Bearer ${token}`
|
|
6152
6489
|
},
|
|
6153
|
-
body: uploadBody
|
|
6490
|
+
body: uploadBody,
|
|
6491
|
+
signal: AbortSignal.timeout(MCP_LOG_UPLOAD_TIMEOUT_MS)
|
|
6154
6492
|
});
|
|
6155
6493
|
if (res.status === 401 && options.getAuthTokenForceRefresh) {
|
|
6156
6494
|
try {
|
|
@@ -6161,7 +6499,8 @@ function createMcpLogUploader(options) {
|
|
|
6161
6499
|
"Content-Type": "application/json",
|
|
6162
6500
|
Authorization: `Bearer ${freshToken}`
|
|
6163
6501
|
},
|
|
6164
|
-
body: uploadBody
|
|
6502
|
+
body: uploadBody,
|
|
6503
|
+
signal: AbortSignal.timeout(MCP_LOG_UPLOAD_TIMEOUT_MS)
|
|
6165
6504
|
});
|
|
6166
6505
|
} catch {
|
|
6167
6506
|
}
|
|
@@ -6195,7 +6534,7 @@ function pendingPath(watermarkPath) {
|
|
|
6195
6534
|
}
|
|
6196
6535
|
async function readPendingBatch(watermarkPath) {
|
|
6197
6536
|
try {
|
|
6198
|
-
const body = await
|
|
6537
|
+
const body = await readFile13(pendingPath(watermarkPath), "utf-8");
|
|
6199
6538
|
const trimmed = body.trim();
|
|
6200
6539
|
return /^[A-Za-z0-9-]{8,128}$/.test(trimmed) ? trimmed : null;
|
|
6201
6540
|
} catch {
|
|
@@ -6204,18 +6543,18 @@ async function readPendingBatch(watermarkPath) {
|
|
|
6204
6543
|
}
|
|
6205
6544
|
async function writePendingBatch(watermarkPath, batchId) {
|
|
6206
6545
|
await mkdir13(dirname13(watermarkPath), { recursive: true });
|
|
6207
|
-
await
|
|
6546
|
+
await writeFile14(pendingPath(watermarkPath), batchId, { encoding: "utf-8", mode: 384 });
|
|
6208
6547
|
}
|
|
6209
6548
|
async function clearPendingBatch(watermarkPath) {
|
|
6210
|
-
const { unlink:
|
|
6549
|
+
const { unlink: unlink14 } = await import("fs/promises");
|
|
6211
6550
|
try {
|
|
6212
|
-
await
|
|
6551
|
+
await unlink14(pendingPath(watermarkPath));
|
|
6213
6552
|
} catch {
|
|
6214
6553
|
}
|
|
6215
6554
|
}
|
|
6216
6555
|
async function readWatermark(path) {
|
|
6217
6556
|
try {
|
|
6218
|
-
const body = await
|
|
6557
|
+
const body = await readFile13(path, "utf-8");
|
|
6219
6558
|
const parsed = Number.parseInt(body.trim(), 10);
|
|
6220
6559
|
return Number.isFinite(parsed) && parsed >= 0 ? parsed : 0;
|
|
6221
6560
|
} catch (err) {
|
|
@@ -6227,13 +6566,13 @@ async function readWatermark(path) {
|
|
|
6227
6566
|
async function writeWatermark(path, offset) {
|
|
6228
6567
|
await mkdir13(dirname13(path), { recursive: true });
|
|
6229
6568
|
const tmp = `${path}.tmp`;
|
|
6230
|
-
await
|
|
6231
|
-
const { rename:
|
|
6232
|
-
await
|
|
6569
|
+
await writeFile14(tmp, offset.toString() + "\n", { encoding: "utf-8", mode: 384 });
|
|
6570
|
+
const { rename: rename8 } = await import("fs/promises");
|
|
6571
|
+
await rename8(tmp, path);
|
|
6233
6572
|
}
|
|
6234
6573
|
async function isLocallyConsented(flagPath2) {
|
|
6235
6574
|
try {
|
|
6236
|
-
const body = await
|
|
6575
|
+
const body = await readFile13(flagPath2, "utf-8");
|
|
6237
6576
|
const parsed = JSON.parse(body);
|
|
6238
6577
|
return parsed.enabled !== false;
|
|
6239
6578
|
} catch {
|
|
@@ -6244,8 +6583,8 @@ async function isLocallyConsented(flagPath2) {
|
|
|
6244
6583
|
// ../listener/dist/mcp/mcp-server.js
|
|
6245
6584
|
init_config_dir();
|
|
6246
6585
|
import { createServer } from "http";
|
|
6247
|
-
import { mkdir as mkdir14, writeFile as
|
|
6248
|
-
import { dirname as dirname14, join as
|
|
6586
|
+
import { mkdir as mkdir14, writeFile as writeFile15, unlink as unlink11 } from "fs/promises";
|
|
6587
|
+
import { dirname as dirname14, join as join23 } from "path";
|
|
6249
6588
|
import { createHash as createHash4, randomUUID as randomUUID2 } from "crypto";
|
|
6250
6589
|
|
|
6251
6590
|
// ../listener/dist/mcp/sanitize.js
|
|
@@ -7309,7 +7648,16 @@ async function startMcpServer(options) {
|
|
|
7309
7648
|
secret: secretState.value.current,
|
|
7310
7649
|
close: () => new Promise((resolve5, reject) => {
|
|
7311
7650
|
server.close((err) => err ? reject(err) : resolve5());
|
|
7312
|
-
})
|
|
7651
|
+
}),
|
|
7652
|
+
suspendEndpoint: async () => {
|
|
7653
|
+
try {
|
|
7654
|
+
await unlink11(endpointFilePath);
|
|
7655
|
+
} catch (err) {
|
|
7656
|
+
if (err.code !== "ENOENT")
|
|
7657
|
+
throw err;
|
|
7658
|
+
}
|
|
7659
|
+
},
|
|
7660
|
+
resumeEndpoint: () => writeEndpointFile(endpointFilePath, endpoint, secretState.value.current)
|
|
7313
7661
|
};
|
|
7314
7662
|
}
|
|
7315
7663
|
function handleRequest(req, res, options, sessions, secretCtx) {
|
|
@@ -7333,11 +7681,13 @@ function handleRequest(req, res, options, sessions, secretCtx) {
|
|
|
7333
7681
|
const rawHost = req.headers.host;
|
|
7334
7682
|
const hostHeader = typeof rawHost === "string" ? rawHost : Array.isArray(rawHost) && typeof rawHost[0] === "string" ? rawHost[0] : void 0;
|
|
7335
7683
|
if (!isHostAllowed(hostHeader)) {
|
|
7684
|
+
options.heartbeat?.recordHealth("preAuthFail");
|
|
7336
7685
|
sendJson(res, 403, { error: "invalid_host" });
|
|
7337
7686
|
return;
|
|
7338
7687
|
}
|
|
7339
7688
|
const bearer = extractBearer(req.headers.authorization);
|
|
7340
7689
|
if (!isTokenValid(bearer, secretCtx.getSecret(), secretCtx.clock())) {
|
|
7690
|
+
options.heartbeat?.recordHealth("preAuthFail");
|
|
7341
7691
|
sendJson(res, 401, { error: "invalid_token" });
|
|
7342
7692
|
return;
|
|
7343
7693
|
}
|
|
@@ -7355,6 +7705,7 @@ function handleRequest(req, res, options, sessions, secretCtx) {
|
|
|
7355
7705
|
const rawMsg = err instanceof Error ? err.message : String(err);
|
|
7356
7706
|
const safeMsg = stripAbsolutePaths(rawMsg);
|
|
7357
7707
|
if (err?.code === "ENOENT") {
|
|
7708
|
+
options.heartbeat?.recordHealth("connectFail");
|
|
7358
7709
|
return sendJson(res, 404, { error: "unknown_repo" });
|
|
7359
7710
|
}
|
|
7360
7711
|
sendJson(res, 500, { error: safeMsg });
|
|
@@ -7529,7 +7880,7 @@ function handleRequest(req, res, options, sessions, secretCtx) {
|
|
|
7529
7880
|
})();
|
|
7530
7881
|
if (method === "POST" && lspDispatch[url]) {
|
|
7531
7882
|
const lspHandler = lspDispatch[url];
|
|
7532
|
-
dispatchSessionTool(req, res, url, sessions, options.mcpLogger, async (session, body) => {
|
|
7883
|
+
dispatchSessionTool(req, res, url, sessions, options.mcpLogger, options.heartbeat, async (session, body) => {
|
|
7533
7884
|
if (options.getLspEnabled && options.getLspEnabled() === false) {
|
|
7534
7885
|
return {
|
|
7535
7886
|
ok: true,
|
|
@@ -7551,14 +7902,17 @@ function handleRequest(req, res, options, sessions, secretCtx) {
|
|
|
7551
7902
|
}
|
|
7552
7903
|
};
|
|
7553
7904
|
}
|
|
7554
|
-
const value = await Promise.race([
|
|
7905
|
+
const value = await Promise.race([
|
|
7906
|
+
lspHandler(body, root),
|
|
7907
|
+
timeoutAfter(TOOL_TIMEOUT_MS_LSP)
|
|
7908
|
+
]);
|
|
7555
7909
|
return { ok: true, value };
|
|
7556
7910
|
});
|
|
7557
7911
|
return;
|
|
7558
7912
|
}
|
|
7559
7913
|
if (method === "POST" && toolDispatch[url]) {
|
|
7560
7914
|
const handler = toolDispatch[url];
|
|
7561
|
-
dispatchSessionTool(req, res, url, sessions, options.mcpLogger, async (session, body) => {
|
|
7915
|
+
dispatchSessionTool(req, res, url, sessions, options.mcpLogger, options.heartbeat, async (session, body) => {
|
|
7562
7916
|
const graph = options.graphCache.peek(session.repoId);
|
|
7563
7917
|
if (!graph) {
|
|
7564
7918
|
return {
|
|
@@ -7591,7 +7945,7 @@ function handleRequest(req, res, options, sessions, secretCtx) {
|
|
|
7591
7945
|
}
|
|
7592
7946
|
sendJson(res, 404, { error: "not_found" });
|
|
7593
7947
|
}
|
|
7594
|
-
function dispatchSessionTool(req, res, url, sessions, logger, handler) {
|
|
7948
|
+
function dispatchSessionTool(req, res, url, sessions, logger, heartbeat, handler) {
|
|
7595
7949
|
const tool = url.replace("/mcp/tools/", "");
|
|
7596
7950
|
const startedAt = Date.now();
|
|
7597
7951
|
void readJson(req).then(async (body) => {
|
|
@@ -7610,6 +7964,7 @@ function dispatchSessionTool(req, res, url, sessions, logger, handler) {
|
|
|
7610
7964
|
error: "unknown_session"
|
|
7611
7965
|
});
|
|
7612
7966
|
logToStdout("rejected", tool, null, Date.now() - startedAt, "unknown_session");
|
|
7967
|
+
heartbeat?.record(tool, { status: "rejected" });
|
|
7613
7968
|
return sendJson(res, 404, { error: "unknown_session" });
|
|
7614
7969
|
}
|
|
7615
7970
|
try {
|
|
@@ -7625,6 +7980,8 @@ function dispatchSessionTool(req, res, url, sessions, logger, handler) {
|
|
|
7625
7980
|
latencyMs: Date.now() - startedAt
|
|
7626
7981
|
});
|
|
7627
7982
|
logToStdout("ok", tool, session.repoId, Date.now() - startedAt);
|
|
7983
|
+
const classification = classifyToolResult(tool, result.value);
|
|
7984
|
+
heartbeat?.record(tool, classification.kind === "health" ? { health: classification.health } : { status: classification.status, miss: classification.miss });
|
|
7628
7985
|
return sendJson(res, 200, result.value);
|
|
7629
7986
|
}
|
|
7630
7987
|
const rejectErr = typeof result.body["error"] === "string" ? result.body["error"] : "tool_rejected";
|
|
@@ -7639,9 +7996,11 @@ function dispatchSessionTool(req, res, url, sessions, logger, handler) {
|
|
|
7639
7996
|
error: rejectErr
|
|
7640
7997
|
});
|
|
7641
7998
|
logToStdout("rejected", tool, session.repoId, Date.now() - startedAt, rejectErr);
|
|
7999
|
+
heartbeat?.record(tool, { status: "rejected" });
|
|
7642
8000
|
return sendJson(res, result.status, result.body);
|
|
7643
8001
|
} catch (err) {
|
|
7644
|
-
const
|
|
8002
|
+
const thrown = classifyThrow(err);
|
|
8003
|
+
const isTimeout = thrown.status === "timeout";
|
|
7645
8004
|
const rawMsg = err instanceof Error ? err.message : String(err);
|
|
7646
8005
|
const safeMsg = stripAbsolutePaths(rawMsg);
|
|
7647
8006
|
recordLog(logger, {
|
|
@@ -7655,6 +8014,7 @@ function dispatchSessionTool(req, res, url, sessions, logger, handler) {
|
|
|
7655
8014
|
error: safeMsg
|
|
7656
8015
|
});
|
|
7657
8016
|
logToStdout("error", tool, session.repoId, Date.now() - startedAt, safeMsg);
|
|
8017
|
+
heartbeat?.record(tool, { status: thrown.status });
|
|
7658
8018
|
if (isTimeout) {
|
|
7659
8019
|
return sendJson(res, 200, {
|
|
7660
8020
|
error: `tool-timeout: ${tool} exceeded its execution budget`,
|
|
@@ -7731,6 +8091,73 @@ var ToolTimeoutError = class extends Error {
|
|
|
7731
8091
|
this.timeoutMs = ms;
|
|
7732
8092
|
}
|
|
7733
8093
|
};
|
|
8094
|
+
function asRecord(value) {
|
|
8095
|
+
return typeof value === "object" && value !== null ? value : void 0;
|
|
8096
|
+
}
|
|
8097
|
+
function arrayLen(rec, key) {
|
|
8098
|
+
const val = rec?.[key];
|
|
8099
|
+
return Array.isArray(val) ? val.length : void 0;
|
|
8100
|
+
}
|
|
8101
|
+
function nested(rec, key) {
|
|
8102
|
+
return asRecord(rec?.[key]);
|
|
8103
|
+
}
|
|
8104
|
+
var MISS_PREDICATES = {
|
|
8105
|
+
find_symbol: (v) => arrayLen(v, "matches") === 0,
|
|
8106
|
+
search_pattern: (v) => arrayLen(v, "matches") === 0,
|
|
8107
|
+
get_symbol: (v) => v?.["symbol"] === null,
|
|
8108
|
+
find_callers: (v) => arrayLen(v, "callers") === 0,
|
|
8109
|
+
find_references: (v) => arrayLen(v, "references") === 0,
|
|
8110
|
+
get_deps: (v) => arrayLen(v, "dependencies") === 0,
|
|
8111
|
+
find_tests_for_symbol: (v) => arrayLen(v, "tests") === 0,
|
|
8112
|
+
get_impact: (v) => arrayLen(v, "impacted") === 0,
|
|
8113
|
+
list_edges: (v) => nested(v, "coverage")?.["filteredEdges"] === 0,
|
|
8114
|
+
get_call_graph: (v) => arrayLen(nested(v, "subgraph"), "edges") === 0,
|
|
8115
|
+
lsp_definition: (v) => arrayLen(v, "locations") === 0,
|
|
8116
|
+
lsp_implementation: (v) => arrayLen(v, "locations") === 0,
|
|
8117
|
+
lsp_references: (v) => arrayLen(v, "locations") === 0,
|
|
8118
|
+
lsp_hover: (v) => !v?.["contents"],
|
|
8119
|
+
lsp_workspace_symbol: (v) => arrayLen(v, "symbols") === 0,
|
|
8120
|
+
lsp_document_symbol: (v) => arrayLen(v, "symbols") === 0,
|
|
8121
|
+
lsp_call_hierarchy: (v) => arrayLen(v, "edges") === 0,
|
|
8122
|
+
lsp_type_hierarchy: (v) => arrayLen(v, "entries") === 0
|
|
8123
|
+
};
|
|
8124
|
+
function isMiss(tool, value) {
|
|
8125
|
+
const pred = MISS_PREDICATES[tool];
|
|
8126
|
+
if (!pred)
|
|
8127
|
+
return false;
|
|
8128
|
+
try {
|
|
8129
|
+
return pred(asRecord(value));
|
|
8130
|
+
} catch {
|
|
8131
|
+
return false;
|
|
8132
|
+
}
|
|
8133
|
+
}
|
|
8134
|
+
function classifyToolResult(tool, value) {
|
|
8135
|
+
const v = asRecord(value);
|
|
8136
|
+
if (v && v["isError"] === true) {
|
|
8137
|
+
const err = typeof v["error"] === "string" ? v["error"] : "";
|
|
8138
|
+
if (err === "graph_not_loaded")
|
|
8139
|
+
return { kind: "health", health: "graphNotLoaded" };
|
|
8140
|
+
if (err === "unknown_repo")
|
|
8141
|
+
return { kind: "health", health: "unknownRepo" };
|
|
8142
|
+
if (err === "lsp_disabled")
|
|
8143
|
+
return { kind: "health", health: "lspDisabled" };
|
|
8144
|
+
if (err.startsWith("no-server-available") || err.startsWith("unsupported-language")) {
|
|
8145
|
+
return { kind: "health", health: "lspUnavailable" };
|
|
8146
|
+
}
|
|
8147
|
+
if (v["invalidPattern"] === true || v["unsafePattern"] === true || / required \(/.test(err)) {
|
|
8148
|
+
return { kind: "status", status: "rejected", miss: false };
|
|
8149
|
+
}
|
|
8150
|
+
return { kind: "status", status: "error", miss: false };
|
|
8151
|
+
}
|
|
8152
|
+
return { kind: "status", status: "ok", miss: isMiss(tool, value) };
|
|
8153
|
+
}
|
|
8154
|
+
function classifyThrow(err) {
|
|
8155
|
+
return {
|
|
8156
|
+
kind: "status",
|
|
8157
|
+
status: err instanceof ToolTimeoutError ? "timeout" : "error",
|
|
8158
|
+
miss: false
|
|
8159
|
+
};
|
|
8160
|
+
}
|
|
7734
8161
|
function stripAbsolutePaths(msg) {
|
|
7735
8162
|
return msg.replace(/(?:^|(?<=\s|[:(,]))\/[\w.\-/]+/g, "<redacted-path>");
|
|
7736
8163
|
}
|
|
@@ -7764,7 +8191,7 @@ async function readJson(req) {
|
|
|
7764
8191
|
}
|
|
7765
8192
|
async function writeEndpointFile(path, endpoint, secret) {
|
|
7766
8193
|
await mkdir14(dirname14(path), { recursive: true });
|
|
7767
|
-
await
|
|
8194
|
+
await writeFile15(path, formatEndpointFile({ endpoint, secret }), {
|
|
7768
8195
|
encoding: "utf-8",
|
|
7769
8196
|
mode: 384
|
|
7770
8197
|
});
|
|
@@ -7777,7 +8204,7 @@ function extractBearer(header) {
|
|
|
7777
8204
|
return m ? m[1] : null;
|
|
7778
8205
|
}
|
|
7779
8206
|
function defaultEndpointFile() {
|
|
7780
|
-
return
|
|
8207
|
+
return join23(getConfigDir(), "listener.endpoint");
|
|
7781
8208
|
}
|
|
7782
8209
|
|
|
7783
8210
|
// ../listener/dist/mcp/bootstrap.js
|
|
@@ -7785,7 +8212,7 @@ async function startMcp(options) {
|
|
|
7785
8212
|
const disabled = process.env.REPOWISE_MCP_DISABLED === "true";
|
|
7786
8213
|
const graphsDir = options.graphsDir ?? defaultGraphsDir();
|
|
7787
8214
|
const graphCache = createGraphCache({
|
|
7788
|
-
resolveGraphPath: (repoId) =>
|
|
8215
|
+
resolveGraphPath: (repoId) => join24(graphsDir, `${repoId}.json`)
|
|
7789
8216
|
});
|
|
7790
8217
|
if (disabled) {
|
|
7791
8218
|
return {
|
|
@@ -7807,13 +8234,14 @@ async function startMcp(options) {
|
|
|
7807
8234
|
repoIdToRoot.set(repo.repoId, repo.localPath);
|
|
7808
8235
|
}
|
|
7809
8236
|
const firstRepoLocal = options.repos.find((r) => r.localPath)?.localPath;
|
|
8237
|
+
const rawMcpLogEnabled = process.env["REPOWISE_MCP_LOG_RAW_ENABLED"] === "true";
|
|
7810
8238
|
const mcpHome = defaultMcpHome();
|
|
7811
|
-
const logFilePath =
|
|
7812
|
-
const keyStore = createFileKeyStore(
|
|
7813
|
-
const mcpLogger = createMcpLogger({ filePath: logFilePath, keyStore });
|
|
7814
|
-
const flagFilePath =
|
|
7815
|
-
const watermarkFilePath =
|
|
7816
|
-
const consentSentMarkerPath =
|
|
8239
|
+
const logFilePath = join24(mcpHome, "mcp-log.jsonl.enc");
|
|
8240
|
+
const keyStore = createFileKeyStore(join24(mcpHome, "mcp-log.key"));
|
|
8241
|
+
const mcpLogger = rawMcpLogEnabled ? createMcpLogger({ filePath: logFilePath, keyStore }) : void 0;
|
|
8242
|
+
const flagFilePath = join24(mcpHome, "mcp-log.flag");
|
|
8243
|
+
const watermarkFilePath = join24(mcpHome, "mcp-log.watermark");
|
|
8244
|
+
const consentSentMarkerPath = join24(mcpHome, "mcp-log.consent-sent");
|
|
7817
8245
|
let uploader = null;
|
|
7818
8246
|
let lastConsentState = false;
|
|
7819
8247
|
let serverConsentEnsuredThisProcess = false;
|
|
@@ -7835,6 +8263,18 @@ async function startMcp(options) {
|
|
|
7835
8263
|
});
|
|
7836
8264
|
return uploader;
|
|
7837
8265
|
}
|
|
8266
|
+
const heartbeatEnabled = process.env["REPOWISE_MCP_HEARTBEAT_DISABLED"] !== "true";
|
|
8267
|
+
const heartbeatApiUrl = options.repos.find((r) => Boolean(r.apiUrl))?.apiUrl ?? options.repos[0]?.apiUrl ?? "";
|
|
8268
|
+
const heartbeatEmitter = heartbeatEnabled ? createHeartbeatEmitter({
|
|
8269
|
+
listenerVersion: options.listenerVersion ?? "",
|
|
8270
|
+
sink: createHeartbeatSink({
|
|
8271
|
+
apiUrl: heartbeatApiUrl,
|
|
8272
|
+
getAuthToken: options.getAuthToken,
|
|
8273
|
+
flagFilePath,
|
|
8274
|
+
...options.fetchImpl ? { fetchImpl: options.fetchImpl } : {}
|
|
8275
|
+
})
|
|
8276
|
+
}) : null;
|
|
8277
|
+
const heartbeatAbort = heartbeatEmitter ? new AbortController() : null;
|
|
7838
8278
|
const server = await startMcpServer({
|
|
7839
8279
|
graphCache,
|
|
7840
8280
|
...lspWorkspaces ? { lspWorkspaces } : {},
|
|
@@ -7842,8 +8282,14 @@ async function startMcp(options) {
|
|
|
7842
8282
|
repoIdToRoot: () => repoIdToRoot,
|
|
7843
8283
|
...options.getLspOverrides ? { getLspOverrides: options.getLspOverrides } : {},
|
|
7844
8284
|
...options.getLspEnabled ? { getLspEnabled: options.getLspEnabled } : {},
|
|
7845
|
-
|
|
8285
|
+
...heartbeatEmitter ? { heartbeat: heartbeatEmitter } : {},
|
|
8286
|
+
// T11 — undefined unless the raw-log escape hatch is set; recordLog
|
|
8287
|
+
// no-ops on an undefined logger so dispatch is unaffected.
|
|
8288
|
+
...mcpLogger ? { mcpLogger } : {}
|
|
7846
8289
|
});
|
|
8290
|
+
if (heartbeatEmitter && heartbeatAbort) {
|
|
8291
|
+
void runHeartbeatLoop(heartbeatEmitter, { signal: heartbeatAbort.signal });
|
|
8292
|
+
}
|
|
7847
8293
|
const reapInterval = lspWorkspaces ? setInterval(() => {
|
|
7848
8294
|
void lspWorkspaces.reapIdle().catch((err) => {
|
|
7849
8295
|
console.warn(`[lsp] reapIdle failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -7860,7 +8306,7 @@ async function startMcp(options) {
|
|
|
7860
8306
|
apiBaseUrl: repo.apiUrl,
|
|
7861
8307
|
getAuthToken: options.getAuthToken,
|
|
7862
8308
|
repoId: repo.repoId,
|
|
7863
|
-
targetPath:
|
|
8309
|
+
targetPath: join24(graphsDir, `${repo.repoId}.json`),
|
|
7864
8310
|
graphCache,
|
|
7865
8311
|
...options.fetchImpl ? { fetchImpl: options.fetchImpl } : {}
|
|
7866
8312
|
}));
|
|
@@ -7886,6 +8332,9 @@ async function startMcp(options) {
|
|
|
7886
8332
|
return results;
|
|
7887
8333
|
},
|
|
7888
8334
|
async runMcpLogUpload() {
|
|
8335
|
+
if (!rawMcpLogEnabled) {
|
|
8336
|
+
return { uploaded: 0, skipped: "no-consent" };
|
|
8337
|
+
}
|
|
7889
8338
|
if (process.env["REPOWISE_MCP_LOG_UPLOAD_DISABLED"] === "true") {
|
|
7890
8339
|
return { uploaded: 0, skipped: "no-consent" };
|
|
7891
8340
|
}
|
|
@@ -7945,7 +8394,7 @@ async function startMcp(options) {
|
|
|
7945
8394
|
apiBaseUrl: repo.apiUrl,
|
|
7946
8395
|
getAuthToken: options.getAuthToken,
|
|
7947
8396
|
repoId: repo.repoId,
|
|
7948
|
-
targetPath:
|
|
8397
|
+
targetPath: join24(graphsDir, `${repo.repoId}.json`),
|
|
7949
8398
|
graphCache,
|
|
7950
8399
|
...options.fetchImpl ? { fetchImpl: options.fetchImpl } : {}
|
|
7951
8400
|
}));
|
|
@@ -7954,6 +8403,10 @@ async function startMcp(options) {
|
|
|
7954
8403
|
async close() {
|
|
7955
8404
|
if (reapInterval)
|
|
7956
8405
|
clearInterval(reapInterval);
|
|
8406
|
+
if (heartbeatAbort)
|
|
8407
|
+
heartbeatAbort.abort();
|
|
8408
|
+
if (heartbeatEmitter)
|
|
8409
|
+
await heartbeatEmitter.flush();
|
|
7957
8410
|
await server.close();
|
|
7958
8411
|
if (lspWorkspaces) {
|
|
7959
8412
|
try {
|
|
@@ -7965,8 +8418,40 @@ async function startMcp(options) {
|
|
|
7965
8418
|
}
|
|
7966
8419
|
};
|
|
7967
8420
|
}
|
|
8421
|
+
function createHeartbeatSink(deps) {
|
|
8422
|
+
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
8423
|
+
return {
|
|
8424
|
+
async post(payload) {
|
|
8425
|
+
if (process.env["REPOWISE_MCP_HEARTBEAT_DISABLED"] === "true")
|
|
8426
|
+
return;
|
|
8427
|
+
if (!deps.apiUrl)
|
|
8428
|
+
return;
|
|
8429
|
+
if (!await isLocallyConsented(deps.flagFilePath))
|
|
8430
|
+
return;
|
|
8431
|
+
let token;
|
|
8432
|
+
try {
|
|
8433
|
+
token = await deps.getAuthToken();
|
|
8434
|
+
} catch {
|
|
8435
|
+
return;
|
|
8436
|
+
}
|
|
8437
|
+
if (!token)
|
|
8438
|
+
return;
|
|
8439
|
+
const res = await fetchImpl(`${deps.apiUrl}/v1/listeners/mcp-heartbeat`, {
|
|
8440
|
+
method: "POST",
|
|
8441
|
+
headers: {
|
|
8442
|
+
"Content-Type": "application/json",
|
|
8443
|
+
Authorization: `Bearer ${token}`
|
|
8444
|
+
},
|
|
8445
|
+
body: JSON.stringify(payload)
|
|
8446
|
+
});
|
|
8447
|
+
if (res && !res.ok) {
|
|
8448
|
+
console.warn(`[mcp-heartbeat] upload rejected (HTTP ${res.status}) \u2014 possible payload/contract drift`);
|
|
8449
|
+
}
|
|
8450
|
+
}
|
|
8451
|
+
};
|
|
8452
|
+
}
|
|
7968
8453
|
function defaultGraphsDir() {
|
|
7969
|
-
return
|
|
8454
|
+
return join24(getConfigDir(), "graphs");
|
|
7970
8455
|
}
|
|
7971
8456
|
function defaultMcpHome() {
|
|
7972
8457
|
return getConfigDir();
|
|
@@ -7984,8 +8469,8 @@ async function ensureServerConsent(opts) {
|
|
|
7984
8469
|
if (!opts.apiUrl)
|
|
7985
8470
|
return;
|
|
7986
8471
|
try {
|
|
7987
|
-
const { readFile:
|
|
7988
|
-
await
|
|
8472
|
+
const { readFile: readFile19 } = await import("fs/promises");
|
|
8473
|
+
await readFile19(opts.markerPath, "utf-8");
|
|
7989
8474
|
return;
|
|
7990
8475
|
} catch {
|
|
7991
8476
|
}
|
|
@@ -8024,7 +8509,7 @@ import { basename as basename3 } from "path";
|
|
|
8024
8509
|
|
|
8025
8510
|
// ../listener/dist/mcp/auto-config/writers/claude-code.js
|
|
8026
8511
|
import { promises as fs6 } from "fs";
|
|
8027
|
-
import { join as
|
|
8512
|
+
import { join as join25 } from "path";
|
|
8028
8513
|
|
|
8029
8514
|
// ../listener/dist/mcp/auto-config/markers.js
|
|
8030
8515
|
import { promises as fs5 } from "fs";
|
|
@@ -8104,10 +8589,10 @@ function safeExistingContent(path, current) {
|
|
|
8104
8589
|
var claudeCodeWriter = {
|
|
8105
8590
|
tool: "claude-code",
|
|
8106
8591
|
async detect(repoRoot, home) {
|
|
8107
|
-
return await fileExists2(
|
|
8592
|
+
return await fileExists2(join25(repoRoot, "CLAUDE.md")) || await hasClaudeBinary(home);
|
|
8108
8593
|
},
|
|
8109
8594
|
async write(ctx) {
|
|
8110
|
-
const path =
|
|
8595
|
+
const path = join25(ctx.repoRoot, ".mcp.json");
|
|
8111
8596
|
await removeFromConfig(path, `repowise-${ctx.repoId}`);
|
|
8112
8597
|
const status2 = await writeMergedConfig({
|
|
8113
8598
|
path,
|
|
@@ -8117,7 +8602,7 @@ var claudeCodeWriter = {
|
|
|
8117
8602
|
return { status: status2, path };
|
|
8118
8603
|
},
|
|
8119
8604
|
async remove(ctx) {
|
|
8120
|
-
const path =
|
|
8605
|
+
const path = join25(ctx.repoRoot, ".mcp.json");
|
|
8121
8606
|
await removeFromConfig(path, `RepoWise MCP for ${ctx.repoName}`);
|
|
8122
8607
|
await removeFromConfig(path, `repowise-${ctx.repoId}`);
|
|
8123
8608
|
}
|
|
@@ -8131,20 +8616,20 @@ async function fileExists2(path) {
|
|
|
8131
8616
|
}
|
|
8132
8617
|
}
|
|
8133
8618
|
async function hasClaudeBinary(home) {
|
|
8134
|
-
return fileExists2(
|
|
8619
|
+
return fileExists2(join25(home, ".claude", "claude.json"));
|
|
8135
8620
|
}
|
|
8136
8621
|
|
|
8137
8622
|
// ../listener/dist/mcp/auto-config/writers/claude-code-hooks.js
|
|
8138
8623
|
import { promises as fs7 } from "fs";
|
|
8139
|
-
import { join as
|
|
8624
|
+
import { join as join26 } from "path";
|
|
8140
8625
|
var claudeCodeHooksWriter = {
|
|
8141
8626
|
tool: "claude-code-hooks",
|
|
8142
8627
|
async detect(repoRoot, home) {
|
|
8143
|
-
return await fileExists3(
|
|
8628
|
+
return await fileExists3(join26(repoRoot, "CLAUDE.md")) || await fileExists3(join26(home, ".claude.json"));
|
|
8144
8629
|
},
|
|
8145
8630
|
async write(ctx) {
|
|
8146
8631
|
const result = await writeClaudeHooksToRepo(ctx.repoRoot, ctx.contextFolder ?? "repowise-context", ctx.variant);
|
|
8147
|
-
return { status: result.status, path:
|
|
8632
|
+
return { status: result.status, path: join26(ctx.repoRoot, result.relPath) };
|
|
8148
8633
|
},
|
|
8149
8634
|
async remove(ctx) {
|
|
8150
8635
|
await removeClaudeHooksFromRepo(ctx.repoRoot);
|
|
@@ -8161,14 +8646,14 @@ async function fileExists3(path) {
|
|
|
8161
8646
|
|
|
8162
8647
|
// ../listener/dist/mcp/auto-config/writers/cline.js
|
|
8163
8648
|
import { promises as fs8 } from "fs";
|
|
8164
|
-
import { join as
|
|
8649
|
+
import { join as join27 } from "path";
|
|
8165
8650
|
var clineWriter = {
|
|
8166
8651
|
tool: "cline",
|
|
8167
8652
|
async detect(repoRoot, home) {
|
|
8168
|
-
return await fileExists4(
|
|
8653
|
+
return await fileExists4(join27(repoRoot, ".clinerules")) || await fileExists4(join27(home, ".cline"));
|
|
8169
8654
|
},
|
|
8170
8655
|
async write(ctx) {
|
|
8171
|
-
const path =
|
|
8656
|
+
const path = join27(ctx.home, ".cline", "mcp.json");
|
|
8172
8657
|
await removeFromConfig(path, `repowise-${ctx.repoId}`);
|
|
8173
8658
|
const status2 = await writeMergedConfig({
|
|
8174
8659
|
path,
|
|
@@ -8178,7 +8663,7 @@ var clineWriter = {
|
|
|
8178
8663
|
return { status: status2, path };
|
|
8179
8664
|
},
|
|
8180
8665
|
async remove(ctx) {
|
|
8181
|
-
const path =
|
|
8666
|
+
const path = join27(ctx.home, ".cline", "mcp.json");
|
|
8182
8667
|
await removeFromConfig(path, `RepoWise MCP for ${ctx.repoName}`);
|
|
8183
8668
|
await removeFromConfig(path, `repowise-${ctx.repoId}`);
|
|
8184
8669
|
}
|
|
@@ -8194,13 +8679,13 @@ async function fileExists4(path) {
|
|
|
8194
8679
|
|
|
8195
8680
|
// ../listener/dist/mcp/auto-config/writers/cline-hooks.js
|
|
8196
8681
|
import { promises as fs9 } from "fs";
|
|
8197
|
-
import { join as
|
|
8682
|
+
import { join as join28 } from "path";
|
|
8198
8683
|
var SCRIPT_REL = ".clinerules/hooks/UserPromptSubmit";
|
|
8199
8684
|
var OWNERSHIP_MARKER = "managed by RepoWise";
|
|
8200
8685
|
var clineHooksWriter = {
|
|
8201
8686
|
tool: "cline-hooks",
|
|
8202
8687
|
async detect(repoRoot) {
|
|
8203
|
-
return fileExists5(
|
|
8688
|
+
return fileExists5(join28(repoRoot, ".clinerules"));
|
|
8204
8689
|
},
|
|
8205
8690
|
async write(ctx) {
|
|
8206
8691
|
if (await isGitTracked(ctx.repoRoot, SCRIPT_REL)) {
|
|
@@ -8210,7 +8695,7 @@ var clineHooksWriter = {
|
|
|
8210
8695
|
reason: `${SCRIPT_REL} is git-tracked; update it manually to opt in`
|
|
8211
8696
|
};
|
|
8212
8697
|
}
|
|
8213
|
-
const path =
|
|
8698
|
+
const path = join28(ctx.repoRoot, SCRIPT_REL);
|
|
8214
8699
|
const next = buildClineHookScript({
|
|
8215
8700
|
repoName: ctx.repoName,
|
|
8216
8701
|
contextFolder: ctx.contextFolder ?? "repowise-context",
|
|
@@ -8227,14 +8712,14 @@ var clineHooksWriter = {
|
|
|
8227
8712
|
await ensureExecutable(path);
|
|
8228
8713
|
return { status: "unchanged", path };
|
|
8229
8714
|
}
|
|
8230
|
-
await fs9.mkdir(
|
|
8715
|
+
await fs9.mkdir(join28(ctx.repoRoot, ".clinerules", "hooks"), { recursive: true });
|
|
8231
8716
|
await fs9.writeFile(path, next, { encoding: "utf-8", mode: 493 });
|
|
8232
8717
|
await ensureExecutable(path);
|
|
8233
8718
|
await applyGitignoreChanges(ctx.repoRoot, { add: [SCRIPT_REL] });
|
|
8234
8719
|
return { status: "written", path };
|
|
8235
8720
|
},
|
|
8236
8721
|
async remove(ctx) {
|
|
8237
|
-
const path =
|
|
8722
|
+
const path = join28(ctx.repoRoot, SCRIPT_REL);
|
|
8238
8723
|
const existing = await readOrNull(path);
|
|
8239
8724
|
if (existing !== null && existing.includes(OWNERSHIP_MARKER)) {
|
|
8240
8725
|
await fs9.unlink(path);
|
|
@@ -8269,14 +8754,14 @@ async function readOrNull(path) {
|
|
|
8269
8754
|
|
|
8270
8755
|
// ../listener/dist/mcp/auto-config/writers/codex.js
|
|
8271
8756
|
import { promises as fs10 } from "fs";
|
|
8272
|
-
import { join as
|
|
8757
|
+
import { join as join29 } from "path";
|
|
8273
8758
|
var codexWriter = {
|
|
8274
8759
|
tool: "codex",
|
|
8275
8760
|
async detect(_repoRoot, home) {
|
|
8276
|
-
return fileExists6(
|
|
8761
|
+
return fileExists6(join29(home, ".codex"));
|
|
8277
8762
|
},
|
|
8278
8763
|
async write(ctx) {
|
|
8279
|
-
const path =
|
|
8764
|
+
const path = join29(ctx.home, ".codex", "mcp.json");
|
|
8280
8765
|
await removeFromConfig(path, `repowise-${ctx.repoId}`);
|
|
8281
8766
|
const status2 = await writeMergedConfig({
|
|
8282
8767
|
path,
|
|
@@ -8286,7 +8771,7 @@ var codexWriter = {
|
|
|
8286
8771
|
return { status: status2, path };
|
|
8287
8772
|
},
|
|
8288
8773
|
async remove(ctx) {
|
|
8289
|
-
const path =
|
|
8774
|
+
const path = join29(ctx.home, ".codex", "mcp.json");
|
|
8290
8775
|
await removeFromConfig(path, `RepoWise MCP for ${ctx.repoName}`);
|
|
8291
8776
|
await removeFromConfig(path, `repowise-${ctx.repoId}`);
|
|
8292
8777
|
}
|
|
@@ -8302,15 +8787,15 @@ async function fileExists6(path) {
|
|
|
8302
8787
|
|
|
8303
8788
|
// ../listener/dist/mcp/auto-config/writers/codex-hooks.js
|
|
8304
8789
|
import { promises as fs11 } from "fs";
|
|
8305
|
-
import { join as
|
|
8790
|
+
import { join as join30 } from "path";
|
|
8306
8791
|
var HOOKS_REL = ".codex/hooks.json";
|
|
8307
8792
|
var codexHooksWriter = {
|
|
8308
8793
|
tool: "codex-hooks",
|
|
8309
8794
|
async detect(repoRoot, home) {
|
|
8310
|
-
return await fileExists7(
|
|
8795
|
+
return await fileExists7(join30(home, ".codex")) || await fileExists7(join30(repoRoot, ".codex"));
|
|
8311
8796
|
},
|
|
8312
8797
|
async write(ctx) {
|
|
8313
|
-
const repoSignal = await fileExists7(
|
|
8798
|
+
const repoSignal = await fileExists7(join30(ctx.repoRoot, ".codex"));
|
|
8314
8799
|
const selected = ctx.selectedTools?.includes("codex") ?? false;
|
|
8315
8800
|
if (!repoSignal && !selected) {
|
|
8316
8801
|
return { status: "skipped", reason: "no repo-scoped Codex signal and not selected" };
|
|
@@ -8322,7 +8807,7 @@ var codexHooksWriter = {
|
|
|
8322
8807
|
reason: `${HOOKS_REL} is git-tracked; add the RepoWise UserPromptSubmit hook manually to opt in`
|
|
8323
8808
|
};
|
|
8324
8809
|
}
|
|
8325
|
-
const path =
|
|
8810
|
+
const path = join30(ctx.repoRoot, HOOKS_REL);
|
|
8326
8811
|
const existing = await readOrNull2(path);
|
|
8327
8812
|
const merged = mergeCodexHookSettings(existing, {
|
|
8328
8813
|
repoName: ctx.repoName,
|
|
@@ -8332,13 +8817,13 @@ var codexHooksWriter = {
|
|
|
8332
8817
|
if (merged === existing) {
|
|
8333
8818
|
return { status: "unchanged", path };
|
|
8334
8819
|
}
|
|
8335
|
-
await fs11.mkdir(
|
|
8820
|
+
await fs11.mkdir(join30(ctx.repoRoot, ".codex"), { recursive: true });
|
|
8336
8821
|
await fs11.writeFile(path, merged, "utf-8");
|
|
8337
8822
|
await applyGitignoreChanges(ctx.repoRoot, { add: [HOOKS_REL] });
|
|
8338
8823
|
return { status: "written", path };
|
|
8339
8824
|
},
|
|
8340
8825
|
async remove(ctx) {
|
|
8341
|
-
const path =
|
|
8826
|
+
const path = join30(ctx.repoRoot, HOOKS_REL);
|
|
8342
8827
|
const existing = await readOrNull2(path);
|
|
8343
8828
|
if (existing !== null) {
|
|
8344
8829
|
const next = removeCodexHookSettings(existing);
|
|
@@ -8369,14 +8854,14 @@ async function readOrNull2(path) {
|
|
|
8369
8854
|
|
|
8370
8855
|
// ../listener/dist/mcp/auto-config/writers/copilot.js
|
|
8371
8856
|
import { promises as fs12 } from "fs";
|
|
8372
|
-
import { join as
|
|
8857
|
+
import { join as join31 } from "path";
|
|
8373
8858
|
var copilotWriter = {
|
|
8374
8859
|
tool: "copilot",
|
|
8375
8860
|
async detect(repoRoot) {
|
|
8376
|
-
return fileExists8(
|
|
8861
|
+
return fileExists8(join31(repoRoot, ".vscode"));
|
|
8377
8862
|
},
|
|
8378
8863
|
async write(ctx) {
|
|
8379
|
-
const path =
|
|
8864
|
+
const path = join31(ctx.repoRoot, ".vscode", "mcp.json");
|
|
8380
8865
|
await removeFromConfig(path, `repowise-${ctx.repoId}`);
|
|
8381
8866
|
const status2 = await writeMergedConfig({
|
|
8382
8867
|
path,
|
|
@@ -8386,7 +8871,7 @@ var copilotWriter = {
|
|
|
8386
8871
|
return { status: status2, path };
|
|
8387
8872
|
},
|
|
8388
8873
|
async remove(ctx) {
|
|
8389
|
-
const path =
|
|
8874
|
+
const path = join31(ctx.repoRoot, ".vscode", "mcp.json");
|
|
8390
8875
|
await removeFromConfig(path, `RepoWise MCP for ${ctx.repoName}`);
|
|
8391
8876
|
await removeFromConfig(path, `repowise-${ctx.repoId}`);
|
|
8392
8877
|
}
|
|
@@ -8402,15 +8887,15 @@ async function fileExists8(path) {
|
|
|
8402
8887
|
|
|
8403
8888
|
// ../listener/dist/mcp/auto-config/writers/copilot-hooks.js
|
|
8404
8889
|
import { promises as fs13 } from "fs";
|
|
8405
|
-
import { join as
|
|
8890
|
+
import { join as join32 } from "path";
|
|
8406
8891
|
var HOOKS_REL2 = ".github/hooks/repowise.json";
|
|
8407
8892
|
var copilotHooksWriter = {
|
|
8408
8893
|
tool: "copilot-hooks",
|
|
8409
8894
|
async detect(repoRoot) {
|
|
8410
|
-
return fileExists9(
|
|
8895
|
+
return fileExists9(join32(repoRoot, ".github", "copilot-instructions.md"));
|
|
8411
8896
|
},
|
|
8412
8897
|
async write(ctx) {
|
|
8413
|
-
const path =
|
|
8898
|
+
const path = join32(ctx.repoRoot, HOOKS_REL2);
|
|
8414
8899
|
const next = buildCopilotHooksFile({
|
|
8415
8900
|
repoName: ctx.repoName,
|
|
8416
8901
|
contextFolder: ctx.contextFolder ?? "repowise-context",
|
|
@@ -8430,14 +8915,14 @@ var copilotHooksWriter = {
|
|
|
8430
8915
|
if (existing === next) {
|
|
8431
8916
|
return { status: "unchanged", path };
|
|
8432
8917
|
}
|
|
8433
|
-
await fs13.mkdir(
|
|
8918
|
+
await fs13.mkdir(join32(ctx.repoRoot, ".github", "hooks"), { recursive: true });
|
|
8434
8919
|
await fs13.writeFile(path, next, "utf-8");
|
|
8435
8920
|
const userSelected = ctx.selectedTools?.includes("copilot") ?? false;
|
|
8436
8921
|
await applyGitignoreChanges(ctx.repoRoot, userSelected ? { remove: [HOOKS_REL2] } : { add: [HOOKS_REL2] });
|
|
8437
8922
|
return { status: "written", path };
|
|
8438
8923
|
},
|
|
8439
8924
|
async remove(ctx) {
|
|
8440
|
-
const path =
|
|
8925
|
+
const path = join32(ctx.repoRoot, HOOKS_REL2);
|
|
8441
8926
|
if (!await isGitTracked(ctx.repoRoot, HOOKS_REL2)) {
|
|
8442
8927
|
await fs13.unlink(path).catch(() => {
|
|
8443
8928
|
});
|
|
@@ -8463,14 +8948,14 @@ async function readOrNull3(path) {
|
|
|
8463
8948
|
|
|
8464
8949
|
// ../listener/dist/mcp/auto-config/writers/cursor.js
|
|
8465
8950
|
import { promises as fs14 } from "fs";
|
|
8466
|
-
import { join as
|
|
8951
|
+
import { join as join33 } from "path";
|
|
8467
8952
|
var cursorWriter = {
|
|
8468
8953
|
tool: "cursor",
|
|
8469
8954
|
async detect(repoRoot) {
|
|
8470
|
-
return await fileExists10(
|
|
8955
|
+
return await fileExists10(join33(repoRoot, ".cursor")) || await fileExists10(join33(repoRoot, ".cursorrules"));
|
|
8471
8956
|
},
|
|
8472
8957
|
async write(ctx) {
|
|
8473
|
-
const path =
|
|
8958
|
+
const path = join33(ctx.repoRoot, ".cursor", "mcp.json");
|
|
8474
8959
|
await removeFromConfig(path, `repowise-${ctx.repoId}`);
|
|
8475
8960
|
const status2 = await writeMergedConfig({
|
|
8476
8961
|
path,
|
|
@@ -8480,7 +8965,7 @@ var cursorWriter = {
|
|
|
8480
8965
|
return { status: status2, path };
|
|
8481
8966
|
},
|
|
8482
8967
|
async remove(ctx) {
|
|
8483
|
-
const path =
|
|
8968
|
+
const path = join33(ctx.repoRoot, ".cursor", "mcp.json");
|
|
8484
8969
|
await removeFromConfig(path, `RepoWise MCP for ${ctx.repoName}`);
|
|
8485
8970
|
await removeFromConfig(path, `repowise-${ctx.repoId}`);
|
|
8486
8971
|
}
|
|
@@ -8496,14 +8981,14 @@ async function fileExists10(path) {
|
|
|
8496
8981
|
|
|
8497
8982
|
// ../listener/dist/mcp/auto-config/writers/gemini-cli.js
|
|
8498
8983
|
import { promises as fs15 } from "fs";
|
|
8499
|
-
import { join as
|
|
8984
|
+
import { join as join34 } from "path";
|
|
8500
8985
|
var geminiCliWriter = {
|
|
8501
8986
|
tool: "gemini-cli",
|
|
8502
8987
|
async detect(_repoRoot, home) {
|
|
8503
|
-
return fileExists11(
|
|
8988
|
+
return fileExists11(join34(home, ".gemini"));
|
|
8504
8989
|
},
|
|
8505
8990
|
async write(ctx) {
|
|
8506
|
-
const path =
|
|
8991
|
+
const path = join34(ctx.home, ".gemini", "settings.json");
|
|
8507
8992
|
await removeFromConfig(path, `repowise-${ctx.repoId}`);
|
|
8508
8993
|
const status2 = await writeMergedConfig({
|
|
8509
8994
|
path,
|
|
@@ -8513,7 +8998,7 @@ var geminiCliWriter = {
|
|
|
8513
8998
|
return { status: status2, path };
|
|
8514
8999
|
},
|
|
8515
9000
|
async remove(ctx) {
|
|
8516
|
-
const path =
|
|
9001
|
+
const path = join34(ctx.home, ".gemini", "settings.json");
|
|
8517
9002
|
await removeFromConfig(path, `RepoWise MCP for ${ctx.repoName}`);
|
|
8518
9003
|
await removeFromConfig(path, `repowise-${ctx.repoId}`);
|
|
8519
9004
|
}
|
|
@@ -8529,15 +9014,15 @@ async function fileExists11(path) {
|
|
|
8529
9014
|
|
|
8530
9015
|
// ../listener/dist/mcp/auto-config/writers/gemini-hooks.js
|
|
8531
9016
|
import { promises as fs16 } from "fs";
|
|
8532
|
-
import { join as
|
|
9017
|
+
import { join as join35 } from "path";
|
|
8533
9018
|
var SETTINGS_REL = ".gemini/settings.json";
|
|
8534
9019
|
var geminiHooksWriter = {
|
|
8535
9020
|
tool: "gemini-hooks",
|
|
8536
9021
|
async detect(repoRoot, home) {
|
|
8537
|
-
return await fileExists12(
|
|
9022
|
+
return await fileExists12(join35(home, ".gemini")) || await fileExists12(join35(repoRoot, ".gemini"));
|
|
8538
9023
|
},
|
|
8539
9024
|
async write(ctx) {
|
|
8540
|
-
const repoSignal = await fileExists12(
|
|
9025
|
+
const repoSignal = await fileExists12(join35(ctx.repoRoot, ".gemini")) || await fileExists12(join35(ctx.repoRoot, "GEMINI.md"));
|
|
8541
9026
|
const selected = ctx.selectedTools?.includes("gemini") ?? false;
|
|
8542
9027
|
if (!repoSignal && !selected) {
|
|
8543
9028
|
return { status: "skipped", reason: "no repo-scoped Gemini signal and not selected" };
|
|
@@ -8549,7 +9034,7 @@ var geminiHooksWriter = {
|
|
|
8549
9034
|
reason: `${SETTINGS_REL} is git-tracked; add the RepoWise BeforeAgent hook manually to opt in`
|
|
8550
9035
|
};
|
|
8551
9036
|
}
|
|
8552
|
-
const path =
|
|
9037
|
+
const path = join35(ctx.repoRoot, SETTINGS_REL);
|
|
8553
9038
|
const existing = await readOrNull4(path);
|
|
8554
9039
|
const merged = mergeGeminiHookSettings(existing, {
|
|
8555
9040
|
repoName: ctx.repoName,
|
|
@@ -8559,13 +9044,13 @@ var geminiHooksWriter = {
|
|
|
8559
9044
|
if (merged === existing) {
|
|
8560
9045
|
return { status: "unchanged", path };
|
|
8561
9046
|
}
|
|
8562
|
-
await fs16.mkdir(
|
|
9047
|
+
await fs16.mkdir(join35(ctx.repoRoot, ".gemini"), { recursive: true });
|
|
8563
9048
|
await fs16.writeFile(path, merged, "utf-8");
|
|
8564
9049
|
await applyGitignoreChanges(ctx.repoRoot, { add: [SETTINGS_REL] });
|
|
8565
9050
|
return { status: "written", path };
|
|
8566
9051
|
},
|
|
8567
9052
|
async remove(ctx) {
|
|
8568
|
-
const path =
|
|
9053
|
+
const path = join35(ctx.repoRoot, SETTINGS_REL);
|
|
8569
9054
|
const existing = await readOrNull4(path);
|
|
8570
9055
|
if (existing !== null) {
|
|
8571
9056
|
const next = removeGeminiHookSettings(existing);
|
|
@@ -8596,14 +9081,14 @@ async function readOrNull4(path) {
|
|
|
8596
9081
|
|
|
8597
9082
|
// ../listener/dist/mcp/auto-config/writers/roo.js
|
|
8598
9083
|
import { promises as fs17 } from "fs";
|
|
8599
|
-
import { join as
|
|
9084
|
+
import { join as join36 } from "path";
|
|
8600
9085
|
var rooWriter = {
|
|
8601
9086
|
tool: "roo",
|
|
8602
9087
|
async detect(repoRoot, home) {
|
|
8603
|
-
return await fileExists13(
|
|
9088
|
+
return await fileExists13(join36(repoRoot, ".roo")) || await fileExists13(join36(home, ".roo"));
|
|
8604
9089
|
},
|
|
8605
9090
|
async write(ctx) {
|
|
8606
|
-
const repoConfig =
|
|
9091
|
+
const repoConfig = join36(ctx.repoRoot, ".roo", "mcp.json");
|
|
8607
9092
|
const before = await readOrNull5(repoConfig);
|
|
8608
9093
|
await this.remove(ctx);
|
|
8609
9094
|
const after = await readOrNull5(repoConfig);
|
|
@@ -8613,9 +9098,9 @@ var rooWriter = {
|
|
|
8613
9098
|
};
|
|
8614
9099
|
},
|
|
8615
9100
|
async remove(ctx) {
|
|
8616
|
-
const repoConfig =
|
|
8617
|
-
const homeConfig =
|
|
8618
|
-
const homeSettings =
|
|
9101
|
+
const repoConfig = join36(ctx.repoRoot, ".roo", "mcp.json");
|
|
9102
|
+
const homeConfig = join36(ctx.home, ".roo", "mcp.json");
|
|
9103
|
+
const homeSettings = join36(ctx.home, ".roo", "settings.json");
|
|
8619
9104
|
for (const path of [repoConfig, homeConfig, homeSettings]) {
|
|
8620
9105
|
await removeFromConfig(path, `RepoWise MCP for ${ctx.repoName}`).catch(() => {
|
|
8621
9106
|
});
|
|
@@ -8642,14 +9127,14 @@ async function readOrNull5(path) {
|
|
|
8642
9127
|
|
|
8643
9128
|
// ../listener/dist/mcp/auto-config/writers/windsurf.js
|
|
8644
9129
|
import { promises as fs18 } from "fs";
|
|
8645
|
-
import { join as
|
|
9130
|
+
import { join as join37 } from "path";
|
|
8646
9131
|
var windsurfWriter = {
|
|
8647
9132
|
tool: "windsurf",
|
|
8648
9133
|
async detect(_repoRoot, home) {
|
|
8649
|
-
return fileExists14(
|
|
9134
|
+
return fileExists14(join37(home, ".codeium", "windsurf"));
|
|
8650
9135
|
},
|
|
8651
9136
|
async write(ctx) {
|
|
8652
|
-
const path =
|
|
9137
|
+
const path = join37(ctx.home, ".codeium", "windsurf", "mcp_config.json");
|
|
8653
9138
|
await removeFromConfig(path, `repowise-${ctx.repoId}`);
|
|
8654
9139
|
const status2 = await writeMergedConfig({
|
|
8655
9140
|
path,
|
|
@@ -8659,7 +9144,7 @@ var windsurfWriter = {
|
|
|
8659
9144
|
return { status: status2, path };
|
|
8660
9145
|
},
|
|
8661
9146
|
async remove(ctx) {
|
|
8662
|
-
const path =
|
|
9147
|
+
const path = join37(ctx.home, ".codeium", "windsurf", "mcp_config.json");
|
|
8663
9148
|
await removeFromConfig(path, `RepoWise MCP for ${ctx.repoName}`);
|
|
8664
9149
|
await removeFromConfig(path, `repowise-${ctx.repoId}`);
|
|
8665
9150
|
}
|
|
@@ -8741,7 +9226,7 @@ init_installer();
|
|
|
8741
9226
|
init_registry();
|
|
8742
9227
|
init_lsp_tools();
|
|
8743
9228
|
import { promises as fs19 } from "fs";
|
|
8744
|
-
import { join as
|
|
9229
|
+
import { join as join38 } from "path";
|
|
8745
9230
|
async function collectRepresentativeFiles(repoRoot) {
|
|
8746
9231
|
const reps = /* @__PURE__ */ new Map();
|
|
8747
9232
|
let inspected = 0;
|
|
@@ -8782,7 +9267,7 @@ async function collectRepresentativeFiles(repoRoot) {
|
|
|
8782
9267
|
continue;
|
|
8783
9268
|
if (name === "node_modules" || name === "dist" || name === "build")
|
|
8784
9269
|
continue;
|
|
8785
|
-
const sub =
|
|
9270
|
+
const sub = join38(repoRoot, name);
|
|
8786
9271
|
try {
|
|
8787
9272
|
const stat8 = await fs19.stat(sub);
|
|
8788
9273
|
if (stat8.isDirectory())
|
|
@@ -8830,7 +9315,7 @@ init_config_dir();
|
|
|
8830
9315
|
import { promises as fs20 } from "fs";
|
|
8831
9316
|
import { spawn as spawn9, execFile as execFile5 } from "child_process";
|
|
8832
9317
|
import { createWriteStream as createWriteStream2 } from "fs";
|
|
8833
|
-
import { join as
|
|
9318
|
+
import { join as join39 } from "path";
|
|
8834
9319
|
import { promisify as promisify3 } from "util";
|
|
8835
9320
|
var execFileAsync3 = promisify3(execFile5);
|
|
8836
9321
|
async function isCommandOnPath2(cmd) {
|
|
@@ -8894,7 +9379,7 @@ async function maybeWriteClangdStub(repoRoot, repoId) {
|
|
|
8894
9379
|
if (e.isDirectory()) {
|
|
8895
9380
|
if (skip.has(e.name) || e.name.startsWith("."))
|
|
8896
9381
|
continue;
|
|
8897
|
-
await rec(
|
|
9382
|
+
await rec(join39(dir, e.name));
|
|
8898
9383
|
continue;
|
|
8899
9384
|
}
|
|
8900
9385
|
const dot = e.name.lastIndexOf(".");
|
|
@@ -8903,7 +9388,7 @@ async function maybeWriteClangdStub(repoRoot, repoId) {
|
|
|
8903
9388
|
const ext = e.name.slice(dot);
|
|
8904
9389
|
if (!C_EXTS.has(ext) && !CPP_EXTS.has(ext))
|
|
8905
9390
|
continue;
|
|
8906
|
-
sources.push({ path:
|
|
9391
|
+
sources.push({ path: join39(dir, e.name), ext });
|
|
8907
9392
|
}
|
|
8908
9393
|
}
|
|
8909
9394
|
await rec(repoRoot);
|
|
@@ -8919,9 +9404,9 @@ async function maybeWriteClangdStub(repoRoot, repoId) {
|
|
|
8919
9404
|
command: `${driver} ${std} -I. -I./include -c "${src}"`
|
|
8920
9405
|
};
|
|
8921
9406
|
});
|
|
8922
|
-
const outDir =
|
|
9407
|
+
const outDir = join39(repoRoot, ".repowise");
|
|
8923
9408
|
await fs20.mkdir(outDir, { recursive: true });
|
|
8924
|
-
const outPath =
|
|
9409
|
+
const outPath = join39(outDir, "compile_commands.json");
|
|
8925
9410
|
await fs20.writeFile(outPath, JSON.stringify(entries, null, 2));
|
|
8926
9411
|
const capHit = sources.length >= MAX_STUB_SOURCES;
|
|
8927
9412
|
console.log(`[workspace-prep] ${repoId}: wrote ${entries.length.toString()}-entry compile_commands stub to .repowise/${capHit ? " (cap reached \u2014 additional sources truncated)" : ""}`);
|
|
@@ -8930,7 +9415,7 @@ async function maybeWriteClangdStub(repoRoot, repoId) {
|
|
|
8930
9415
|
var DEFAULT_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
8931
9416
|
async function fileExists15(repoRoot, name) {
|
|
8932
9417
|
try {
|
|
8933
|
-
await fs20.access(
|
|
9418
|
+
await fs20.access(join39(repoRoot, name));
|
|
8934
9419
|
return true;
|
|
8935
9420
|
} catch {
|
|
8936
9421
|
return false;
|
|
@@ -8941,7 +9426,7 @@ async function detectWorkspaceDeps(repoRoot) {
|
|
|
8941
9426
|
if (await fileExists15(repoRoot, "package.json")) {
|
|
8942
9427
|
let pkgRaw = "";
|
|
8943
9428
|
try {
|
|
8944
|
-
pkgRaw = await fs20.readFile(
|
|
9429
|
+
pkgRaw = await fs20.readFile(join39(repoRoot, "package.json"), "utf8");
|
|
8945
9430
|
} catch {
|
|
8946
9431
|
}
|
|
8947
9432
|
if (pkgRaw) {
|
|
@@ -9044,7 +9529,7 @@ async function runWorkspacePreps(opts) {
|
|
|
9044
9529
|
if (opts.missing.length === 0)
|
|
9045
9530
|
return [];
|
|
9046
9531
|
const safeRepoId = opts.repoId.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
9047
|
-
const logPath2 =
|
|
9532
|
+
const logPath2 = join39(getConfigDir(), `install-log.${safeRepoId}.txt`);
|
|
9048
9533
|
await fs20.mkdir(getConfigDir(), { recursive: true });
|
|
9049
9534
|
const stream = opts.logStream ?? createWriteStream2(logPath2, { flags: "a" });
|
|
9050
9535
|
stream.on("error", () => {
|
|
@@ -9195,7 +9680,7 @@ async function prepareWorkspaceDepsForRepos(repos) {
|
|
|
9195
9680
|
init_registry();
|
|
9196
9681
|
import { execFile as execFileCb, spawn as spawn10 } from "child_process";
|
|
9197
9682
|
import { access as access3, readdir as readdir4 } from "fs/promises";
|
|
9198
|
-
import { join as
|
|
9683
|
+
import { join as join40 } from "path";
|
|
9199
9684
|
import { promisify as promisify4 } from "util";
|
|
9200
9685
|
var execFile6 = promisify4(execFileCb);
|
|
9201
9686
|
var DEFAULT_BUILD_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
@@ -9248,7 +9733,7 @@ async function ensureIndexable(indexRoot, language, options = {}) {
|
|
|
9248
9733
|
}
|
|
9249
9734
|
}
|
|
9250
9735
|
try {
|
|
9251
|
-
await access3(
|
|
9736
|
+
await access3(join40(indexRoot, spec.indexStoreProbe));
|
|
9252
9737
|
return { ready: true, built: false };
|
|
9253
9738
|
} catch {
|
|
9254
9739
|
}
|
|
@@ -9982,13 +10467,13 @@ init_config_dir();
|
|
|
9982
10467
|
import { execFile as execFileCb3 } from "child_process";
|
|
9983
10468
|
import { createHash as createHash5 } from "crypto";
|
|
9984
10469
|
import { mkdir as mkdir15, readdir as readdir5, rm, stat as stat6 } from "fs/promises";
|
|
9985
|
-
import { join as
|
|
10470
|
+
import { join as join41 } from "path";
|
|
9986
10471
|
import { promisify as promisify6 } from "util";
|
|
9987
10472
|
var execFile8 = promisify6(execFileCb3);
|
|
9988
10473
|
var GIT_OP_TIMEOUT_MS = 6e4;
|
|
9989
10474
|
var MAX_CONCURRENT_WORKTREES = 3;
|
|
9990
10475
|
function worktreeBaseDir() {
|
|
9991
|
-
return
|
|
10476
|
+
return join41(getConfigDir(), "lsp-worktrees");
|
|
9992
10477
|
}
|
|
9993
10478
|
async function reapOrphanWorktrees(maxAgeMs = 24 * 60 * 60 * 1e3, base = worktreeBaseDir()) {
|
|
9994
10479
|
let reaped = 0;
|
|
@@ -10000,7 +10485,7 @@ async function reapOrphanWorktrees(maxAgeMs = 24 * 60 * 60 * 1e3, base = worktre
|
|
|
10000
10485
|
}
|
|
10001
10486
|
const cutoff = Date.now() - maxAgeMs;
|
|
10002
10487
|
for (const entry of entries) {
|
|
10003
|
-
const dir =
|
|
10488
|
+
const dir = join41(base, entry);
|
|
10004
10489
|
try {
|
|
10005
10490
|
const info = await stat6(dir);
|
|
10006
10491
|
if (!info.isDirectory() || info.mtimeMs > cutoff)
|
|
@@ -10033,7 +10518,7 @@ async function prepareCommittedTree(repoRoot, commitSha, options = {}) {
|
|
|
10033
10518
|
}
|
|
10034
10519
|
const base = options.worktreeBase ?? worktreeBaseDir();
|
|
10035
10520
|
const dirName = createHash5("sha256").update(`${repoRoot}\0${commitSha}`).digest("hex").slice(0, 16) + "-" + commitSha.slice(0, 12);
|
|
10036
|
-
const worktreePath =
|
|
10521
|
+
const worktreePath = join41(base, dirName);
|
|
10037
10522
|
try {
|
|
10038
10523
|
await mkdir15(base, { recursive: true });
|
|
10039
10524
|
await git(["-C", repoRoot, "worktree", "prune"]).catch(() => void 0);
|
|
@@ -10369,8 +10854,8 @@ var CRASH_LOOP_WINDOW_MS = 3e4;
|
|
|
10369
10854
|
var CRASH_LOOP_THRESHOLD = 3;
|
|
10370
10855
|
async function readRawToolConfig() {
|
|
10371
10856
|
try {
|
|
10372
|
-
const configPath =
|
|
10373
|
-
const data = await
|
|
10857
|
+
const configPath = join42(getConfigDir(), "config.json");
|
|
10858
|
+
const data = await readFile14(configPath, "utf-8");
|
|
10374
10859
|
const raw = JSON.parse(data);
|
|
10375
10860
|
return {
|
|
10376
10861
|
aiTools: Array.isArray(raw["aiTools"]) ? raw["aiTools"] : void 0,
|
|
@@ -10396,10 +10881,9 @@ async function updateToolConfigsForRepo(localPath, config2, state, repoId) {
|
|
|
10396
10881
|
}
|
|
10397
10882
|
await migrateRooToKilo(localPath).catch(() => {
|
|
10398
10883
|
});
|
|
10399
|
-
const variant = config2.graphOnly ? "graph" : "full";
|
|
10400
10884
|
const contextFiles = await scanLocalContextFiles(localPath, contextFolder);
|
|
10401
|
-
|
|
10402
|
-
|
|
10885
|
+
const marker = await readDocsMarker(localPath, contextFolder);
|
|
10886
|
+
const variant = effectiveToolVariant(!!config2.graphOnly, contextFiles.length > 0, marker, state.repos[repoId]?.lastSyncCommitSha);
|
|
10403
10887
|
const hash = JSON.stringify({
|
|
10404
10888
|
rev: AI_TOOLS_REFERENCE_REV,
|
|
10405
10889
|
block: AGENT_INSTRUCTIONS_HASH,
|
|
@@ -10426,10 +10910,15 @@ async function updateToolConfigsForRepo(localPath, config2, state, repoId) {
|
|
|
10426
10910
|
if (!written.has("AGENTS.md")) {
|
|
10427
10911
|
await updateToolConfig(localPath, "codex", repoName, contextFolder, contextFiles, variant);
|
|
10428
10912
|
}
|
|
10913
|
+
const prevToolVariant = state.repos[repoId]?.lastToolVariant;
|
|
10429
10914
|
if (!state.repos[repoId]) {
|
|
10430
10915
|
state.repos[repoId] = { lastSyncTimestamp: (/* @__PURE__ */ new Date()).toISOString(), lastSyncCommitSha: null };
|
|
10431
10916
|
}
|
|
10432
10917
|
state.repos[repoId].lastToolConfigHash = hash;
|
|
10918
|
+
state.repos[repoId].lastToolVariant = variant;
|
|
10919
|
+
if (!config2.graphOnly && variant === "full" && prevToolVariant === "graph") {
|
|
10920
|
+
notifyDocsReady(repoId);
|
|
10921
|
+
}
|
|
10433
10922
|
console.log(`[ai-tools] Updated tool configs for ${repoId}`);
|
|
10434
10923
|
}
|
|
10435
10924
|
var running = false;
|
|
@@ -10446,8 +10935,8 @@ function resolveAuditRoots() {
|
|
|
10446
10935
|
const out = /* @__PURE__ */ new Set();
|
|
10447
10936
|
let dir = dirname17(process.execPath);
|
|
10448
10937
|
for (let i = 0; i < 8; i += 1) {
|
|
10449
|
-
out.add(
|
|
10450
|
-
out.add(
|
|
10938
|
+
out.add(join42(dir, "node_modules"));
|
|
10939
|
+
out.add(join42(dir, "lib", "node_modules"));
|
|
10451
10940
|
const parent = dirname17(dir);
|
|
10452
10941
|
if (parent === dir)
|
|
10453
10942
|
break;
|
|
@@ -10537,6 +11026,36 @@ async function waitForCredentials(isRunning2) {
|
|
|
10537
11026
|
watcher.close();
|
|
10538
11027
|
}
|
|
10539
11028
|
}
|
|
11029
|
+
async function waitForCredentialsOrTerminal(isRunning2) {
|
|
11030
|
+
let changed = false;
|
|
11031
|
+
let watcher = null;
|
|
11032
|
+
try {
|
|
11033
|
+
const fs31 = await import("fs");
|
|
11034
|
+
watcher = fs31.watch(getConfigDir(), (_event, filename) => {
|
|
11035
|
+
if (!filename || filename === "credentials.json")
|
|
11036
|
+
changed = true;
|
|
11037
|
+
});
|
|
11038
|
+
} catch {
|
|
11039
|
+
}
|
|
11040
|
+
try {
|
|
11041
|
+
while (isRunning2()) {
|
|
11042
|
+
await sleep(changed ? 0 : 1e4);
|
|
11043
|
+
changed = false;
|
|
11044
|
+
if (!isRunning2())
|
|
11045
|
+
break;
|
|
11046
|
+
const fresh = await getValidCredentials({ forceRefresh: true });
|
|
11047
|
+
if (fresh)
|
|
11048
|
+
return { kind: "recovered", creds: fresh };
|
|
11049
|
+
const oc = getLastRefreshOutcome();
|
|
11050
|
+
if (oc === "terminal" || oc === "no-creds")
|
|
11051
|
+
return { kind: "terminal" };
|
|
11052
|
+
}
|
|
11053
|
+
return { kind: "stopped" };
|
|
11054
|
+
} finally {
|
|
11055
|
+
if (watcher)
|
|
11056
|
+
watcher.close();
|
|
11057
|
+
}
|
|
11058
|
+
}
|
|
10540
11059
|
function decodeEmailFromIdToken(idToken) {
|
|
10541
11060
|
try {
|
|
10542
11061
|
const payload = JSON.parse(Buffer.from(idToken.split(".")[1], "base64url").toString());
|
|
@@ -10564,13 +11083,14 @@ async function handleSyncCompleted(notif, ctx) {
|
|
|
10564
11083
|
return { stateUpdated: false };
|
|
10565
11084
|
}
|
|
10566
11085
|
notifyContextUpdated(notif.repoId, result.updatedFiles.length);
|
|
10567
|
-
|
|
10568
|
-
await refreshAiToolConfigs(notif, localPath, ctx);
|
|
11086
|
+
await writeDocsMarker(localPath, "repowise-context", notif.commitSha ?? null, result.updatedFiles.length);
|
|
10569
11087
|
ctx.state.repos[notif.repoId] = {
|
|
10570
11088
|
...ctx.state.repos[notif.repoId],
|
|
10571
11089
|
lastSyncTimestamp: notif.createdAt,
|
|
10572
11090
|
lastSyncCommitSha: notif.commitSha ?? ctx.state.repos[notif.repoId]?.lastSyncCommitSha ?? null
|
|
10573
11091
|
};
|
|
11092
|
+
invalidateLspBuffersOnce(notif, localPath, ctx);
|
|
11093
|
+
await refreshAiToolConfigs(notif, localPath, ctx);
|
|
10574
11094
|
await runProducerHook(notif, localPath, ctx);
|
|
10575
11095
|
return { stateUpdated: true };
|
|
10576
11096
|
}
|
|
@@ -10739,14 +11259,14 @@ async function checkStaleContext(repos, state, groups) {
|
|
|
10739
11259
|
if (group?.offline.isOffline)
|
|
10740
11260
|
continue;
|
|
10741
11261
|
const { statSync: statSync2, readdirSync: readdirSync2 } = await import("fs");
|
|
10742
|
-
const contextPath =
|
|
11262
|
+
const contextPath = join42(repo.localPath, "repowise-context");
|
|
10743
11263
|
let isMissingOrEmpty = false;
|
|
10744
11264
|
try {
|
|
10745
11265
|
const s = statSync2(contextPath);
|
|
10746
11266
|
if (!s.isDirectory()) {
|
|
10747
11267
|
isMissingOrEmpty = true;
|
|
10748
11268
|
} else {
|
|
10749
|
-
const entries = readdirSync2(contextPath);
|
|
11269
|
+
const entries = readdirSync2(contextPath).filter((e) => !e.startsWith("."));
|
|
10750
11270
|
isMissingOrEmpty = entries.length === 0;
|
|
10751
11271
|
}
|
|
10752
11272
|
} catch {
|
|
@@ -10762,6 +11282,7 @@ async function checkStaleContext(repos, state, groups) {
|
|
|
10762
11282
|
const result = await fetchContextFromServer(repo.repoId, repo.localPath, apiUrl);
|
|
10763
11283
|
if (result.success && result.updatedFiles.length > 0) {
|
|
10764
11284
|
console.log(`[self-heal] Re-downloaded ${result.updatedFiles.length} files for ${repo.repoId}`);
|
|
11285
|
+
await writeDocsMarker(repo.localPath, "repowise-context", repoState.lastSyncCommitSha ?? null, result.updatedFiles.length);
|
|
10765
11286
|
notifyContextUpdated(repo.repoId, result.updatedFiles.length);
|
|
10766
11287
|
}
|
|
10767
11288
|
}
|
|
@@ -10774,7 +11295,6 @@ function mcpAiToolsKey(aiTools) {
|
|
|
10774
11295
|
async function reconcileMcpConfigs(repos, packageName) {
|
|
10775
11296
|
const shimCmd = packageName;
|
|
10776
11297
|
const { contextFolder, aiTools, graphOnly } = await readRawToolConfig();
|
|
10777
|
-
const variant = graphOnly ? "graph" : "full";
|
|
10778
11298
|
for (const repo of repos) {
|
|
10779
11299
|
if (!repo.localPath)
|
|
10780
11300
|
continue;
|
|
@@ -10785,6 +11305,8 @@ async function reconcileMcpConfigs(repos, packageName) {
|
|
|
10785
11305
|
} catch {
|
|
10786
11306
|
continue;
|
|
10787
11307
|
}
|
|
11308
|
+
const docsPresent = (await scanLocalContextFiles(repo.localPath, contextFolder ?? "repowise-context")).length > 0;
|
|
11309
|
+
const variant = effectiveToolVariant(!!graphOnly, docsPresent, null, null);
|
|
10788
11310
|
try {
|
|
10789
11311
|
const results = await runAutoConfig({
|
|
10790
11312
|
repoRoot: repo.localPath,
|
|
@@ -10807,10 +11329,10 @@ async function reconcileAgentInstructions(repos) {
|
|
|
10807
11329
|
const { graphOnly } = await readRawToolConfig();
|
|
10808
11330
|
const variant = graphOnly ? "graph" : "full";
|
|
10809
11331
|
for (const repo of repos) {
|
|
10810
|
-
const path =
|
|
11332
|
+
const path = join42(repo.localPath, "repowise-context", "project-overview.md");
|
|
10811
11333
|
let content;
|
|
10812
11334
|
try {
|
|
10813
|
-
content = await
|
|
11335
|
+
content = await readFile14(path, "utf-8");
|
|
10814
11336
|
} catch (err) {
|
|
10815
11337
|
const code = err?.code;
|
|
10816
11338
|
if (code === "ENOENT" || code === "ENOTDIR" || code === "EACCES" || code === "EPERM" || code === "EISDIR") {
|
|
@@ -10823,7 +11345,7 @@ async function reconcileAgentInstructions(repos) {
|
|
|
10823
11345
|
if (injected === content)
|
|
10824
11346
|
continue;
|
|
10825
11347
|
try {
|
|
10826
|
-
await
|
|
11348
|
+
await writeFile16(path, injected, "utf-8");
|
|
10827
11349
|
console.log(`[reconcile] Reconciled agent instructions for ${repo.repoId}`);
|
|
10828
11350
|
} catch (err) {
|
|
10829
11351
|
console.warn(`[reconcile] write failed for ${repo.repoId}:`, err instanceof Error ? err.message : String(err));
|
|
@@ -10891,11 +11413,11 @@ async function startListener() {
|
|
|
10891
11413
|
}
|
|
10892
11414
|
const configDir = getConfigDir();
|
|
10893
11415
|
await mkdir16(configDir, { recursive: true });
|
|
10894
|
-
const lockPath =
|
|
10895
|
-
await
|
|
11416
|
+
const lockPath = join42(configDir, "listener.lock");
|
|
11417
|
+
await writeFile16(lockPath, "", { flag: "a" });
|
|
10896
11418
|
let lockIsHeld = false;
|
|
10897
11419
|
try {
|
|
10898
|
-
lockIsHeld = await
|
|
11420
|
+
lockIsHeld = await lockfile5.check(lockPath, { stale: 3e4, realpath: false });
|
|
10899
11421
|
} catch {
|
|
10900
11422
|
}
|
|
10901
11423
|
if (!lockIsHeld) {
|
|
@@ -10909,7 +11431,7 @@ async function startListener() {
|
|
|
10909
11431
|
}
|
|
10910
11432
|
}
|
|
10911
11433
|
try {
|
|
10912
|
-
releaseLock = await
|
|
11434
|
+
releaseLock = await lockfile5.lock(lockPath, { stale: 3e4, realpath: false });
|
|
10913
11435
|
} catch {
|
|
10914
11436
|
console.error(`Listener already running. Stop it first with \`${true ? "repowisestage" : "repowise"} stop\`.`);
|
|
10915
11437
|
process.exitCode = 1;
|
|
@@ -10934,7 +11456,7 @@ async function startListener() {
|
|
|
10934
11456
|
return;
|
|
10935
11457
|
}
|
|
10936
11458
|
if (config2.repos.length === 0 && !config2.autoDiscoverRepos) {
|
|
10937
|
-
console.error(`No repos configured. Add repos to ${
|
|
11459
|
+
console.error(`No repos configured. Add repos to ${join42(configDir, "config.json")}`);
|
|
10938
11460
|
await releaseLockAndExit();
|
|
10939
11461
|
process.exitCode = 1;
|
|
10940
11462
|
return;
|
|
@@ -11005,8 +11527,8 @@ async function startListener() {
|
|
|
11005
11527
|
let currentVersion = "";
|
|
11006
11528
|
try {
|
|
11007
11529
|
const selfDir = dirname17(fileURLToPath3(import.meta.url));
|
|
11008
|
-
const pkgJsonPath =
|
|
11009
|
-
const pkgJson = JSON.parse(await
|
|
11530
|
+
const pkgJsonPath = join42(selfDir, "..", "..", "package.json");
|
|
11531
|
+
const pkgJson = JSON.parse(await readFile14(pkgJsonPath, "utf-8"));
|
|
11010
11532
|
currentVersion = pkgJson.version;
|
|
11011
11533
|
} catch (err) {
|
|
11012
11534
|
console.log(`[auto-update] Version detection failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -11028,6 +11550,8 @@ async function startListener() {
|
|
|
11028
11550
|
console.warn = (...args) => origWarn(`[${ts()}]`, ...args);
|
|
11029
11551
|
console.log(`RepoWise Listener started \u2014 watching ${allRepoIds.length} repo(s)`);
|
|
11030
11552
|
let mcpRuntime = null;
|
|
11553
|
+
let proactiveRefreshTimer = null;
|
|
11554
|
+
const PROACTIVE_REFRESH_MS = 10 * 60 * 1e3;
|
|
11031
11555
|
try {
|
|
11032
11556
|
mcpRuntime = await startMcp({
|
|
11033
11557
|
repos: config2.repos.map((r) => ({
|
|
@@ -11035,6 +11559,8 @@ async function startListener() {
|
|
|
11035
11559
|
apiUrl: r.apiUrl ?? config2.defaultApiUrl,
|
|
11036
11560
|
...r.localPath ? { localPath: r.localPath } : {}
|
|
11037
11561
|
})),
|
|
11562
|
+
// Rides the anonymous MCP-usage heartbeat for fleet version spread.
|
|
11563
|
+
listenerVersion: currentVersion,
|
|
11038
11564
|
getAuthToken: async () => {
|
|
11039
11565
|
const fresh = await getValidCredentials();
|
|
11040
11566
|
if (!fresh)
|
|
@@ -11168,6 +11694,10 @@ async function startListener() {
|
|
|
11168
11694
|
const shutdown = async () => {
|
|
11169
11695
|
console.log("Shutting down...");
|
|
11170
11696
|
stop();
|
|
11697
|
+
if (proactiveRefreshTimer) {
|
|
11698
|
+
clearInterval(proactiveRefreshTimer);
|
|
11699
|
+
proactiveRefreshTimer = null;
|
|
11700
|
+
}
|
|
11171
11701
|
if (mcpRuntime) {
|
|
11172
11702
|
try {
|
|
11173
11703
|
await mcpRuntime.close();
|
|
@@ -11198,7 +11728,22 @@ async function startListener() {
|
|
|
11198
11728
|
}).finally(() => process.exit(0));
|
|
11199
11729
|
});
|
|
11200
11730
|
}
|
|
11731
|
+
if (!proactiveRefreshTimer) {
|
|
11732
|
+
proactiveRefreshTimer = setInterval(() => {
|
|
11733
|
+
void getValidCredentials().catch(() => {
|
|
11734
|
+
});
|
|
11735
|
+
}, PROACTIVE_REFRESH_MS);
|
|
11736
|
+
proactiveRefreshTimer.unref();
|
|
11737
|
+
}
|
|
11738
|
+
let lastCycleAt = Date.now();
|
|
11201
11739
|
while (running) {
|
|
11740
|
+
const wakeDrift = Date.now() - lastCycleAt;
|
|
11741
|
+
if (wakeDrift > pollIntervalMs + 6e4) {
|
|
11742
|
+
console.log(`[wake] ~${Math.round(wakeDrift / 1e3).toString()}s gap detected (likely sleep) \u2014 refreshing credentials`);
|
|
11743
|
+
await getValidCredentials({ forceRefresh: true }).catch(() => {
|
|
11744
|
+
});
|
|
11745
|
+
}
|
|
11746
|
+
lastCycleAt = Date.now();
|
|
11202
11747
|
let anyAuthError = null;
|
|
11203
11748
|
let authErrorGroup = null;
|
|
11204
11749
|
let minPollInterval = pollIntervalMs;
|
|
@@ -11415,10 +11960,10 @@ async function startListener() {
|
|
|
11415
11960
|
}
|
|
11416
11961
|
}
|
|
11417
11962
|
group.offline.attemptCount++;
|
|
11418
|
-
const
|
|
11419
|
-
group.offline.nextRetryAt = Date.now() +
|
|
11963
|
+
const delay3 = group.backoff.nextDelay();
|
|
11964
|
+
group.offline.nextRetryAt = Date.now() + delay3;
|
|
11420
11965
|
const message = err instanceof Error ? err.message : "Unknown error";
|
|
11421
|
-
console.error(`Poll failed for ${group.apiUrl} (attempt ${group.offline.attemptCount}): ${message}. Retrying in ${Math.round(
|
|
11966
|
+
console.error(`Poll failed for ${group.apiUrl} (attempt ${group.offline.attemptCount}): ${message}. Retrying in ${Math.round(delay3 / 1e3)}s`);
|
|
11422
11967
|
}
|
|
11423
11968
|
}
|
|
11424
11969
|
if (shouldReconcile) {
|
|
@@ -11495,9 +12040,10 @@ async function startListener() {
|
|
|
11495
12040
|
}
|
|
11496
12041
|
if (anyAuthError) {
|
|
11497
12042
|
let recovered = false;
|
|
11498
|
-
|
|
11499
|
-
|
|
11500
|
-
|
|
12043
|
+
const backoffs = [5e3, 1e4, 2e4];
|
|
12044
|
+
for (let attempt = 0; attempt < backoffs.length; attempt++) {
|
|
12045
|
+
console.warn(`Auth error \u2014 retrying credentials (attempt ${(attempt + 1).toString()}/${backoffs.length.toString()})...`);
|
|
12046
|
+
await sleep(backoffs[attempt]);
|
|
11501
12047
|
if (!running)
|
|
11502
12048
|
break;
|
|
11503
12049
|
const fresh2 = await getValidCredentials({ forceRefresh: true });
|
|
@@ -11509,22 +12055,47 @@ async function startListener() {
|
|
|
11509
12055
|
}
|
|
11510
12056
|
if (recovered)
|
|
11511
12057
|
continue;
|
|
11512
|
-
|
|
12058
|
+
const outcome = getLastRefreshOutcome();
|
|
12059
|
+
let terminal = outcome === "terminal" || outcome === "no-creds";
|
|
12060
|
+
if (!terminal) {
|
|
12061
|
+
console.warn("[auth] Refresh still failing (transient/offline) \u2014 keeping MCP up, waiting for connectivity...");
|
|
12062
|
+
const waited = await waitForCredentialsOrTerminal(() => running);
|
|
12063
|
+
if (waited.kind === "stopped")
|
|
12064
|
+
continue;
|
|
12065
|
+
if (waited.kind === "recovered") {
|
|
12066
|
+
console.log("Reconnected \u2014 resuming listener");
|
|
12067
|
+
continue;
|
|
12068
|
+
}
|
|
12069
|
+
console.warn("[auth] Session became terminal during the offline wait \u2014 prompting re-login");
|
|
12070
|
+
terminal = true;
|
|
12071
|
+
}
|
|
12072
|
+
console.error("Session expired (refresh token rejected). Prompting re-login and waiting...");
|
|
12073
|
+
try {
|
|
12074
|
+
await mcpRuntime?.server?.suspendEndpoint();
|
|
12075
|
+
} catch (err) {
|
|
12076
|
+
console.warn("[mcp] suspendEndpoint failed:", err instanceof Error ? err.message : String(err));
|
|
12077
|
+
}
|
|
11513
12078
|
const creds = await getStoredCredentials();
|
|
11514
12079
|
const email = creds?.idToken ? decodeEmailFromIdToken(creds.idToken) : null;
|
|
11515
12080
|
if (email && authErrorGroup) {
|
|
11516
12081
|
try {
|
|
11517
|
-
await
|
|
12082
|
+
await fetchWithTimeout(`${authErrorGroup.apiUrl}/v1/public/notify-auth-expired`, {
|
|
11518
12083
|
method: "POST",
|
|
11519
12084
|
headers: { "Content-Type": "application/json" },
|
|
11520
12085
|
body: JSON.stringify({ email })
|
|
11521
|
-
});
|
|
12086
|
+
}, 5e3);
|
|
11522
12087
|
} catch {
|
|
11523
12088
|
}
|
|
11524
12089
|
}
|
|
11525
12090
|
const fresh = await waitForCredentials(() => running);
|
|
11526
|
-
if (fresh)
|
|
12091
|
+
if (fresh) {
|
|
11527
12092
|
console.log("Re-authenticated \u2014 resuming listener");
|
|
12093
|
+
try {
|
|
12094
|
+
await mcpRuntime?.server?.resumeEndpoint();
|
|
12095
|
+
} catch (err) {
|
|
12096
|
+
console.warn("[mcp] resumeEndpoint failed:", err instanceof Error ? err.message : String(err));
|
|
12097
|
+
}
|
|
12098
|
+
}
|
|
11528
12099
|
continue;
|
|
11529
12100
|
}
|
|
11530
12101
|
pollIntervalMs = minPollInterval;
|
|
@@ -11548,7 +12119,7 @@ if (isDirectRun) {
|
|
|
11548
12119
|
|
|
11549
12120
|
// src/lib/env.ts
|
|
11550
12121
|
import { homedir as homedir7 } from "os";
|
|
11551
|
-
import { join as
|
|
12122
|
+
import { join as join43 } from "path";
|
|
11552
12123
|
var IS_STAGING2 = true ? true : false;
|
|
11553
12124
|
var PRODUCTION = {
|
|
11554
12125
|
apiUrl: "https://api.repowise.ai",
|
|
@@ -11568,7 +12139,7 @@ function getEnvConfig() {
|
|
|
11568
12139
|
return IS_STAGING2 ? STAGING : PRODUCTION;
|
|
11569
12140
|
}
|
|
11570
12141
|
function getConfigDir2() {
|
|
11571
|
-
return
|
|
12142
|
+
return join43(homedir7(), IS_STAGING2 ? ".repowise-staging" : ".repowise");
|
|
11572
12143
|
}
|
|
11573
12144
|
function getPackageName() {
|
|
11574
12145
|
return true ? "repowisestage" : "repowise";
|
|
@@ -11578,12 +12149,12 @@ function getPackageName() {
|
|
|
11578
12149
|
import chalk from "chalk";
|
|
11579
12150
|
|
|
11580
12151
|
// src/lib/config.ts
|
|
11581
|
-
import { readFile as
|
|
11582
|
-
import { join as
|
|
11583
|
-
import
|
|
12152
|
+
import { readFile as readFile15, writeFile as writeFile17, mkdir as mkdir17, rename as rename6, unlink as unlink12 } from "fs/promises";
|
|
12153
|
+
import { join as join44 } from "path";
|
|
12154
|
+
import lockfile6 from "proper-lockfile";
|
|
11584
12155
|
async function getConfig() {
|
|
11585
12156
|
try {
|
|
11586
|
-
const data = await
|
|
12157
|
+
const data = await readFile15(join44(getConfigDir2(), "config.json"), "utf-8");
|
|
11587
12158
|
return JSON.parse(data);
|
|
11588
12159
|
} catch {
|
|
11589
12160
|
return {};
|
|
@@ -11591,15 +12162,15 @@ async function getConfig() {
|
|
|
11591
12162
|
}
|
|
11592
12163
|
async function saveConfig(config2) {
|
|
11593
12164
|
const dir = getConfigDir2();
|
|
11594
|
-
const path =
|
|
12165
|
+
const path = join44(dir, "config.json");
|
|
11595
12166
|
await mkdir17(dir, { recursive: true });
|
|
11596
12167
|
const tmpPath = path + ".tmp";
|
|
11597
12168
|
try {
|
|
11598
|
-
await
|
|
11599
|
-
await
|
|
12169
|
+
await writeFile17(tmpPath, JSON.stringify(config2, null, 2));
|
|
12170
|
+
await rename6(tmpPath, path);
|
|
11600
12171
|
} catch (err) {
|
|
11601
12172
|
try {
|
|
11602
|
-
await
|
|
12173
|
+
await unlink12(tmpPath);
|
|
11603
12174
|
} catch {
|
|
11604
12175
|
}
|
|
11605
12176
|
throw err;
|
|
@@ -11607,18 +12178,18 @@ async function saveConfig(config2) {
|
|
|
11607
12178
|
}
|
|
11608
12179
|
async function mergeAndSaveConfig(updates) {
|
|
11609
12180
|
const dir = getConfigDir2();
|
|
11610
|
-
const path =
|
|
12181
|
+
const path = join44(dir, "config.json");
|
|
11611
12182
|
await mkdir17(dir, { recursive: true });
|
|
11612
12183
|
try {
|
|
11613
|
-
await
|
|
12184
|
+
await writeFile17(path, "", { flag: "a" });
|
|
11614
12185
|
} catch {
|
|
11615
12186
|
}
|
|
11616
12187
|
let release = null;
|
|
11617
12188
|
try {
|
|
11618
|
-
release = await
|
|
12189
|
+
release = await lockfile6.lock(path, { stale: 1e4, retries: 3, realpath: false });
|
|
11619
12190
|
let raw = {};
|
|
11620
12191
|
try {
|
|
11621
|
-
raw = JSON.parse(await
|
|
12192
|
+
raw = JSON.parse(await readFile15(path, "utf-8"));
|
|
11622
12193
|
} catch {
|
|
11623
12194
|
}
|
|
11624
12195
|
const merged = { ...raw, ...updates };
|
|
@@ -11678,18 +12249,33 @@ async function showWelcome(currentVersion) {
|
|
|
11678
12249
|
|
|
11679
12250
|
// src/commands/create.ts
|
|
11680
12251
|
import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
11681
|
-
import { dirname as dirname19, join as
|
|
12252
|
+
import { dirname as dirname19, join as join51 } from "path";
|
|
11682
12253
|
init_src();
|
|
11683
12254
|
import chalk8 from "chalk";
|
|
11684
12255
|
import ora from "ora";
|
|
11685
12256
|
|
|
11686
12257
|
// src/lib/auth.ts
|
|
11687
12258
|
import { createHash as createHash6, randomBytes as randomBytes3 } from "crypto";
|
|
11688
|
-
import { readFile as
|
|
12259
|
+
import { readFile as readFile16, writeFile as writeFile18, mkdir as mkdir18, chmod as chmod4, unlink as unlink13, rename as rename7, open as open5 } from "fs/promises";
|
|
11689
12260
|
import http from "http";
|
|
11690
|
-
import { join as
|
|
12261
|
+
import { join as join45 } from "path";
|
|
12262
|
+
import lockfile7 from "proper-lockfile";
|
|
12263
|
+
|
|
12264
|
+
// src/lib/http.ts
|
|
12265
|
+
async function fetchWithTimeout2(url, init, timeoutMs) {
|
|
12266
|
+
const controller = new AbortController();
|
|
12267
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
12268
|
+
try {
|
|
12269
|
+
return await fetch(url, { ...init, signal: controller.signal });
|
|
12270
|
+
} finally {
|
|
12271
|
+
clearTimeout(timer);
|
|
12272
|
+
}
|
|
12273
|
+
}
|
|
12274
|
+
|
|
12275
|
+
// src/lib/auth.ts
|
|
11691
12276
|
var CLI_CALLBACK_PORT = 19876;
|
|
11692
12277
|
var CALLBACK_TIMEOUT_MS = 12e4;
|
|
12278
|
+
var TOKEN_ENDPOINT_TIMEOUT_MS2 = 3e4;
|
|
11693
12279
|
function getCognitoConfigForStorage() {
|
|
11694
12280
|
const { domain, clientId, region, customDomain } = getCognitoConfig();
|
|
11695
12281
|
return { domain, clientId, region, customDomain };
|
|
@@ -11830,31 +12416,65 @@ async function exchangeCodeForTokens(code, codeVerifier) {
|
|
|
11830
12416
|
};
|
|
11831
12417
|
}
|
|
11832
12418
|
async function refreshTokens2(refreshToken) {
|
|
11833
|
-
|
|
11834
|
-
|
|
11835
|
-
|
|
11836
|
-
|
|
11837
|
-
|
|
11838
|
-
|
|
11839
|
-
|
|
11840
|
-
|
|
11841
|
-
|
|
12419
|
+
let response;
|
|
12420
|
+
try {
|
|
12421
|
+
response = await fetchWithTimeout2(
|
|
12422
|
+
getTokenUrl2(),
|
|
12423
|
+
{
|
|
12424
|
+
method: "POST",
|
|
12425
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
12426
|
+
body: new URLSearchParams({
|
|
12427
|
+
grant_type: "refresh_token",
|
|
12428
|
+
client_id: getCognitoConfig().clientId,
|
|
12429
|
+
refresh_token: refreshToken
|
|
12430
|
+
})
|
|
12431
|
+
},
|
|
12432
|
+
TOKEN_ENDPOINT_TIMEOUT_MS2
|
|
12433
|
+
);
|
|
12434
|
+
} catch (err) {
|
|
12435
|
+
throw new RefreshTokenError(
|
|
12436
|
+
"transient",
|
|
12437
|
+
`Token refresh network error: ${err instanceof Error ? err.message : String(err)}`,
|
|
12438
|
+
null
|
|
12439
|
+
);
|
|
12440
|
+
}
|
|
11842
12441
|
if (!response.ok) {
|
|
11843
|
-
|
|
12442
|
+
const body = await response.text().catch(() => "");
|
|
12443
|
+
throw new RefreshTokenError(
|
|
12444
|
+
classifyRefreshFailure(response.status, body),
|
|
12445
|
+
`Token refresh failed: ${response.status}`,
|
|
12446
|
+
response.status
|
|
12447
|
+
);
|
|
11844
12448
|
}
|
|
11845
12449
|
const data = await response.json();
|
|
11846
12450
|
return {
|
|
11847
12451
|
accessToken: data.access_token,
|
|
11848
|
-
|
|
11849
|
-
//
|
|
12452
|
+
// Carry forward a rotated refresh token when Cognito returns one (rotation
|
|
12453
|
+
// enabled); otherwise reuse the current token (rotation disabled today).
|
|
12454
|
+
// Persisting the newest token is the hard prerequisite for enabling
|
|
12455
|
+
// refresh-token rotation server-side.
|
|
12456
|
+
refreshToken: data.refresh_token ?? refreshToken,
|
|
11850
12457
|
idToken: data.id_token,
|
|
11851
12458
|
expiresAt: Date.now() + data.expires_in * 1e3
|
|
11852
12459
|
};
|
|
11853
12460
|
}
|
|
12461
|
+
async function revokeRefreshToken(refreshToken) {
|
|
12462
|
+
const { clientId } = getCognitoConfig();
|
|
12463
|
+
if (!clientId || !refreshToken) return;
|
|
12464
|
+
await fetchWithTimeout2(
|
|
12465
|
+
`${getCognitoBaseUrl()}/oauth2/revoke`,
|
|
12466
|
+
{
|
|
12467
|
+
method: "POST",
|
|
12468
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
12469
|
+
body: new URLSearchParams({ token: refreshToken, client_id: clientId })
|
|
12470
|
+
},
|
|
12471
|
+
TOKEN_ENDPOINT_TIMEOUT_MS2
|
|
12472
|
+
);
|
|
12473
|
+
}
|
|
11854
12474
|
async function getStoredCredentials2() {
|
|
11855
12475
|
try {
|
|
11856
|
-
const credPath =
|
|
11857
|
-
const data = await
|
|
12476
|
+
const credPath = join45(getConfigDir2(), "credentials.json");
|
|
12477
|
+
const data = await readFile16(credPath, "utf-8");
|
|
11858
12478
|
return JSON.parse(data);
|
|
11859
12479
|
} catch (err) {
|
|
11860
12480
|
if (err.code === "ENOENT" || err instanceof SyntaxError) {
|
|
@@ -11865,37 +12485,78 @@ async function getStoredCredentials2() {
|
|
|
11865
12485
|
}
|
|
11866
12486
|
async function storeCredentials2(credentials) {
|
|
11867
12487
|
const dir = getConfigDir2();
|
|
11868
|
-
const credPath =
|
|
12488
|
+
const credPath = join45(dir, "credentials.json");
|
|
11869
12489
|
await mkdir18(dir, { recursive: true, mode: 448 });
|
|
11870
|
-
|
|
11871
|
-
|
|
12490
|
+
try {
|
|
12491
|
+
await writeFile18(credPath, "", { flag: "a", mode: 384 });
|
|
12492
|
+
} catch {
|
|
12493
|
+
}
|
|
12494
|
+
let release = null;
|
|
12495
|
+
try {
|
|
12496
|
+
release = await lockfile7.lock(credPath, { stale: 1e4, retries: 3, realpath: false });
|
|
12497
|
+
const tmpPath = credPath + ".tmp";
|
|
12498
|
+
try {
|
|
12499
|
+
await writeFile18(tmpPath, JSON.stringify(credentials, null, 2), { mode: 384 });
|
|
12500
|
+
let fh;
|
|
12501
|
+
try {
|
|
12502
|
+
fh = await open5(tmpPath, "r+");
|
|
12503
|
+
await fh.datasync();
|
|
12504
|
+
} finally {
|
|
12505
|
+
await fh?.close();
|
|
12506
|
+
}
|
|
12507
|
+
await rename7(tmpPath, credPath);
|
|
12508
|
+
await chmod4(credPath, 384);
|
|
12509
|
+
} catch (err) {
|
|
12510
|
+
try {
|
|
12511
|
+
await unlink13(tmpPath);
|
|
12512
|
+
} catch {
|
|
12513
|
+
}
|
|
12514
|
+
throw err;
|
|
12515
|
+
}
|
|
12516
|
+
} finally {
|
|
12517
|
+
if (release) {
|
|
12518
|
+
try {
|
|
12519
|
+
await release();
|
|
12520
|
+
} catch {
|
|
12521
|
+
}
|
|
12522
|
+
}
|
|
12523
|
+
}
|
|
11872
12524
|
}
|
|
11873
12525
|
async function clearCredentials() {
|
|
11874
12526
|
try {
|
|
11875
|
-
await
|
|
12527
|
+
await unlink13(join45(getConfigDir2(), "credentials.json"));
|
|
11876
12528
|
} catch (err) {
|
|
11877
12529
|
if (err.code !== "ENOENT") throw err;
|
|
11878
12530
|
}
|
|
11879
12531
|
}
|
|
12532
|
+
var refreshInFlight2 = null;
|
|
11880
12533
|
async function getValidCredentials2(opts) {
|
|
11881
12534
|
const creds = await getStoredCredentials2();
|
|
11882
12535
|
if (!creds) return null;
|
|
11883
12536
|
const needsRefresh = opts?.forceRefresh || Date.now() > creds.expiresAt - 5 * 60 * 1e3;
|
|
11884
|
-
if (needsRefresh)
|
|
11885
|
-
|
|
11886
|
-
|
|
11887
|
-
|
|
11888
|
-
|
|
11889
|
-
|
|
11890
|
-
|
|
11891
|
-
|
|
11892
|
-
|
|
11893
|
-
|
|
12537
|
+
if (!needsRefresh) return creds;
|
|
12538
|
+
if (!refreshInFlight2) {
|
|
12539
|
+
refreshInFlight2 = doRefresh2(creds).finally(() => {
|
|
12540
|
+
refreshInFlight2 = null;
|
|
12541
|
+
});
|
|
12542
|
+
}
|
|
12543
|
+
return refreshInFlight2;
|
|
12544
|
+
}
|
|
12545
|
+
async function doRefresh2(creds) {
|
|
12546
|
+
try {
|
|
12547
|
+
const refreshed = await refreshTokens2(creds.refreshToken);
|
|
12548
|
+
refreshed.cognito = creds.cognito;
|
|
12549
|
+
await storeCredentials2(refreshed);
|
|
12550
|
+
return refreshed;
|
|
12551
|
+
} catch (err) {
|
|
12552
|
+
const kind = err instanceof RefreshTokenError ? err.kind : "transient";
|
|
12553
|
+
if (kind === "terminal") {
|
|
11894
12554
|
await clearCredentials();
|
|
11895
12555
|
return null;
|
|
11896
12556
|
}
|
|
12557
|
+
if (Date.now() < creds.expiresAt) return creds;
|
|
12558
|
+
return null;
|
|
11897
12559
|
}
|
|
11898
|
-
return creds;
|
|
11899
12560
|
}
|
|
11900
12561
|
async function performLogin() {
|
|
11901
12562
|
const codeVerifier = generateCodeVerifier();
|
|
@@ -11904,8 +12565,8 @@ async function performLogin() {
|
|
|
11904
12565
|
const authorizeUrl = getAuthorizeUrl(codeChallenge, state);
|
|
11905
12566
|
const callbackPromise = startCallbackServer();
|
|
11906
12567
|
try {
|
|
11907
|
-
const
|
|
11908
|
-
await
|
|
12568
|
+
const open6 = (await import("open")).default;
|
|
12569
|
+
await open6(authorizeUrl);
|
|
11909
12570
|
} catch {
|
|
11910
12571
|
console.log(`
|
|
11911
12572
|
Open this URL in your browser to authenticate:
|
|
@@ -12003,7 +12664,6 @@ async function apiRequest(path, options) {
|
|
|
12003
12664
|
});
|
|
12004
12665
|
}
|
|
12005
12666
|
if (!refreshed || response.status === 401) {
|
|
12006
|
-
await clearCredentials();
|
|
12007
12667
|
throw new Error("Session expired. Run `repowise login` again.");
|
|
12008
12668
|
}
|
|
12009
12669
|
}
|
|
@@ -12120,7 +12780,7 @@ async function writeToolConfigsForRepo(opts) {
|
|
|
12120
12780
|
}
|
|
12121
12781
|
|
|
12122
12782
|
// src/lib/mcp-resolver.ts
|
|
12123
|
-
import { basename as basename4, join as
|
|
12783
|
+
import { basename as basename4, join as join46 } from "path";
|
|
12124
12784
|
import { promises as fs21 } from "fs";
|
|
12125
12785
|
var MCP_WRITER_TOOLS = /* @__PURE__ */ new Set([
|
|
12126
12786
|
"claude-code",
|
|
@@ -12134,19 +12794,19 @@ var MCP_WRITER_TOOLS = /* @__PURE__ */ new Set([
|
|
|
12134
12794
|
function mcpPathFor(tool, repoRoot, home) {
|
|
12135
12795
|
switch (tool) {
|
|
12136
12796
|
case "claude-code":
|
|
12137
|
-
return { path:
|
|
12797
|
+
return { path: join46(repoRoot, ".mcp.json"), scope: "repo" };
|
|
12138
12798
|
case "cursor":
|
|
12139
|
-
return { path:
|
|
12799
|
+
return { path: join46(repoRoot, ".cursor", "mcp.json"), scope: "repo" };
|
|
12140
12800
|
case "copilot":
|
|
12141
|
-
return { path:
|
|
12801
|
+
return { path: join46(repoRoot, ".vscode", "mcp.json"), scope: "repo" };
|
|
12142
12802
|
case "codex":
|
|
12143
|
-
return { path:
|
|
12803
|
+
return { path: join46(home, ".codex", "mcp.json"), scope: "global" };
|
|
12144
12804
|
case "windsurf":
|
|
12145
|
-
return { path:
|
|
12805
|
+
return { path: join46(home, ".codeium", "windsurf", "mcp_config.json"), scope: "global" };
|
|
12146
12806
|
case "cline":
|
|
12147
|
-
return { path:
|
|
12807
|
+
return { path: join46(home, ".cline", "mcp.json"), scope: "global" };
|
|
12148
12808
|
case "gemini":
|
|
12149
|
-
return { path:
|
|
12809
|
+
return { path: join46(home, ".gemini", "settings.json"), scope: "global" };
|
|
12150
12810
|
default:
|
|
12151
12811
|
return null;
|
|
12152
12812
|
}
|
|
@@ -12196,9 +12856,9 @@ async function mcpStatusForRepo(opts) {
|
|
|
12196
12856
|
|
|
12197
12857
|
// src/lib/gitignore.ts
|
|
12198
12858
|
import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync2 } from "fs";
|
|
12199
|
-
import { join as
|
|
12859
|
+
import { join as join47 } from "path";
|
|
12200
12860
|
function ensureGitignore(repoRoot, entry) {
|
|
12201
|
-
const gitignorePath =
|
|
12861
|
+
const gitignorePath = join47(repoRoot, ".gitignore");
|
|
12202
12862
|
if (existsSync2(gitignorePath)) {
|
|
12203
12863
|
const content = readFileSync2(gitignorePath, "utf-8");
|
|
12204
12864
|
const lines = content.split("\n").map((l) => l.trim());
|
|
@@ -12215,7 +12875,7 @@ function ensureGitignore(repoRoot, entry) {
|
|
|
12215
12875
|
// src/lib/graph-cache.ts
|
|
12216
12876
|
init_config_dir();
|
|
12217
12877
|
import { mkdirSync, writeFileSync as writeFileSync2, renameSync, unlinkSync } from "fs";
|
|
12218
|
-
import { dirname as dirname18, join as
|
|
12878
|
+
import { dirname as dirname18, join as join48 } from "path";
|
|
12219
12879
|
var SAFE_REPO_ID = /^[A-Za-z0-9_.-]{1,128}$/;
|
|
12220
12880
|
function assertSafeRepoId2(repoId) {
|
|
12221
12881
|
if (!repoId || typeof repoId !== "string" || !SAFE_REPO_ID.test(repoId) || repoId === "." || repoId === ".." || repoId.startsWith(".")) {
|
|
@@ -12224,7 +12884,7 @@ function assertSafeRepoId2(repoId) {
|
|
|
12224
12884
|
}
|
|
12225
12885
|
function graphCachePath(repoId) {
|
|
12226
12886
|
assertSafeRepoId2(repoId);
|
|
12227
|
-
return
|
|
12887
|
+
return join48(getConfigDir(), "graphs", `${repoId}.json`);
|
|
12228
12888
|
}
|
|
12229
12889
|
function isUsableGraph(parsed) {
|
|
12230
12890
|
if (typeof parsed !== "object" || parsed === null) return false;
|
|
@@ -12653,19 +13313,17 @@ var ProgressRenderer = class {
|
|
|
12653
13313
|
});
|
|
12654
13314
|
}
|
|
12655
13315
|
/**
|
|
12656
|
-
*
|
|
12657
|
-
*
|
|
12658
|
-
*
|
|
13316
|
+
* Latch marking the end of the user-driven interview and the start of the
|
|
13317
|
+
* long machine-driven scan/generate/validate phases. The post-interview
|
|
13318
|
+
* "coffee" line now rides with the paid "RepoWise MCP is live" message
|
|
13319
|
+
* (create.ts), emitted once the knowledge graph is actually downloadable \u2014
|
|
13320
|
+
* so it no longer fires here. The stop/start keeps the spinner cadence
|
|
13321
|
+
* stable across the interview\u2192scan handoff.
|
|
12659
13322
|
*/
|
|
12660
13323
|
renderInterviewComplete(spinner) {
|
|
12661
13324
|
if (this.interviewCompleteShown) return;
|
|
12662
13325
|
this.interviewCompleteShown = true;
|
|
12663
13326
|
spinner.stop();
|
|
12664
|
-
console.log("");
|
|
12665
|
-
console.log(
|
|
12666
|
-
chalk4.cyan(" \u2615 Sit back and grab a coffee \u2014 we'll handle the rest from here.")
|
|
12667
|
-
);
|
|
12668
|
-
console.log("");
|
|
12669
13327
|
spinner.start();
|
|
12670
13328
|
}
|
|
12671
13329
|
renderScanProgress(progress, spinner) {
|
|
@@ -12952,7 +13610,7 @@ import chalk6 from "chalk";
|
|
|
12952
13610
|
|
|
12953
13611
|
// src/lib/dep-installer.ts
|
|
12954
13612
|
import { promises as fs22 } from "fs";
|
|
12955
|
-
import { join as
|
|
13613
|
+
import { join as join49 } from "path";
|
|
12956
13614
|
var SUPPORTED_DEP_LANGUAGES = /* @__PURE__ */ new Set([
|
|
12957
13615
|
"typescript",
|
|
12958
13616
|
"javascript",
|
|
@@ -12969,7 +13627,7 @@ var exists = async (p) => {
|
|
|
12969
13627
|
}
|
|
12970
13628
|
};
|
|
12971
13629
|
async function fileExists16(repoRoot, name) {
|
|
12972
|
-
return exists(
|
|
13630
|
+
return exists(join49(repoRoot, name));
|
|
12973
13631
|
}
|
|
12974
13632
|
async function detectNodePackageManager(repoRoot) {
|
|
12975
13633
|
if (await fileExists16(repoRoot, "pnpm-lock.yaml")) {
|
|
@@ -13047,11 +13705,11 @@ async function detectMissingDeps(repoRoot, scopedLanguages) {
|
|
|
13047
13705
|
import { spawn as spawn11 } from "child_process";
|
|
13048
13706
|
import { createWriteStream as createWriteStream3 } from "fs";
|
|
13049
13707
|
import { promises as fs23 } from "fs";
|
|
13050
|
-
import { join as
|
|
13708
|
+
import { join as join50 } from "path";
|
|
13051
13709
|
var DEFAULT_INSTALL_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
13052
13710
|
async function runMissingDepInstalls(opts) {
|
|
13053
13711
|
const safeRepoId = opts.repoId.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
13054
|
-
const logPath2 =
|
|
13712
|
+
const logPath2 = join50(getConfigDir2(), `install-log.${safeRepoId}.txt`);
|
|
13055
13713
|
await fs23.mkdir(getConfigDir2(), { recursive: true });
|
|
13056
13714
|
const stream = opts.logStream ?? createWriteStream3(logPath2, { flags: "a" });
|
|
13057
13715
|
stream.on("error", () => {
|
|
@@ -13098,7 +13756,7 @@ async function runOne2(dep, repoRoot, stream, timeoutMs) {
|
|
|
13098
13756
|
}
|
|
13099
13757
|
async function runPipFlow(repoRoot, stream, timeoutMs) {
|
|
13100
13758
|
await runSimple(["python3", "-m", "venv", ".venv"], repoRoot, stream, timeoutMs);
|
|
13101
|
-
const venvPip = process.platform === "win32" ?
|
|
13759
|
+
const venvPip = process.platform === "win32" ? join50(".venv", "Scripts", "pip.exe") : join50(".venv", "bin", "pip");
|
|
13102
13760
|
await runSimple([venvPip, "install", "-r", "requirements.txt"], repoRoot, stream, timeoutMs);
|
|
13103
13761
|
}
|
|
13104
13762
|
function runSimple(cmd, repoRoot, stream, timeoutMs) {
|
|
@@ -13257,13 +13915,15 @@ function reportLspInstallOutcomes(results) {
|
|
|
13257
13915
|
// src/commands/create.ts
|
|
13258
13916
|
function formatElapsed(ms) {
|
|
13259
13917
|
const totalSeconds = Math.round(ms / 1e3);
|
|
13260
|
-
const
|
|
13918
|
+
const hours = Math.floor(totalSeconds / 3600);
|
|
13919
|
+
const minutes = Math.floor(totalSeconds % 3600 / 60);
|
|
13261
13920
|
const seconds = totalSeconds % 60;
|
|
13921
|
+
if (hours > 0) return minutes > 0 ? `${hours}h ${minutes}m` : `${hours}h`;
|
|
13262
13922
|
if (minutes === 0) return `${seconds}s`;
|
|
13263
13923
|
return seconds > 0 ? `${minutes}m ${seconds}s` : `${minutes}m`;
|
|
13264
13924
|
}
|
|
13265
13925
|
var POLL_INTERVAL_MS = 3e3;
|
|
13266
|
-
var MAX_POLL_ATTEMPTS =
|
|
13926
|
+
var MAX_POLL_ATTEMPTS = 14600;
|
|
13267
13927
|
var DEFAULT_CONTEXT_FOLDER = "repowise-context";
|
|
13268
13928
|
function buildWatchedRepoEntry(args) {
|
|
13269
13929
|
const entry = {
|
|
@@ -13506,6 +14166,8 @@ async function create() {
|
|
|
13506
14166
|
}
|
|
13507
14167
|
let pollAttempts = 0;
|
|
13508
14168
|
let pollErrors = 0;
|
|
14169
|
+
let graphEarlyWriteDone = false;
|
|
14170
|
+
let nonAwaitingPolls = 0;
|
|
13509
14171
|
const MAX_POLL_ERRORS = 5;
|
|
13510
14172
|
const progressRenderer = new ProgressRenderer();
|
|
13511
14173
|
let depInstallShown = false;
|
|
@@ -13576,8 +14238,17 @@ async function create() {
|
|
|
13576
14238
|
if (!interviewComplete && graphOnly) {
|
|
13577
14239
|
interviewComplete = true;
|
|
13578
14240
|
progressRenderer.setInterviewComplete(spinner);
|
|
14241
|
+
spinner.stop();
|
|
14242
|
+
console.log(
|
|
14243
|
+
chalk8.cyan(
|
|
14244
|
+
" \u2615 Building your code graph \u2014 MCP will be live in a moment (Free plan: graph + MCP)."
|
|
14245
|
+
)
|
|
14246
|
+
);
|
|
14247
|
+
spinner.start();
|
|
13579
14248
|
}
|
|
13580
14249
|
progressRenderer.update(syncResult, spinner);
|
|
14250
|
+
if (syncResult.status === "awaiting_input") nonAwaitingPolls = 0;
|
|
14251
|
+
else nonAwaitingPolls += 1;
|
|
13581
14252
|
if (!graphOnly && syncResult.status === "in_progress") {
|
|
13582
14253
|
if (syncResult.lspWaitState === "awaiting" || syncResult.lspWaitState === "analyzing") {
|
|
13583
14254
|
lspWaitStartedAt ??= Date.now();
|
|
@@ -13591,6 +14262,45 @@ async function create() {
|
|
|
13591
14262
|
spinner.start();
|
|
13592
14263
|
}
|
|
13593
14264
|
}
|
|
14265
|
+
const interviewPhaseOver = interviewComplete || nonAwaitingPolls >= 3;
|
|
14266
|
+
if (!graphOnly && !graphEarlyWriteDone && syncResult.graphReady === true && syncResult.status !== "awaiting_input" && interviewPhaseOver && repoRoot) {
|
|
14267
|
+
const earlyCreds = await getValidCredentials2();
|
|
14268
|
+
if (earlyCreds) {
|
|
14269
|
+
graphEarlyWriteDone = true;
|
|
14270
|
+
if (!interviewComplete) {
|
|
14271
|
+
interviewComplete = true;
|
|
14272
|
+
progressRenderer.setInterviewComplete(spinner);
|
|
14273
|
+
}
|
|
14274
|
+
spinner.stop();
|
|
14275
|
+
await ensureGraphDownloaded({
|
|
14276
|
+
repoId,
|
|
14277
|
+
accessToken: earlyCreds.accessToken,
|
|
14278
|
+
apiUrl: getEnvConfig().apiUrl
|
|
14279
|
+
});
|
|
14280
|
+
await writeToolConfigsForRepo({
|
|
14281
|
+
repoRoot,
|
|
14282
|
+
tools,
|
|
14283
|
+
repoName,
|
|
14284
|
+
contextFolder: DEFAULT_CONTEXT_FOLDER,
|
|
14285
|
+
contextFiles: [],
|
|
14286
|
+
variant: "graph",
|
|
14287
|
+
migrateLegacy: true
|
|
14288
|
+
});
|
|
14289
|
+
if (!sigintBusy) {
|
|
14290
|
+
console.log("");
|
|
14291
|
+
console.log(
|
|
14292
|
+
chalk8.green(" \u2714 RepoWise MCP is live \u2014 start exploring your codebase now.")
|
|
14293
|
+
);
|
|
14294
|
+
console.log(
|
|
14295
|
+
chalk8.cyan(
|
|
14296
|
+
" \u2615 Full docs are still generating in the background \u2014 your AI tools auto-upgrade when they're ready (graph precision keeps improving too)."
|
|
14297
|
+
)
|
|
14298
|
+
);
|
|
14299
|
+
console.log("");
|
|
14300
|
+
spinner.start();
|
|
14301
|
+
}
|
|
14302
|
+
}
|
|
14303
|
+
}
|
|
13594
14304
|
if (syncResult.status === "awaiting_input") {
|
|
13595
14305
|
seenAwaitingInput = true;
|
|
13596
14306
|
}
|
|
@@ -13669,7 +14379,7 @@ async function create() {
|
|
|
13669
14379
|
const listResult = await apiRequest(`/v1/repos/${repoId}/context`);
|
|
13670
14380
|
const files = listResult.data?.files ?? listResult.files ?? [];
|
|
13671
14381
|
if (files.length > 0) {
|
|
13672
|
-
const contextDir =
|
|
14382
|
+
const contextDir = join51(repoRoot, DEFAULT_CONTEXT_FOLDER);
|
|
13673
14383
|
mkdirSync2(contextDir, { recursive: true });
|
|
13674
14384
|
let downloadedCount = 0;
|
|
13675
14385
|
let failedCount = 0;
|
|
@@ -13683,7 +14393,7 @@ async function create() {
|
|
|
13683
14393
|
const response = await fetch(presignedUrl);
|
|
13684
14394
|
if (response.ok) {
|
|
13685
14395
|
const content = await response.text();
|
|
13686
|
-
const filePath =
|
|
14396
|
+
const filePath = join51(contextDir, file.fileName);
|
|
13687
14397
|
mkdirSync2(dirname19(filePath), { recursive: true });
|
|
13688
14398
|
writeFileSync3(filePath, content, "utf-8");
|
|
13689
14399
|
downloadedCount++;
|
|
@@ -13778,6 +14488,13 @@ Files are stored on our servers (not in git). Retry when online.`
|
|
|
13778
14488
|
});
|
|
13779
14489
|
spinner.succeed("AI tools configured");
|
|
13780
14490
|
console.log(chalk8.dim(results.map((r) => ` ${r}`).join("\n")));
|
|
14491
|
+
if (!graphOnly && contextFiles.length > 0) {
|
|
14492
|
+
console.log(
|
|
14493
|
+
chalk8.cyan(
|
|
14494
|
+
" \u2728 Full context docs are ready \u2014 start a new AI session (or restart your AI tool) to pick up the upgraded instructions."
|
|
14495
|
+
)
|
|
14496
|
+
);
|
|
14497
|
+
}
|
|
13781
14498
|
}
|
|
13782
14499
|
const priorAutoInstall = await getPriorConsent(repoId);
|
|
13783
14500
|
const updatedRepos = [];
|
|
@@ -13905,7 +14622,7 @@ Files are stored on our servers (not in git). Retry when online.`
|
|
|
13905
14622
|
|
|
13906
14623
|
// src/commands/member.ts
|
|
13907
14624
|
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync4 } from "fs";
|
|
13908
|
-
import { dirname as dirname20, join as
|
|
14625
|
+
import { dirname as dirname20, join as join52, resolve, sep } from "path";
|
|
13909
14626
|
import chalk9 from "chalk";
|
|
13910
14627
|
import ora2 from "ora";
|
|
13911
14628
|
var DEFAULT_CONTEXT_FOLDER2 = "repowise-context";
|
|
@@ -14059,7 +14776,7 @@ async function member() {
|
|
|
14059
14776
|
spinner.succeed(`Found ${chalk9.bold(files.length)} context files on server`);
|
|
14060
14777
|
const { tools } = await selectAiTools();
|
|
14061
14778
|
spinner.start("Downloading context files...");
|
|
14062
|
-
const contextDir =
|
|
14779
|
+
const contextDir = join52(repoRoot, DEFAULT_CONTEXT_FOLDER2);
|
|
14063
14780
|
mkdirSync3(contextDir, { recursive: true });
|
|
14064
14781
|
let downloadedCount = 0;
|
|
14065
14782
|
let failedCount = 0;
|
|
@@ -14239,9 +14956,9 @@ import ora3 from "ora";
|
|
|
14239
14956
|
// src/lib/tenant-graph-purge.ts
|
|
14240
14957
|
import { promises as fs24 } from "fs";
|
|
14241
14958
|
import { homedir as homedir8 } from "os";
|
|
14242
|
-
import { join as
|
|
14959
|
+
import { join as join53 } from "path";
|
|
14243
14960
|
async function purgeForeignGraphs(validRepoIds, home = homedir8()) {
|
|
14244
|
-
const graphsDir =
|
|
14961
|
+
const graphsDir = join53(home, ".repowise", "graphs");
|
|
14245
14962
|
const result = { kept: [], removed: [] };
|
|
14246
14963
|
let entries;
|
|
14247
14964
|
try {
|
|
@@ -14259,7 +14976,7 @@ async function purgeForeignGraphs(validRepoIds, home = homedir8()) {
|
|
|
14259
14976
|
result.kept.push(entry);
|
|
14260
14977
|
continue;
|
|
14261
14978
|
}
|
|
14262
|
-
const path =
|
|
14979
|
+
const path = join53(graphsDir, entry);
|
|
14263
14980
|
try {
|
|
14264
14981
|
const stat8 = await fs24.lstat(path);
|
|
14265
14982
|
if (stat8.isSymbolicLink()) {
|
|
@@ -14295,8 +15012,8 @@ Waiting for authentication...`);
|
|
|
14295
15012
|
} else {
|
|
14296
15013
|
spinner.text = "Opening browser for authentication...";
|
|
14297
15014
|
try {
|
|
14298
|
-
const
|
|
14299
|
-
await
|
|
15015
|
+
const open6 = (await import("open")).default;
|
|
15016
|
+
await open6(authorizeUrl);
|
|
14300
15017
|
spinner.text = "Waiting for authentication in browser...";
|
|
14301
15018
|
} catch {
|
|
14302
15019
|
spinner.stop();
|
|
@@ -14363,30 +15080,25 @@ async function logout() {
|
|
|
14363
15080
|
console.log(chalk11.yellow("Not logged in."));
|
|
14364
15081
|
return;
|
|
14365
15082
|
}
|
|
15083
|
+
try {
|
|
15084
|
+
await revokeRefreshToken(creds.refreshToken);
|
|
15085
|
+
} catch {
|
|
15086
|
+
}
|
|
14366
15087
|
await clearCredentials();
|
|
14367
15088
|
console.log(chalk11.green("Logged out successfully."));
|
|
14368
15089
|
}
|
|
14369
15090
|
|
|
14370
15091
|
// src/commands/status.ts
|
|
14371
|
-
import { readFile as
|
|
14372
|
-
import { basename as basename5, join as
|
|
15092
|
+
import { readFile as readFile17 } from "fs/promises";
|
|
15093
|
+
import { basename as basename5, join as join54 } from "path";
|
|
14373
15094
|
import { homedir as homedir10 } from "os";
|
|
14374
|
-
|
|
14375
|
-
// ../../packages/shared/dist/lib/creds.js
|
|
14376
|
-
function isCredsHardExpired(creds) {
|
|
14377
|
-
if (!creds || typeof creds.expiresAt !== "number")
|
|
14378
|
-
return true;
|
|
14379
|
-
return Date.now() >= creds.expiresAt;
|
|
14380
|
-
}
|
|
14381
|
-
|
|
14382
|
-
// src/commands/status.ts
|
|
14383
15095
|
async function status() {
|
|
14384
15096
|
const configDir = getConfigDir2();
|
|
14385
|
-
const STATE_PATH =
|
|
14386
|
-
const CONFIG_PATH =
|
|
15097
|
+
const STATE_PATH = join54(configDir, "listener-state.json");
|
|
15098
|
+
const CONFIG_PATH = join54(configDir, "config.json");
|
|
14387
15099
|
let state = null;
|
|
14388
15100
|
try {
|
|
14389
|
-
const data = await
|
|
15101
|
+
const data = await readFile17(STATE_PATH, "utf-8");
|
|
14390
15102
|
state = JSON.parse(data);
|
|
14391
15103
|
} catch {
|
|
14392
15104
|
}
|
|
@@ -14430,7 +15142,7 @@ async function status() {
|
|
|
14430
15142
|
const repoPaths = /* @__PURE__ */ new Map();
|
|
14431
15143
|
let aiTools = [];
|
|
14432
15144
|
try {
|
|
14433
|
-
const configData = await
|
|
15145
|
+
const configData = await readFile17(CONFIG_PATH, "utf-8");
|
|
14434
15146
|
const config2 = JSON.parse(configData);
|
|
14435
15147
|
for (const repo of config2.repos ?? []) {
|
|
14436
15148
|
repoNames.set(repo.repoId, basename5(repo.localPath));
|
|
@@ -14472,16 +15184,18 @@ async function status() {
|
|
|
14472
15184
|
|
|
14473
15185
|
// src/commands/sync.ts
|
|
14474
15186
|
import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync5 } from "fs";
|
|
14475
|
-
import { dirname as dirname21, join as
|
|
15187
|
+
import { dirname as dirname21, join as join55 } from "path";
|
|
14476
15188
|
import chalk12 from "chalk";
|
|
14477
15189
|
import ora4 from "ora";
|
|
14478
15190
|
var POLL_INTERVAL_MS2 = 3e3;
|
|
14479
|
-
var MAX_POLL_ATTEMPTS2 =
|
|
15191
|
+
var MAX_POLL_ATTEMPTS2 = 14600;
|
|
14480
15192
|
var DEFAULT_CONTEXT_FOLDER3 = "repowise-context";
|
|
14481
15193
|
function formatElapsed3(ms) {
|
|
14482
15194
|
const totalSeconds = Math.round(ms / 1e3);
|
|
14483
|
-
const
|
|
15195
|
+
const hours = Math.floor(totalSeconds / 3600);
|
|
15196
|
+
const minutes = Math.floor(totalSeconds % 3600 / 60);
|
|
14484
15197
|
const seconds = totalSeconds % 60;
|
|
15198
|
+
if (hours > 0) return minutes > 0 ? `${hours}h ${minutes}m` : `${hours}h`;
|
|
14485
15199
|
if (minutes === 0) return `${seconds}s`;
|
|
14486
15200
|
return seconds > 0 ? `${minutes}m ${seconds}s` : `${minutes}m`;
|
|
14487
15201
|
}
|
|
@@ -14621,7 +15335,7 @@ async function sync() {
|
|
|
14621
15335
|
const listResult = await apiRequest(`/v1/repos/${repoId}/context`);
|
|
14622
15336
|
const files = listResult.data?.files ?? listResult.files ?? [];
|
|
14623
15337
|
if (files.length > 0) {
|
|
14624
|
-
const contextDir =
|
|
15338
|
+
const contextDir = join55(repoRoot, DEFAULT_CONTEXT_FOLDER3);
|
|
14625
15339
|
mkdirSync4(contextDir, { recursive: true });
|
|
14626
15340
|
let downloadedCount = 0;
|
|
14627
15341
|
let failedCount = 0;
|
|
@@ -14635,7 +15349,7 @@ async function sync() {
|
|
|
14635
15349
|
const response = await fetch(presignedUrl);
|
|
14636
15350
|
if (response.ok) {
|
|
14637
15351
|
const content = await response.text();
|
|
14638
|
-
const filePath =
|
|
15352
|
+
const filePath = join55(contextDir, file.fileName);
|
|
14639
15353
|
mkdirSync4(dirname21(filePath), { recursive: true });
|
|
14640
15354
|
writeFileSync5(filePath, content, "utf-8");
|
|
14641
15355
|
downloadedCount++;
|
|
@@ -15038,8 +15752,8 @@ async function toolsList() {
|
|
|
15038
15752
|
|
|
15039
15753
|
// src/commands/mcp-log.ts
|
|
15040
15754
|
import { createDecipheriv as createDecipheriv2 } from "crypto";
|
|
15041
|
-
import { mkdir as mkdir19, readFile as
|
|
15042
|
-
import { dirname as dirname22, join as
|
|
15755
|
+
import { mkdir as mkdir19, readFile as readFile18, stat as stat7, writeFile as writeFile19 } from "fs/promises";
|
|
15756
|
+
import { dirname as dirname22, join as join56 } from "path";
|
|
15043
15757
|
var FLAG_FILE = "mcp-log.flag";
|
|
15044
15758
|
var LOG_FILE = "mcp-log.jsonl.enc";
|
|
15045
15759
|
var KEY_FILE = "mcp-log.key";
|
|
@@ -15047,20 +15761,20 @@ var ENDPOINT_FILE = "listener.endpoint";
|
|
|
15047
15761
|
var IV_BYTES2 = 12;
|
|
15048
15762
|
var TAG_BYTES2 = 16;
|
|
15049
15763
|
function flagPath() {
|
|
15050
|
-
return
|
|
15764
|
+
return join56(getConfigDir2(), FLAG_FILE);
|
|
15051
15765
|
}
|
|
15052
15766
|
function logPath() {
|
|
15053
|
-
return
|
|
15767
|
+
return join56(getConfigDir2(), LOG_FILE);
|
|
15054
15768
|
}
|
|
15055
15769
|
async function writeFlag(flag) {
|
|
15056
15770
|
const path = flagPath();
|
|
15057
15771
|
await mkdir19(dirname22(path), { recursive: true });
|
|
15058
|
-
await
|
|
15772
|
+
await writeFile19(path, JSON.stringify(flag, null, 2), { encoding: "utf-8", mode: 384 });
|
|
15059
15773
|
}
|
|
15060
15774
|
async function mcpLogOn() {
|
|
15061
15775
|
let flagOnDisk = null;
|
|
15062
15776
|
try {
|
|
15063
|
-
flagOnDisk = JSON.parse(await
|
|
15777
|
+
flagOnDisk = JSON.parse(await readFile18(flagPath(), "utf-8"));
|
|
15064
15778
|
} catch {
|
|
15065
15779
|
flagOnDisk = null;
|
|
15066
15780
|
}
|
|
@@ -15070,11 +15784,12 @@ async function mcpLogOn() {
|
|
|
15070
15784
|
enabled: true
|
|
15071
15785
|
};
|
|
15072
15786
|
await writeFlag(next);
|
|
15073
|
-
process.stderr.write(
|
|
15074
|
-
|
|
15787
|
+
process.stderr.write(
|
|
15788
|
+
"MCP telemetry re-enabled. The pseudonymous usage heartbeat will resume.\n"
|
|
15789
|
+
);
|
|
15075
15790
|
return;
|
|
15076
15791
|
}
|
|
15077
|
-
process.stderr.write("MCP
|
|
15792
|
+
process.stderr.write("MCP telemetry is already enabled.\n");
|
|
15078
15793
|
if (!flagOnDisk || !flagOnDisk.consentSentToServer) {
|
|
15079
15794
|
const synced = await trySendConsentToServer();
|
|
15080
15795
|
if (synced) {
|
|
@@ -15091,14 +15806,14 @@ async function trySendConsentToServer() {
|
|
|
15091
15806
|
let apiUrl = null;
|
|
15092
15807
|
let token = null;
|
|
15093
15808
|
try {
|
|
15094
|
-
const body = await
|
|
15809
|
+
const body = await readFile18(join56(getConfigDir2(), "config.json"), "utf-8");
|
|
15095
15810
|
const parsed = JSON.parse(body);
|
|
15096
15811
|
apiUrl = parsed.repos?.find((r) => Boolean(r.apiUrl))?.apiUrl ?? parsed.defaultApiUrl ?? null;
|
|
15097
15812
|
} catch {
|
|
15098
15813
|
return false;
|
|
15099
15814
|
}
|
|
15100
15815
|
try {
|
|
15101
|
-
const body = await
|
|
15816
|
+
const body = await readFile18(join56(getConfigDir2(), "credentials.json"), "utf-8");
|
|
15102
15817
|
const parsed = JSON.parse(body);
|
|
15103
15818
|
token = parsed.idToken ?? null;
|
|
15104
15819
|
} catch {
|
|
@@ -15121,12 +15836,12 @@ async function trySendConsentToServer() {
|
|
|
15121
15836
|
async function mcpLogOff() {
|
|
15122
15837
|
let flagOnDisk = null;
|
|
15123
15838
|
try {
|
|
15124
|
-
flagOnDisk = JSON.parse(await
|
|
15839
|
+
flagOnDisk = JSON.parse(await readFile18(flagPath(), "utf-8"));
|
|
15125
15840
|
} catch {
|
|
15126
15841
|
flagOnDisk = null;
|
|
15127
15842
|
}
|
|
15128
15843
|
if (flagOnDisk && flagOnDisk.enabled === false) {
|
|
15129
|
-
process.stderr.write("MCP
|
|
15844
|
+
process.stderr.write("MCP telemetry is already disabled.\n");
|
|
15130
15845
|
process.exitCode = 1;
|
|
15131
15846
|
return;
|
|
15132
15847
|
}
|
|
@@ -15137,38 +15852,38 @@ async function mcpLogOff() {
|
|
|
15137
15852
|
};
|
|
15138
15853
|
await writeFlag(next);
|
|
15139
15854
|
process.stderr.write(
|
|
15140
|
-
"MCP
|
|
15855
|
+
"MCP telemetry disabled. The pseudonymous usage heartbeat is now off; the server-side consent record is NOT revoked (GDPR Art. 7(1) audit trail).\n"
|
|
15141
15856
|
);
|
|
15142
15857
|
}
|
|
15143
15858
|
async function mcpLogStatus() {
|
|
15144
15859
|
let flagOnDisk = null;
|
|
15145
15860
|
try {
|
|
15146
|
-
flagOnDisk = JSON.parse(await
|
|
15861
|
+
flagOnDisk = JSON.parse(await readFile18(flagPath(), "utf-8"));
|
|
15147
15862
|
} catch {
|
|
15148
15863
|
flagOnDisk = null;
|
|
15149
15864
|
}
|
|
15150
15865
|
const enabled = !flagOnDisk || flagOnDisk.enabled !== false;
|
|
15151
15866
|
const enabledLabel = flagOnDisk == null ? "yes (default)" : flagOnDisk.enabled ? "yes" : "no";
|
|
15152
15867
|
void enabled;
|
|
15153
|
-
process.stderr.write("MCP
|
|
15154
|
-
process.stderr.write("
|
|
15155
|
-
process.stderr.write(`
|
|
15156
|
-
`);
|
|
15157
|
-
process.stderr.write(`Consent granted: ${flagOnDisk?.consentGrantedAt ?? "never"}
|
|
15868
|
+
process.stderr.write("MCP Telemetry Status\n");
|
|
15869
|
+
process.stderr.write("====================\n");
|
|
15870
|
+
process.stderr.write(`Heartbeat enabled: ${enabledLabel}
|
|
15158
15871
|
`);
|
|
15159
|
-
process.stderr.write(`
|
|
15872
|
+
process.stderr.write(`Opt-in since: ${flagOnDisk?.consentGrantedAt ?? "never"}
|
|
15160
15873
|
`);
|
|
15874
|
+
process.stderr.write("Raw local logging: disabled (heartbeat only)\n");
|
|
15161
15875
|
try {
|
|
15162
15876
|
const s = await stat7(logPath());
|
|
15163
|
-
|
|
15164
|
-
|
|
15877
|
+
if (s.size > 0) {
|
|
15878
|
+
process.stderr.write(
|
|
15879
|
+
`Raw log file: ${logPath()} \u2014 ${formatBytes(s.size)} (escape hatch enabled)
|
|
15165
15880
|
`
|
|
15166
|
-
|
|
15881
|
+
);
|
|
15882
|
+
}
|
|
15167
15883
|
} catch {
|
|
15168
|
-
process.stderr.write("Log size: no file yet\n");
|
|
15169
15884
|
}
|
|
15170
15885
|
try {
|
|
15171
|
-
const endpointBody = await
|
|
15886
|
+
const endpointBody = await readFile18(join56(getConfigDir2(), ENDPOINT_FILE), "utf-8");
|
|
15172
15887
|
const match = /endpoint=([^\n]+)/.exec(endpointBody);
|
|
15173
15888
|
process.stderr.write(`MCP endpoint: ${match?.[1] ?? "(malformed endpoint file)"}
|
|
15174
15889
|
`);
|
|
@@ -15184,13 +15899,13 @@ function formatBytes(n) {
|
|
|
15184
15899
|
async function mcpLogViewingFlags(flags = {}) {
|
|
15185
15900
|
let flagOnDisk = null;
|
|
15186
15901
|
try {
|
|
15187
|
-
flagOnDisk = JSON.parse(await
|
|
15902
|
+
flagOnDisk = JSON.parse(await readFile18(flagPath(), "utf-8"));
|
|
15188
15903
|
} catch {
|
|
15189
15904
|
flagOnDisk = null;
|
|
15190
15905
|
}
|
|
15191
15906
|
if (flagOnDisk && flagOnDisk.enabled === false) {
|
|
15192
15907
|
process.stderr.write(
|
|
15193
|
-
`MCP
|
|
15908
|
+
`MCP telemetry is disabled. Run \`repowise mcp-log on\` to re-enable.
|
|
15194
15909
|
`
|
|
15195
15910
|
);
|
|
15196
15911
|
return;
|
|
@@ -15199,7 +15914,7 @@ async function mcpLogViewingFlags(flags = {}) {
|
|
|
15199
15914
|
const key = await readKey();
|
|
15200
15915
|
if (!key) {
|
|
15201
15916
|
process.stderr.write(
|
|
15202
|
-
`No encryption key at ${
|
|
15917
|
+
`No encryption key at ${join56(getConfigDir2(), KEY_FILE)} \u2014 listener may not have started yet.
|
|
15203
15918
|
`
|
|
15204
15919
|
);
|
|
15205
15920
|
return;
|
|
@@ -15227,7 +15942,7 @@ async function mcpLogViewingFlags(flags = {}) {
|
|
|
15227
15942
|
}
|
|
15228
15943
|
};
|
|
15229
15944
|
try {
|
|
15230
|
-
const raw = await
|
|
15945
|
+
const raw = await readFile18(path, "utf-8");
|
|
15231
15946
|
const decoded = [];
|
|
15232
15947
|
for (const line of raw.split("\n")) {
|
|
15233
15948
|
const r = filter(line);
|
|
@@ -15251,7 +15966,7 @@ async function mcpLogViewingFlags(flags = {}) {
|
|
|
15251
15966
|
try {
|
|
15252
15967
|
const s = await stat7(path);
|
|
15253
15968
|
if (s.size > lastSize) {
|
|
15254
|
-
const fresh = await
|
|
15969
|
+
const fresh = await readFile18(path, "utf-8");
|
|
15255
15970
|
const tail = fresh.slice(lastSize);
|
|
15256
15971
|
for (const line of tail.split("\n")) {
|
|
15257
15972
|
const r = filter(line);
|
|
@@ -15275,7 +15990,7 @@ async function mcpLogViewingFlags(flags = {}) {
|
|
|
15275
15990
|
}
|
|
15276
15991
|
async function readKey() {
|
|
15277
15992
|
try {
|
|
15278
|
-
const body = await
|
|
15993
|
+
const body = await readFile18(join56(getConfigDir2(), KEY_FILE), "utf-8");
|
|
15279
15994
|
const parsed = Buffer.from(body.trim(), "base64");
|
|
15280
15995
|
if (parsed.length !== 32) return null;
|
|
15281
15996
|
return parsed;
|
|
@@ -15319,7 +16034,7 @@ import chalk15 from "chalk";
|
|
|
15319
16034
|
|
|
15320
16035
|
// src/lib/graph-loader.ts
|
|
15321
16036
|
import { promises as fs25 } from "fs";
|
|
15322
|
-
import { join as
|
|
16037
|
+
import { join as join57, resolve as resolve2 } from "path";
|
|
15323
16038
|
import { gunzipSync } from "zlib";
|
|
15324
16039
|
var RELATIVE_GRAPH_PATH = "repowise-context/.meta/dependency-graph.json";
|
|
15325
16040
|
var GZIPPED_GRAPH_PATH = "repowise-context/.meta/dependency-graph.json.gz";
|
|
@@ -15336,8 +16051,8 @@ var GraphNotFoundError = class extends Error {
|
|
|
15336
16051
|
var cache = /* @__PURE__ */ new Map();
|
|
15337
16052
|
async function loadGraph(repoRoot = process.cwd()) {
|
|
15338
16053
|
const root = resolve2(repoRoot);
|
|
15339
|
-
const gzPath =
|
|
15340
|
-
const plainPath =
|
|
16054
|
+
const gzPath = join57(root, GZIPPED_GRAPH_PATH);
|
|
16055
|
+
const plainPath = join57(root, RELATIVE_GRAPH_PATH);
|
|
15341
16056
|
const cached = cache.get(root);
|
|
15342
16057
|
if (cached) {
|
|
15343
16058
|
return { graph: cached, path: plainPath, bytes: 0, parseMs: 0, fromCache: true };
|
|
@@ -15753,12 +16468,12 @@ function registerQueryCommand(program2) {
|
|
|
15753
16468
|
// src/commands/uninstall.ts
|
|
15754
16469
|
import { promises as fs29 } from "fs";
|
|
15755
16470
|
import { homedir as homedir12 } from "os";
|
|
15756
|
-
import { join as
|
|
16471
|
+
import { join as join61 } from "path";
|
|
15757
16472
|
import chalk16 from "chalk";
|
|
15758
16473
|
|
|
15759
16474
|
// src/lib/cleanup/marker-blocks.ts
|
|
15760
16475
|
import { promises as fs26 } from "fs";
|
|
15761
|
-
import { join as
|
|
16476
|
+
import { join as join58 } from "path";
|
|
15762
16477
|
var MARKER_START = "<!-- repowise-start -->";
|
|
15763
16478
|
var MARKER_END = "<!-- repowise-end -->";
|
|
15764
16479
|
var CONTEXT_FILES = [
|
|
@@ -15798,7 +16513,7 @@ async function stripMarkerBlock(filePath) {
|
|
|
15798
16513
|
async function stripAllMarkerBlocks(repoRoot) {
|
|
15799
16514
|
const out = [];
|
|
15800
16515
|
for (const relative of CONTEXT_FILES) {
|
|
15801
|
-
const full =
|
|
16516
|
+
const full = join58(repoRoot, relative);
|
|
15802
16517
|
const result = await stripMarkerBlock(full).catch((err) => ({
|
|
15803
16518
|
path: full,
|
|
15804
16519
|
status: "untouched",
|
|
@@ -15811,18 +16526,18 @@ async function stripAllMarkerBlocks(repoRoot) {
|
|
|
15811
16526
|
|
|
15812
16527
|
// src/lib/cleanup/mcp-configs.ts
|
|
15813
16528
|
import { promises as fs27 } from "fs";
|
|
15814
|
-
import { join as
|
|
16529
|
+
import { join as join59 } from "path";
|
|
15815
16530
|
function mcpConfigPaths(repoRoot, home) {
|
|
15816
16531
|
return [
|
|
15817
|
-
|
|
15818
|
-
|
|
15819
|
-
|
|
15820
|
-
|
|
15821
|
-
|
|
15822
|
-
|
|
15823
|
-
|
|
15824
|
-
|
|
15825
|
-
|
|
16532
|
+
join59(repoRoot, ".mcp.json"),
|
|
16533
|
+
join59(repoRoot, ".cursor", "mcp.json"),
|
|
16534
|
+
join59(repoRoot, ".vscode", "mcp.json"),
|
|
16535
|
+
join59(repoRoot, ".roo", "mcp.json"),
|
|
16536
|
+
join59(home, ".cline", "mcp.json"),
|
|
16537
|
+
join59(home, ".codeium", "windsurf", "mcp_config.json"),
|
|
16538
|
+
join59(home, ".gemini", "settings.json"),
|
|
16539
|
+
join59(home, ".codex", "mcp.json"),
|
|
16540
|
+
join59(home, ".roo", "mcp.json")
|
|
15826
16541
|
];
|
|
15827
16542
|
}
|
|
15828
16543
|
async function removeRepowiseFromConfig(path, serverName) {
|
|
@@ -15863,10 +16578,10 @@ async function removeAllMcpEntries(repoRoot, home, repoId) {
|
|
|
15863
16578
|
// src/lib/cleanup/local-state.ts
|
|
15864
16579
|
import { promises as fs28 } from "fs";
|
|
15865
16580
|
import { homedir as homedir11 } from "os";
|
|
15866
|
-
import { join as
|
|
16581
|
+
import { join as join60, resolve as resolve3 } from "path";
|
|
15867
16582
|
async function clearLocalState(homeOverride) {
|
|
15868
16583
|
const home = homeOverride ?? homedir11();
|
|
15869
|
-
const target = resolve3(
|
|
16584
|
+
const target = resolve3(join60(home, ".repowise"));
|
|
15870
16585
|
if (target === resolve3(home) || !target.startsWith(resolve3(home))) {
|
|
15871
16586
|
return { path: target, status: "error", error: "refused: not under home" };
|
|
15872
16587
|
}
|
|
@@ -15910,7 +16625,7 @@ async function uninstall2(opts = {}) {
|
|
|
15910
16625
|
else if (svc.error) report.skipped.push({ path: "listener service", reason: svc.error });
|
|
15911
16626
|
if (tier === "stop") return report;
|
|
15912
16627
|
try {
|
|
15913
|
-
await fs29.unlink(
|
|
16628
|
+
await fs29.unlink(join61(home, ".repowise", "credentials.json"));
|
|
15914
16629
|
report.removed.push("credentials");
|
|
15915
16630
|
} catch (err) {
|
|
15916
16631
|
if (err.code !== "ENOENT") {
|
|
@@ -15949,7 +16664,7 @@ async function uninstall2(opts = {}) {
|
|
|
15949
16664
|
}
|
|
15950
16665
|
async function defaultLoadRepoIds(home) {
|
|
15951
16666
|
try {
|
|
15952
|
-
const raw = await fs29.readFile(
|
|
16667
|
+
const raw = await fs29.readFile(join61(home, ".repowise", "config.json"), "utf-8");
|
|
15953
16668
|
const parsed = JSON.parse(raw);
|
|
15954
16669
|
return (parsed.repos ?? []).map((r) => r.repoId);
|
|
15955
16670
|
} catch {
|
|
@@ -15998,11 +16713,12 @@ Done \u2014 ${report.removed.length} removed, ${report.skipped.length} skipped.
|
|
|
15998
16713
|
init_config_dir();
|
|
15999
16714
|
import { promises as fs30 } from "fs";
|
|
16000
16715
|
import { createInterface as createInterface2 } from "readline";
|
|
16001
|
-
import { join as
|
|
16716
|
+
import { join as join62 } from "path";
|
|
16002
16717
|
var DEFAULT_MAX = 200 * 1024;
|
|
16718
|
+
var SHIM_REQUEST_TIMEOUT_MS = 45e3;
|
|
16003
16719
|
async function mcpShim(opts) {
|
|
16004
|
-
const endpointPath = opts.endpointFile ??
|
|
16005
|
-
const credsPath = opts.credentialsFile ??
|
|
16720
|
+
const endpointPath = opts.endpointFile ?? join62(getConfigDir(), "listener.endpoint");
|
|
16721
|
+
const credsPath = opts.credentialsFile ?? join62(getConfigDir(), "credentials.json");
|
|
16006
16722
|
const stdin = opts.stdin ?? process.stdin;
|
|
16007
16723
|
const stdout = opts.stdout ?? process.stdout;
|
|
16008
16724
|
const stderr = opts.stderr ?? process.stderr;
|
|
@@ -16042,7 +16758,7 @@ async function mcpShim(opts) {
|
|
|
16042
16758
|
// Agent-directed: the calling LLM can run this itself for a smoother UX
|
|
16043
16759
|
// (it auto-opens the browser; the user still completes sign-in). The
|
|
16044
16760
|
// command is a fixed, argument-free string (nothing injectable).
|
|
16045
|
-
"RepoWise isn't authenticated (the session
|
|
16761
|
+
"RepoWise isn't authenticated (this is rare \u2014 the session lasts about a year). To fix it, run the shell command `repowise login` on the user's behalf \u2014 it opens a browser for them to sign in and blocks until they finish. Run it once, wait for it to complete, then retry this request. Do not repeat the command."
|
|
16046
16762
|
) : "listener endpoint unavailable \u2014 is the listener running?";
|
|
16047
16763
|
writeJson(stdout, {
|
|
16048
16764
|
jsonrpc: "2.0",
|
|
@@ -16066,12 +16782,19 @@ async function mcpShim(opts) {
|
|
|
16066
16782
|
} catch (err) {
|
|
16067
16783
|
stderr.write(`mcp-shim: ${err.message}
|
|
16068
16784
|
`);
|
|
16785
|
+
const name = err.name;
|
|
16786
|
+
const detail = err.message;
|
|
16787
|
+
const isTimeout = name === "TimeoutError" || name === "AbortError" || /timeout|aborted|abort/i.test(detail);
|
|
16069
16788
|
writeJson(stdout, {
|
|
16070
16789
|
jsonrpc: "2.0",
|
|
16071
|
-
error: {
|
|
16790
|
+
error: isTimeout ? {
|
|
16791
|
+
code: -32001,
|
|
16792
|
+
message: "RepoWise listener did not respond in time \u2014 it may be stuck. Restart it with `repowise restart`, then retry.",
|
|
16793
|
+
data: { detail }
|
|
16794
|
+
} : {
|
|
16072
16795
|
code: -32001,
|
|
16073
16796
|
message: "listener request failed",
|
|
16074
|
-
data: { detail
|
|
16797
|
+
data: { detail }
|
|
16075
16798
|
}
|
|
16076
16799
|
});
|
|
16077
16800
|
}
|
|
@@ -16208,7 +16931,8 @@ async function defaultPostJson(url, body, headers = { "content-type": "applicati
|
|
|
16208
16931
|
const res = await fetch(url, {
|
|
16209
16932
|
method: "POST",
|
|
16210
16933
|
headers,
|
|
16211
|
-
body: JSON.stringify(body)
|
|
16934
|
+
body: JSON.stringify(body),
|
|
16935
|
+
signal: AbortSignal.timeout(SHIM_REQUEST_TIMEOUT_MS)
|
|
16212
16936
|
});
|
|
16213
16937
|
if (!res.ok) {
|
|
16214
16938
|
let detail = "";
|
|
@@ -16222,7 +16946,11 @@ async function defaultPostJson(url, body, headers = { "content-type": "applicati
|
|
|
16222
16946
|
return await res.json();
|
|
16223
16947
|
}
|
|
16224
16948
|
async function defaultGetJson(url, headers = {}) {
|
|
16225
|
-
const res = await fetch(url, {
|
|
16949
|
+
const res = await fetch(url, {
|
|
16950
|
+
method: "GET",
|
|
16951
|
+
headers,
|
|
16952
|
+
signal: AbortSignal.timeout(SHIM_REQUEST_TIMEOUT_MS)
|
|
16953
|
+
});
|
|
16226
16954
|
if (!res.ok) {
|
|
16227
16955
|
let detail = "";
|
|
16228
16956
|
try {
|
|
@@ -16771,7 +17499,7 @@ async function lspOn() {
|
|
|
16771
17499
|
// bin/repowise.ts
|
|
16772
17500
|
var __filename = fileURLToPath4(import.meta.url);
|
|
16773
17501
|
var __dirname = dirname23(__filename);
|
|
16774
|
-
var pkg = JSON.parse(readFileSync3(
|
|
17502
|
+
var pkg = JSON.parse(readFileSync3(join63(__dirname, "..", "..", "package.json"), "utf-8"));
|
|
16775
17503
|
var program = new Command();
|
|
16776
17504
|
program.name(getPackageName()).description("AI-optimized codebase context generator").version(pkg.version).hook("preAction", async () => {
|
|
16777
17505
|
await showWelcome(pkg.version);
|