repowisestage 0.0.71 → 0.0.72

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/bin/repowise.js +1180 -454
  2. package/package.json +1 -1
@@ -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 readFile6, writeFile as writeFile7, mkdir as mkdir7, unlink as unlink5 } from "fs/promises";
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 join10 } from "path";
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 join10(repowiseDir(), "listener.pid");
520
+ return join11(repowiseDir(), "listener.pid");
521
521
  }
522
522
  function logDirPath() {
523
- return join10(repowiseDir(), "logs");
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 readFile6(pidPath(), "utf-8");
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(join10(logDir2, "listener-stdout.log"), "a");
564
- const stderrFd = openSync(join10(logDir2, "listener-stderr.log"), "a");
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 writeFile7(pidPath(), String(pid));
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 unlink5(pidPath());
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 join13, dirname as dirname5 } from "path";
1173
+ import { join as join14, dirname as dirname5 } from "path";
1174
1174
  function getNativeLspDir() {
1175
- return join13(getLspInstallDir(), "native");
1175
+ return join14(getLspInstallDir(), "native");
1176
1176
  }
1177
1177
  function getNativeLspBinDir() {
1178
- return join13(getNativeLspDir(), "bin");
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 = join13(getNativeLspDir(), lspKey, entry.version);
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 = join13(getNativeLspBinDir(), asset.wrapperBinaryName);
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 = join13(versionDir, asset.binaryPath);
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 = join13(versionDir, `.${asset.filename}.download`);
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 = join13(versionDir, asset.binaryPath);
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 = join13(dir, parts[i]);
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 ? join13(dir, last) : null;
1299
+ return last ? join14(dir, last) : null;
1300
1300
  }
1301
1301
  async function pruneStaleVersions(lspKey, currentVersion) {
1302
- const lspDir = join13(getNativeLspDir(), lspKey);
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(join13(lspDir, name), { recursive: true, force: true });
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 = join13(binDir, input.binaryName);
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 = join13(input.versionDir, "workspaces");
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 = join13(binDir, linkName);
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 = join13(destDir, asset.binaryPath);
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, join13(destDir, asset.binaryPath));
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 join14 } from "path";
1479
+ import { join as join15 } from "path";
1480
1480
  function getCoursierBinDir() {
1481
- return join14(getLspInstallDir(), "coursier-bin");
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 = join14(binDir, entry.appName);
1490
- const binaryPathExe = join14(binDir, `${entry.appName}.bat`);
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 = join14(getNativeLspBinDir(), "coursier");
1504
- const csPathExe = join14(getNativeLspBinDir(), "coursier.exe");
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 join15 } from "path";
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 = join15(binDir, entry.binaryName);
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"] ?? join15(homedir3(), "go");
1636
- return join15(gopath, "bin");
1635
+ const gopath = process.env["GOPATH"] ?? join16(homedir3(), "go");
1636
+ return join16(gopath, "bin");
1637
1637
  }
1638
- return join15(homedir3(), entry.toolchainBinDir);
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 join15(userdir.trim(), "bin");
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"] ?? join15(homedir3(), "go");
1653
- dirs.add(join15(gopath, "bin"));
1652
+ const gopath = process.env["GOPATH"] ?? join16(homedir3(), "go");
1653
+ dirs.add(join16(gopath, "bin"));
1654
1654
  } else {
1655
- dirs.add(join15(homedir3(), entry.toolchainBinDir));
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 join16, dirname as dirname6 } from "path";
1723
+ import { join as join17, dirname as dirname6 } from "path";
1724
1724
  import { spawn as spawn6 } from "child_process";
1725
- import lockfile3 from "proper-lockfile";
1725
+ import lockfile4 from "proper-lockfile";
1726
1726
  function getLspInstallDir() {
1727
- return join16(getConfigDir(), "lsp-servers");
1727
+ return join17(getConfigDir(), "lsp-servers");
1728
1728
  }
1729
1729
  function getLspBinDir() {
1730
- return join16(getLspInstallDir(), "node_modules", ".bin");
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 = join16(installDir, "node_modules", ".bin", config2.command);
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 lockfile3.lock(installDir, {
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 = join16(installDir, "package.json");
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 [join16(nodeDir, "npm"), join16(nodeDir, "npm.cmd")]) {
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 = join16(repoRoot, name);
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 join17 } from "path";
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
- join17(home, ".local", "bin"),
2007
+ join18(home, ".local", "bin"),
2008
2008
  // pipx / pip --user / uv
2009
- join17(home, ".npm-global", "bin"),
2009
+ join18(home, ".npm-global", "bin"),
2010
2010
  // common npm global prefix
2011
- join17(home, "go", "bin"),
2011
+ join18(home, "go", "bin"),
2012
2012
  // gopls
2013
- join17(home, ".cargo", "bin")
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 writeFile8, mkdir as mkdir8, unlink as unlink6 } from "fs/promises";
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 join18 } from "path";
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 join18(homedir5(), "Library", "LaunchAgents", `${PLIST_LABEL}.plist`);
2070
+ return join19(homedir5(), "Library", "LaunchAgents", `${PLIST_LABEL}.plist`);
2071
2071
  }
2072
2072
  function logDir() {
2073
- return join18(getConfigDir(), "logs");
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(join18(logs, "listener-stdout.log"))}</string>
2107
+ <string>${xmlEscape(join19(logs, "listener-stdout.log"))}</string>
2108
2108
  <key>StandardErrorPath</key>
