repowisestage 0.0.78 → 0.0.79

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 +167 -64
  2. package/package.json +1 -1
@@ -2382,7 +2382,9 @@ var init_service_installer = __esm({
2382
2382
  var sidecar_cache_exports = {};
2383
2383
  __export(sidecar_cache_exports, {
2384
2384
  loadMergedSidecar: () => loadMergedSidecar,
2385
+ loadSidecarEtag: () => loadSidecarEtag,
2385
2386
  persistMergedSidecar: () => persistMergedSidecar,
2387
+ persistSidecarEtag: () => persistSidecarEtag,
2386
2388
  sidecarCachePaths: () => sidecarCachePaths
2387
2389
  });
2388
2390
  import { mkdir as mkdir9, readFile as readFile9, readdir as readdir3, stat as stat2, unlink as unlink9, writeFile as writeFile10 } from "fs/promises";
@@ -2409,8 +2411,8 @@ async function persistMergedSidecar(repoId, commitSha, sidecar) {
2409
2411
  await mkdir9(repoDir, { recursive: true });
2410
2412
  const tmpPath = `${fullPath}.tmp`;
2411
2413
  await writeFile10(tmpPath, JSON.stringify(sidecar), "utf-8");
2412
- const { rename: rename8 } = await import("fs/promises");
2413
- await rename8(tmpPath, fullPath);
2414
+ const { rename: rename9 } = await import("fs/promises");
2415
+ await rename9(tmpPath, fullPath);
2414
2416
  try {
2415
2417
  await sweepSidecarDir(repoDir);
2416
2418
  } catch {
@@ -2438,9 +2440,32 @@ async function sweepSidecarDir(repoDir) {
2438
2440
  await unlink9(join21(repoDir, name));
2439
2441
  } catch {
2440
2442
  }
2443
+ try {
2444
+ await unlink9(join21(repoDir, `${name}.etag`));
2445
+ } catch {
2446
+ }
2441
2447
  }
2442
2448
  }
2443
2449
  }
2450
+ async function persistSidecarEtag(repoId, commitSha, etag) {
2451
+ const { fullPath, repoDir } = sidecarCachePaths(repoId, commitSha);
2452
+ await mkdir9(repoDir, { recursive: true });
2453
+ const etagPath = `${fullPath}.etag`;
2454
+ const tmpPath = `${etagPath}.tmp`;
2455
+ await writeFile10(tmpPath, etag, "utf-8");
2456
+ const { rename: rename9 } = await import("fs/promises");
2457
+ await rename9(tmpPath, etagPath);
2458
+ }
2459
+ async function loadSidecarEtag(repoId, commitSha) {
2460
+ try {
2461
+ const { fullPath } = sidecarCachePaths(repoId, commitSha);
2462
+ const body = await readFile9(`${fullPath}.etag`, "utf-8");
2463
+ const trimmed = body.trim();
2464
+ return trimmed.length > 0 ? trimmed : null;
2465
+ } catch {
2466
+ return null;
2467
+ }
2468
+ }
2444
2469
  function isValidSidecarEntry(e) {
2445
2470
  if (!e || typeof e !== "object")
2446
2471
  return false;
@@ -2576,22 +2601,28 @@ async function uploadSidecarLegacy(req, body, fetchImpl) {
2576
2601
  }
2577
2602
  async function downloadMergedSidecar(req, fetchImpl = fetch) {
2578
2603
  const url = `${req.apiUrl.replace(/\/$/, "")}/v1/repos/${encodeURIComponent(req.repoId)}/typed-resolution/${encodeURIComponent(req.commitSha)}`;
2579
- const res = await fetchImpl(url, {
2580
- method: "GET",
2581
- headers: { authorization: `Bearer ${req.authToken}` }
2582
- });
2604
+ const headers = { authorization: `Bearer ${req.authToken}` };
2605
+ if (req.ifNoneMatch)
2606
+ headers["if-none-match"] = req.ifNoneMatch;
2607
+ const res = await fetchImpl(url, { method: "GET", headers });
2608
+ if (res.status === 304)
2609
+ return { status: "not-modified" };
2583
2610
  if (res.status === 404)
2584
- return null;
2611
+ return { status: "absent" };
2585
2612
  if (!res.ok) {
2586
2613
  throw new Error(`download failed: HTTP ${res.status.toString()}`);
2587
2614
  }
2588
2615
  const body = await res.json();
2589
2616
  if (typeof body.schemaVersion !== "number" || !ACCEPTED_TYPED_RESOLUTION_SCHEMA_VERSIONS.includes(body.schemaVersion)) {
2590
- return null;
2617
+ return { status: "absent" };
2591
2618
  }
2592
2619
  if (!Array.isArray(body.resolutions))
2593
- return null;
2594
- return body;
2620
+ return { status: "absent" };
2621
+ return {
2622
+ status: "sidecar",
2623
+ sidecar: body,
2624
+ etag: res.headers.get("etag")
2625
+ };
2595
2626
  }
2596
2627
  function buildSidecarIndex(sidecar) {
2597
2628
  const map = /* @__PURE__ */ new Map();
@@ -6445,8 +6476,8 @@ function createFileKeyStore(keyPath) {
6445
6476
  encoding: "utf-8",
6446
6477
  mode: 384
6447
6478
  });
6448
- const { rename: rename8 } = await import("fs/promises");
6449
- await rename8(tmp, keyPath);
6479
+ const { rename: rename9 } = await import("fs/promises");
6480
+ await rename9(tmp, keyPath);
6450
6481
  cached = fresh;
6451
6482
  try {
6452
6483
  const s = await stat4(keyPath);
@@ -6695,9 +6726,9 @@ async function writePendingBatch(watermarkPath, batchId) {
6695
6726
  await writeFile14(pendingPath(watermarkPath), batchId, { encoding: "utf-8", mode: 384 });
6696
6727
  }
6697
6728
  async function clearPendingBatch(watermarkPath) {
6698
- const { unlink: unlink14 } = await import("fs/promises");
6729
+ const { unlink: unlink15 } = await import("fs/promises");
6699
6730
  try {
6700
- await unlink14(pendingPath(watermarkPath));
6731
+ await unlink15(pendingPath(watermarkPath));
6701
6732
  } catch {
6702
6733
  }
6703
6734
  }
@@ -6716,8 +6747,8 @@ async function writeWatermark(path, offset) {
6716
6747
  await mkdir13(dirname14(path), { recursive: true });
6717
6748
  const tmp = `${path}.tmp`;
6718
6749
  await writeFile14(tmp, offset.toString() + "\n", { encoding: "utf-8", mode: 384 });
6719
- const { rename: rename8 } = await import("fs/promises");
6720
- await rename8(tmp, path);
6750
+ const { rename: rename9 } = await import("fs/promises");
6751
+ await rename9(tmp, path);
6721
6752
  }
6722
6753
  async function isLocallyConsented(flagPath2) {
6723
6754
  try {
@@ -6732,9 +6763,37 @@ async function isLocallyConsented(flagPath2) {
6732
6763
  // ../listener/dist/mcp/mcp-server.js
6733
6764
  init_config_dir();
6734
6765
  import { createServer } from "http";
6735
- import { mkdir as mkdir14, writeFile as writeFile15, unlink as unlink11 } from "fs/promises";
6736
- import { dirname as dirname15, join as join23 } from "path";
6737
- import { createHash as createHash4, randomUUID as randomUUID3 } from "crypto";
6766
+ import { unlink as unlink12 } from "fs/promises";
6767
+ import { join as join23 } from "path";
6768
+ import { createHash as createHash4, randomUUID as randomUUID4 } from "crypto";
6769
+
6770
+ // ../listener/dist/lib/atomic-write.js
6771
+ import { mkdir as mkdir14, writeFile as writeFile15, chmod as chmod4, rename as rename6, unlink as unlink11, open as open5 } from "fs/promises";
6772
+ import { dirname as dirname15 } from "path";
6773
+ import { randomUUID as randomUUID3 } from "crypto";
6774
+ async function writeFileAtomic(path, contents, opts = {}) {
6775
+ const mode = opts.mode ?? 384;
6776
+ await mkdir14(dirname15(path), { recursive: true });
6777
+ const tmpPath = `${path}.${randomUUID3()}.tmp`;
6778
+ try {
6779
+ await writeFile15(tmpPath, contents, { encoding: "utf-8", mode });
6780
+ await chmod4(tmpPath, mode);
6781
+ let fh;
6782
+ try {
6783
+ fh = await open5(tmpPath, "r+");
6784
+ await fh.datasync();
6785
+ } finally {
6786
+ await fh?.close();
6787
+ }
6788
+ await rename6(tmpPath, path);
6789
+ } catch (err) {
6790
+ try {
6791
+ await unlink11(tmpPath);
6792
+ } catch {
6793
+ }
6794
+ throw err;
6795
+ }
6796
+ }
6738
6797
 
6739
6798
  // ../listener/dist/mcp/sanitize.js
6740
6799
  var MCP_RESPONSE_BYTE_CAP = 200 * 1024;
@@ -7768,6 +7827,25 @@ async function startMcpServer(options) {
7768
7827
  const clock = options.clock ?? Date.now;
7769
7828
  const requireAuth = options.requireAuth ?? true;
7770
7829
  const secretState = { value: createRotatingSecret(clock()) };
7830
+ const endpointFilePath = options.endpointFilePath ?? defaultEndpointFile();
7831
+ let endpointUrl = null;
7832
+ let lastPersistedSecret = null;
7833
+ let suspended = false;
7834
+ let persisting = false;
7835
+ let currentPersist = null;
7836
+ const persistSecretIfStale = () => {
7837
+ const live = secretState.value.current;
7838
+ if (endpointUrl === null || suspended || persisting || live === lastPersistedSecret)
7839
+ return;
7840
+ persisting = true;
7841
+ currentPersist = writeEndpointFileWithRetry(endpointFilePath, endpointUrl, live).then(() => {
7842
+ lastPersistedSecret = live;
7843
+ }).catch((err) => {
7844
+ console.warn("[mcp] endpoint secret persist failed (will retry on next request):", err instanceof Error ? err.message : String(err));
7845
+ }).finally(() => {
7846
+ persisting = false;
7847
+ });
7848
+ };
7771
7849
  const server = createServer((req, res) => handleRequest(req, res, options, sessions, {
7772
7850
  getSecret: () => secretState.value,
7773
7851
  rotate: () => {
@@ -7775,6 +7853,7 @@ async function startMcpServer(options) {
7775
7853
  if (next !== secretState.value) {
7776
7854
  secretState.value = next;
7777
7855
  }
7856
+ persistSecretIfStale();
7778
7857
  return secretState.value;
7779
7858
  },
7780
7859
  requireAuth,
@@ -7788,38 +7867,34 @@ async function startMcpServer(options) {
7788
7867
  });
7789
7868
  });
7790
7869
  const addr = server.address();
7791
- const endpoint = `http://127.0.0.1:${addr.port}`;
7792
- const endpointFilePath = options.endpointFilePath ?? defaultEndpointFile();
7793
- await writeEndpointFile(endpointFilePath, endpoint, secretState.value.current);
7870
+ endpointUrl = `http://127.0.0.1:${addr.port}`;
7871
+ await writeEndpointFileWithRetry(endpointFilePath, endpointUrl, secretState.value.current);
7872
+ lastPersistedSecret = secretState.value.current;
7794
7873
  return {
7795
- endpoint,
7874
+ endpoint: endpointUrl,
7796
7875
  port: addr.port,
7797
7876
  secret: secretState.value.current,
7877
+ currentSecret: () => secretState.value.current,
7798
7878
  close: () => new Promise((resolve5, reject) => {
7799
7879
  server.close((err) => err ? reject(err) : resolve5());
7800
7880
  }),
7801
7881
  suspendEndpoint: async () => {
7882
+ suspended = true;
7883
+ await currentPersist?.catch(() => void 0);
7884
+ lastPersistedSecret = null;
7802
7885
  try {
7803
- await unlink11(endpointFilePath);
7886
+ await unlink12(endpointFilePath);
7804
7887
  } catch (err) {
7805
7888
  if (err.code !== "ENOENT")
7806
7889
  throw err;
7807
7890
  }
7808
7891
  },
7809
7892
  resumeEndpoint: async () => {
7810
- let lastErr;
7811
- for (let attempt = 1; attempt <= 3; attempt++) {
7812
- try {
7813
- await writeEndpointFile(endpointFilePath, endpoint, secretState.value.current);
7814
- return;
7815
- } catch (err) {
7816
- lastErr = err;
7817
- if (attempt < 3)
7818
- await new Promise((r) => setTimeout(r, 100 * attempt));
7819
- }
7820
- }
7821
- throw lastErr;
7822
- }
7893
+ suspended = false;
7894
+ await writeEndpointFileWithRetry(endpointFilePath, endpointUrl, secretState.value.current);
7895
+ lastPersistedSecret = secretState.value.current;
7896
+ },
7897
+ retryPendingPersist: () => persistSecretIfStale()
7823
7898
  };
7824
7899
  }
7825
7900
  function handleRequest(req, res, options, sessions, secretCtx) {
@@ -7859,7 +7934,7 @@ function handleRequest(req, res, options, sessions, secretCtx) {
7859
7934
  if (!body?.repoId)
7860
7935
  return sendJson(res, 400, { error: "missing_repoId" });
7861
7936
  return options.graphCache.acquire(body.repoId).then(() => {
7862
- const sessionId = randomUUID3();
7937
+ const sessionId = randomUUID4();
7863
7938
  sessions.set(sessionId, { repoId: body.repoId });
7864
7939
  return sendJson(res, 200, { sessionId });
7865
7940
  });
@@ -8352,11 +8427,21 @@ async function readJson(req) {
8352
8427
  }
8353
8428
  }
8354
8429
  async function writeEndpointFile(path, endpoint, secret) {
8355
- await mkdir14(dirname15(path), { recursive: true });
8356
- await writeFile15(path, formatEndpointFile({ endpoint, secret }), {
8357
- encoding: "utf-8",
8358
- mode: 384
8359
- });
8430
+ await writeFileAtomic(path, formatEndpointFile({ endpoint, secret }), { mode: 384 });
8431
+ }
8432
+ async function writeEndpointFileWithRetry(path, endpoint, secret) {
8433
+ let lastErr;
8434
+ for (let attempt = 1; attempt <= 3; attempt++) {
8435
+ try {
8436
+ await writeEndpointFile(path, endpoint, secret);
8437
+ return;
8438
+ } catch (err) {
8439
+ lastErr = err;
8440
+ if (attempt < 3)
8441
+ await new Promise((r) => setTimeout(r, 100 * attempt));
8442
+ }
8443
+ }
8444
+ throw lastErr;
8360
8445
  }
8361
8446
  function extractBearer(header) {
8362
8447
  const raw = Array.isArray(header) ? header[0] : header;
@@ -10629,16 +10714,28 @@ async function runProducerCycle(input, fetchImpl = fetch) {
10629
10714
  async function runDownloaderCycle(input, fetchImpl = fetch) {
10630
10715
  if (isDisabled())
10631
10716
  return null;
10632
- const sidecar = await downloadMergedSidecar({
10717
+ const cached = await loadMergedSidecar(input.repoId, input.commitSha);
10718
+ const cachedEtag = cached ? await loadSidecarEtag(input.repoId, input.commitSha) : null;
10719
+ const res = await downloadMergedSidecar({
10633
10720
  apiUrl: input.apiUrl,
10634
10721
  authToken: input.authToken,
10635
10722
  repoId: input.repoId,
10636
- commitSha: input.commitSha
10723
+ commitSha: input.commitSha,
10724
+ ...cachedEtag ? { ifNoneMatch: cachedEtag } : {}
10637
10725
  }, fetchImpl);
10638
- if (!sidecar)
10726
+ if (res.status === "not-modified") {
10727
+ if (!cached || !input.graph?.edges)
10728
+ return { downloaded: false, edgesUpgraded: 0 };
10729
+ const cachedResult = overlayMergedSidecar(input.graph.edges, cached);
10730
+ return { downloaded: false, edgesUpgraded: cachedResult.edgesUpgraded };
10731
+ }
10732
+ if (res.status === "absent")
10639
10733
  return { downloaded: false, edgesUpgraded: 0 };
10734
+ const sidecar = res.sidecar;
10640
10735
  try {
10641
10736
  await persistMergedSidecar(input.repoId, input.commitSha, sidecar);
10737
+ if (res.etag)
10738
+ await persistSidecarEtag(input.repoId, input.commitSha, res.etag);
10642
10739
  } catch (persistErr) {
10643
10740
  console.warn(`[typed-resolution] persist failed for ${input.repoId}: ${persistErr instanceof Error ? persistErr.message : String(persistErr)}`);
10644
10741
  }
@@ -11318,14 +11415,17 @@ async function runProducerHook(notif, localPath, ctx) {
11318
11415
  console.warn(`[typed-resolution] producer failed for ${notif.repoId}: ${prodErr instanceof Error ? prodErr.message : String(prodErr)}`);
11319
11416
  }
11320
11417
  }
11321
- var processedSideEffectNotifs = /* @__PURE__ */ new Set();
11418
+ var processedSideEffectNotifs = /* @__PURE__ */ new Map();
11322
11419
  var MAX_PROCESSED_SIDE_EFFECT_NOTIFS = 512;
11323
- function alreadyProcessedSideEffect(notificationId) {
11324
- if (processedSideEffectNotifs.has(notificationId))
11420
+ var SIDECAR_MERGED_RECHECK_MS = 5 * 60 * 1e3;
11421
+ function alreadyProcessedSideEffect(notificationId, ttlMs) {
11422
+ const now = Date.now();
11423
+ const expiresAt = processedSideEffectNotifs.get(notificationId);
11424
+ if (expiresAt !== void 0 && now < expiresAt)
11325
11425
  return true;
11326
- processedSideEffectNotifs.add(notificationId);
11426
+ processedSideEffectNotifs.set(notificationId, ttlMs === void 0 ? Infinity : now + ttlMs);
11327
11427
  if (processedSideEffectNotifs.size > MAX_PROCESSED_SIDE_EFFECT_NOTIFS) {
11328
- const oldest = processedSideEffectNotifs.values().next().value;
11428
+ const oldest = processedSideEffectNotifs.keys().next().value;
11329
11429
  if (oldest !== void 0)
11330
11430
  processedSideEffectNotifs.delete(oldest);
11331
11431
  }
@@ -11362,6 +11462,8 @@ async function handleGraphReady(notif, ctx) {
11362
11462
  async function handleSidecarMerged(notif, ctx) {
11363
11463
  if (!ctx.typedResolutionHooks || !notif.commitSha)
11364
11464
  return;
11465
+ if (alreadyProcessedSideEffect(notif.notificationId, SIDECAR_MERGED_RECHECK_MS))
11466
+ return;
11365
11467
  try {
11366
11468
  await ctx.typedResolutionHooks.onSidecarMerged(notif.repoId, notif.commitSha);
11367
11469
  } catch (dlErr) {
@@ -11951,6 +12053,7 @@ async function startListener() {
11951
12053
  }
11952
12054
  }
11953
12055
  }
12056
+ mcpRuntime?.server?.retryPendingPersist?.();
11954
12057
  let anyAuthError = null;
11955
12058
  let authErrorGroup = null;
11956
12059
  let minPollInterval = pollIntervalMs;
@@ -12358,7 +12461,7 @@ function getPackageName() {
12358
12461
  import chalk from "chalk";
12359
12462
 
12360
12463
  // src/lib/config.ts
12361
- import { readFile as readFile15, writeFile as writeFile17, mkdir as mkdir17, rename as rename6, unlink as unlink12 } from "fs/promises";
12464
+ import { readFile as readFile15, writeFile as writeFile17, mkdir as mkdir17, rename as rename7, unlink as unlink13 } from "fs/promises";
12362
12465
  import { join as join44 } from "path";
12363
12466
  import lockfile6 from "proper-lockfile";
12364
12467
  async function getConfig() {
@@ -12376,10 +12479,10 @@ async function saveConfig(config2) {
12376
12479
  const tmpPath = path + ".tmp";
12377
12480
  try {
12378
12481
  await writeFile17(tmpPath, JSON.stringify(config2, null, 2));
12379
- await rename6(tmpPath, path);
12482
+ await rename7(tmpPath, path);
12380
12483
  } catch (err) {
12381
12484
  try {
12382
- await unlink12(tmpPath);
12485
+ await unlink13(tmpPath);
12383
12486
  } catch {
12384
12487
  }
12385
12488
  throw err;
@@ -12465,7 +12568,7 @@ import ora from "ora";
12465
12568
 
12466
12569
  // src/lib/auth.ts
12467
12570
  import { createHash as createHash6, randomBytes as randomBytes3 } from "crypto";
12468
- 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";
12571
+ import { readFile as readFile16, writeFile as writeFile18, mkdir as mkdir18, chmod as chmod5, unlink as unlink14, rename as rename8, open as open6 } from "fs/promises";
12469
12572
  import http from "http";
12470
12573
  import { join as join45 } from "path";
12471
12574
  import lockfile7 from "proper-lockfile";
@@ -12708,16 +12811,16 @@ async function storeCredentials2(credentials) {
12708
12811
  await writeFile18(tmpPath, JSON.stringify(credentials, null, 2), { mode: 384 });
12709
12812
  let fh;
12710
12813
  try {
12711
- fh = await open5(tmpPath, "r+");
12814
+ fh = await open6(tmpPath, "r+");
12712
12815
  await fh.datasync();
12713
12816
  } finally {
12714
12817
  await fh?.close();
12715
12818
  }
12716
- await rename7(tmpPath, credPath);
12717
- await chmod4(credPath, 384);
12819
+ await rename8(tmpPath, credPath);
12820
+ await chmod5(credPath, 384);
12718
12821
  } catch (err) {
12719
12822
  try {
12720
- await unlink13(tmpPath);
12823
+ await unlink14(tmpPath);
12721
12824
  } catch {
12722
12825
  }
12723
12826
  throw err;
@@ -12733,7 +12836,7 @@ async function storeCredentials2(credentials) {
12733
12836
  }
12734
12837
  async function clearCredentials() {
12735
12838
  try {
12736
- await unlink13(join45(getConfigDir2(), "credentials.json"));
12839
+ await unlink14(join45(getConfigDir2(), "credentials.json"));
12737
12840
  } catch (err) {
12738
12841
  if (err.code !== "ENOENT") throw err;
12739
12842
  }
@@ -12774,8 +12877,8 @@ async function performLogin() {
12774
12877
  const authorizeUrl = getAuthorizeUrl(codeChallenge, state);
12775
12878
  const callbackPromise = startCallbackServer();
12776
12879
  try {
12777
- const open6 = (await import("open")).default;
12778
- await open6(authorizeUrl);
12880
+ const open7 = (await import("open")).default;
12881
+ await open7(authorizeUrl);
12779
12882
  } catch {
12780
12883
  console.log(`
12781
12884
  Open this URL in your browser to authenticate:
@@ -15233,8 +15336,8 @@ Waiting for authentication...`);
15233
15336
  } else {
15234
15337
  spinner.text = "Opening browser for authentication...";
15235
15338
  try {
15236
- const open6 = (await import("open")).default;
15237
- await open6(authorizeUrl);
15339
+ const open7 = (await import("open")).default;
15340
+ await open7(authorizeUrl);
15238
15341
  spinner.text = "Waiting for authentication in browser...";
15239
15342
  } catch {
15240
15343
  spinner.stop();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "repowisestage",
3
- "version": "0.0.78",
3
+ "version": "0.0.79",
4
4
  "type": "module",
5
5
  "description": "AI-optimized codebase context generator",
6
6
  "bin": {