repowisestage 0.0.70 → 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 +1737 -666
  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 join61 } 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;
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;
4467
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
  }
@@ -8007,10 +8492,10 @@ async function ensureServerConsent(opts) {
8007
8492
  });
8008
8493
  if (!res.ok)
8009
8494
  return;
8010
- const fs30 = await import("fs/promises");
8495
+ const fs31 = await import("fs/promises");
8011
8496
  const path = await import("path");
8012
- await fs30.mkdir(path.dirname(opts.markerPath), { recursive: true });
8013
- await fs30.writeFile(opts.markerPath, (/* @__PURE__ */ new Date()).toISOString() + "\n", {
8497
+ await fs31.mkdir(path.dirname(opts.markerPath), { recursive: true });
8498
+ await fs31.writeFile(opts.markerPath, (/* @__PURE__ */ new Date()).toISOString() + "\n", {
8014
8499
  encoding: "utf-8",
8015
8500
  mode: 384
8016
8501
  });
@@ -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;
@@ -10510,15 +10999,72 @@ function sleep(ms) {
10510
10999
  }, ms);
10511
11000
  });
10512
11001
  }
10513
- function decodeEmailFromIdToken(idToken) {
11002
+ async function waitForCredentials(isRunning2) {
11003
+ let changed = false;
11004
+ let watcher = null;
10514
11005
  try {
10515
- const payload = JSON.parse(Buffer.from(idToken.split(".")[1], "base64url").toString());
10516
- return typeof payload.email === "string" ? payload.email : null;
11006
+ const fs31 = await import("fs");
11007
+ watcher = fs31.watch(getConfigDir(), (_event, filename) => {
11008
+ if (!filename || filename === "credentials.json")
11009
+ changed = true;
11010
+ });
10517
11011
  } catch {
10518
- return null;
10519
11012
  }
10520
- }
10521
- async function handleSyncCompleted(notif, ctx) {
11013
+ try {
11014
+ while (isRunning2()) {
11015
+ await sleep(changed ? 0 : 1e4);
11016
+ changed = false;
11017
+ if (!isRunning2())
11018
+ break;
11019
+ const fresh = await getValidCredentials({ forceRefresh: true });
11020
+ if (fresh)
11021
+ return fresh;
11022
+ }
11023
+ return null;
11024
+ } finally {
11025
+ if (watcher)
11026
+ watcher.close();
11027
+ }
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
+ }
11059
+ function decodeEmailFromIdToken(idToken) {
11060
+ try {
11061
+ const payload = JSON.parse(Buffer.from(idToken.split(".")[1], "base64url").toString());
11062
+ return typeof payload.email === "string" ? payload.email : null;
11063
+ } catch {
11064
+ return null;
11065
+ }
11066
+ }
11067
+ async function handleSyncCompleted(notif, ctx) {
10522
11068
  if (!notif.commitSha) {
10523
11069
  console.warn(`sync.completed for ${notif.repoId} has no commitSha`);
10524
11070
  }
@@ -10537,13 +11083,14 @@ async function handleSyncCompleted(notif, ctx) {
10537
11083
  return { stateUpdated: false };
10538
11084
  }
10539
11085
  notifyContextUpdated(notif.repoId, result.updatedFiles.length);
10540
- invalidateLspBuffersOnce(notif, localPath, ctx);
10541
- await refreshAiToolConfigs(notif, localPath, ctx);
11086
+ await writeDocsMarker(localPath, "repowise-context", notif.commitSha ?? null, result.updatedFiles.length);
10542
11087
  ctx.state.repos[notif.repoId] = {
10543
11088
  ...ctx.state.repos[notif.repoId],
10544
11089
  lastSyncTimestamp: notif.createdAt,
10545
11090
  lastSyncCommitSha: notif.commitSha ?? ctx.state.repos[notif.repoId]?.lastSyncCommitSha ?? null
10546
11091
  };
11092
+ invalidateLspBuffersOnce(notif, localPath, ctx);
11093
+ await refreshAiToolConfigs(notif, localPath, ctx);
10547
11094
  await runProducerHook(notif, localPath, ctx);
10548
11095
  return { stateUpdated: true };
10549
11096
  }
@@ -10712,14 +11259,14 @@ async function checkStaleContext(repos, state, groups) {
10712
11259
  if (group?.offline.isOffline)
10713
11260
  continue;
10714
11261
  const { statSync: statSync2, readdirSync: readdirSync2 } = await import("fs");
10715
- const contextPath = join41(repo.localPath, "repowise-context");
11262
+ const contextPath = join42(repo.localPath, "repowise-context");
10716
11263
  let isMissingOrEmpty = false;
10717
11264
  try {
10718
11265
  const s = statSync2(contextPath);
10719
11266
  if (!s.isDirectory()) {
10720
11267
  isMissingOrEmpty = true;
10721
11268
  } else {
10722
- const entries = readdirSync2(contextPath);
11269
+ const entries = readdirSync2(contextPath).filter((e) => !e.startsWith("."));
10723
11270
  isMissingOrEmpty = entries.length === 0;
10724
11271
  }
10725
11272
  } catch {
@@ -10735,16 +11282,19 @@ async function checkStaleContext(repos, state, groups) {
10735
11282
  const result = await fetchContextFromServer(repo.repoId, repo.localPath, apiUrl);
10736
11283
  if (result.success && result.updatedFiles.length > 0) {
10737
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);
10738
11286
  notifyContextUpdated(repo.repoId, result.updatedFiles.length);
10739
11287
  }
10740
11288
  }
10741
11289
  }
10742
11290
  return dirty;
10743
11291
  }