2109
- <string>${xmlEscape(join18(logs, "listener-stderr.log"))}</string>
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(join18(homedir5(), "Library", "LaunchAgents"), { recursive: true });
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 writeFile8(plistPath(), buildPlist());
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 unlink6(plistPath());
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 join18(homedir5(), ".config", "systemd", "user", `${SYSTEMD_SERVICE}.service`);
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:${join18(logs, "listener-stdout.log")}
2186
- StandardError=append:${join18(logs, "listener-stderr.log")}
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(join18(homedir5(), ".config", "systemd", "user"), { recursive: true });
2194
- await writeFile8(unitPath(), buildUnit());
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 unlink6(unitPath());
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 readFile8, readdir as readdir3, stat as stat2, unlink as unlink8, writeFile as writeFile9 } from "fs/promises";
2389
- import { dirname as dirname9, join as join20 } from "path";
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 = join20(getConfigDir(), "typed-resolution", repoId);
2404
- const fullPath = join20(repoDir, `${commitSha}.jsonl`);
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 writeFile9(tmpPath, JSON.stringify(sidecar), "utf-8");
2412
- const { rename: rename5 } = await import("fs/promises");
2413
- await rename5(tmpPath, fullPath);
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(join20(repoDir, name));
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 unlink8(join20(repoDir, name));
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 readFile8(fullPath, "utf-8");
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 join62 } from "path";
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 readFile13, writeFile as writeFile15, mkdir as mkdir16, stat as fsStat } from "fs/promises";
3142
- import { join as join41, dirname as dirname17 } from "path";
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 lockfile4 from "proper-lockfile";
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 Error("No Cognito client ID available. Run `repowise login` first.");
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
- throw new Error(`Token refresh failed: ${response.status}`);
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
- refreshToken,
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
- await writeFile5(credPath, JSON.stringify(credentials, null, 2));
4449
- await chmod3(credPath, 384);
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
- console.warn(`Token refresh failed: ${message}`);
4465
- if (now < creds.expiresAt) {
4466
- return creds;
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 creds;
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`, { headers });
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 join11 } from "path";
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 = join11(dirname4(process.execPath), "npm");
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 = join11(prefix.trim(), "lib", "node_modules");
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 = join11(prefix.trim(), "lib", "node_modules", packageName, "package.json");
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 open3, readdir as readdir2 } from "fs/promises";
5037
+ import { lstat, open as open4, readdir as readdir2 } from "fs/promises";
4860
5038
  import { constants as fsConstants } from "fs";
4861
- import { join as join12, resolve as pathResolve } from "path";
5039
+ import { join as join13, resolve as pathResolve } from "path";
4862
5040
  async function verifyBinary(check) {
4863
- const fh = await open3(check.path, fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW);
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(join12(dir, entryName));
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 unlink7 } from "fs/promises";
4953
- import { join as join19 } from "path";
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 unlink7(join19(getConfigDir(), "listener.pid"));
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 join23 } from "path";
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 readFile7 } from "fs/promises";
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 readFile7(absolute, "utf-8");
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 readFile9, stat as stat3 } from "fs/promises";
5724
- import { join as join21 } from "path";
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) => join21(defaultRepoWiseHome(), "graphs", `${repoId}.json`));
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 readFile9(graphPath, "utf-8");
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 rename3, writeFile as writeFile10 } from "fs/promises";
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
- const graphRes = await fetchFn(`${options.apiBaseUrl}/v1/repos/${options.repoId}/graph`, {
5868
- headers: { Authorization: `Bearer ${token}` }
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 writeFile10(tmpPath, body, { encoding: "utf-8", mode: 384 });
5887
- await rename3(tmpPath, options.targetPath);
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 readFile10, stat as stat4, writeFile as writeFile11 } from "fs/promises";
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 readFile10(keyPath, "utf-8");
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 writeFile11(tmp, fresh.toString("base64") + "\n", {
6295
+ await writeFile12(tmp, fresh.toString("base64") + "\n", {
5960
6296
  encoding: "utf-8",
5961
6297
  mode: 384
5962
6298
  });
5963
- const { rename: rename5 } = await import("fs/promises");
5964
- await rename5(tmp, keyPath);
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 unlink9, writeFile as writeFile12, readFile as readFile11 } from "fs/promises";
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 unlink9(legacyPath);
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 readFile11(path, "utf-8");
6380
+ const body = await readFile12(path, "utf-8");
6045
6381
  const idx = body.indexOf("\n", dropAt);
6046
6382
  if (idx < 0) {
6047
- await writeFile12(path, "", { encoding: "utf-8", mode: 384 });
6383
+ await writeFile13(path, "", { encoding: "utf-8", mode: 384 });
6048
6384
  return;
6049
6385
  }
6050
- await writeFile12(path, body.slice(idx + 1), { encoding: "utf-8", mode: 384 });
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 readFile12, writeFile as writeFile13, mkdir as mkdir13 } from "fs/promises";
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 readFile12(options.logFilePath, "utf-8");
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 readFile12(pendingPath(watermarkPath), "utf-8");
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 writeFile13(pendingPath(watermarkPath), batchId, { encoding: "utf-8", mode: 384 });
6546
+ await writeFile14(pendingPath(watermarkPath), batchId, { encoding: "utf-8", mode: 384 });
6208
6547
  }
6209
6548
  async function clearPendingBatch(watermarkPath) {
6210
- const { unlink: unlink12 } = await import("fs/promises");
6549
+ const { unlink: unlink14 } = await import("fs/promises");
6211
6550
  try {
6212
- await unlink12(pendingPath(watermarkPath));
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 readFile12(path, "utf-8");
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 writeFile13(tmp, offset.toString() + "\n", { encoding: "utf-8", mode: 384 });
6231
- const { rename: rename5 } = await import("fs/promises");
6232
- await rename5(tmp, path);
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 readFile12(flagPath2, "utf-8");
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 writeFile14 } from "fs/promises";
6248
- import { dirname as dirname14, join as join22 } from "path";
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([lspHandler(body, root), timeoutAfter(TOOL_TIMEOUT_MS_LSP)]);
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 isTimeout = err instanceof ToolTimeoutError;
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 writeFile14(path, formatEndpointFile({ endpoint, secret }), {
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 join22(getConfigDir(), "listener.endpoint");
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) => join23(graphsDir, `${repoId}.json`)
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 = join23(mcpHome, "mcp-log.jsonl.enc");
7812
- const keyStore = createFileKeyStore(join23(mcpHome, "mcp-log.key"));
7813
- const mcpLogger = createMcpLogger({ filePath: logFilePath, keyStore });
7814
- const flagFilePath = join23(mcpHome, "mcp-log.flag");
7815
- const watermarkFilePath = join23(mcpHome, "mcp-log.watermark");
7816
- const consentSentMarkerPath = join23(mcpHome, "mcp-log.consent-sent");
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
- mcpLogger
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: join23(graphsDir, `${repo.repoId}.json`),
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: join23(graphsDir, `${repo.repoId}.json`),
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 join23(getConfigDir(), "graphs");
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: readFile18 } = await import("fs/promises");
7988
- await readFile18(opts.markerPath, "utf-8");
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 join24 } from "path";
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(join24(repoRoot, "CLAUDE.md")) || await hasClaudeBinary(home);
8592
+ return await fileExists2(join25(repoRoot, "CLAUDE.md")) || await hasClaudeBinary(home);
8108
8593
  },
8109
8594
  async write(ctx) {
8110
- const path = join24(ctx.repoRoot, ".mcp.json");
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 = join24(ctx.repoRoot, ".mcp.json");
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(join24(home, ".claude", "claude.json"));
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 join25 } from "path";
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(join25(repoRoot, "CLAUDE.md")) || await fileExists3(join25(home, ".claude.json"));
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: join25(ctx.repoRoot, result.relPath) };
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 join26 } from "path";
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(join26(repoRoot, ".clinerules")) || await fileExists4(join26(home, ".cline"));
8653
+ return await fileExists4(join27(repoRoot, ".clinerules")) || await fileExists4(join27(home, ".cline"));
8169
8654
  },
8170
8655
  async write(ctx) {
8171
- const path = join26(ctx.home, ".cline", "mcp.json");
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 = join26(ctx.home, ".cline", "mcp.json");
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 join27 } from "path";
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(join27(repoRoot, ".clinerules"));
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 = join27(ctx.repoRoot, SCRIPT_REL);
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(join27(ctx.repoRoot, ".clinerules", "hooks"), { recursive: true });
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 = join27(ctx.repoRoot, SCRIPT_REL);
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 join28 } from "path";
8757
+ import { join as join29 } from "path";
8273
8758
  var codexWriter = {
8274
8759
  tool: "codex",
8275
8760
  async detect(_repoRoot, home) {
8276
- return fileExists6(join28(home, ".codex"));
8761
+ return fileExists6(join29(home, ".codex"));
8277
8762
  },
8278
8763
  async write(ctx) {
8279
- const path = join28(ctx.home, ".codex", "mcp.json");
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 = join28(ctx.home, ".codex", "mcp.json");
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 join29 } from "path";
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(join29(home, ".codex")) || await fileExists7(join29(repoRoot, ".codex"));
8795
+ return await fileExists7(join30(home, ".codex")) || await fileExists7(join30(repoRoot, ".codex"));
8311
8796
  },
8312
8797
  async write(ctx) {
8313
- const repoSignal = await fileExists7(join29(ctx.repoRoot, ".codex"));
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 = join29(ctx.repoRoot, HOOKS_REL);
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(join29(ctx.repoRoot, ".codex"), { recursive: true });
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 = join29(ctx.repoRoot, HOOKS_REL);
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 join30 } from "path";
8857
+ import { join as join31 } from "path";
8373
8858
  var copilotWriter = {
8374
8859
  tool: "copilot",
8375
8860
  async detect(repoRoot) {
8376
- return fileExists8(join30(repoRoot, ".vscode"));
8861
+ return fileExists8(join31(repoRoot, ".vscode"));
8377
8862
  },
8378
8863
  async write(ctx) {
8379
- const path = join30(ctx.repoRoot, ".vscode", "mcp.json");
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 = join30(ctx.repoRoot, ".vscode", "mcp.json");
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 join31 } from "path";
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(join31(repoRoot, ".github", "copilot-instructions.md"));
8895
+ return fileExists9(join32(repoRoot, ".github", "copilot-instructions.md"));
8411
8896
  },
8412
8897
  async write(ctx) {
8413
- const path = join31(ctx.repoRoot, HOOKS_REL2);
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(join31(ctx.repoRoot, ".github", "hooks"), { recursive: true });
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 = join31(ctx.repoRoot, HOOKS_REL2);
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 join32 } from "path";
8951
+ import { join as join33 } from "path";
8467
8952
  var cursorWriter = {
8468
8953
  tool: "cursor",
8469
8954
  async detect(repoRoot) {
8470
- return await fileExists10(join32(repoRoot, ".cursor")) || await fileExists10(join32(repoRoot, ".cursorrules"));
8955
+ return await fileExists10(join33(repoRoot, ".cursor")) || await fileExists10(join33(repoRoot, ".cursorrules"));
8471
8956
  },
8472
8957
  async write(ctx) {
8473
- const path = join32(ctx.repoRoot, ".cursor", "mcp.json");
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 = join32(ctx.repoRoot, ".cursor", "mcp.json");
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 join33 } from "path";
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(join33(home, ".gemini"));
8988
+ return fileExists11(join34(home, ".gemini"));
8504
8989
  },
8505
8990
  async write(ctx) {
8506
- const path = join33(ctx.home, ".gemini", "settings.json");
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 = join33(ctx.home, ".gemini", "settings.json");
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 join34 } from "path";
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(join34(home, ".gemini")) || await fileExists12(join34(repoRoot, ".gemini"));
9022
+ return await fileExists12(join35(home, ".gemini")) || await fileExists12(join35(repoRoot, ".gemini"));
8538
9023
  },
8539
9024
  async write(ctx) {
8540
- const repoSignal = await fileExists12(join34(ctx.repoRoot, ".gemini")) || await fileExists12(join34(ctx.repoRoot, "GEMINI.md"));
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 = join34(ctx.repoRoot, SETTINGS_REL);
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(join34(ctx.repoRoot, ".gemini"), { recursive: true });
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 = join34(ctx.repoRoot, SETTINGS_REL);
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 join35 } from "path";
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(join35(repoRoot, ".roo")) || await fileExists13(join35(home, ".roo"));
9088
+ return await fileExists13(join36(repoRoot, ".roo")) || await fileExists13(join36(home, ".roo"));
8604
9089
  },
8605
9090
  async write(ctx) {
8606
- const repoConfig = join35(ctx.repoRoot, ".roo", "mcp.json");
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 = join35(ctx.repoRoot, ".roo", "mcp.json");
8617
- const homeConfig = join35(ctx.home, ".roo", "mcp.json");
8618
- const homeSettings = join35(ctx.home, ".roo", "settings.json");
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 join36 } from "path";
9130
+ import { join as join37 } from "path";
8646
9131
  var windsurfWriter = {
8647
9132
  tool: "windsurf",
8648
9133
  async detect(_repoRoot, home) {
8649
- return fileExists14(join36(home, ".codeium", "windsurf"));
9134
+ return fileExists14(join37(home, ".codeium", "windsurf"));
8650
9135
  },
8651
9136
  async write(ctx) {
8652
- const path = join36(ctx.home, ".codeium", "windsurf", "mcp_config.json");
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 = join36(ctx.home, ".codeium", "windsurf", "mcp_config.json");
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 join37 } from "path";
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 = join37(repoRoot, name);
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 join38 } from "path";
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(join38(dir, e.name));
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: join38(dir, e.name), ext });
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 = join38(repoRoot, ".repowise");
9407
+ const outDir = join39(repoRoot, ".repowise");
8923
9408
  await fs20.mkdir(outDir, { recursive: true });
8924
- const outPath = join38(outDir, "compile_commands.json");
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(join38(repoRoot, name));
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(join38(repoRoot, "package.json"), "utf8");
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 = join38(getConfigDir(), `install-log.${safeRepoId}.txt`);
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 join39 } from "path";
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(join39(indexRoot, spec.indexStoreProbe));
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 join40 } from "path";
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 join40(getConfigDir(), "lsp-worktrees");
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 = join40(base, entry);
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 = join40(base, dirName);
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 = join41(getConfigDir(), "config.json");
10373
- const data = await readFile13(configPath, "utf-8");
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
- if (contextFiles.length === 0 && variant === "full")
10402
- return;
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(join41(dir, "node_modules"));
10450
- out.add(join41(dir, "lib", "node_modules"));
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
- invalidateLspBuffersOnce(notif, localPath, ctx);
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 = join41(repo.localPath, "repowise-context");
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 = join41(repo.localPath, "repowise-context", "project-overview.md");
11332
+ const path = join42(repo.localPath, "repowise-context", "project-overview.md");
10811
11333
  let content;
10812
11334
  try {
10813
- content = await readFile13(path, "utf-8");
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 writeFile15(path, injected, "utf-8");
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 = join41(configDir, "listener.lock");
10895
- await writeFile15(lockPath, "", { flag: "a" });
11416
+ const lockPath = join42(configDir, "listener.lock");
11417
+ await writeFile16(lockPath, "", { flag: "a" });
10896
11418
  let lockIsHeld = false;
10897
11419
  try {
10898
- lockIsHeld = await lockfile4.check(lockPath, { stale: 3e4, realpath: false });
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 lockfile4.lock(lockPath, { stale: 3e4, realpath: false });
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 ${join41(configDir, "config.json")}`);
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 = join41(selfDir, "..", "..", "package.json");
11009
- const pkgJson = JSON.parse(await readFile13(pkgJsonPath, "utf-8"));
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 delay2 = group.backoff.nextDelay();
11419
- group.offline.nextRetryAt = Date.now() + delay2;
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(delay2 / 1e3)}s`);
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
- for (let attempt = 1; attempt <= 3; attempt++) {
11499
- console.warn(`Auth error \u2014 retrying credentials (attempt ${attempt}/3)...`);
11500
- await sleep(5e3);
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
- console.error("Session expired after 3 retries. Sending notification email and waiting for re-authentication...");
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 fetch(`${authErrorGroup.apiUrl}/v1/public/notify-auth-expired`, {
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 join42 } from "path";
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 join42(homedir7(), IS_STAGING2 ? ".repowise-staging" : ".repowise");
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 readFile14, writeFile as writeFile16, mkdir as mkdir17, rename as rename4, unlink as unlink10 } from "fs/promises";
11582
- import { join as join43 } from "path";
11583
- import lockfile5 from "proper-lockfile";
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 readFile14(join43(getConfigDir2(), "config.json"), "utf-8");
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 = join43(dir, "config.json");
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 writeFile16(tmpPath, JSON.stringify(config2, null, 2));
11599
- await rename4(tmpPath, path);
12169
+ await writeFile17(tmpPath, JSON.stringify(config2, null, 2));
12170
+ await rename6(tmpPath, path);
11600
12171
  } catch (err) {
11601
12172
  try {
11602
- await unlink10(tmpPath);
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 = join43(dir, "config.json");
12181
+ const path = join44(dir, "config.json");
11611
12182
  await mkdir17(dir, { recursive: true });
11612
12183
  try {
11613
- await writeFile16(path, "", { flag: "a" });
12184
+ await writeFile17(path, "", { flag: "a" });
11614
12185
  } catch {
11615
12186
  }
11616
12187
  let release = null;
11617
12188
  try {
11618
- release = await lockfile5.lock(path, { stale: 1e4, retries: 3, realpath: false });
12189
+ release = await lockfile6.lock(path, { stale: 1e4, retries: 3, realpath: false });
11619
12190
  let raw = {};
11620
12191
  try {
11621
- raw = JSON.parse(await readFile14(path, "utf-8"));
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 join50 } from "path";
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 readFile15, writeFile as writeFile17, mkdir as mkdir18, chmod as chmod4, unlink as unlink11 } from "fs/promises";
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 join44 } from "path";
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
- const response = await fetch(getTokenUrl2(), {
11834
- method: "POST",
11835
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
11836
- body: new URLSearchParams({
11837
- grant_type: "refresh_token",
11838
- client_id: getCognitoConfig().clientId,
11839
- refresh_token: refreshToken
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
- throw new Error(`Token refresh failed: ${response.status}`);
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
- refreshToken,
11849
- // Cognito does not return a new refresh token
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 = join44(getConfigDir2(), "credentials.json");
11857
- const data = await readFile15(credPath, "utf-8");
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 = join44(dir, "credentials.json");
12488
+ const credPath = join45(dir, "credentials.json");
11869
12489
  await mkdir18(dir, { recursive: true, mode: 448 });
11870
- await writeFile17(credPath, JSON.stringify(credentials, null, 2));
11871
- await chmod4(credPath, 384);
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 unlink11(join44(getConfigDir2(), "credentials.json"));
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
- try {
11886
- const refreshed = await refreshTokens2(creds.refreshToken);
11887
- refreshed.cognito = creds.cognito;
11888
- await storeCredentials2(refreshed);
11889
- return refreshed;
11890
- } catch {
11891
- if (Date.now() < creds.expiresAt) {
11892
- return creds;
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 open4 = (await import("open")).default;
11908
- await open4(authorizeUrl);
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 join45 } from "path";
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: join45(repoRoot, ".mcp.json"), scope: "repo" };
12797
+ return { path: join46(repoRoot, ".mcp.json"), scope: "repo" };
12138
12798
  case "cursor":
12139
- return { path: join45(repoRoot, ".cursor", "mcp.json"), scope: "repo" };
12799
+ return { path: join46(repoRoot, ".cursor", "mcp.json"), scope: "repo" };
12140
12800
  case "copilot":
12141
- return { path: join45(repoRoot, ".vscode", "mcp.json"), scope: "repo" };
12801
+ return { path: join46(repoRoot, ".vscode", "mcp.json"), scope: "repo" };
12142
12802
  case "codex":
12143
- return { path: join45(home, ".codex", "mcp.json"), scope: "global" };
12803
+ return { path: join46(home, ".codex", "mcp.json"), scope: "global" };
12144
12804
  case "windsurf":
12145
- return { path: join45(home, ".codeium", "windsurf", "mcp_config.json"), scope: "global" };
12805
+ return { path: join46(home, ".codeium", "windsurf", "mcp_config.json"), scope: "global" };
12146
12806
  case "cline":
12147
- return { path: join45(home, ".cline", "mcp.json"), scope: "global" };
12807
+ return { path: join46(home, ".cline", "mcp.json"), scope: "global" };
12148
12808
  case "gemini":
12149
- return { path: join45(home, ".gemini", "settings.json"), scope: "global" };
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 join46 } from "path";
12859
+ import { join as join47 } from "path";
12200
12860
  function ensureGitignore(repoRoot, entry) {
12201
- const gitignorePath = join46(repoRoot, ".gitignore");
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 join47 } from "path";
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 join47(getConfigDir(), "graphs", `${repoId}.json`);
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
- * Coffee-and-context message shown once, after the user-driven interview
12657
- * completes. This is the gate between user input and the long
12658
- * machine-driven scan/generate/validate phases.
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 join48 } from "path";
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(join48(repoRoot, name));
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 join49 } from "path";
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 = join49(getConfigDir2(), `install-log.${safeRepoId}.txt`);
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" ? join49(".venv", "Scripts", "pip.exe") : join49(".venv", "bin", "pip");
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 minutes = Math.floor(totalSeconds / 60);
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 = 7200;
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 = join50(repoRoot, DEFAULT_CONTEXT_FOLDER);
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 = join50(contextDir, file.fileName);
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 join51, resolve, sep } from "path";
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 = join51(repoRoot, DEFAULT_CONTEXT_FOLDER2);
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 join52 } from "path";
14959
+ import { join as join53 } from "path";
14243
14960
  async function purgeForeignGraphs(validRepoIds, home = homedir8()) {
14244
- const graphsDir = join52(home, ".repowise", "graphs");
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 = join52(graphsDir, entry);
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 open4 = (await import("open")).default;
14299
- await open4(authorizeUrl);
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 readFile16 } from "fs/promises";
14372
- import { basename as basename5, join as join53 } from "path";
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 = join53(configDir, "listener-state.json");
14386
- const CONFIG_PATH = join53(configDir, "config.json");
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 readFile16(STATE_PATH, "utf-8");
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 readFile16(CONFIG_PATH, "utf-8");
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 join54 } from "path";
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 = 7200;
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 minutes = Math.floor(totalSeconds / 60);
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 = join54(repoRoot, DEFAULT_CONTEXT_FOLDER3);
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 = join54(contextDir, file.fileName);
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 readFile17, stat as stat7, writeFile as writeFile18 } from "fs/promises";
15042
- import { dirname as dirname22, join as join55 } from "path";
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 join55(getConfigDir2(), FLAG_FILE);
15764
+ return join56(getConfigDir2(), FLAG_FILE);
15051
15765
  }
15052
15766
  function logPath() {
15053
- return join55(getConfigDir2(), LOG_FILE);
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 writeFile18(path, JSON.stringify(flag, null, 2), { encoding: "utf-8", mode: 384 });
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 readFile17(flagPath(), "utf-8"));
15777
+ flagOnDisk = JSON.parse(await readFile18(flagPath(), "utf-8"));
15064
15778
  } catch {
15065
15779
  flagOnDisk = null;
15066
15780
  }
@@ -15070,11 +15784,10 @@ async function mcpLogOn() {
15070
15784
  enabled: true
15071
15785
  };
15072
15786
  await writeFlag(next);
15073
- process.stderr.write(`MCP logging re-enabled. Log file: ${logPath()}
15074
- `);
15787
+ process.stderr.write("MCP telemetry re-enabled. The anonymous usage heartbeat will resume.\n");
15075
15788
  return;
15076
15789
  }
15077
- process.stderr.write("MCP logging is already enabled.\n");
15790
+ process.stderr.write("MCP telemetry is already enabled.\n");
15078
15791
  if (!flagOnDisk || !flagOnDisk.consentSentToServer) {
15079
15792
  const synced = await trySendConsentToServer();
15080
15793
  if (synced) {
@@ -15091,14 +15804,14 @@ async function trySendConsentToServer() {
15091
15804
  let apiUrl = null;
15092
15805
  let token = null;
15093
15806
  try {
15094
- const body = await readFile17(join55(getConfigDir2(), "config.json"), "utf-8");
15807
+ const body = await readFile18(join56(getConfigDir2(), "config.json"), "utf-8");
15095
15808
  const parsed = JSON.parse(body);
15096
15809
  apiUrl = parsed.repos?.find((r) => Boolean(r.apiUrl))?.apiUrl ?? parsed.defaultApiUrl ?? null;
15097
15810
  } catch {
15098
15811
  return false;
15099
15812
  }
15100
15813
  try {
15101
- const body = await readFile17(join55(getConfigDir2(), "credentials.json"), "utf-8");
15814
+ const body = await readFile18(join56(getConfigDir2(), "credentials.json"), "utf-8");
15102
15815
  const parsed = JSON.parse(body);
15103
15816
  token = parsed.idToken ?? null;
15104
15817
  } catch {
@@ -15121,12 +15834,12 @@ async function trySendConsentToServer() {
15121
15834
  async function mcpLogOff() {
15122
15835
  let flagOnDisk = null;
15123
15836
  try {
15124
- flagOnDisk = JSON.parse(await readFile17(flagPath(), "utf-8"));
15837
+ flagOnDisk = JSON.parse(await readFile18(flagPath(), "utf-8"));
15125
15838
  } catch {
15126
15839
  flagOnDisk = null;
15127
15840
  }
15128
15841
  if (flagOnDisk && flagOnDisk.enabled === false) {
15129
- process.stderr.write("MCP logging is already disabled.\n");
15842
+ process.stderr.write("MCP telemetry is already disabled.\n");
15130
15843
  process.exitCode = 1;
15131
15844
  return;
15132
15845
  }
@@ -15137,38 +15850,38 @@ async function mcpLogOff() {
15137
15850
  };
15138
15851
  await writeFlag(next);
15139
15852
  process.stderr.write(
15140
- "MCP logging disabled. Local log file is preserved; consent record is NOT revoked.\n"
15853
+ "MCP telemetry disabled. The anonymous usage heartbeat is now off; the server-side consent record is NOT revoked (GDPR Art. 7(1) audit trail).\n"
15141
15854
  );
15142
15855
  }
15143
15856
  async function mcpLogStatus() {
15144
15857
  let flagOnDisk = null;
15145
15858
  try {
15146
- flagOnDisk = JSON.parse(await readFile17(flagPath(), "utf-8"));
15859
+ flagOnDisk = JSON.parse(await readFile18(flagPath(), "utf-8"));
15147
15860
  } catch {
15148
15861
  flagOnDisk = null;
15149
15862
  }
15150
15863
  const enabled = !flagOnDisk || flagOnDisk.enabled !== false;
15151
15864
  const enabledLabel = flagOnDisk == null ? "yes (default)" : flagOnDisk.enabled ? "yes" : "no";
15152
15865
  void enabled;
15153
- process.stderr.write("MCP Log Status\n");
15154
- process.stderr.write("==============\n");
15155
- process.stderr.write(`Enabled: ${enabledLabel}
15156
- `);
15157
- process.stderr.write(`Consent granted: ${flagOnDisk?.consentGrantedAt ?? "never"}
15866
+ process.stderr.write("MCP Telemetry Status\n");
15867
+ process.stderr.write("====================\n");
15868
+ process.stderr.write(`Heartbeat enabled: ${enabledLabel}
15158
15869
  `);