11292
+ function mcpAiToolsKey(aiTools) {
11293
+ return JSON.stringify([...aiTools ?? []].sort());
11294
+ }
10744
11295
  async function reconcileMcpConfigs(repos, packageName) {
10745
11296
  const shimCmd = packageName;
10746
11297
  const { contextFolder, aiTools, graphOnly } = await readRawToolConfig();
10747
- const variant = graphOnly ? "graph" : "full";
10748
11298
  for (const repo of repos) {
10749
11299
  if (!repo.localPath)
10750
11300
  continue;
@@ -10755,6 +11305,8 @@ async function reconcileMcpConfigs(repos, packageName) {
10755
11305
  } catch {
10756
11306
  continue;
10757
11307
  }
11308
+ const docsPresent = (await scanLocalContextFiles(repo.localPath, contextFolder ?? "repowise-context")).length > 0;
11309
+ const variant = effectiveToolVariant(!!graphOnly, docsPresent, null, null);
10758
11310
  try {
10759
11311
  const results = await runAutoConfig({
10760
11312
  repoRoot: repo.localPath,
@@ -10777,10 +11329,10 @@ async function reconcileAgentInstructions(repos) {
10777
11329
  const { graphOnly } = await readRawToolConfig();
10778
11330
  const variant = graphOnly ? "graph" : "full";
10779
11331
  for (const repo of repos) {
10780
- const path = join41(repo.localPath, "repowise-context", "project-overview.md");
11332
+ const path = join42(repo.localPath, "repowise-context", "project-overview.md");
10781
11333
  let content;
10782
11334
  try {
10783
- content = await readFile13(path, "utf-8");
11335
+ content = await readFile14(path, "utf-8");
10784
11336
  } catch (err) {
10785
11337
  const code = err?.code;
10786
11338
  if (code === "ENOENT" || code === "ENOTDIR" || code === "EACCES" || code === "EPERM" || code === "EISDIR") {
@@ -10793,7 +11345,7 @@ async function reconcileAgentInstructions(repos) {
10793
11345
  if (injected === content)
10794
11346
  continue;
10795
11347
  try {
10796
- await writeFile15(path, injected, "utf-8");
11348
+ await writeFile16(path, injected, "utf-8");
10797
11349
  console.log(`[reconcile] Reconciled agent instructions for ${repo.repoId}`);
10798
11350
  } catch (err) {
10799
11351
  console.warn(`[reconcile] write failed for ${repo.repoId}:`, err instanceof Error ? err.message : String(err));
@@ -10861,11 +11413,11 @@ async function startListener() {
10861
11413
  }
10862
11414
  const configDir = getConfigDir();
10863
11415
  await mkdir16(configDir, { recursive: true });
10864
- const lockPath = join41(configDir, "listener.lock");
10865
- await writeFile15(lockPath, "", { flag: "a" });
11416
+ const lockPath = join42(configDir, "listener.lock");
11417
+ await writeFile16(lockPath, "", { flag: "a" });
10866
11418
  let lockIsHeld = false;
10867
11419
  try {
10868
- lockIsHeld = await lockfile4.check(lockPath, { stale: 3e4, realpath: false });
11420
+ lockIsHeld = await lockfile5.check(lockPath, { stale: 3e4, realpath: false });
10869
11421
  } catch {
10870
11422
  }
10871
11423
  if (!lockIsHeld) {
@@ -10879,7 +11431,7 @@ async function startListener() {
10879
11431
  }
10880
11432
  }
10881
11433
  try {
10882
- releaseLock = await lockfile4.lock(lockPath, { stale: 3e4, realpath: false });
11434
+ releaseLock = await lockfile5.lock(lockPath, { stale: 3e4, realpath: false });
10883
11435
  } catch {
10884
11436
  console.error(`Listener already running. Stop it first with \`${true ? "repowisestage" : "repowise"} stop\`.`);
10885
11437
  process.exitCode = 1;
@@ -10904,17 +11456,20 @@ async function startListener() {
10904
11456
  return;
10905
11457
  }
10906
11458
  if (config2.repos.length === 0 && !config2.autoDiscoverRepos) {
10907
- 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")}`);
10908
11460
  await releaseLockAndExit();
10909
11461
  process.exitCode = 1;
10910
11462
  return;
10911
11463
  }
10912
- const credentials = await getValidCredentials();
11464
+ let credentials = await getValidCredentials();
10913
11465
  if (!credentials) {
10914
- console.error("Not logged in. Run `repowise login` first.");
10915
- await releaseLockAndExit();
10916
- process.exitCode = 1;
10917
- return;
11466
+ console.warn("Not logged in. Run `repowise login` \u2014 the listener will start automatically once you sign in.");
11467
+ credentials = await waitForCredentials(() => running);
11468
+ if (!credentials) {
11469
+ await releaseLockAndExit();
11470
+ return;
11471
+ }
11472
+ console.log("Logged in \u2014 starting RepoWise listener.");
10918
11473
  }
10919
11474
  let state;
10920
11475
  try {
@@ -10972,8 +11527,8 @@ async function startListener() {
10972
11527
  let currentVersion = "";
10973
11528
  try {
10974
11529
  const selfDir = dirname17(fileURLToPath3(import.meta.url));
10975
- const pkgJsonPath = join41(selfDir, "..", "..", "package.json");
10976
- 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"));
10977
11532
  currentVersion = pkgJson.version;
10978
11533
  } catch (err) {
10979
11534
  console.log(`[auto-update] Version detection failed: ${err instanceof Error ? err.message : String(err)}`);
@@ -10981,6 +11536,7 @@ async function startListener() {
10981
11536
  let pollIntervalMs = 5e3;
10982
11537
  let pollCycleCount = 0;
10983
11538
  const RECONCILE_EVERY_N_CYCLES = 60;
11539
+ let lastMcpAiToolsKey = null;
10984
11540
  const origLog = console.log.bind(console);
10985
11541
  const origError = console.error.bind(console);
10986
11542
  const origWarn = console.warn.bind(console);
@@ -10994,6 +11550,8 @@ async function startListener() {
10994
11550
  console.warn = (...args) => origWarn(`[${ts()}]`, ...args);
10995
11551
  console.log(`RepoWise Listener started \u2014 watching ${allRepoIds.length} repo(s)`);
10996
11552
  let mcpRuntime = null;
11553
+ let proactiveRefreshTimer = null;
11554
+ const PROACTIVE_REFRESH_MS = 10 * 60 * 1e3;
10997
11555
  try {
10998
11556
  mcpRuntime = await startMcp({
10999
11557
  repos: config2.repos.map((r) => ({
@@ -11001,6 +11559,8 @@ async function startListener() {
11001
11559
  apiUrl: r.apiUrl ?? config2.defaultApiUrl,
11002
11560
  ...r.localPath ? { localPath: r.localPath } : {}
11003
11561
  })),
11562
+ // Rides the anonymous MCP-usage heartbeat for fleet version spread.
11563
+ listenerVersion: currentVersion,
11004
11564
  getAuthToken: async () => {
11005
11565
  const fresh = await getValidCredentials();
11006
11566
  if (!fresh)
@@ -11134,6 +11694,10 @@ async function startListener() {
11134
11694
  const shutdown = async () => {
11135
11695
  console.log("Shutting down...");
11136
11696
  stop();
11697
+ if (proactiveRefreshTimer) {
11698
+ clearInterval(proactiveRefreshTimer);
11699
+ proactiveRefreshTimer = null;
11700
+ }
11137
11701
  if (mcpRuntime) {
11138
11702
  try {
11139
11703
  await mcpRuntime.close();
@@ -11164,7 +11728,22 @@ async function startListener() {
11164
11728
  }).finally(() => process.exit(0));
11165
11729
  });
11166
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();
11167
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();
11168
11747
  let anyAuthError = null;
11169
11748
  let authErrorGroup = null;
11170
11749
  let minPollInterval = pollIntervalMs;
@@ -11207,6 +11786,16 @@ async function startListener() {
11207
11786
  console.warn("[mcp-config] front-load for newly-registered repo(s) failed:", err instanceof Error ? err.message : String(err));
11208
11787
  });
11209
11788
  }
11789
+ const aiToolsKey = mcpAiToolsKey((await readRawToolConfig()).aiTools);
11790
+ if (lastMcpAiToolsKey === null) {
11791
+ lastMcpAiToolsKey = aiToolsKey;
11792
+ } else if (aiToolsKey !== lastMcpAiToolsKey) {
11793
+ lastMcpAiToolsKey = aiToolsKey;
11794
+ console.log("[config-sync] AI-tool selection changed \u2014 front-loading MCP config");
11795
+ void reconcileMcpConfigs(freshRepos, packageName).catch((err) => {
11796
+ console.warn("[mcp-config] front-load for aiTools change failed:", err instanceof Error ? err.message : String(err));
11797
+ });
11798
+ }
11210
11799
  } catch {
11211
11800
  }
11212
11801
  for (const group of groups) {
@@ -11371,10 +11960,10 @@ async function startListener() {
11371
11960
  }
11372
11961
  }
11373
11962
  group.offline.attemptCount++;
11374
- const delay2 = group.backoff.nextDelay();
11375
- group.offline.nextRetryAt = Date.now() + delay2;
11963
+ const delay3 = group.backoff.nextDelay();
11964
+ group.offline.nextRetryAt = Date.now() + delay3;
11376
11965
  const message = err instanceof Error ? err.message : "Unknown error";
11377
- 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`);
11378
11967
  }
11379
11968
  }
11380
11969
  if (shouldReconcile) {
@@ -11451,13 +12040,14 @@ async function startListener() {
11451
12040
  }
11452
12041
  if (anyAuthError) {
11453
12042
  let recovered = false;
11454
- for (let attempt = 1; attempt <= 3; attempt++) {
11455
- console.warn(`Auth error \u2014 retrying credentials (attempt ${attempt}/3)...`);
11456
- 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]);
11457
12047
  if (!running)
11458
12048
  break;
11459
- const fresh = await getValidCredentials({ forceRefresh: true });
11460
- if (fresh) {
12049
+ const fresh2 = await getValidCredentials({ forceRefresh: true });
12050
+ if (fresh2) {
11461
12051
  console.log("Credentials recovered \u2014 resuming listener");
11462
12052
  recovered = true;
11463
12053
  break;
@@ -11465,42 +12055,47 @@ async function startListener() {
11465
12055
  }
11466
12056
  if (recovered)
11467
12057
  continue;
11468
- 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
+ }
11469
12078
  const creds = await getStoredCredentials();
11470
12079
  const email = creds?.idToken ? decodeEmailFromIdToken(creds.idToken) : null;
11471
12080
  if (email && authErrorGroup) {
11472
12081
  try {
11473
- await fetch(`${authErrorGroup.apiUrl}/v1/public/notify-auth-expired`, {
12082
+ await fetchWithTimeout(`${authErrorGroup.apiUrl}/v1/public/notify-auth-expired`, {
11474
12083
  method: "POST",
11475
12084
  headers: { "Content-Type": "application/json" },
11476
12085
  body: JSON.stringify({ email })
11477
- });
12086
+ }, 5e3);
11478
12087
  } catch {
11479
12088
  }
11480
12089
  }
11481
- const credentialsPath = join41(getConfigDir(), "credentials.json");
11482
- let credentialsChanged = false;
11483
- let watcher = null;
11484
- try {
11485
- const fs30 = await import("fs");
11486
- watcher = fs30.watch(credentialsPath, () => {
11487
- credentialsChanged = true;
11488
- });
11489
- } catch {
11490
- }
11491
- while (running) {
11492
- await sleep(credentialsChanged ? 0 : 1e4);
11493
- credentialsChanged = false;
11494
- if (!running)
11495
- break;
11496
- const fresh = await getValidCredentials({ forceRefresh: true });
11497
- if (fresh) {
11498
- console.log("Re-authenticated \u2014 resuming listener");
11499
- break;
12090
+ const fresh = await waitForCredentials(() => running);
12091
+ if (fresh) {
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));
11500
12097
  }
11501
12098
  }
11502
- if (watcher)
11503
- watcher.close();
11504
12099
  continue;
11505
12100
  }
11506
12101
  pollIntervalMs = minPollInterval;
@@ -11524,7 +12119,7 @@ if (isDirectRun) {
11524
12119
 
11525
12120
  // src/lib/env.ts
11526
12121
  import { homedir as homedir7 } from "os";
11527
- import { join as join42 } from "path";
12122
+ import { join as join43 } from "path";
11528
12123
  var IS_STAGING2 = true ? true : false;
11529
12124
  var PRODUCTION = {
11530
12125
  apiUrl: "https://api.repowise.ai",
@@ -11544,7 +12139,7 @@ function getEnvConfig() {
11544
12139
  return IS_STAGING2 ? STAGING : PRODUCTION;
11545
12140
  }
11546
12141
  function getConfigDir2() {
11547
- return join42(homedir7(), IS_STAGING2 ? ".repowise-staging" : ".repowise");
12142
+ return join43(homedir7(), IS_STAGING2 ? ".repowise-staging" : ".repowise");
11548
12143
  }
11549
12144
  function getPackageName() {
11550
12145
  return true ? "repowisestage" : "repowise";
@@ -11554,12 +12149,12 @@ function getPackageName() {
11554
12149
  import chalk from "chalk";
11555
12150
 
11556
12151
  // src/lib/config.ts
11557
- import { readFile as readFile14, writeFile as writeFile16, mkdir as mkdir17, rename as rename4, unlink as unlink10 } from "fs/promises";
11558
- import { join as join43 } from "path";
11559
- 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";
11560
12155
  async function getConfig() {
11561
12156
  try {
11562
- const data = await readFile14(join43(getConfigDir2(), "config.json"), "utf-8");
12157
+ const data = await readFile15(join44(getConfigDir2(), "config.json"), "utf-8");
11563
12158
  return JSON.parse(data);
11564
12159
  } catch {
11565
12160
  return {};
@@ -11567,15 +12162,15 @@ async function getConfig() {
11567
12162
  }
11568
12163
  async function saveConfig(config2) {
11569
12164
  const dir = getConfigDir2();
11570
- const path = join43(dir, "config.json");
12165
+ const path = join44(dir, "config.json");
11571
12166
  await mkdir17(dir, { recursive: true });
11572
12167
  const tmpPath = path + ".tmp";
11573
12168
  try {
11574
- await writeFile16(tmpPath, JSON.stringify(config2, null, 2));
11575
- await rename4(tmpPath, path);
12169
+ await writeFile17(tmpPath, JSON.stringify(config2, null, 2));
12170
+ await rename6(tmpPath, path);
11576
12171
  } catch (err) {
11577
12172
  try {
11578
- await unlink10(tmpPath);
12173
+ await unlink12(tmpPath);
11579
12174
  } catch {
11580
12175
  }
11581
12176
  throw err;
@@ -11583,18 +12178,18 @@ async function saveConfig(config2) {
11583
12178
  }
11584
12179
  async function mergeAndSaveConfig(updates) {
11585
12180
  const dir = getConfigDir2();
11586
- const path = join43(dir, "config.json");
12181
+ const path = join44(dir, "config.json");
11587
12182
  await mkdir17(dir, { recursive: true });
11588
12183
  try {
11589
- await writeFile16(path, "", { flag: "a" });
12184
+ await writeFile17(path, "", { flag: "a" });
11590
12185
  } catch {
11591
12186
  }
11592
12187
  let release = null;
11593
12188
  try {
11594
- release = await lockfile5.lock(path, { stale: 1e4, retries: 3, realpath: false });
12189
+ release = await lockfile6.lock(path, { stale: 1e4, retries: 3, realpath: false });
11595
12190
  let raw = {};
11596
12191
  try {
11597
- raw = JSON.parse(await readFile14(path, "utf-8"));
12192
+ raw = JSON.parse(await readFile15(path, "utf-8"));
11598
12193
  } catch {
11599
12194
  }
11600
12195
  const merged = { ...raw, ...updates };
@@ -11654,18 +12249,33 @@ async function showWelcome(currentVersion) {
11654
12249
 
11655
12250
  // src/commands/create.ts
11656
12251
  import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync3 } from "fs";
11657
- import { dirname as dirname19, join as join49 } from "path";
12252
+ import { dirname as dirname19, join as join51 } from "path";
11658
12253
  init_src();
11659
12254
  import chalk8 from "chalk";
11660
12255
  import ora from "ora";
11661
12256
 
11662
12257
  // src/lib/auth.ts
11663
12258
  import { createHash as createHash6, randomBytes as randomBytes3 } from "crypto";
11664
- 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";
11665
12260
  import http from "http";
11666
- 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
11667
12276
  var CLI_CALLBACK_PORT = 19876;
11668
12277
  var CALLBACK_TIMEOUT_MS = 12e4;
12278
+ var TOKEN_ENDPOINT_TIMEOUT_MS2 = 3e4;
11669
12279
  function getCognitoConfigForStorage() {
11670
12280
  const { domain, clientId, region, customDomain } = getCognitoConfig();
11671
12281
  return { domain, clientId, region, customDomain };
@@ -11806,31 +12416,65 @@ async function exchangeCodeForTokens(code, codeVerifier) {
11806
12416
  };
11807
12417
  }
11808
12418
  async function refreshTokens2(refreshToken) {
11809
- const response = await fetch(getTokenUrl2(), {
11810
- method: "POST",
11811
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
11812
- body: new URLSearchParams({
11813
- grant_type: "refresh_token",
11814
- client_id: getCognitoConfig().clientId,
11815
- refresh_token: refreshToken
11816
- })
11817
- });
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
+ }
11818
12441
  if (!response.ok) {
11819
- 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
+ );
11820
12448
  }
11821
12449
  const data = await response.json();
11822
12450
  return {
11823
12451
  accessToken: data.access_token,
11824
- refreshToken,
11825
- // 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,
11826
12457
  idToken: data.id_token,
11827
12458
  expiresAt: Date.now() + data.expires_in * 1e3
11828
12459
  };
11829
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
+ }
11830
12474
  async function getStoredCredentials2() {
11831
12475
  try {
11832
- const credPath = join44(getConfigDir2(), "credentials.json");
11833
- const data = await readFile15(credPath, "utf-8");
12476
+ const credPath = join45(getConfigDir2(), "credentials.json");
12477
+ const data = await readFile16(credPath, "utf-8");
11834
12478
  return JSON.parse(data);
11835
12479
  } catch (err) {
11836
12480
  if (err.code === "ENOENT" || err instanceof SyntaxError) {
@@ -11841,37 +12485,78 @@ async function getStoredCredentials2() {
11841
12485
  }
11842
12486
  async function storeCredentials2(credentials) {
11843
12487
  const dir = getConfigDir2();
11844
- const credPath = join44(dir, "credentials.json");
12488
+ const credPath = join45(dir, "credentials.json");
11845
12489
  await mkdir18(dir, { recursive: true, mode: 448 });
11846
- await writeFile17(credPath, JSON.stringify(credentials, null, 2));
11847
- 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
+ }
11848
12524
  }
11849
12525
  async function clearCredentials() {
11850
12526
  try {
11851
- await unlink11(join44(getConfigDir2(), "credentials.json"));
12527
+ await unlink13(join45(getConfigDir2(), "credentials.json"));
11852
12528
  } catch (err) {
11853
12529
  if (err.code !== "ENOENT") throw err;
11854
12530
  }
11855
12531
  }
12532
+ var refreshInFlight2 = null;
11856
12533
  async function getValidCredentials2(opts) {
11857
12534
  const creds = await getStoredCredentials2();
11858
12535
  if (!creds) return null;
11859
12536
  const needsRefresh = opts?.forceRefresh || Date.now() > creds.expiresAt - 5 * 60 * 1e3;
11860
- if (needsRefresh) {
11861
- try {
11862
- const refreshed = await refreshTokens2(creds.refreshToken);
11863
- refreshed.cognito = creds.cognito;
11864
- await storeCredentials2(refreshed);
11865
- return refreshed;
11866
- } catch {
11867
- if (Date.now() < creds.expiresAt) {
11868
- return creds;
11869
- }
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") {
11870
12554
  await clearCredentials();
11871
12555
  return null;
11872
12556
  }
12557
+ if (Date.now() < creds.expiresAt) return creds;
12558
+ return null;
11873
12559
  }
11874
- return creds;
11875
12560
  }
11876
12561
  async function performLogin() {
11877
12562
  const codeVerifier = generateCodeVerifier();
@@ -11880,8 +12565,8 @@ async function performLogin() {
11880
12565
  const authorizeUrl = getAuthorizeUrl(codeChallenge, state);
11881
12566
  const callbackPromise = startCallbackServer();
11882
12567
  try {
11883
- const open4 = (await import("open")).default;
11884
- await open4(authorizeUrl);
12568
+ const open6 = (await import("open")).default;
12569
+ await open6(authorizeUrl);
11885
12570
  } catch {
11886
12571
  console.log(`
11887
12572
  Open this URL in your browser to authenticate:
@@ -11979,7 +12664,6 @@ async function apiRequest(path, options) {
11979
12664
  });
11980
12665
  }
11981
12666
  if (!refreshed || response.status === 401) {
11982
- await clearCredentials();
11983
12667
  throw new Error("Session expired. Run `repowise login` again.");
11984
12668
  }
11985
12669
  }
@@ -12013,25 +12697,26 @@ async function apiRequest(path, options) {
12013
12697
  // src/lib/prompts.ts
12014
12698
  import { checkbox, confirm, Separator } from "@inquirer/prompts";
12015
12699
  import chalk2 from "chalk";
12016
- async function selectAiTools() {
12700
+ async function selectAiTools(opts) {
12701
+ const checkedSet = new Set(opts?.defaults ?? []);
12017
12702
  const choices = [
12018
12703
  new Separator(chalk2.dim("\u2500\u2500 Popular \u2500\u2500")),
12019
- { name: "Cursor", value: "cursor" },
12020
- { name: "Claude Code", value: "claude-code" },
12021
- { name: "GitHub Copilot", value: "copilot" },
12022
- { name: "Windsurf", value: "windsurf" },
12704
+ { name: "Cursor", value: "cursor", checked: checkedSet.has("cursor") },
12705
+ { name: "Claude Code", value: "claude-code", checked: checkedSet.has("claude-code") },
12706
+ { name: "GitHub Copilot", value: "copilot", checked: checkedSet.has("copilot") },
12707
+ { name: "Windsurf", value: "windsurf", checked: checkedSet.has("windsurf") },
12023
12708
  new Separator(chalk2.dim("\u2500\u2500 More Tools \u2500\u2500")),
12024
- { name: "Cline", value: "cline" },
12025
- { name: "Codex", value: "codex" },
12709
+ { name: "Cline", value: "cline", checked: checkedSet.has("cline") },
12710
+ { name: "Codex", value: "codex", checked: checkedSet.has("codex") },
12026
12711
  // Roo Code shut down 2026-05-15; Kilo Code is the community successor.
12027
- { name: "Kilo Code", value: "kilo" },
12028
- { name: "Gemini CLI", value: "gemini" },
12712
+ { name: "Kilo Code", value: "kilo", checked: checkedSet.has("kilo") },
12713
+ { name: "Gemini CLI", value: "gemini", checked: checkedSet.has("gemini") },
12029
12714
  new Separator(chalk2.dim("\u2500\u2500 Cloud Agents \u2500\u2500")),
12030
- { name: "Warp", value: "warp" },
12031
- { name: "JetBrains Junie", value: "junie" },
12032
- { name: "Google Jules", value: "jules" },
12033
- { name: "Amp", value: "amp" },
12034
- { name: "Devin", value: "devin" },
12715
+ { name: "Warp", value: "warp", checked: checkedSet.has("warp") },
12716
+ { name: "JetBrains Junie", value: "junie", checked: checkedSet.has("junie") },
12717
+ { name: "Google Jules", value: "jules", checked: checkedSet.has("jules") },
12718
+ { name: "Amp", value: "amp", checked: checkedSet.has("amp") },
12719
+ { name: "Devin", value: "devin", checked: checkedSet.has("devin") },
12035
12720
  new Separator(chalk2.dim("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")),
12036
12721
  { name: "Other (manual setup)", value: "other" }
12037
12722
  ];
@@ -12054,11 +12739,126 @@ async function selectAiTools() {
12054
12739
  }
12055
12740
  }
12056
12741
 
12742
+ // src/lib/setup-tools.ts
12743
+ async function writeToolConfigsForRepo(opts) {
12744
+ const { repoRoot, tools, repoName, contextFolder, contextFiles, variant, migrateLegacy } = opts;
12745
+ const results = [];
12746
+ const written = /* @__PURE__ */ new Set();
12747
+ for (const tool of tools) {
12748
+ const config2 = AI_TOOL_CONFIG[tool];
12749
+ if (written.has(config2.filePath)) continue;
12750
+ written.add(config2.filePath);
12751
+ if (migrateLegacy && config2.legacyFilePath) {
12752
+ await migrateToolConfig(repoRoot, tool, repoName, contextFolder, contextFiles, variant);
12753
+ }
12754
+ const { created } = await updateToolConfig(
12755
+ repoRoot,
12756
+ tool,
12757
+ repoName,
12758
+ contextFolder,
12759
+ contextFiles,
12760
+ variant
12761
+ );
12762
+ results.push(`${created ? "Created" : "Updated"} ${config2.filePath}`);
12763
+ }
12764
+ if (!written.has("AGENTS.md")) {
12765
+ const { created } = await updateToolConfig(
12766
+ repoRoot,
12767
+ "codex",
12768
+ repoName,
12769
+ contextFolder,
12770
+ contextFiles,
12771
+ variant
12772
+ );
12773
+ results.push(`${created ? "Created" : "Updated"} AGENTS.md`);
12774
+ }
12775
+ if (tools.includes("claude-code")) {
12776
+ const { relPath } = await writeClaudeHooksToRepo(repoRoot, contextFolder, variant);
12777
+ results.push(`Configured ${relPath} (RepoWise-first + SubagentStart hooks, local-only)`);
12778
+ }
12779
+ return results;
12780
+ }
12781
+
12782
+ // src/lib/mcp-resolver.ts
12783
+ import { basename as basename4, join as join46 } from "path";
12784
+ import { promises as fs21 } from "fs";
12785
+ var MCP_WRITER_TOOLS = /* @__PURE__ */ new Set([
12786
+ "claude-code",
12787
+ "cursor",
12788
+ "copilot",
12789
+ "codex",
12790
+ "windsurf",
12791
+ "cline",
12792
+ "gemini"
12793
+ ]);
12794
+ function mcpPathFor(tool, repoRoot, home) {
12795
+ switch (tool) {
12796
+ case "claude-code":
12797
+ return { path: join46(repoRoot, ".mcp.json"), scope: "repo" };
12798
+ case "cursor":
12799
+ return { path: join46(repoRoot, ".cursor", "mcp.json"), scope: "repo" };
12800
+ case "copilot":
12801
+ return { path: join46(repoRoot, ".vscode", "mcp.json"), scope: "repo" };
12802
+ case "codex":
12803
+ return { path: join46(home, ".codex", "mcp.json"), scope: "global" };
12804
+ case "windsurf":
12805
+ return { path: join46(home, ".codeium", "windsurf", "mcp_config.json"), scope: "global" };
12806
+ case "cline":
12807
+ return { path: join46(home, ".cline", "mcp.json"), scope: "global" };
12808
+ case "gemini":
12809
+ return { path: join46(home, ".gemini", "settings.json"), scope: "global" };
12810
+ default:
12811
+ return null;
12812
+ }
12813
+ }
12814
+ function mcpServerKeyFor(repoRoot) {
12815
+ return `RepoWise MCP for ${basename4(repoRoot)}`;
12816
+ }
12817
+ function mcpConfigTargetForTool(tool, opts) {
12818
+ const loc = mcpPathFor(tool, opts.repoRoot, opts.home);
12819
+ if (!loc) return null;
12820
+ return { tool, path: loc.path, scope: loc.scope, serverKey: mcpServerKeyFor(opts.repoRoot) };
12821
+ }
12822
+ async function mcpConfigState(target) {
12823
+ let raw;
12824
+ try {
12825
+ raw = await fs21.readFile(target.path, "utf-8");
12826
+ } catch {
12827
+ return "absent";
12828
+ }
12829
+ try {
12830
+ const parsed = JSON.parse(raw);
12831
+ return parsed.mcpServers && target.serverKey in parsed.mcpServers ? "configured" : "pending";
12832
+ } catch {
12833
+ return "absent";
12834
+ }
12835
+ }
12836
+ function mcpActivationHint(tool, repoName) {
12837
+ if (!MCP_WRITER_TOOLS.has(tool)) return null;
12838
+ if (tool === "claude-code") {
12839
+ return `In Claude Code, run \`/mcp\` and approve the "RepoWise MCP for ${repoName}" server.`;
12840
+ }
12841
+ return `Reload your AI tool and approve the "RepoWise MCP for ${repoName}" server when prompted.`;
12842
+ }
12843
+ async function mcpStatusForRepo(opts) {
12844
+ const repoName = basename4(opts.repoRoot);
12845
+ const out = [];
12846
+ for (const tool of opts.aiTools) {
12847
+ const target = mcpConfigTargetForTool(tool, { repoRoot: opts.repoRoot, home: opts.home });
12848
+ if (!target) continue;
12849
+ const state = await mcpConfigState(target);
12850
+ if (state === "absent") continue;
12851
+ const configured = state === "configured";
12852
+ out.push({ tool, configured, hint: configured ? null : mcpActivationHint(tool, repoName) });
12853
+ }
12854
+ return out;
12855
+ }
12856
+
12057
12857
  // src/lib/gitignore.ts
12058
12858
  import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync2 } from "fs";
12059
- import { join as join45 } from "path";
12859
+ import { join as join47 } from "path";
12060
12860
  function ensureGitignore(repoRoot, entry) {
12061
- const gitignorePath = join45(repoRoot, ".gitignore");
12861
+ const gitignorePath = join47(repoRoot, ".gitignore");
12062
12862
  if (existsSync2(gitignorePath)) {
12063
12863
  const content = readFileSync2(gitignorePath, "utf-8");
12064
12864
  const lines = content.split("\n").map((l) => l.trim());
@@ -12075,7 +12875,7 @@ function ensureGitignore(repoRoot, entry) {
12075
12875
  // src/lib/graph-cache.ts
12076
12876
  init_config_dir();
12077
12877
  import { mkdirSync, writeFileSync as writeFileSync2, renameSync, unlinkSync } from "fs";
12078
- import { dirname as dirname18, join as join46 } from "path";
12878
+ import { dirname as dirname18, join as join48 } from "path";
12079
12879
  var SAFE_REPO_ID = /^[A-Za-z0-9_.-]{1,128}$/;
12080
12880
  function assertSafeRepoId2(repoId) {
12081
12881
  if (!repoId || typeof repoId !== "string" || !SAFE_REPO_ID.test(repoId) || repoId === "." || repoId === ".." || repoId.startsWith(".")) {
@@ -12084,7 +12884,7 @@ function assertSafeRepoId2(repoId) {
12084
12884
  }
12085
12885
  function graphCachePath(repoId) {
12086
12886
  assertSafeRepoId2(repoId);
12087
- return join46(getConfigDir(), "graphs", `${repoId}.json`);
12887
+ return join48(getConfigDir(), "graphs", `${repoId}.json`);
12088
12888
  }
12089
12889
  function isUsableGraph(parsed) {
12090
12890
  if (typeof parsed !== "object" || parsed === null) return false;
@@ -12513,19 +13313,17 @@ var ProgressRenderer = class {
12513
13313
  });
12514
13314
  }
12515
13315
  /**
12516
- * Coffee-and-context message shown once, after the user-driven interview
12517
- * completes. This is the gate between user input and the long
12518
- * 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.
12519
13322
  */
12520
13323
  renderInterviewComplete(spinner) {
12521
13324
  if (this.interviewCompleteShown) return;
12522
13325
  this.interviewCompleteShown = true;
12523
13326
  spinner.stop();
12524
- console.log("");
12525
- console.log(
12526
- chalk4.cyan(" \u2615 Sit back and grab a coffee \u2014 we'll handle the rest from here.")
12527
- );
12528
- console.log("");
12529
13327
  spinner.start();
12530
13328
  }
12531
13329
  renderScanProgress(progress, spinner) {
@@ -12700,17 +13498,27 @@ var ProgressRenderer = class {
12700
13498
  if (generating) {
12701
13499
  const genBaseName = generating.fileName.split("/").pop() ?? generating.fileName;
12702
13500
  const isCore = CORE_FILES.has(genBaseName);
12703
- const sectionFiles = gp.fileStatuses.filter((f) => {
13501
+ const sectionFiles2 = gp.fileStatuses.filter((f) => {
12704
13502
  const bn = f.fileName.split("/").pop() ?? f.fileName;
12705
13503
  return isCore ? CORE_FILES.has(bn) : !CORE_FILES.has(bn);
12706
13504
  });
12707
- const sectionCompleted = sectionFiles.filter((f) => f.status === "completed").length;
12708
- return `${generating.fileName} (${sectionCompleted}/${sectionFiles.length}) ${chalk4.dim(`(${overallPct}%)`)}`;
13505
+ const sectionCompleted = sectionFiles2.filter((f) => f.status === "completed").length;
13506
+ return `${generating.fileName} (${sectionCompleted}/${sectionFiles2.length}) ${chalk4.dim(`(${overallPct}%)`)}`;
12709
13507
  }
12710
13508
  const allDone = gp.fileStatuses.every((f) => f.status === "completed");
12711
13509
  if (allDone) {
12712
13510
  return `${stepLabel}... ${chalk4.dim(`(${overallPct}%)`)}`;
12713
13511
  }
13512
+ const baseNameOf = (f) => f.fileName.split("/").pop() ?? f.fileName;
13513
+ const coreFiles = gp.fileStatuses.filter((f) => CORE_FILES.has(baseNameOf(f)));
13514
+ const optionalFiles = gp.fileStatuses.filter((f) => !CORE_FILES.has(baseNameOf(f)));
13515
+ const coreDone = coreFiles.length > 0 && coreFiles.every((f) => f.status === "completed");
13516
+ const sectionFiles = coreDone && optionalFiles.length > 0 ? optionalFiles : coreFiles;
13517
+ if (sectionFiles.length > 0) {
13518
+ const sectionCompleted = sectionFiles.filter((f) => f.status === "completed").length;
13519
+ const name = gp.currentFileName || stepLabel;
13520
+ return `${name} (${sectionCompleted}/${sectionFiles.length}) ${chalk4.dim(`(${overallPct}%)`)}`;
13521
+ }
12714
13522
  }
12715
13523
  }
12716
13524
  return `${stepLabel}... ${chalk4.dim(`(${overallPct}%)`)}`;
@@ -12801,8 +13609,8 @@ async function promptDepInstallConsent(missing, opts) {
12801
13609
  import chalk6 from "chalk";
12802
13610
 
12803
13611
  // src/lib/dep-installer.ts
12804
- import { promises as fs21 } from "fs";
12805
- import { join as join47 } from "path";
13612
+ import { promises as fs22 } from "fs";
13613
+ import { join as join49 } from "path";
12806
13614
  var SUPPORTED_DEP_LANGUAGES = /* @__PURE__ */ new Set([
12807
13615
  "typescript",
12808
13616
  "javascript",
@@ -12812,14 +13620,14 @@ var SUPPORTED_DEP_LANGUAGES = /* @__PURE__ */ new Set([
12812
13620
  ]);
12813
13621
  var exists = async (p) => {
12814
13622
  try {
12815
- await fs21.access(p);
13623
+ await fs22.access(p);
12816
13624
  return true;
12817
13625
  } catch {
12818
13626
  return false;
12819
13627
  }
12820
13628
  };
12821
13629
  async function fileExists16(repoRoot, name) {
12822
- return exists(join47(repoRoot, name));
13630
+ return exists(join49(repoRoot, name));
12823
13631
  }
12824
13632
  async function detectNodePackageManager(repoRoot) {
12825
13633
  if (await fileExists16(repoRoot, "pnpm-lock.yaml")) {
@@ -12896,13 +13704,13 @@ async function detectMissingDeps(repoRoot, scopedLanguages) {
12896
13704
  // src/lib/dep-installer-runner.ts
12897
13705
  import { spawn as spawn11 } from "child_process";
12898
13706
  import { createWriteStream as createWriteStream3 } from "fs";
12899
- import { promises as fs22 } from "fs";
12900
- import { join as join48 } from "path";
13707
+ import { promises as fs23 } from "fs";
13708
+ import { join as join50 } from "path";
12901
13709
  var DEFAULT_INSTALL_TIMEOUT_MS = 10 * 60 * 1e3;
12902
13710
  async function runMissingDepInstalls(opts) {
12903
13711
  const safeRepoId = opts.repoId.replace(/[^a-zA-Z0-9_-]/g, "_");
12904
- const logPath2 = join48(getConfigDir2(), `install-log.${safeRepoId}.txt`);
12905
- await fs22.mkdir(getConfigDir2(), { recursive: true });
13712
+ const logPath2 = join50(getConfigDir2(), `install-log.${safeRepoId}.txt`);
13713
+ await fs23.mkdir(getConfigDir2(), { recursive: true });
12906
13714
  const stream = opts.logStream ?? createWriteStream3(logPath2, { flags: "a" });
12907
13715
  stream.on("error", () => {
12908
13716
  });
@@ -12948,7 +13756,7 @@ async function runOne2(dep, repoRoot, stream, timeoutMs) {
12948
13756
  }
12949
13757
  async function runPipFlow(repoRoot, stream, timeoutMs) {
12950
13758
  await runSimple(["python3", "-m", "venv", ".venv"], repoRoot, stream, timeoutMs);
12951
- const venvPip = process.platform === "win32" ? join48(".venv", "Scripts", "pip.exe") : join48(".venv", "bin", "pip");
13759
+ const venvPip = process.platform === "win32" ? join50(".venv", "Scripts", "pip.exe") : join50(".venv", "bin", "pip");
12952
13760
  await runSimple([venvPip, "install", "-r", "requirements.txt"], repoRoot, stream, timeoutMs);
12953
13761
  }
12954
13762
  function runSimple(cmd, repoRoot, stream, timeoutMs) {
@@ -13107,13 +13915,15 @@ function reportLspInstallOutcomes(results) {
13107
13915
  // src/commands/create.ts
13108
13916
  function formatElapsed(ms) {
13109
13917
  const totalSeconds = Math.round(ms / 1e3);
13110
- const minutes = Math.floor(totalSeconds / 60);
13918
+ const hours = Math.floor(totalSeconds / 3600);
13919
+ const minutes = Math.floor(totalSeconds % 3600 / 60);
13111
13920
  const seconds = totalSeconds % 60;
13921
+ if (hours > 0) return minutes > 0 ? `${hours}h ${minutes}m` : `${hours}h`;
13112
13922
  if (minutes === 0) return `${seconds}s`;
13113
13923
  return seconds > 0 ? `${minutes}m ${seconds}s` : `${minutes}m`;
13114
13924
  }
13115
13925
  var POLL_INTERVAL_MS = 3e3;
13116
- var MAX_POLL_ATTEMPTS = 7200;
13926
+ var MAX_POLL_ATTEMPTS = 14600;
13117
13927
  var DEFAULT_CONTEXT_FOLDER = "repowise-context";
13118
13928
  function buildWatchedRepoEntry(args) {
13119
13929
  const entry = {
@@ -13286,9 +14096,7 @@ async function create() {
13286
14096
  }
13287
14097
  if (tools.length === 0 && !hasOther) {
13288
14098
  console.log(
13289
- chalk8.yellow(
13290
- "\nNo AI tools selected. You can configure them later with `repowise config`."
13291
- )
14099
+ chalk8.yellow("\nNo AI tools selected. You can set them up later with `repowise tools`.")
13292
14100
  );
13293
14101
  }
13294
14102
  if (repoRoot) {
@@ -13358,6 +14166,8 @@ async function create() {
13358
14166
  }
13359
14167
  let pollAttempts = 0;
13360
14168
  let pollErrors = 0;
14169
+ let graphEarlyWriteDone = false;
14170
+ let nonAwaitingPolls = 0;
13361
14171
  const MAX_POLL_ERRORS = 5;
13362
14172
  const progressRenderer = new ProgressRenderer();
13363
14173
  let depInstallShown = false;
@@ -13428,8 +14238,17 @@ async function create() {
13428
14238
  if (!interviewComplete && graphOnly) {
13429
14239
  interviewComplete = true;
13430
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();
13431
14248
  }
13432
14249
  progressRenderer.update(syncResult, spinner);
14250
+ if (syncResult.status === "awaiting_input") nonAwaitingPolls = 0;
14251
+ else nonAwaitingPolls += 1;
13433
14252
  if (!graphOnly && syncResult.status === "in_progress") {
13434
14253
  if (syncResult.lspWaitState === "awaiting" || syncResult.lspWaitState === "analyzing") {
13435
14254
  lspWaitStartedAt ??= Date.now();
@@ -13443,6 +14262,45 @@ async function create() {
13443
14262
  spinner.start();
13444
14263
  }
13445
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
+ }
13446
14304
  if (syncResult.status === "awaiting_input") {
13447
14305
  seenAwaitingInput = true;
13448
14306
  }
@@ -13521,7 +14379,7 @@ async function create() {
13521
14379
  const listResult = await apiRequest(`/v1/repos/${repoId}/context`);
13522
14380
  const files = listResult.data?.files ?? listResult.files ?? [];
13523
14381
  if (files.length > 0) {
13524
- const contextDir = join49(repoRoot, DEFAULT_CONTEXT_FOLDER);
14382
+ const contextDir = join51(repoRoot, DEFAULT_CONTEXT_FOLDER);
13525
14383
  mkdirSync2(contextDir, { recursive: true });
13526
14384
  let downloadedCount = 0;
13527
14385
  let failedCount = 0;
@@ -13535,7 +14393,7 @@ async function create() {
13535
14393
  const response = await fetch(presignedUrl);
13536
14394
  if (response.ok) {
13537
14395
  const content = await response.text();
13538
- const filePath = join49(contextDir, file.fileName);
14396
+ const filePath = join51(contextDir, file.fileName);
13539
14397
  mkdirSync2(dirname19(filePath), { recursive: true });
13540
14398
  writeFileSync3(filePath, content, "utf-8");
13541
14399
  downloadedCount++;
@@ -13619,51 +14477,24 @@ Files are stored on our servers (not in git). Retry when online.`
13619
14477
  }
13620
14478
  if (repoRoot) {
13621
14479
  spinner.start("Configuring AI tools...");
13622
- const results = [];
13623
- const written = /* @__PURE__ */ new Set();
13624
- const toolVariant = graphOnly ? "graph" : "full";
13625
- for (const tool of tools) {
13626
- const config2 = AI_TOOL_CONFIG[tool];
13627
- if (written.has(config2.filePath)) continue;
13628
- written.add(config2.filePath);
13629
- if (config2.legacyFilePath) {
13630
- await migrateToolConfig(
13631
- repoRoot,
13632
- tool,
13633
- repoName,
13634
- contextFolder,
13635
- contextFiles,
13636
- toolVariant
13637
- );
13638
- }
13639
- const { created: wasCreated } = await updateToolConfig(
13640
- repoRoot,
13641
- tool,
13642
- repoName,
13643
- contextFolder,
13644
- contextFiles,
13645
- toolVariant
13646
- );
13647
- const action = wasCreated ? "Created" : "Updated";
13648
- results.push(` ${action} ${config2.filePath}`);
13649
- }
13650
- if (!written.has("AGENTS.md")) {
13651
- const { created: wasCreated } = await updateToolConfig(
13652
- repoRoot,
13653
- "codex",
13654
- repoName,
13655
- contextFolder,
13656
- contextFiles,
13657
- toolVariant
14480
+ const results = await writeToolConfigsForRepo({
14481
+ repoRoot,
14482
+ tools,
14483
+ repoName,
14484
+ contextFolder,
14485
+ contextFiles,
14486
+ variant: graphOnly ? "graph" : "full",
14487
+ migrateLegacy: true
14488
+ });
14489
+ spinner.succeed("AI tools configured");
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
+ )
13658
14496
  );
13659
- results.push(` ${wasCreated ? "Created" : "Updated"} AGENTS.md`);
13660
- }
13661
- if (tools.includes("claude-code")) {
13662
- const { relPath } = await writeClaudeHooksToRepo(repoRoot, contextFolder, toolVariant);
13663
- results.push(` Configured ${relPath} (RepoWise-first + SubagentStart hooks, local-only)`);
13664
14497
  }
13665
- spinner.succeed("AI tools configured");
13666
- console.log(chalk8.dim(results.join("\n")));
13667
14498
  }
13668
14499
  const priorAutoInstall = await getPriorConsent(repoId);
13669
14500
  const updatedRepos = [];
@@ -13772,6 +14603,11 @@ Files are stored on our servers (not in git). Retry when online.`
13772
14603
  console.log(
13773
14604
  ` ${chalk8.cyan("\u2022")} Open Claude Code / Cursor and ask: "What does this repo do?"`
13774
14605
  );
14606
+ if (tools.some((t) => MCP_WRITER_TOOLS.has(t))) {
14607
+ console.log(
14608
+ ` ${chalk8.cyan("\u2022")} If your AI tool asks to approve the "RepoWise MCP" server, approve it (in Claude Code, run ${chalk8.cyan("/mcp")}) \u2014 it can take a minute to appear.`
14609
+ );
14610
+ }
13775
14611
  console.log(
13776
14612
  ` ${chalk8.cyan("\u2022")} Head to the dashboard \u2192 "Complete Onboarding" to explore quality scores and gaps.`
13777
14613
  );
@@ -13786,7 +14622,7 @@ Files are stored on our servers (not in git). Retry when online.`
13786
14622
 
13787
14623
  // src/commands/member.ts
13788
14624
  import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync4 } from "fs";
13789
- import { dirname as dirname20, join as join50, resolve, sep } from "path";
14625
+ import { dirname as dirname20, join as join52, resolve, sep } from "path";
13790
14626
  import chalk9 from "chalk";
13791
14627
  import ora2 from "ora";
13792
14628
  var DEFAULT_CONTEXT_FOLDER2 = "repowise-context";
@@ -13940,7 +14776,7 @@ async function member() {
13940
14776
  spinner.succeed(`Found ${chalk9.bold(files.length)} context files on server`);
13941
14777
  const { tools } = await selectAiTools();
13942
14778
  spinner.start("Downloading context files...");
13943
- const contextDir = join50(repoRoot, DEFAULT_CONTEXT_FOLDER2);
14779
+ const contextDir = join52(repoRoot, DEFAULT_CONTEXT_FOLDER2);
13944
14780
  mkdirSync3(contextDir, { recursive: true });
13945
14781
  let downloadedCount = 0;
13946
14782
  let failedCount = 0;
@@ -13985,35 +14821,14 @@ async function member() {
13985
14821
  {
13986
14822
  spinner.start("Configuring AI tools...");
13987
14823
  const contextFiles = await scanLocalContextFiles(repoRoot, DEFAULT_CONTEXT_FOLDER2);
13988
- const configured = [];
13989
- const written = /* @__PURE__ */ new Set();
13990
- for (const tool of tools) {
13991
- const config2 = AI_TOOL_CONFIG[tool];
13992
- if (written.has(config2.filePath)) continue;
13993
- written.add(config2.filePath);
13994
- const { created } = await updateToolConfig(
13995
- repoRoot,
13996
- tool,
13997
- repoName,
13998
- DEFAULT_CONTEXT_FOLDER2,
13999
- contextFiles
14000
- );
14001
- configured.push(`${created ? "Created" : "Updated"} ${config2.filePath}`);
14002
- }
14003
- if (!written.has("AGENTS.md")) {
14004
- const { created } = await updateToolConfig(
14005
- repoRoot,
14006
- "codex",
14007
- repoName,
14008
- DEFAULT_CONTEXT_FOLDER2,
14009
- contextFiles
14010
- );
14011
- configured.push(`${created ? "Created" : "Updated"} AGENTS.md`);
14012
- }
14013
- if (tools.includes("claude-code")) {
14014
- const { relPath } = await writeClaudeHooksToRepo(repoRoot, DEFAULT_CONTEXT_FOLDER2);
14015
- configured.push(`Configured ${relPath} (RepoWise-first + SubagentStart hooks, local-only)`);
14016
- }
14824
+ const configured = await writeToolConfigsForRepo({
14825
+ repoRoot,
14826
+ tools,
14827
+ repoName,
14828
+ contextFolder: DEFAULT_CONTEXT_FOLDER2,
14829
+ contextFiles
14830
+ // member historically passed no variant and did not migrate legacy paths.
14831
+ });
14017
14832
  spinner.succeed("AI tools configured");
14018
14833
  for (const msg of configured) {
14019
14834
  console.log(chalk9.dim(` ${msg}`));
@@ -14134,19 +14949,20 @@ async function member() {
14134
14949
  }
14135
14950
 
14136
14951
  // src/commands/login.ts
14952
+ import { homedir as homedir9 } from "os";
14137
14953
  import chalk10 from "chalk";
14138
14954
  import ora3 from "ora";
14139
14955
 
14140
14956
  // src/lib/tenant-graph-purge.ts
14141
- import { promises as fs23 } from "fs";
14957
+ import { promises as fs24 } from "fs";
14142
14958
  import { homedir as homedir8 } from "os";
14143
- import { join as join51 } from "path";
14959
+ import { join as join53 } from "path";
14144
14960
  async function purgeForeignGraphs(validRepoIds, home = homedir8()) {
14145
- const graphsDir = join51(home, ".repowise", "graphs");
14961
+ const graphsDir = join53(home, ".repowise", "graphs");
14146
14962
  const result = { kept: [], removed: [] };
14147
14963
  let entries;
14148
14964
  try {
14149
- entries = await fs23.readdir(graphsDir);
14965
+ entries = await fs24.readdir(graphsDir);
14150
14966
  } catch (err) {
14151
14967
  if (err.code === "ENOENT") return result;
14152
14968
  throw err;
@@ -14160,15 +14976,15 @@ async function purgeForeignGraphs(validRepoIds, home = homedir8()) {
14160
14976
  result.kept.push(entry);
14161
14977
  continue;
14162
14978
  }
14163
- const path = join51(graphsDir, entry);
14979
+ const path = join53(graphsDir, entry);
14164
14980
  try {
14165
- const stat8 = await fs23.lstat(path);
14981
+ const stat8 = await fs24.lstat(path);
14166
14982
  if (stat8.isSymbolicLink()) {
14167
- await fs23.unlink(path);
14983
+ await fs24.unlink(path);
14168
14984
  result.removed.push(entry);
14169
14985
  continue;
14170
14986
  }
14171
- await fs23.rm(path, { recursive: true, force: true });
14987
+ await fs24.rm(path, { recursive: true, force: true });
14172
14988
  result.removed.push(entry);
14173
14989
  } catch {
14174
14990
  }
@@ -14196,8 +15012,8 @@ Waiting for authentication...`);
14196
15012
  } else {
14197
15013
  spinner.text = "Opening browser for authentication...";
14198
15014
  try {
14199
- const open4 = (await import("open")).default;
14200
- await open4(authorizeUrl);
15015
+ const open6 = (await import("open")).default;
15016
+ await open6(authorizeUrl);
14201
15017
  spinner.text = "Waiting for authentication in browser...";
14202
15018
  } catch {
14203
15019
  spinner.stop();
@@ -14232,6 +15048,23 @@ Waiting for authentication...`);
14232
15048
  }
14233
15049
  } catch {
14234
15050
  }
15051
+ try {
15052
+ const repoRoot = detectRepoRoot();
15053
+ const cfg = await getConfig();
15054
+ const registered = (cfg.repos ?? []).some((r) => r.localPath === repoRoot);
15055
+ const tools = cfg.aiTools ?? [];
15056
+ if (registered && tools.length > 0) {
15057
+ const pending = (await mcpStatusForRepo({ repoRoot, home: homedir9(), aiTools: tools })).filter((s) => !s.configured);
15058
+ if (pending.length > 0) {
15059
+ console.log("");
15060
+ console.log(chalk10.dim(" To finish enabling RepoWise in your AI tools:"));
15061
+ for (const p of pending) {
15062
+ if (p.hint) console.log(chalk10.dim(` \u2022 ${p.hint}`));
15063
+ }
15064
+ }
15065
+ }
15066
+ } catch {
15067
+ }
14235
15068
  } catch (err) {
14236
15069
  const message = err instanceof Error ? err.message : "Login failed";
14237
15070
  spinner.fail(chalk10.red(message));
@@ -14247,20 +15080,25 @@ async function logout() {
14247
15080
  console.log(chalk11.yellow("Not logged in."));
14248
15081
  return;
14249
15082
  }
15083
+ try {
15084
+ await revokeRefreshToken(creds.refreshToken);
15085
+ } catch {
15086
+ }
14250
15087
  await clearCredentials();
14251
15088
  console.log(chalk11.green("Logged out successfully."));
14252
15089
  }
14253
15090
 
14254
15091
  // src/commands/status.ts
14255
- import { readFile as readFile16 } from "fs/promises";
14256
- import { basename as basename4, join as join52 } from "path";
15092
+ import { readFile as readFile17 } from "fs/promises";
15093
+ import { basename as basename5, join as join54 } from "path";
15094
+ import { homedir as homedir10 } from "os";
14257
15095
  async function status() {
14258
15096
  const configDir = getConfigDir2();
14259
- const STATE_PATH = join52(configDir, "listener-state.json");
14260
- const CONFIG_PATH = join52(configDir, "config.json");
15097
+ const STATE_PATH = join54(configDir, "listener-state.json");
15098
+ const CONFIG_PATH = join54(configDir, "config.json");
14261
15099
  let state = null;
14262
15100
  try {
14263
- const data = await readFile16(STATE_PATH, "utf-8");
15101
+ const data = await readFile17(STATE_PATH, "utf-8");
14264
15102
  state = JSON.parse(data);
14265
15103
  } catch {
14266
15104
  }
@@ -14279,42 +15117,85 @@ async function status() {
14279
15117
  } else {
14280
15118
  console.log("Auto-start: disabled");
14281
15119
  }
15120
+ let creds = null;
15121
+ try {
15122
+ creds = await getStoredCredentials2();
15123
+ } catch {
15124
+ }
15125
+ if (isCredsHardExpired(creds)) {
15126
+ console.log("Authentication: not logged in \u2014 run `repowise login`");
15127
+ } else {
15128
+ let email = null;
15129
+ try {
15130
+ const decoded = creds?.idToken ? decodeIdToken(creds.idToken).email : null;
15131
+ email = decoded && decoded !== "unknown" ? decoded : null;
15132
+ } catch {
15133
+ }
15134
+ console.log(`Authentication: logged in${email ? ` as ${email}` : ""}`);
15135
+ }
14282
15136
  console.log("");
14283
15137
  if (!state || Object.keys(state.repos).length === 0) {
14284
15138
  console.log("No sync history. Run `repowise listen` to start syncing.");
14285
15139
  return;
14286
15140
  }
14287
15141
  const repoNames = /* @__PURE__ */ new Map();
15142
+ const repoPaths = /* @__PURE__ */ new Map();
15143
+ let aiTools = [];
14288
15144
  try {
14289
- const configData = await readFile16(CONFIG_PATH, "utf-8");
15145
+ const configData = await readFile17(CONFIG_PATH, "utf-8");
14290
15146
  const config2 = JSON.parse(configData);
14291
15147
  for (const repo of config2.repos ?? []) {
14292
- repoNames.set(repo.repoId, basename4(repo.localPath));
15148
+ repoNames.set(repo.repoId, basename5(repo.localPath));
15149
+ repoPaths.set(repo.repoId, repo.localPath);
14293
15150
  }
15151
+ aiTools = config2.aiTools ?? [];
14294
15152
  } catch {
14295
15153
  }
14296
15154
  console.log("Watched Repos:");
15155
+ const home = homedir10();
14297
15156
  for (const [repoId, repoState] of Object.entries(state.repos)) {
14298
15157
  const name = repoNames.get(repoId);
14299
15158
  const label = name ? `${name} (${repoId.slice(0, 8)})` : repoId;
14300
15159
  const syncTime = repoState.lastSyncTimestamp ? new Date(repoState.lastSyncTimestamp).toLocaleString() : "never";
14301
15160
  const commit = repoState.lastSyncCommitSha ? repoState.lastSyncCommitSha.slice(0, 7) : "none";
14302
15161
  console.log(` ${label}: last sync ${syncTime} (commit: ${commit})`);
15162
+ const localPath = repoPaths.get(repoId);
15163
+ if (localPath && aiTools.length > 0) {
15164
+ try {
15165
+ const statuses = await mcpStatusForRepo({ repoRoot: localPath, home, aiTools });
15166
+ if (statuses.length > 0) {
15167
+ const pending = statuses.filter((s) => !s.configured);
15168
+ if (pending.length === 0) {
15169
+ console.log(` MCP: active in all ${statuses.length} tool(s)`);
15170
+ } else {
15171
+ console.log(
15172
+ ` MCP: ${statuses.length - pending.length}/${statuses.length} tool(s) active \u2014 to finish:`
15173
+ );
15174
+ for (const p of pending) {
15175
+ if (p.hint) console.log(` \u2022 ${p.hint}`);
15176
+ }
15177
+ }
15178
+ }
15179
+ } catch {
15180
+ }
15181
+ }
14303
15182
  }
14304
15183
  }
14305
15184
 
14306
15185
  // src/commands/sync.ts
14307
15186
  import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync5 } from "fs";
14308
- import { dirname as dirname21, join as join53 } from "path";
15187
+ import { dirname as dirname21, join as join55 } from "path";
14309
15188
  import chalk12 from "chalk";
14310
15189
  import ora4 from "ora";
14311
15190
  var POLL_INTERVAL_MS2 = 3e3;
14312
- var MAX_POLL_ATTEMPTS2 = 7200;
15191
+ var MAX_POLL_ATTEMPTS2 = 14600;
14313
15192
  var DEFAULT_CONTEXT_FOLDER3 = "repowise-context";
14314
15193
  function formatElapsed3(ms) {
14315
15194
  const totalSeconds = Math.round(ms / 1e3);
14316
- const minutes = Math.floor(totalSeconds / 60);
15195
+ const hours = Math.floor(totalSeconds / 3600);
15196
+ const minutes = Math.floor(totalSeconds % 3600 / 60);
14317
15197
  const seconds = totalSeconds % 60;
15198
+ if (hours > 0) return minutes > 0 ? `${hours}h ${minutes}m` : `${hours}h`;
14318
15199
  if (minutes === 0) return `${seconds}s`;
14319
15200
  return seconds > 0 ? `${minutes}m ${seconds}s` : `${minutes}m`;
14320
15201
  }
@@ -14454,7 +15335,7 @@ async function sync() {
14454
15335
  const listResult = await apiRequest(`/v1/repos/${repoId}/context`);
14455
15336
  const files = listResult.data?.files ?? listResult.files ?? [];
14456
15337
  if (files.length > 0) {
14457
- const contextDir = join53(repoRoot, DEFAULT_CONTEXT_FOLDER3);
15338
+ const contextDir = join55(repoRoot, DEFAULT_CONTEXT_FOLDER3);
14458
15339
  mkdirSync4(contextDir, { recursive: true });
14459
15340
  let downloadedCount = 0;
14460
15341
  let failedCount = 0;
@@ -14468,7 +15349,7 @@ async function sync() {
14468
15349
  const response = await fetch(presignedUrl);
14469
15350
  if (response.ok) {
14470
15351
  const content = await response.text();
14471
- const filePath = join53(contextDir, file.fileName);
15352
+ const filePath = join55(contextDir, file.fileName);
14472
15353
  mkdirSync4(dirname21(filePath), { recursive: true });
14473
15354
  writeFileSync5(filePath, content, "utf-8");
14474
15355
  downloadedCount++;
@@ -14721,10 +15602,158 @@ async function config() {
14721
15602
  }
14722
15603
  }
14723
15604
 
15605
+ // src/commands/tools.ts
15606
+ import { basename as basename6 } from "path";
15607
+ import chalk14 from "chalk";
15608
+ import ora6 from "ora";
15609
+ var DEFAULT_CONTEXT_FOLDER4 = "repowise-context";
15610
+ var VALID_TOOLS = Object.keys(AI_TOOL_CONFIG);
15611
+ async function resolveRepoOrExit() {
15612
+ let creds = null;
15613
+ try {
15614
+ creds = await getValidCredentials2();
15615
+ } catch {
15616
+ creds = null;
15617
+ }
15618
+ if (!creds) {
15619
+ console.error(chalk14.red("Not logged in. Run `repowise login` first."));
15620
+ process.exitCode = 1;
15621
+ return null;
15622
+ }
15623
+ let repoRoot;
15624
+ try {
15625
+ repoRoot = detectRepoRoot();
15626
+ } catch {
15627
+ console.error(chalk14.red("Not inside a git repository \u2014 `cd` into your repo first."));
15628
+ process.exitCode = 1;
15629
+ return null;
15630
+ }
15631
+ const config2 = await getConfig();
15632
+ const registered = (config2.repos ?? []).some((r) => r.localPath === repoRoot);
15633
+ if (!registered) {
15634
+ console.error(
15635
+ chalk14.red("This repo isn't set up with RepoWise yet. Run `repowise create` first.")
15636
+ );
15637
+ process.exitCode = 1;
15638
+ return null;
15639
+ }
15640
+ return {
15641
+ repoRoot,
15642
+ repoName: detectRepoName(repoRoot),
15643
+ current: config2.aiTools ?? [],
15644
+ contextFolder: config2.contextFolder ?? DEFAULT_CONTEXT_FOLDER4,
15645
+ variant: config2.graphOnly ? "graph" : "full"
15646
+ };
15647
+ }
15648
+ async function applyToolSelection(ctx, next) {
15649
+ const added = next.filter((t) => !ctx.current.includes(t));
15650
+ const spinner = ora6("Configuring AI tools...").start();
15651
+ const contextFiles = await scanLocalContextFiles(ctx.repoRoot, ctx.contextFolder);
15652
+ const results = await writeToolConfigsForRepo({
15653
+ repoRoot: ctx.repoRoot,
15654
+ tools: next,
15655
+ repoName: ctx.repoName,
15656
+ contextFolder: ctx.contextFolder,
15657
+ contextFiles,
15658
+ variant: ctx.variant,
15659
+ migrateLegacy: true
15660
+ });
15661
+ await mergeAndSaveConfig({ aiTools: next });
15662
+ spinner.succeed("AI tools configured");
15663
+ console.log(chalk14.dim(results.map((r) => ` ${r}`).join("\n")));
15664
+ try {
15665
+ await ensureListenerRunning();
15666
+ } catch {
15667
+ }
15668
+ const serverRepoName = basename6(ctx.repoRoot);
15669
+ const newMcp = added.filter((t) => MCP_WRITER_TOOLS.has(t));
15670
+ if (newMcp.length > 0) {
15671
+ console.log("");
15672
+ console.log(
15673
+ chalk14.dim(
15674
+ " The listener will wire these into your AI tools' MCP within a few seconds, then:"
15675
+ )
15676
+ );
15677
+ for (const t of newMcp) {
15678
+ const hint = mcpActivationHint(t, serverRepoName);
15679
+ if (hint) console.log(chalk14.dim(` \u2022 ${hint}`));
15680
+ }
15681
+ }
15682
+ }
15683
+ async function pickUnion(ctx) {
15684
+ const { tools: picked, hasOther } = await selectAiTools({ defaults: ctx.current });
15685
+ const removed = ctx.current.filter((t) => !picked.includes(t));
15686
+ if (removed.length > 0) {
15687
+ console.log(
15688
+ chalk14.yellow(`Note: removing tools isn't supported yet \u2014 keeping ${removed.join(", ")}.`)
15689
+ );
15690
+ }
15691
+ return { next: Array.from(/* @__PURE__ */ new Set([...ctx.current, ...picked])), hasOther };
15692
+ }
15693
+ function reportNothingNew(hasOther) {
15694
+ if (hasOther) {
15695
+ console.log(
15696
+ chalk14.dim(
15697
+ "For tools not listed, the instruction files work with any tool that reads the filesystem.\nTo request full support for a new AI tool, email support@repowise.ai"
15698
+ )
15699
+ );
15700
+ } else {
15701
+ console.log(chalk14.dim("No new tools added."));
15702
+ }
15703
+ }
15704
+ async function toolsPick() {
15705
+ const ctx = await resolveRepoOrExit();
15706
+ if (!ctx) return;
15707
+ const { next, hasOther } = await pickUnion(ctx);
15708
+ if (next.length === ctx.current.length) {
15709
+ reportNothingNew(hasOther);
15710
+ return;
15711
+ }
15712
+ await applyToolSelection(ctx, next);
15713
+ }
15714
+ async function toolsAdd(list) {
15715
+ const ctx = await resolveRepoOrExit();
15716
+ if (!ctx) return;
15717
+ let next;
15718
+ let hasOther = false;
15719
+ if (list.length === 0) {
15720
+ ({ next, hasOther } = await pickUnion(ctx));
15721
+ } else {
15722
+ const invalid = list.filter((t) => !VALID_TOOLS.includes(t));
15723
+ if (invalid.length > 0) {
15724
+ console.error(chalk14.red(`Unknown tool(s): ${invalid.join(", ")}`));
15725
+ console.error(chalk14.dim(`Valid tools: ${VALID_TOOLS.join(", ")}`));
15726
+ process.exitCode = 1;
15727
+ return;
15728
+ }
15729
+ next = Array.from(/* @__PURE__ */ new Set([...ctx.current, ...list]));
15730
+ }
15731
+ if (next.length === ctx.current.length) {
15732
+ if (list.length === 0) reportNothingNew(hasOther);
15733
+ else console.log(chalk14.dim("Nothing new to add \u2014 those tools are already configured."));
15734
+ return;
15735
+ }
15736
+ await applyToolSelection(ctx, next);
15737
+ }
15738
+ async function toolsList() {
15739
+ const ctx = await resolveRepoOrExit();
15740
+ if (!ctx) return;
15741
+ if (ctx.current.length === 0) {
15742
+ console.log("No AI tools configured. Run `repowise tools` to add some.");
15743
+ return;
15744
+ }
15745
+ console.log(chalk14.bold("Configured AI tools:"));
15746
+ for (const t of ctx.current) {
15747
+ const label = AI_TOOL_CONFIG[t]?.label ?? t;
15748
+ const suffix = MCP_WRITER_TOOLS.has(t) ? "" : chalk14.dim(" (instruction files only)");
15749
+ console.log(` \u2022 ${label}${suffix}`);
15750
+ }
15751
+ }
15752
+
14724
15753
  // src/commands/mcp-log.ts
14725
15754
  import { createDecipheriv as createDecipheriv2 } from "crypto";
14726
- import { mkdir as mkdir19, readFile as readFile17, stat as stat7, writeFile as writeFile18 } from "fs/promises";
14727
- import { dirname as dirname22, join as join54 } 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";
14728
15757
  var FLAG_FILE = "mcp-log.flag";
14729
15758
  var LOG_FILE = "mcp-log.jsonl.enc";
14730
15759
  var KEY_FILE = "mcp-log.key";
@@ -14732,20 +15761,20 @@ var ENDPOINT_FILE = "listener.endpoint";
14732
15761
  var IV_BYTES2 = 12;
14733
15762
  var TAG_BYTES2 = 16;
14734
15763
  function flagPath() {
14735
- return join54(getConfigDir2(), FLAG_FILE);
15764
+ return join56(getConfigDir2(), FLAG_FILE);
14736
15765
  }
14737
15766
  function logPath() {
14738
- return join54(getConfigDir2(), LOG_FILE);
15767
+ return join56(getConfigDir2(), LOG_FILE);
14739
15768
  }
14740
15769
  async function writeFlag(flag) {
14741
15770
  const path = flagPath();
14742
15771
  await mkdir19(dirname22(path), { recursive: true });
14743
- 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 });
14744
15773
  }
14745
15774
  async function mcpLogOn() {
14746
15775
  let flagOnDisk = null;
14747
15776
  try {
14748
- flagOnDisk = JSON.parse(await readFile17(flagPath(), "utf-8"));
15777
+ flagOnDisk = JSON.parse(await readFile18(flagPath(), "utf-8"));
14749
15778
  } catch {
14750
15779
  flagOnDisk = null;
14751
15780
  }
@@ -14755,11 +15784,10 @@ async function mcpLogOn() {
14755
15784
  enabled: true
14756
15785
  };
14757
15786
  await writeFlag(next);
14758
- process.stderr.write(`MCP logging re-enabled. Log file: ${logPath()}
14759
- `);
15787
+ process.stderr.write("MCP telemetry re-enabled. The anonymous usage heartbeat will resume.\n");
14760
15788
  return;
14761
15789
  }
14762
- process.stderr.write("MCP logging is already enabled.\n");
15790
+ process.stderr.write("MCP telemetry is already enabled.\n");
14763
15791
  if (!flagOnDisk || !flagOnDisk.consentSentToServer) {
14764
15792
  const synced = await trySendConsentToServer();
14765
15793
  if (synced) {
@@ -14776,14 +15804,14 @@ async function trySendConsentToServer() {
14776
15804
  let apiUrl = null;
14777
15805
  let token = null;
14778
15806
  try {
14779
- const body = await readFile17(join54(getConfigDir2(), "config.json"), "utf-8");
15807
+ const body = await readFile18(join56(getConfigDir2(), "config.json"), "utf-8");
14780
15808
  const parsed = JSON.parse(body);
14781
15809
  apiUrl = parsed.repos?.find((r) => Boolean(r.apiUrl))?.apiUrl ?? parsed.defaultApiUrl ?? null;
14782
15810
  } catch {
14783
15811
  return false;
14784
15812
  }
14785
15813
  try {
14786
- const body = await readFile17(join54(getConfigDir2(), "credentials.json"), "utf-8");
15814
+ const body = await readFile18(join56(getConfigDir2(), "credentials.json"), "utf-8");
14787
15815
  const parsed = JSON.parse(body);
14788
15816
  token = parsed.idToken ?? null;
14789
15817
  } catch {
@@ -14806,12 +15834,12 @@ async function trySendConsentToServer() {
14806
15834
  async function mcpLogOff() {
14807
15835
  let flagOnDisk = null;
14808
15836
  try {
14809
- flagOnDisk = JSON.parse(await readFile17(flagPath(), "utf-8"));
15837
+ flagOnDisk = JSON.parse(await readFile18(flagPath(), "utf-8"));
14810
15838
  } catch {
14811
15839
  flagOnDisk = null;
14812
15840
  }
14813
15841
  if (flagOnDisk && flagOnDisk.enabled === false) {
14814
- process.stderr.write("MCP logging is already disabled.\n");
15842
+ process.stderr.write("MCP telemetry is already disabled.\n");
14815
15843
  process.exitCode = 1;
14816
15844
  return;
14817
15845
  }
@@ -14822,38 +15850,38 @@ async function mcpLogOff() {
14822
15850
  };
14823
15851
  await writeFlag(next);
14824
15852
  process.stderr.write(
14825
- "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"
14826
15854
  );
14827
15855
  }
14828
15856
  async function mcpLogStatus() {
14829
15857
  let flagOnDisk = null;
14830
15858
  try {
14831
- flagOnDisk = JSON.parse(await readFile17(flagPath(), "utf-8"));
15859
+ flagOnDisk = JSON.parse(await readFile18(flagPath(), "utf-8"));
14832
15860
  } catch {
14833
15861
  flagOnDisk = null;
14834
15862
  }
14835
15863
  const enabled = !flagOnDisk || flagOnDisk.enabled !== false;
14836
15864
  const enabledLabel = flagOnDisk == null ? "yes (default)" : flagOnDisk.enabled ? "yes" : "no";
14837
15865
  void enabled;
14838
- process.stderr.write("MCP Log Status\n");
14839
- process.stderr.write("==============\n");
14840
- process.stderr.write(`Enabled: ${enabledLabel}
15866
+ process.stderr.write("MCP Telemetry Status\n");
15867
+ process.stderr.write("====================\n");
15868
+ process.stderr.write(`Heartbeat enabled: ${enabledLabel}
14841
15869
  `);
14842
- process.stderr.write(`Consent granted: ${flagOnDisk?.consentGrantedAt ?? "never"}
14843
- `);
14844
- process.stderr.write(`Log file: ${logPath()}
15870
+ process.stderr.write(`Opt-in since: ${flagOnDisk?.consentGrantedAt ?? "never"}
14845
15871
  `);
15872
+ process.stderr.write("Raw local logging: disabled (heartbeat only)\n");
14846
15873
  try {
14847
15874
  const s = await stat7(logPath());
14848
- process.stderr.write(
14849
- `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)
14850
15878
  `
14851
- );
15879
+ );
15880
+ }
14852
15881
  } catch {
14853
- process.stderr.write("Log size: no file yet\n");
14854
15882
  }
14855
15883
  try {
14856
- const endpointBody = await readFile17(join54(getConfigDir2(), ENDPOINT_FILE), "utf-8");
15884
+ const endpointBody = await readFile18(join56(getConfigDir2(), ENDPOINT_FILE), "utf-8");
14857
15885
  const match = /endpoint=([^\n]+)/.exec(endpointBody);
14858
15886
  process.stderr.write(`MCP endpoint: ${match?.[1] ?? "(malformed endpoint file)"}
14859
15887
  `);
@@ -14869,13 +15897,13 @@ function formatBytes(n) {
14869
15897
  async function mcpLogViewingFlags(flags = {}) {
14870
15898
  let flagOnDisk = null;
14871
15899
  try {
14872
- flagOnDisk = JSON.parse(await readFile17(flagPath(), "utf-8"));
15900
+ flagOnDisk = JSON.parse(await readFile18(flagPath(), "utf-8"));
14873
15901
  } catch {
14874
15902
  flagOnDisk = null;
14875
15903
  }
14876
15904
  if (flagOnDisk && flagOnDisk.enabled === false) {
14877
15905
  process.stderr.write(
14878
- `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.
14879
15907
  `
14880
15908
  );
14881
15909
  return;
@@ -14884,7 +15912,7 @@ async function mcpLogViewingFlags(flags = {}) {
14884
15912
  const key = await readKey();
14885
15913
  if (!key) {
14886
15914
  process.stderr.write(
14887
- `No encryption key at ${join54(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.
14888
15916
  `
14889
15917
  );
14890
15918
  return;
@@ -14912,7 +15940,7 @@ async function mcpLogViewingFlags(flags = {}) {
14912
15940
  }
14913
15941
  };
14914
15942
  try {
14915
- const raw = await readFile17(path, "utf-8");
15943
+ const raw = await readFile18(path, "utf-8");
14916
15944
  const decoded = [];
14917
15945
  for (const line of raw.split("\n")) {
14918
15946
  const r = filter(line);
@@ -14936,7 +15964,7 @@ async function mcpLogViewingFlags(flags = {}) {
14936
15964
  try {
14937
15965
  const s = await stat7(path);
14938
15966
  if (s.size > lastSize) {
14939
- const fresh = await readFile17(path, "utf-8");
15967
+ const fresh = await readFile18(path, "utf-8");
14940
15968
  const tail = fresh.slice(lastSize);
14941
15969
  for (const line of tail.split("\n")) {
14942
15970
  const r = filter(line);
@@ -14960,7 +15988,7 @@ async function mcpLogViewingFlags(flags = {}) {
14960
15988
  }
14961
15989
  async function readKey() {
14962
15990
  try {
14963
- const body = await readFile17(join54(getConfigDir2(), KEY_FILE), "utf-8");
15991
+ const body = await readFile18(join56(getConfigDir2(), KEY_FILE), "utf-8");
14964
15992
  const parsed = Buffer.from(body.trim(), "base64");
14965
15993
  if (parsed.length !== 32) return null;
14966
15994
  return parsed;
@@ -15000,11 +16028,11 @@ async function mcpLog(subcommand, flags = {}) {
15000
16028
  }
15001
16029
 
15002
16030
  // src/commands/query/_shared.ts
15003
- import chalk14 from "chalk";
16031
+ import chalk15 from "chalk";
15004
16032
 
15005
16033
  // src/lib/graph-loader.ts
15006
- import { promises as fs24 } from "fs";
15007
- import { join as join55, resolve as resolve2 } from "path";
16034
+ import { promises as fs25 } from "fs";
16035
+ import { join as join57, resolve as resolve2 } from "path";
15008
16036
  import { gunzipSync } from "zlib";
15009
16037
  var RELATIVE_GRAPH_PATH = "repowise-context/.meta/dependency-graph.json";
15010
16038
  var GZIPPED_GRAPH_PATH = "repowise-context/.meta/dependency-graph.json.gz";
@@ -15021,8 +16049,8 @@ var GraphNotFoundError = class extends Error {
15021
16049
  var cache = /* @__PURE__ */ new Map();
15022
16050
  async function loadGraph(repoRoot = process.cwd()) {
15023
16051
  const root = resolve2(repoRoot);
15024
- const gzPath = join55(root, GZIPPED_GRAPH_PATH);
15025
- const plainPath = join55(root, RELATIVE_GRAPH_PATH);
16052
+ const gzPath = join57(root, GZIPPED_GRAPH_PATH);
16053
+ const plainPath = join57(root, RELATIVE_GRAPH_PATH);
15026
16054
  const cached = cache.get(root);
15027
16055
  if (cached) {
15028
16056
  return { graph: cached, path: plainPath, bytes: 0, parseMs: 0, fromCache: true };
@@ -15030,14 +16058,14 @@ async function loadGraph(repoRoot = process.cwd()) {
15030
16058
  let graphPath = null;
15031
16059
  let raw = null;
15032
16060
  try {
15033
- raw = await fs24.readFile(gzPath);
16061
+ raw = await fs25.readFile(gzPath);
15034
16062
  graphPath = gzPath;
15035
16063
  } catch (err) {
15036
16064
  if (err.code !== "ENOENT") throw err;
15037
16065
  }
15038
16066
  if (!raw) {
15039
16067
  try {
15040
- raw = await fs24.readFile(plainPath);
16068
+ raw = await fs25.readFile(plainPath);
15041
16069
  graphPath = plainPath;
15042
16070
  } catch (err) {
15043
16071
  if (err.code === "ENOENT") {
@@ -15240,7 +16268,7 @@ async function loadService() {
15240
16268
  return createGraphQueryService(graph);
15241
16269
  } catch (err) {
15242
16270
  if (err instanceof GraphNotFoundError) {
15243
- process.stderr.write(chalk14.red(`\u2717 ${err.message}
16271
+ process.stderr.write(chalk15.red(`\u2717 ${err.message}
15244
16272
  `));
15245
16273
  process.exit(err.exitCode);
15246
16274
  }
@@ -15436,14 +16464,14 @@ function registerQueryCommand(program2) {
15436
16464
  }
15437
16465
 
15438
16466
  // src/commands/uninstall.ts
15439
- import { promises as fs28 } from "fs";
15440
- import { homedir as homedir10 } from "os";
15441
- import { join as join59 } from "path";
15442
- import chalk15 from "chalk";
16467
+ import { promises as fs29 } from "fs";
16468
+ import { homedir as homedir12 } from "os";
16469
+ import { join as join61 } from "path";
16470
+ import chalk16 from "chalk";
15443
16471
 
15444
16472
  // src/lib/cleanup/marker-blocks.ts
15445
- import { promises as fs25 } from "fs";
15446
- import { join as join56 } from "path";
16473
+ import { promises as fs26 } from "fs";
16474
+ import { join as join58 } from "path";
15447
16475
  var MARKER_START = "<!-- repowise-start -->";
15448
16476
  var MARKER_END = "<!-- repowise-end -->";
15449
16477
  var CONTEXT_FILES = [
@@ -15459,7 +16487,7 @@ var CONTEXT_FILES = [
15459
16487
  async function stripMarkerBlock(filePath) {
15460
16488
  let raw;
15461
16489
  try {
15462
- raw = await fs25.readFile(filePath, "utf-8");
16490
+ raw = await fs26.readFile(filePath, "utf-8");
15463
16491
  } catch (err) {
15464
16492
  if (err.code === "ENOENT")
15465
16493
  return { path: filePath, status: "missing" };
@@ -15474,16 +16502,16 @@ async function stripMarkerBlock(filePath) {
15474
16502
  const after = raw.slice(endIdx + MARKER_END.length).replace(/^\n+/, "");
15475
16503
  const stripped = (before + (before && after ? "\n\n" : "") + after).trim();
15476
16504
  if (stripped.length === 0) {
15477
- await fs25.unlink(filePath);
16505
+ await fs26.unlink(filePath);
15478
16506
  return { path: filePath, status: "deleted" };
15479
16507
  }
15480
- await fs25.writeFile(filePath, stripped + "\n", "utf-8");
16508
+ await fs26.writeFile(filePath, stripped + "\n", "utf-8");
15481
16509
  return { path: filePath, status: "stripped" };
15482
16510
  }
15483
16511
  async function stripAllMarkerBlocks(repoRoot) {
15484
16512
  const out = [];
15485
16513
  for (const relative of CONTEXT_FILES) {
15486
- const full = join56(repoRoot, relative);
16514
+ const full = join58(repoRoot, relative);
15487
16515
  const result = await stripMarkerBlock(full).catch((err) => ({
15488
16516
  path: full,
15489
16517
  status: "untouched",
@@ -15495,25 +16523,25 @@ async function stripAllMarkerBlocks(repoRoot) {
15495
16523
  }
15496
16524
 
15497
16525
  // src/lib/cleanup/mcp-configs.ts
15498
- import { promises as fs26 } from "fs";
15499
- import { join as join57 } from "path";
16526
+ import { promises as fs27 } from "fs";
16527
+ import { join as join59 } from "path";
15500
16528
  function mcpConfigPaths(repoRoot, home) {
15501
16529
  return [
15502
- join57(repoRoot, ".mcp.json"),
15503
- join57(repoRoot, ".cursor", "mcp.json"),
15504
- join57(repoRoot, ".vscode", "mcp.json"),
15505
- join57(repoRoot, ".roo", "mcp.json"),
15506
- join57(home, ".cline", "mcp.json"),
15507
- join57(home, ".codeium", "windsurf", "mcp_config.json"),
15508
- join57(home, ".gemini", "settings.json"),
15509
- join57(home, ".codex", "mcp.json"),
15510
- join57(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")
15511
16539
  ];
15512
16540
  }
15513
16541
  async function removeRepowiseFromConfig(path, serverName) {
15514
16542
  let raw;
15515
16543
  try {
15516
- raw = await fs26.readFile(path, "utf-8");
16544
+ raw = await fs27.readFile(path, "utf-8");
15517
16545
  } catch (err) {
15518
16546
  if (err.code === "ENOENT") return { path, status: "not-found" };
15519
16547
  return { path, status: "error", error: err.message };
@@ -15533,7 +16561,7 @@ async function removeRepowiseFromConfig(path, serverName) {
15533
16561
  } else {
15534
16562
  next.mcpServers = servers;
15535
16563
  }
15536
- await fs26.writeFile(path, JSON.stringify(next, null, 2) + "\n", "utf-8");
16564
+ await fs27.writeFile(path, JSON.stringify(next, null, 2) + "\n", "utf-8");
15537
16565
  return { path, status: "removed" };
15538
16566
  }
15539
16567
  async function removeAllMcpEntries(repoRoot, home, repoId) {
@@ -15546,17 +16574,17 @@ async function removeAllMcpEntries(repoRoot, home, repoId) {
15546
16574
  }
15547
16575
 
15548
16576
  // src/lib/cleanup/local-state.ts
15549
- import { promises as fs27 } from "fs";
15550
- import { homedir as homedir9 } from "os";
15551
- import { join as join58, resolve as resolve3 } from "path";
16577
+ import { promises as fs28 } from "fs";
16578
+ import { homedir as homedir11 } from "os";
16579
+ import { join as join60, resolve as resolve3 } from "path";
15552
16580
  async function clearLocalState(homeOverride) {
15553
- const home = homeOverride ?? homedir9();
15554
- const target = resolve3(join58(home, ".repowise"));
16581
+ const home = homeOverride ?? homedir11();
16582
+ const target = resolve3(join60(home, ".repowise"));
15555
16583
  if (target === resolve3(home) || !target.startsWith(resolve3(home))) {
15556
16584
  return { path: target, status: "error", error: "refused: not under home" };
15557
16585
  }
15558
16586
  try {
15559
- await fs27.rm(target, { recursive: true, force: false });
16587
+ await fs28.rm(target, { recursive: true, force: false });
15560
16588
  return { path: target, status: "removed" };
15561
16589
  } catch (err) {
15562
16590
  if (err.code === "ENOENT")
@@ -15586,7 +16614,7 @@ async function stopAndUninstallService(uninstaller) {
15586
16614
  // src/commands/uninstall.ts
15587
16615
  async function uninstall2(opts = {}) {
15588
16616
  const tier = opts.tier ?? "uninstall";
15589
- const home = opts.home ?? homedir10();
16617
+ const home = opts.home ?? homedir12();
15590
16618
  const repoRoot = opts.repoRoot ?? process.cwd();
15591
16619
  const loadRepoIds = opts.loadRepoIds ?? defaultLoadRepoIds;
15592
16620
  const report = { tier, removed: [], preserved: [], skipped: [] };
@@ -15595,7 +16623,7 @@ async function uninstall2(opts = {}) {
15595
16623
  else if (svc.error) report.skipped.push({ path: "listener service", reason: svc.error });
15596
16624
  if (tier === "stop") return report;
15597
16625
  try {
15598
- await fs28.unlink(join59(home, ".repowise", "credentials.json"));
16626
+ await fs29.unlink(join61(home, ".repowise", "credentials.json"));
15599
16627
  report.removed.push("credentials");
15600
16628
  } catch (err) {
15601
16629
  if (err.code !== "ENOENT") {
@@ -15622,7 +16650,7 @@ async function uninstall2(opts = {}) {
15622
16650
  const allPaths = mcpConfigPaths(repoRoot, home);
15623
16651
  for (const p of allPaths) {
15624
16652
  try {
15625
- await fs28.access(p);
16653
+ await fs29.access(p);
15626
16654
  } catch {
15627
16655
  }
15628
16656
  }
@@ -15634,7 +16662,7 @@ async function uninstall2(opts = {}) {
15634
16662
  }
15635
16663
  async function defaultLoadRepoIds(home) {
15636
16664
  try {
15637
- const raw = await fs28.readFile(join59(home, ".repowise", "config.json"), "utf-8");
16665
+ const raw = await fs29.readFile(join61(home, ".repowise", "config.json"), "utf-8");
15638
16666
  const parsed = JSON.parse(raw);
15639
16667
  return (parsed.repos ?? []).map((r) => r.repoId);
15640
16668
  } catch {
@@ -15651,29 +16679,29 @@ async function uninstallCommand(opts = {}) {
15651
16679
  default: false
15652
16680
  });
15653
16681
  if (!proceed) {
15654
- process.stderr.write(chalk15.yellow("Uninstall cancelled.\n"));
16682
+ process.stderr.write(chalk16.yellow("Uninstall cancelled.\n"));
15655
16683
  process.exitCode = 1;
15656
16684
  return;
15657
16685
  }
15658
16686
  }
15659
16687
  }
15660
- process.stderr.write(chalk15.bold(`RepoWise ${tier}
16688
+ process.stderr.write(chalk16.bold(`RepoWise ${tier}
15661
16689
  `));
15662
16690
  const report = await uninstall2({ ...opts, tier });
15663
16691
  for (const p of report.removed) {
15664
- process.stderr.write(chalk15.green(` \u2713 removed: ${p}
16692
+ process.stderr.write(chalk16.green(` \u2713 removed: ${p}
15665
16693
  `));
15666
16694
  }
15667
16695
  for (const p of report.preserved) {
15668
- process.stderr.write(chalk15.gray(` \xB7 preserved: ${p}
16696
+ process.stderr.write(chalk16.gray(` \xB7 preserved: ${p}
15669
16697
  `));
15670
16698
  }
15671
16699
  for (const s of report.skipped) {
15672
- process.stderr.write(chalk15.yellow(` ! skipped: ${s.path} (${s.reason})
16700
+ process.stderr.write(chalk16.yellow(` ! skipped: ${s.path} (${s.reason})
15673
16701
  `));
15674
16702
  }
15675
16703
  process.stderr.write(
15676
- chalk15.bold(`
16704
+ chalk16.bold(`
15677
16705
  Done \u2014 ${report.removed.length} removed, ${report.skipped.length} skipped.
15678
16706
  `)
15679
16707
  );
@@ -15681,12 +16709,14 @@ Done \u2014 ${report.removed.length} removed, ${report.skipped.length} skipped.
15681
16709
 
15682
16710
  // src/commands/mcp-shim.ts
15683
16711
  init_config_dir();
15684
- import { promises as fs29 } from "fs";
16712
+ import { promises as fs30 } from "fs";
15685
16713
  import { createInterface as createInterface2 } from "readline";
15686
- import { join as join60 } from "path";
16714
+ import { join as join62 } from "path";
15687
16715
  var DEFAULT_MAX = 200 * 1024;
16716
+ var SHIM_REQUEST_TIMEOUT_MS = 45e3;
15688
16717
  async function mcpShim(opts) {
15689
- const endpointPath = opts.endpointFile ?? join60(getConfigDir(), "listener.endpoint");
16718
+ const endpointPath = opts.endpointFile ?? join62(getConfigDir(), "listener.endpoint");
16719
+ const credsPath = opts.credentialsFile ?? join62(getConfigDir(), "credentials.json");
15690
16720
  const stdin = opts.stdin ?? process.stdin;
15691
16721
  const stdout = opts.stdout ?? process.stdout;
15692
16722
  const stderr = opts.stderr ?? process.stderr;
@@ -15721,12 +16751,16 @@ async function mcpShim(opts) {
15721
16751
  }
15722
16752
  const info = await readEndpoint(endpointPath);
15723
16753
  if (!info) {
16754
+ const authState = await readAuthState(credsPath);
16755
+ const message = authState === "logged-out" ? (
16756
+ // Agent-directed: the calling LLM can run this itself for a smoother UX
16757
+ // (it auto-opens the browser; the user still completes sign-in). The
16758
+ // command is a fixed, argument-free string (nothing injectable).
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."
16760
+ ) : "listener endpoint unavailable \u2014 is the listener running?";
15724
16761
  writeJson(stdout, {
15725
16762
  jsonrpc: "2.0",
15726
- error: {
15727
- code: -32e3,
15728
- message: "listener endpoint unavailable \u2014 is the listener running?"
15729
- }
16763
+ error: { code: -32e3, message }
15730
16764
  });
15731
16765
  continue;
15732
16766
  }
@@ -15746,12 +16780,19 @@ async function mcpShim(opts) {
15746
16780
  } catch (err) {
15747
16781
  stderr.write(`mcp-shim: ${err.message}
15748
16782
  `);
16783
+ const name = err.name;
16784
+ const detail = err.message;
16785
+ const isTimeout = name === "TimeoutError" || name === "AbortError" || /timeout|aborted|abort/i.test(detail);
15749
16786
  writeJson(stdout, {
15750
16787
  jsonrpc: "2.0",
15751
- 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
+ } : {
15752
16793
  code: -32001,
15753
16794
  message: "listener request failed",
15754
- data: { detail: err.message }
16795
+ data: { detail }
15755
16796
  }
15756
16797
  });
15757
16798
  }
@@ -15759,7 +16800,7 @@ async function mcpShim(opts) {
15759
16800
  }
15760
16801
  async function readEndpoint(path) {
15761
16802
  try {
15762
- const raw = (await fs29.readFile(path, "utf-8")).trim();
16803
+ const raw = (await fs30.readFile(path, "utf-8")).trim();
15763
16804
  if (!raw) return null;
15764
16805
  if (raw.startsWith("http://") || raw.startsWith("https://")) {
15765
16806
  return { endpoint: raw.split("\n")[0].trim(), secret: null };
@@ -15777,6 +16818,22 @@ async function readEndpoint(path) {
15777
16818
  throw err;
15778
16819
  }
15779
16820
  }
16821
+ async function readAuthState(credsPath) {
16822
+ let raw;
16823
+ try {
16824
+ raw = await fs30.readFile(credsPath, "utf-8");
16825
+ } catch (err) {
16826
+ if (err.code === "ENOENT") return "logged-out";
16827
+ return "unknown";
16828
+ }
16829
+ let creds;
16830
+ try {
16831
+ creds = JSON.parse(raw);
16832
+ } catch {
16833
+ return "unknown";
16834
+ }
16835
+ return isCredsHardExpired(creds) ? "logged-out" : "authenticated";
16836
+ }
15780
16837
  function writeJson(stream, value) {
15781
16838
  stream.write(`${JSON.stringify(value)}
15782
16839
  `);
@@ -15872,7 +16929,8 @@ async function defaultPostJson(url, body, headers = { "content-type": "applicati
15872
16929
  const res = await fetch(url, {
15873
16930
  method: "POST",
15874
16931
  headers,
15875
- body: JSON.stringify(body)
16932
+ body: JSON.stringify(body),
16933
+ signal: AbortSignal.timeout(SHIM_REQUEST_TIMEOUT_MS)
15876
16934
  });
15877
16935
  if (!res.ok) {
15878
16936
  let detail = "";
@@ -15886,7 +16944,11 @@ async function defaultPostJson(url, body, headers = { "content-type": "applicati
15886
16944
  return await res.json();
15887
16945
  }
15888
16946
  async function defaultGetJson(url, headers = {}) {
15889
- 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
+ });
15890
16952
  if (!res.ok) {
15891
16953
  let detail = "";
15892
16954
  try {
@@ -16001,7 +17063,7 @@ init_coursier_installer();
16001
17063
  init_toolchain_installer();
16002
17064
  import { spawn as spawn12 } from "child_process";
16003
17065
  import { resolve as resolve4 } from "path";
16004
- import chalk16 from "chalk";
17066
+ import chalk17 from "chalk";
16005
17067
  async function isOnPath(command) {
16006
17068
  if (/[^\w./+-]/.test(command)) return false;
16007
17069
  const isWin = process.platform === "win32";
@@ -16019,14 +17081,14 @@ async function isOnPath(command) {
16019
17081
  async function lspDoctor() {
16020
17082
  const noColor = Boolean(process.env.NO_COLOR) || !process.stdout.isTTY;
16021
17083
  if (noColor) {
16022
- chalk16.level = 0;
17084
+ chalk17.level = 0;
16023
17085
  }
16024
- const okGlyph = noColor ? "[OK]" : chalk16.green("\u2713");
16025
- const missingGlyph = noColor ? "[MISSING]" : chalk16.yellow("\u2717");
16026
- console.log(chalk16.bold("RepoWise LSP doctor"));
16027
- console.log(chalk16.dim("Probing PATH for language servers used by the listener."));
17086
+ const okGlyph = noColor ? "[OK]" : chalk17.green("\u2713");
17087
+ const missingGlyph = noColor ? "[MISSING]" : chalk17.yellow("\u2717");
17088
+ console.log(chalk17.bold("RepoWise LSP doctor"));
17089
+ console.log(chalk17.dim("Probing PATH for language servers used by the listener."));
16028
17090
  console.log(
16029
- chalk16.dim(
17091
+ chalk17.dim(
16030
17092
  "Type-aware resolution covers 9 languages: Go, Python, Rust, Java, Ruby, Dart, Kotlin, PHP, Scala (Phase 7L + 7L++ + 7L+)."
16031
17093
  )
16032
17094
  );
@@ -16058,21 +17120,21 @@ async function lspDoctor() {
16058
17120
  const missing = reports.filter((r) => r.status === "missing").length;
16059
17121
  for (const r of reports) {
16060
17122
  const head = r.status === "found" ? okGlyph : missingGlyph;
16061
- const cmd = r.command ? chalk16.cyan(r.command) : chalk16.dim(r.candidates.join(" / "));
17123
+ const cmd = r.command ? chalk17.cyan(r.command) : chalk17.dim(r.candidates.join(" / "));
16062
17124
  const effectiveConfigs = getEffectiveConfigList(r.language, process.env, lspOverrides);
16063
17125
  const method = describeInstallMethod(effectiveConfigs);
16064
- console.log(` ${head} ${r.language.padEnd(12)} ${cmd} ${chalk16.dim(`[${method}]`)}`);
17126
+ console.log(` ${head} ${r.language.padEnd(12)} ${cmd} ${chalk17.dim(`[${method}]`)}`);
16065
17127
  if (r.status === "missing" && r.hint) {
16066
- console.log(` ${chalk16.dim("install:")} ${r.hint}`);
17128
+ console.log(` ${chalk17.dim("install:")} ${r.hint}`);
16067
17129
  }
16068
17130
  }
16069
17131
  console.log();
16070
17132
  console.log(
16071
- `${chalk16.green(found.toString())} found, ${chalk16.yellow(missing.toString())} missing of ${reports.length.toString()} languages.`
17133
+ `${chalk17.green(found.toString())} found, ${chalk17.yellow(missing.toString())} missing of ${reports.length.toString()} languages.`
16072
17134
  );
16073
17135
  if (missing > 0) {
16074
17136
  console.log(
16075
- chalk16.dim(
17137
+ chalk17.dim(
16076
17138
  "Missing servers degrade LSP-backed MCP tools to AST-only fallback. Run `repowise lsp install` to auto-install."
16077
17139
  )
16078
17140
  );
@@ -16089,15 +17151,15 @@ function describeInstallMethod(configs) {
16089
17151
  }
16090
17152
  async function lspInstall(language) {
16091
17153
  const noColor = Boolean(process.env.NO_COLOR) || !process.stdout.isTTY;
16092
- if (noColor) chalk16.level = 0;
16093
- console.log(chalk16.bold("RepoWise LSP install"));
17154
+ if (noColor) chalk17.level = 0;
17155
+ console.log(chalk17.bold("RepoWise LSP install"));
16094
17156
  const cliConfig = await getConfig();
16095
17157
  const lspOverrides = cliConfig.lspOverrides;
16096
17158
  const targets = language ? [language] : Object.keys(LSP_REGISTRY);
16097
17159
  for (const lang of targets) {
16098
17160
  const configs = getEffectiveConfigList(lang, process.env, lspOverrides);
16099
17161
  if (!configs || configs.length === 0) {
16100
- console.log(` ${chalk16.red("\u2717")} ${lang} ${chalk16.dim("(unknown language)")}`);
17162
+ console.log(` ${chalk17.red("\u2717")} ${lang} ${chalk17.dim("(unknown language)")}`);
16101
17163
  continue;
16102
17164
  }
16103
17165
  const result = await installOneLanguage(configs);
@@ -16165,11 +17227,11 @@ async function installOneLanguage(configs) {
16165
17227
  };
16166
17228
  }
16167
17229
  function formatInstallLine(lang, outcome) {
16168
- const glyph = outcome.ok ? chalk16.green("\u2713") : chalk16.yellow("\u2717");
16169
- const tag = chalk16.dim(`[${outcome.method}]`);
17230
+ const glyph = outcome.ok ? chalk17.green("\u2713") : chalk17.yellow("\u2717");
17231
+ const tag = chalk17.dim(`[${outcome.method}]`);
16170
17232
  console.log(` ${glyph} ${lang.padEnd(12)} ${tag} ${outcome.detail}`);
16171
17233
  if (!outcome.ok && outcome.hint) {
16172
- console.log(` ${chalk16.dim("install:")} ${outcome.hint}`);
17234
+ console.log(` ${chalk17.dim("install:")} ${outcome.hint}`);
16173
17235
  }
16174
17236
  }
16175
17237
  var MAX_KEEP_WARM_MINUTES = 240;
@@ -16207,42 +17269,42 @@ async function autoDetectLanguages(cwd) {
16207
17269
  async function lspWarm(opts = {}) {
16208
17270
  const cwd = resolve4(opts.cwd ?? process.cwd());
16209
17271
  const noColor = Boolean(process.env.NO_COLOR) || !process.stdout.isTTY;
16210
- if (noColor) chalk16.level = 0;
17272
+ if (noColor) chalk17.level = 0;
16211
17273
  let languages;
16212
17274
  if (opts.lang && opts.lang.length > 0) {
16213
17275
  const valid = Object.keys(LSP_REGISTRY);
16214
17276
  languages = opts.lang.filter((l) => valid.includes(l));
16215
17277
  const invalid = opts.lang.filter((l) => !valid.includes(l));
16216
17278
  if (invalid.length > 0) {
16217
- console.log(chalk16.yellow(`[lsp warm] unknown languages skipped: ${invalid.join(", ")}`));
17279
+ console.log(chalk17.yellow(`[lsp warm] unknown languages skipped: ${invalid.join(", ")}`));
16218
17280
  }
16219
17281
  } else {
16220
17282
  languages = await autoDetectLanguages(cwd);
16221
17283
  if (languages.length === 0) {
16222
17284
  console.log(
16223
- chalk16.yellow(
17285
+ chalk17.yellow(
16224
17286
  "[lsp warm] no recognised source files in this repo \u2014 pass --lang <id> to force a server."
16225
17287
  )
16226
17288
  );
16227
17289
  return;
16228
17290
  }
16229
- console.log(chalk16.dim(`[lsp warm] auto-detected languages: ${languages.join(", ")}`));
17291
+ console.log(chalk17.dim(`[lsp warm] auto-detected languages: ${languages.join(", ")}`));
16230
17292
  }
16231
17293
  let keepWarmMinutes = 0;
16232
17294
  if (typeof opts.keepWarm === "number" && opts.keepWarm > 0) {
16233
17295
  keepWarmMinutes = Math.min(opts.keepWarm, MAX_KEEP_WARM_MINUTES);
16234
17296
  if (opts.keepWarm > MAX_KEEP_WARM_MINUTES) {
16235
17297
  console.log(
16236
- chalk16.yellow(
17298
+ chalk17.yellow(
16237
17299
  `[lsp warm] --keep-warm clipped to ${MAX_KEEP_WARM_MINUTES.toString()} minutes (hard cap per Phase 7L plan).`
16238
17300
  )
16239
17301
  );
16240
17302
  }
16241
17303
  }
16242
- console.log(chalk16.bold("RepoWise LSP warm \u2014 preview"));
16243
- console.log(chalk16.dim(`Workspace: ${cwd}`));
17304
+ console.log(chalk17.bold("RepoWise LSP warm \u2014 preview"));
17305
+ console.log(chalk17.dim(`Workspace: ${cwd}`));
16244
17306
  console.log(
16245
- chalk16.dim(
17307
+ chalk17.dim(
16246
17308
  "LSP sessions live inside the listener (lazy-by-default, idle-evict after 5 min). The CLI cannot currently trigger a remote spawn \u2014 this preview shows which servers would be used."
16247
17309
  )
16248
17310
  );
@@ -16258,23 +17320,23 @@ async function lspWarm(opts = {}) {
16258
17320
  if (onPath) {
16259
17321
  foundCount += 1;
16260
17322
  console.log(
16261
- chalk16.green(` \u2713 ${lang.padEnd(12)} ${config2.displayName} (${config2.command}) on PATH`)
17323
+ chalk17.green(` \u2713 ${lang.padEnd(12)} ${config2.displayName} (${config2.command}) on PATH`)
16262
17324
  );
16263
17325
  } else {
16264
17326
  missingCount += 1;
16265
- console.log(chalk16.yellow(` \u2717 ${lang.padEnd(12)} ${config2.displayName} not on PATH`));
17327
+ console.log(chalk17.yellow(` \u2717 ${lang.padEnd(12)} ${config2.displayName} not on PATH`));
16266
17328
  if (config2.installHint) {
16267
- console.log(chalk16.dim(` install: ${config2.installHint}`));
17329
+ console.log(chalk17.dim(` install: ${config2.installHint}`));
16268
17330
  }
16269
17331
  }
16270
17332
  }
16271
17333
  console.log();
16272
17334
  console.log(
16273
- `${chalk16.green(foundCount.toString())} ready, ${chalk16.yellow(missingCount.toString())} missing.`
17335
+ `${chalk17.green(foundCount.toString())} ready, ${chalk17.yellow(missingCount.toString())} missing.`
16274
17336
  );
16275
17337
  if (keepWarmMinutes > 0) {
16276
17338
  console.log(
16277
- chalk16.dim(
17339
+ chalk17.dim(
16278
17340
  `Holding for ${keepWarmMinutes.toString()} minute(s) so the listener has time to spawn on its next sync.completed; Ctrl-C to exit early.`
16279
17341
  )
16280
17342
  );
@@ -16291,20 +17353,20 @@ async function lspWarm(opts = {}) {
16291
17353
  }
16292
17354
  function lspStatus() {
16293
17355
  const noColor = Boolean(process.env.NO_COLOR) || !process.stdout.isTTY;
16294
- if (noColor) chalk16.level = 0;
16295
- console.log(chalk16.bold("RepoWise LSP status"));
17356
+ if (noColor) chalk17.level = 0;
17357
+ console.log(chalk17.bold("RepoWise LSP status"));
16296
17358
  console.log(
16297
- chalk16.dim(
17359
+ chalk17.dim(
16298
17360
  "Active LSP sessions live inside the listener (not the CLI). Sessions are lazy-by-default: spawn on first MCP tool / receiver query, idle-evict after 5 minutes. Scala sessions also evict their paired Bloop daemon (Phase 7L+)."
16299
17361
  )
16300
17362
  );
16301
17363
  console.log();
16302
- console.log(chalk16.dim("See `repowise lsp doctor` for PATH probe + install hints."));
17364
+ console.log(chalk17.dim("See `repowise lsp doctor` for PATH probe + install hints."));
16303
17365
  }
16304
17366
  function lspStop() {
16305
- console.log(chalk16.bold("RepoWise LSP stop \u2014 preview"));
17367
+ console.log(chalk17.bold("RepoWise LSP stop \u2014 preview"));
16306
17368
  console.log(
16307
- chalk16.dim(
17369
+ chalk17.dim(
16308
17370
  "No CLI-owned warm sessions exist. Listener-owned LSP sessions evict automatically after 5 minutes of idle (Phase 7L) and under the RSS soft-cap LRU when memory pressure rises. Explicit listener-side stop IPC is a future follow-up."
16309
17371
  )
16310
17372
  );
@@ -16327,29 +17389,29 @@ var ENABLE_TARGETS = {
16327
17389
  };
16328
17390
  async function lspEnable(target) {
16329
17391
  const noColor = Boolean(process.env.NO_COLOR) || !process.stdout.isTTY;
16330
- if (noColor) chalk16.level = 0;
17392
+ if (noColor) chalk17.level = 0;
16331
17393
  if (!target) {
16332
- console.error(chalk16.red("Usage: repowise lsp enable <target>"));
16333
- console.error(chalk16.dim(` Supported targets: ${Object.keys(ENABLE_TARGETS).join(", ")}`));
17394
+ console.error(chalk17.red("Usage: repowise lsp enable <target>"));
17395
+ console.error(chalk17.dim(` Supported targets: ${Object.keys(ENABLE_TARGETS).join(", ")}`));
16334
17396
  process.exitCode = 1;
16335
17397
  return;
16336
17398
  }
16337
17399
  const spec = ENABLE_TARGETS[target];
16338
17400
  if (!spec) {
16339
- console.error(chalk16.red(`\u2716 Unknown LSP override target: ${target}`));
16340
- console.error(chalk16.dim(` Supported: ${Object.keys(ENABLE_TARGETS).join(", ")}`));
17401
+ console.error(chalk17.red(`\u2716 Unknown LSP override target: ${target}`));
17402
+ console.error(chalk17.dim(` Supported: ${Object.keys(ENABLE_TARGETS).join(", ")}`));
16341
17403
  process.exitCode = 1;
16342
17404
  return;
16343
17405
  }
16344
17406
  const config2 = await getConfig();
16345
17407
  const existing = config2.lspOverrides?.[spec.language];
16346
17408
  if (existing === spec.name) {
16347
- console.log(chalk16.green(`\u2714 ${spec.displayName} is already enabled.`));
16348
- console.log(chalk16.dim(` ${spec.postAcceptTip}`));
17409
+ console.log(chalk17.green(`\u2714 ${spec.displayName} is already enabled.`));
17410
+ console.log(chalk17.dim(` ${spec.postAcceptTip}`));
16349
17411
  return;
16350
17412
  }
16351
17413
  console.log();
16352
- console.log(chalk16.cyan.bold(` \u2500\u2500 Enable ${spec.displayName} \u2500\u2500`));
17414
+ console.log(chalk17.cyan.bold(` \u2500\u2500 Enable ${spec.displayName} \u2500\u2500`));
16353
17415
  for (const line of spec.consentLines) {
16354
17416
  console.log(` ${line}`);
16355
17417
  }
@@ -16361,7 +17423,7 @@ async function lspEnable(target) {
16361
17423
  });
16362
17424
  if (!proceed) {
16363
17425
  console.log(
16364
- chalk16.dim(
17426
+ chalk17.dim(
16365
17427
  ` Cancelled. ${spec.language.toUpperCase()} will continue using ${spec.revertDisplay}.`
16366
17428
  )
16367
17429
  );
@@ -16370,32 +17432,32 @@ async function lspEnable(target) {
16370
17432
  const nextOverrides = { ...config2.lspOverrides ?? {}, [spec.language]: spec.name };
16371
17433
  await mergeAndSaveConfig({ lspOverrides: nextOverrides });
16372
17434
  console.log(
16373
- chalk16.green(
17435
+ chalk17.green(
16374
17436
  ` \u2714 ${spec.displayName} enabled \u2014 listener will use it within ~5 min (reconcile cycle) or on next session restart.`
16375
17437
  )
16376
17438
  );
16377
- console.log(chalk16.dim(` \u2139 ${spec.postAcceptTip}`));
17439
+ console.log(chalk17.dim(` \u2139 ${spec.postAcceptTip}`));
16378
17440
  }
16379
17441
  async function lspDisable(target) {
16380
17442
  const noColor = Boolean(process.env.NO_COLOR) || !process.stdout.isTTY;
16381
- if (noColor) chalk16.level = 0;
17443
+ if (noColor) chalk17.level = 0;
16382
17444
  if (!target) {
16383
- console.error(chalk16.red("Usage: repowise lsp disable <target>"));
16384
- console.error(chalk16.dim(` Supported targets: ${Object.keys(ENABLE_TARGETS).join(", ")}`));
17445
+ console.error(chalk17.red("Usage: repowise lsp disable <target>"));
17446
+ console.error(chalk17.dim(` Supported targets: ${Object.keys(ENABLE_TARGETS).join(", ")}`));
16385
17447
  process.exitCode = 1;
16386
17448
  return;
16387
17449
  }
16388
17450
  const spec = ENABLE_TARGETS[target];
16389
17451
  if (!spec) {
16390
- console.error(chalk16.red(`\u2716 Unknown LSP override target: ${target}`));
16391
- console.error(chalk16.dim(` Supported: ${Object.keys(ENABLE_TARGETS).join(", ")}`));
17452
+ console.error(chalk17.red(`\u2716 Unknown LSP override target: ${target}`));
17453
+ console.error(chalk17.dim(` Supported: ${Object.keys(ENABLE_TARGETS).join(", ")}`));
16392
17454
  process.exitCode = 1;
16393
17455
  return;
16394
17456
  }
16395
17457
  const config2 = await getConfig();
16396
17458
  const current = config2.lspOverrides?.[spec.language];
16397
17459
  if (current !== spec.name) {
16398
- console.log(chalk16.dim(` ${spec.displayName} is not enabled \u2014 nothing to do.`));
17460
+ console.log(chalk17.dim(` ${spec.displayName} is not enabled \u2014 nothing to do.`));
16399
17461
  return;
16400
17462
  }
16401
17463
  const { [spec.language]: _removed, ...rest } = config2.lspOverrides ?? {};
@@ -16405,28 +17467,28 @@ async function lspDisable(target) {
16405
17467
  ...nextOverrides ? { lspOverrides: nextOverrides } : { lspOverrides: void 0 }
16406
17468
  });
16407
17469
  console.log(
16408
- chalk16.green(
17470
+ chalk17.green(
16409
17471
  ` \u2714 ${spec.displayName} disabled \u2014 listener will revert to ${spec.revertDisplay} within ~5 min or on next session restart.`
16410
17472
  )
16411
17473
  );
16412
17474
  }
16413
17475
  async function lspOff() {
16414
17476
  const noColor = Boolean(process.env.NO_COLOR) || !process.stdout.isTTY;
16415
- if (noColor) chalk16.level = 0;
17477
+ if (noColor) chalk17.level = 0;
16416
17478
  await mergeAndSaveConfig({ lspEnabled: false });
16417
17479
  console.log(
16418
- chalk16.green(
17480
+ chalk17.green(
16419
17481
  " \u2714 LSP subsystem disabled. The listener will stop spawning language servers and `lsp_*` tools will report disabled within ~5 min (reconcile) or on next restart."
16420
17482
  )
16421
17483
  );
16422
- console.log(chalk16.dim(" Re-enable with: repowise lsp on"));
17484
+ console.log(chalk17.dim(" Re-enable with: repowise lsp on"));
16423
17485
  }
16424
17486
  async function lspOn() {
16425
17487
  const noColor = Boolean(process.env.NO_COLOR) || !process.stdout.isTTY;
16426
- if (noColor) chalk16.level = 0;
17488
+ if (noColor) chalk17.level = 0;
16427
17489
  await mergeAndSaveConfig({ lspEnabled: true });
16428
17490
  console.log(
16429
- chalk16.green(
17491
+ chalk17.green(
16430
17492
  " \u2714 LSP subsystem enabled. The listener will install + warm language servers for your repos within ~5 min (reconcile) or on next restart."
16431
17493
  )
16432
17494
  );
@@ -16435,7 +17497,7 @@ async function lspOn() {
16435
17497
  // bin/repowise.ts
16436
17498
  var __filename = fileURLToPath4(import.meta.url);
16437
17499
  var __dirname = dirname23(__filename);
16438
- var pkg = JSON.parse(readFileSync3(join61(__dirname, "..", "..", "package.json"), "utf-8"));
17500
+ var pkg = JSON.parse(readFileSync3(join63(__dirname, "..", "..", "package.json"), "utf-8"));
16439
17501
  var program = new Command();
16440
17502
  program.name(getPackageName()).description("AI-optimized codebase context generator").version(pkg.version).hook("preAction", async () => {
16441
17503
  await showWelcome(pkg.version);
@@ -16511,6 +17573,15 @@ lspCommand.command("off").description("Disable the LSP subsystem entirely (kill-
16511
17573
  lspCommand.command("on").description("Re-enable the LSP subsystem after `lsp off`").action(async () => {
16512
17574
  await lspOn();
16513
17575
  });
17576
+ var toolsCommand = program.command("tools").description("Add RepoWise to your AI tools (Claude Code, Cursor, \u2026) after install").action(async () => {
17577
+ await toolsPick();
17578
+ });
17579
+ toolsCommand.command("add [tools...]").description("Add named AI tools (or open the picker with no args)").action(async (list) => {
17580
+ await toolsAdd(list ?? []);
17581
+ });
17582
+ toolsCommand.command("list").description("List the AI tools RepoWise is configured for").action(async () => {
17583
+ await toolsList();
17584
+ });
16514
17585
  program.command("uninstall").description("Remove RepoWise from this machine (3 tiers: stop | logout | uninstall)").option("--tier <tier>", "Cleanup tier: stop | logout | uninstall", "uninstall").option("--yes", "Skip confirm prompt", false).action(async (options) => {
16515
17586
  const tier = options.tier ?? "uninstall";
16516
17587
  await uninstallCommand({ tier, yes: options.yes });