15159
- process.stderr.write(`Log file: ${logPath()}
15870
+ process.stderr.write(`Opt-in since: ${flagOnDisk?.consentGrantedAt ?? "never"}
15160
15871
  `);
15872
+ process.stderr.write("Raw local logging: disabled (heartbeat only)\n");
15161
15873
  try {
15162
15874
  const s = await stat7(logPath());
15163
- process.stderr.write(
15164
- `Log size: ${formatBytes(s.size)} (last modified ${s.mtime.toISOString()})
15875
+ if (s.size > 0) {
15876
+ process.stderr.write(
15877
+ `Raw log file: ${logPath()} \u2014 ${formatBytes(s.size)} (escape hatch enabled)
15165
15878
  `
15166
- );
15879
+ );
15880
+ }
15167
15881
  } catch {
15168
- process.stderr.write("Log size: no file yet\n");
15169
15882
  }
15170
15883
  try {
15171
- const endpointBody = await readFile17(join55(getConfigDir2(), ENDPOINT_FILE), "utf-8");
15884
+ const endpointBody = await readFile18(join56(getConfigDir2(), ENDPOINT_FILE), "utf-8");
15172
15885
  const match = /endpoint=([^\n]+)/.exec(endpointBody);
15173
15886
  process.stderr.write(`MCP endpoint: ${match?.[1] ?? "(malformed endpoint file)"}
15174
15887
  `);
@@ -15184,13 +15897,13 @@ function formatBytes(n) {
15184
15897
  async function mcpLogViewingFlags(flags = {}) {
15185
15898
  let flagOnDisk = null;
15186
15899
  try {
15187
- flagOnDisk = JSON.parse(await readFile17(flagPath(), "utf-8"));
15900
+ flagOnDisk = JSON.parse(await readFile18(flagPath(), "utf-8"));
15188
15901
  } catch {
15189
15902
  flagOnDisk = null;
15190
15903
  }
15191
15904
  if (flagOnDisk && flagOnDisk.enabled === false) {
15192
15905
  process.stderr.write(
15193
- `MCP logging is disabled. Run \`repowise mcp-log on\` to re-enable and view logs.
15906
+ `MCP telemetry is disabled. Run \`repowise mcp-log on\` to re-enable.
15194
15907
  `
15195
15908
  );
15196
15909
  return;
@@ -15199,7 +15912,7 @@ async function mcpLogViewingFlags(flags = {}) {
15199
15912
  const key = await readKey();
15200
15913
  if (!key) {
15201
15914
  process.stderr.write(
15202
- `No encryption key at ${join55(getConfigDir2(), KEY_FILE)} \u2014 listener may not have started yet.
15915
+ `No encryption key at ${join56(getConfigDir2(), KEY_FILE)} \u2014 listener may not have started yet.
15203
15916
  `
15204
15917
  );
15205
15918
  return;
@@ -15227,7 +15940,7 @@ async function mcpLogViewingFlags(flags = {}) {
15227
15940
  }
15228
15941
  };
15229
15942
  try {
15230
- const raw = await readFile17(path, "utf-8");
15943
+ const raw = await readFile18(path, "utf-8");
15231
15944
  const decoded = [];
15232
15945
  for (const line of raw.split("\n")) {
15233
15946
  const r = filter(line);
@@ -15251,7 +15964,7 @@ async function mcpLogViewingFlags(flags = {}) {
15251
15964
  try {
15252
15965
  const s = await stat7(path);
15253
15966
  if (s.size > lastSize) {
15254
- const fresh = await readFile17(path, "utf-8");
15967
+ const fresh = await readFile18(path, "utf-8");
15255
15968
  const tail = fresh.slice(lastSize);
15256
15969
  for (const line of tail.split("\n")) {
15257
15970
  const r = filter(line);
@@ -15275,7 +15988,7 @@ async function mcpLogViewingFlags(flags = {}) {
15275
15988
  }
15276
15989
  async function readKey() {
15277
15990
  try {
15278
- const body = await readFile17(join55(getConfigDir2(), KEY_FILE), "utf-8");
15991
+ const body = await readFile18(join56(getConfigDir2(), KEY_FILE), "utf-8");
15279
15992
  const parsed = Buffer.from(body.trim(), "base64");
15280
15993
  if (parsed.length !== 32) return null;
15281
15994
  return parsed;
@@ -15319,7 +16032,7 @@ import chalk15 from "chalk";
15319
16032
 
15320
16033
  // src/lib/graph-loader.ts
15321
16034
  import { promises as fs25 } from "fs";
15322
- import { join as join56, resolve as resolve2 } from "path";
16035
+ import { join as join57, resolve as resolve2 } from "path";
15323
16036
  import { gunzipSync } from "zlib";
15324
16037
  var RELATIVE_GRAPH_PATH = "repowise-context/.meta/dependency-graph.json";
15325
16038
  var GZIPPED_GRAPH_PATH = "repowise-context/.meta/dependency-graph.json.gz";
@@ -15336,8 +16049,8 @@ var GraphNotFoundError = class extends Error {
15336
16049
  var cache = /* @__PURE__ */ new Map();
15337
16050
  async function loadGraph(repoRoot = process.cwd()) {
15338
16051
  const root = resolve2(repoRoot);
15339
- const gzPath = join56(root, GZIPPED_GRAPH_PATH);
15340
- const plainPath = join56(root, RELATIVE_GRAPH_PATH);
16052
+ const gzPath = join57(root, GZIPPED_GRAPH_PATH);
16053
+ const plainPath = join57(root, RELATIVE_GRAPH_PATH);
15341
16054
  const cached = cache.get(root);
15342
16055
  if (cached) {
15343
16056
  return { graph: cached, path: plainPath, bytes: 0, parseMs: 0, fromCache: true };
@@ -15753,12 +16466,12 @@ function registerQueryCommand(program2) {
15753
16466
  // src/commands/uninstall.ts
15754
16467
  import { promises as fs29 } from "fs";
15755
16468
  import { homedir as homedir12 } from "os";
15756
- import { join as join60 } from "path";
16469
+ import { join as join61 } from "path";
15757
16470
  import chalk16 from "chalk";
15758
16471
 
15759
16472
  // src/lib/cleanup/marker-blocks.ts
15760
16473
  import { promises as fs26 } from "fs";
15761
- import { join as join57 } from "path";
16474
+ import { join as join58 } from "path";
15762
16475
  var MARKER_START = "<!-- repowise-start -->";
15763
16476
  var MARKER_END = "<!-- repowise-end -->";
15764
16477
  var CONTEXT_FILES = [
@@ -15798,7 +16511,7 @@ async function stripMarkerBlock(filePath) {
15798
16511
  async function stripAllMarkerBlocks(repoRoot) {
15799
16512
  const out = [];
15800
16513
  for (const relative of CONTEXT_FILES) {
15801
- const full = join57(repoRoot, relative);
16514
+ const full = join58(repoRoot, relative);
15802
16515
  const result = await stripMarkerBlock(full).catch((err) => ({
15803
16516
  path: full,
15804
16517
  status: "untouched",
@@ -15811,18 +16524,18 @@ async function stripAllMarkerBlocks(repoRoot) {
15811
16524
 
15812
16525
  // src/lib/cleanup/mcp-configs.ts
15813
16526
  import { promises as fs27 } from "fs";
15814
- import { join as join58 } from "path";
16527
+ import { join as join59 } from "path";
15815
16528
  function mcpConfigPaths(repoRoot, home) {
15816
16529
  return [
15817
- join58(repoRoot, ".mcp.json"),
15818
- join58(repoRoot, ".cursor", "mcp.json"),
15819
- join58(repoRoot, ".vscode", "mcp.json"),
15820
- join58(repoRoot, ".roo", "mcp.json"),
15821
- join58(home, ".cline", "mcp.json"),
15822
- join58(home, ".codeium", "windsurf", "mcp_config.json"),
15823
- join58(home, ".gemini", "settings.json"),
15824
- join58(home, ".codex", "mcp.json"),
15825
- join58(home, ".roo", "mcp.json")
16530
+ join59(repoRoot, ".mcp.json"),
16531
+ join59(repoRoot, ".cursor", "mcp.json"),
16532
+ join59(repoRoot, ".vscode", "mcp.json"),
16533
+ join59(repoRoot, ".roo", "mcp.json"),
16534
+ join59(home, ".cline", "mcp.json"),
16535
+ join59(home, ".codeium", "windsurf", "mcp_config.json"),
16536
+ join59(home, ".gemini", "settings.json"),
16537
+ join59(home, ".codex", "mcp.json"),
16538
+ join59(home, ".roo", "mcp.json")
15826
16539
  ];
15827
16540
  }
15828
16541
  async function removeRepowiseFromConfig(path, serverName) {
@@ -15863,10 +16576,10 @@ async function removeAllMcpEntries(repoRoot, home, repoId) {
15863
16576
  // src/lib/cleanup/local-state.ts
15864
16577
  import { promises as fs28 } from "fs";
15865
16578
  import { homedir as homedir11 } from "os";
15866
- import { join as join59, resolve as resolve3 } from "path";
16579
+ import { join as join60, resolve as resolve3 } from "path";
15867
16580
  async function clearLocalState(homeOverride) {
15868
16581
  const home = homeOverride ?? homedir11();
15869
- const target = resolve3(join59(home, ".repowise"));
16582
+ const target = resolve3(join60(home, ".repowise"));
15870
16583
  if (target === resolve3(home) || !target.startsWith(resolve3(home))) {
15871
16584
  return { path: target, status: "error", error: "refused: not under home" };
15872
16585
  }
@@ -15910,7 +16623,7 @@ async function uninstall2(opts = {}) {
15910
16623
  else if (svc.error) report.skipped.push({ path: "listener service", reason: svc.error });
15911
16624
  if (tier === "stop") return report;
15912
16625
  try {
15913
- await fs29.unlink(join60(home, ".repowise", "credentials.json"));
16626
+ await fs29.unlink(join61(home, ".repowise", "credentials.json"));
15914
16627
  report.removed.push("credentials");
15915
16628
  } catch (err) {
15916
16629
  if (err.code !== "ENOENT") {
@@ -15949,7 +16662,7 @@ async function uninstall2(opts = {}) {
15949
16662
  }
15950
16663
  async function defaultLoadRepoIds(home) {
15951
16664
  try {
15952
- const raw = await fs29.readFile(join60(home, ".repowise", "config.json"), "utf-8");
16665
+ const raw = await fs29.readFile(join61(home, ".repowise", "config.json"), "utf-8");
15953
16666
  const parsed = JSON.parse(raw);
15954
16667
  return (parsed.repos ?? []).map((r) => r.repoId);
15955
16668
  } catch {
@@ -15998,11 +16711,12 @@ Done \u2014 ${report.removed.length} removed, ${report.skipped.length} skipped.
15998
16711
  init_config_dir();
15999
16712
  import { promises as fs30 } from "fs";
16000
16713
  import { createInterface as createInterface2 } from "readline";
16001
- import { join as join61 } from "path";
16714
+ import { join as join62 } from "path";
16002
16715
  var DEFAULT_MAX = 200 * 1024;
16716
+ var SHIM_REQUEST_TIMEOUT_MS = 45e3;
16003
16717
  async function mcpShim(opts) {
16004
- const endpointPath = opts.endpointFile ?? join61(getConfigDir(), "listener.endpoint");
16005
- const credsPath = opts.credentialsFile ?? join61(getConfigDir(), "credentials.json");
16718
+ const endpointPath = opts.endpointFile ?? join62(getConfigDir(), "listener.endpoint");
16719
+ const credsPath = opts.credentialsFile ?? join62(getConfigDir(), "credentials.json");
16006
16720
  const stdin = opts.stdin ?? process.stdin;
16007
16721
  const stdout = opts.stdout ?? process.stdout;
16008
16722
  const stderr = opts.stderr ?? process.stderr;
@@ -16042,7 +16756,7 @@ async function mcpShim(opts) {
16042
16756
  // Agent-directed: the calling LLM can run this itself for a smoother UX
16043
16757
  // (it auto-opens the browser; the user still completes sign-in). The
16044
16758
  // command is a fixed, argument-free string (nothing injectable).
16045
- "RepoWise isn't authenticated (the session expires ~monthly). 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."
16759
+ "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
16760
  ) : "listener endpoint unavailable \u2014 is the listener running?";
16047
16761
  writeJson(stdout, {
16048
16762
  jsonrpc: "2.0",
@@ -16066,12 +16780,19 @@ async function mcpShim(opts) {
16066
16780
  } catch (err) {
16067
16781
  stderr.write(`mcp-shim: ${err.message}
16068
16782
  `);
16783
+ const name = err.name;
16784
+ const detail = err.message;
16785
+ const isTimeout = name === "TimeoutError" || name === "AbortError" || /timeout|aborted|abort/i.test(detail);
16069
16786
  writeJson(stdout, {
16070
16787
  jsonrpc: "2.0",
16071
- error: {
16788
+ error: isTimeout ? {
16789
+ code: -32001,
16790
+ message: "RepoWise listener did not respond in time \u2014 it may be stuck. Restart it with `repowise restart`, then retry.",
16791
+ data: { detail }
16792
+ } : {
16072
16793
  code: -32001,
16073
16794
  message: "listener request failed",
16074
- data: { detail: err.message }
16795
+ data: { detail }
16075
16796
  }
16076
16797
  });
16077
16798
  }
@@ -16208,7 +16929,8 @@ async function defaultPostJson(url, body, headers = { "content-type": "applicati
16208
16929
  const res = await fetch(url, {
16209
16930
  method: "POST",
16210
16931
  headers,
16211
- body: JSON.stringify(body)
16932
+ body: JSON.stringify(body),
16933
+ signal: AbortSignal.timeout(SHIM_REQUEST_TIMEOUT_MS)
16212
16934
  });
16213
16935
  if (!res.ok) {
16214
16936
  let detail = "";
@@ -16222,7 +16944,11 @@ async function defaultPostJson(url, body, headers = { "content-type": "applicati
16222
16944
  return await res.json();
16223
16945
  }
16224
16946
  async function defaultGetJson(url, headers = {}) {
16225
- const res = await fetch(url, { method: "GET", headers });
16947
+ const res = await fetch(url, {
16948
+ method: "GET",
16949
+ headers,
16950
+ signal: AbortSignal.timeout(SHIM_REQUEST_TIMEOUT_MS)
16951
+ });
16226
16952
  if (!res.ok) {
16227
16953
  let detail = "";
16228
16954
  try {
@@ -16771,7 +17497,7 @@ async function lspOn() {
16771
17497
  // bin/repowise.ts
16772
17498
  var __filename = fileURLToPath4(import.meta.url);
16773
17499
  var __dirname = dirname23(__filename);
16774
- var pkg = JSON.parse(readFileSync3(join62(__dirname, "..", "..", "package.json"), "utf-8"));
17500
+ var pkg = JSON.parse(readFileSync3(join63(__dirname, "..", "..", "package.json"), "utf-8"));
16775
17501
  var program = new Command();
16776
17502
  program.name(getPackageName()).description("AI-optimized codebase context generator").version(pkg.version).hook("preAction", async () => {
16777
17503
  await showWelcome(pkg.version);