@lumy-pack/line-lore 0.0.2 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
8
  var __esm = (fn, res) => function __init() {
7
9
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
@@ -18,6 +20,14 @@ var __copyProps = (to, from, except, desc) => {
18
20
  }
19
21
  return to;
20
22
  };
23
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
24
+ // If the importer is in node compatibility mode or this is not an ESM
25
+ // file that has been converted to a CommonJS file using a Babel-
26
+ // compatible transform (i.e. "__esModule" has not been set), then set
27
+ // "default" to the CommonJS "module.exports" for node compatibility.
28
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
29
+ mod
30
+ ));
21
31
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
22
32
 
23
33
  // ../../node_modules/tsup/assets/cjs_shims.js
@@ -79,6 +89,185 @@ var init_errors = __esm({
79
89
  }
80
90
  });
81
91
 
92
+ // src/cache/sharded-cache.ts
93
+ function getShardPrefix(key) {
94
+ return key.slice(0, 2).toLowerCase();
95
+ }
96
+ async function cleanupLegacyCache() {
97
+ const legacyDir = (0, import_node_path.join)((0, import_node_os.homedir)(), ".line-lore", "cache");
98
+ try {
99
+ const entries = await (0, import_promises.readdir)(legacyDir, { withFileTypes: true });
100
+ for (const entry of entries) {
101
+ if (entry.isFile() && entry.name.endsWith(".json")) {
102
+ try {
103
+ await (0, import_promises.rm)((0, import_node_path.join)(legacyDir, entry.name), { force: true });
104
+ } catch {
105
+ }
106
+ }
107
+ }
108
+ } catch {
109
+ }
110
+ }
111
+ var import_promises, import_node_os, import_node_path, DEFAULT_CACHE_BASE, DEFAULT_MAX_ENTRIES_PER_SHARD, ShardedCache;
112
+ var init_sharded_cache = __esm({
113
+ "src/cache/sharded-cache.ts"() {
114
+ "use strict";
115
+ init_cjs_shims();
116
+ import_promises = require("fs/promises");
117
+ import_node_os = require("os");
118
+ import_node_path = require("path");
119
+ DEFAULT_CACHE_BASE = (0, import_node_path.join)((0, import_node_os.homedir)(), ".line-lore", "cache");
120
+ DEFAULT_MAX_ENTRIES_PER_SHARD = 1e3;
121
+ ShardedCache = class {
122
+ baseDir;
123
+ maxEntriesPerShard;
124
+ enabled;
125
+ shards = /* @__PURE__ */ new Map();
126
+ constructor(namespace, options) {
127
+ const cacheBase = options?.cacheBase ?? DEFAULT_CACHE_BASE;
128
+ const repoId = options?.repoId ?? {
129
+ host: "_local",
130
+ owner: "_",
131
+ repo: "_default"
132
+ };
133
+ this.baseDir = (0, import_node_path.join)(
134
+ cacheBase,
135
+ repoId.host,
136
+ repoId.owner,
137
+ repoId.repo,
138
+ namespace
139
+ );
140
+ this.maxEntriesPerShard = options?.maxEntriesPerShard ?? DEFAULT_MAX_ENTRIES_PER_SHARD;
141
+ this.enabled = options?.enabled ?? true;
142
+ }
143
+ async get(key) {
144
+ if (!this.enabled) return null;
145
+ const data = await this.readShard(getShardPrefix(key));
146
+ const entry = data[key];
147
+ return entry?.value ?? null;
148
+ }
149
+ async has(key) {
150
+ if (!this.enabled) return false;
151
+ const data = await this.readShard(getShardPrefix(key));
152
+ return key in data;
153
+ }
154
+ set(key, value) {
155
+ if (!this.enabled) return Promise.resolve();
156
+ const prefix = getShardPrefix(key);
157
+ const state = this.getShardState(prefix);
158
+ state.writeQueue = state.writeQueue.then(() => this.doSet(prefix, key, value)).catch(() => {
159
+ });
160
+ return state.writeQueue;
161
+ }
162
+ delete(key) {
163
+ if (!this.enabled) return Promise.resolve(false);
164
+ const prefix = getShardPrefix(key);
165
+ const state = this.getShardState(prefix);
166
+ let deleted = false;
167
+ state.writeQueue = state.writeQueue.then(async () => {
168
+ const data = await this.readShard(prefix);
169
+ if (key in data) {
170
+ delete data[key];
171
+ state.store = data;
172
+ await this.writeShard(prefix, data);
173
+ deleted = true;
174
+ }
175
+ }).catch(() => {
176
+ });
177
+ return state.writeQueue.then(() => deleted);
178
+ }
179
+ async clear() {
180
+ this.shards.clear();
181
+ try {
182
+ await (0, import_promises.rm)(this.baseDir, { recursive: true, force: true });
183
+ } catch {
184
+ }
185
+ }
186
+ async size() {
187
+ let total = 0;
188
+ try {
189
+ const files = await (0, import_promises.readdir)(this.baseDir);
190
+ for (const file of files) {
191
+ if (!file.endsWith(".json")) continue;
192
+ const prefix = file.replace(".json", "");
193
+ const data = await this.readShard(prefix);
194
+ total += Object.keys(data).length;
195
+ }
196
+ } catch {
197
+ }
198
+ return total;
199
+ }
200
+ async destroy() {
201
+ this.shards.clear();
202
+ try {
203
+ await (0, import_promises.rm)(this.baseDir, { recursive: true, force: true });
204
+ } catch {
205
+ }
206
+ }
207
+ getShardState(prefix) {
208
+ let state = this.shards.get(prefix);
209
+ if (!state) {
210
+ state = { store: null, writeQueue: Promise.resolve() };
211
+ this.shards.set(prefix, state);
212
+ }
213
+ return state;
214
+ }
215
+ async doSet(prefix, key, value) {
216
+ const state = this.getShardState(prefix);
217
+ const data = await this.readShard(prefix);
218
+ data[key] = { key, value, createdAt: Date.now() };
219
+ const keys = Object.keys(data);
220
+ if (keys.length > this.maxEntriesPerShard) {
221
+ const sorted = keys.sort((a, b) => data[a].createdAt - data[b].createdAt);
222
+ const toRemove = sorted.slice(0, keys.length - this.maxEntriesPerShard);
223
+ for (const k of toRemove) {
224
+ delete data[k];
225
+ }
226
+ }
227
+ state.store = data;
228
+ await this.writeShard(prefix, data);
229
+ }
230
+ async readShard(prefix) {
231
+ const state = this.getShardState(prefix);
232
+ if (state.store !== null) return state.store;
233
+ const filePath = (0, import_node_path.join)(this.baseDir, `${prefix}.json`);
234
+ try {
235
+ const content = await (0, import_promises.readFile)(filePath, "utf-8");
236
+ state.store = JSON.parse(content);
237
+ return state.store;
238
+ } catch (error) {
239
+ if (error instanceof SyntaxError || error instanceof Error && "code" in error && error.code === "ERR_INVALID_JSON") {
240
+ console.warn(
241
+ `[line-lore] Cache shard corrupted, resetting: ${filePath}`
242
+ );
243
+ state.store = {};
244
+ await this.writeShard(prefix, {});
245
+ return state.store;
246
+ }
247
+ state.store = {};
248
+ return state.store;
249
+ }
250
+ }
251
+ async writeShard(prefix, data) {
252
+ await (0, import_promises.mkdir)(this.baseDir, { recursive: true });
253
+ const filePath = (0, import_node_path.join)(this.baseDir, `${prefix}.json`);
254
+ const tmpPath = `${filePath}.tmp`;
255
+ await (0, import_promises.writeFile)(tmpPath, JSON.stringify(data), "utf-8");
256
+ await (0, import_promises.rename)(tmpPath, filePath);
257
+ }
258
+ };
259
+ }
260
+ });
261
+
262
+ // src/cache/index.ts
263
+ var init_cache = __esm({
264
+ "src/cache/index.ts"() {
265
+ "use strict";
266
+ init_cjs_shims();
267
+ init_sharded_cache();
268
+ }
269
+ });
270
+
82
271
  // src/git/executor.ts
83
272
  async function execCommand(command, args, options, errorCode) {
84
273
  const { cwd, timeout, allowExitCodes = [] } = options ?? {};
@@ -91,9 +280,10 @@ async function execCommand(command, args, options, errorCode) {
91
280
  });
92
281
  const exitCode = result.exitCode ?? 0;
93
282
  if (exitCode !== 0 && !allowExitCodes.includes(exitCode)) {
283
+ const isNotGitRepo = exitCode === 128 && command === "git" && /not a git repository/i.test(result.stderr);
94
284
  throw new LineLoreError(
95
- failCode,
96
- `${command} ${args[0]} failed with exit code ${exitCode}: ${result.stderr}`,
285
+ isNotGitRepo ? LineLoreErrorCode.NOT_GIT_REPO : failCode,
286
+ isNotGitRepo ? `Not a git repository: ${result.stderr.trim()}` : `${command} ${args[0]} failed with exit code ${exitCode}: ${result.stderr}`,
97
287
  { command, args, exitCode, stderr: result.stderr, cwd }
98
288
  );
99
289
  }
@@ -145,116 +335,26 @@ var init_executor = __esm({
145
335
  }
146
336
  });
147
337
 
148
- // src/cache/file-cache.ts
149
- var import_promises, import_node_os, import_node_path, DEFAULT_CACHE_DIR, DEFAULT_MAX_ENTRIES, FileCache;
150
- var init_file_cache = __esm({
151
- "src/cache/file-cache.ts"() {
152
- "use strict";
153
- init_cjs_shims();
154
- import_promises = require("fs/promises");
155
- import_node_os = require("os");
156
- import_node_path = require("path");
157
- DEFAULT_CACHE_DIR = (0, import_node_path.join)((0, import_node_os.homedir)(), ".line-lore", "cache");
158
- DEFAULT_MAX_ENTRIES = 1e4;
159
- FileCache = class {
160
- filePath;
161
- maxEntries;
162
- writeQueue = Promise.resolve();
163
- constructor(fileName, options) {
164
- const cacheDir = options?.cacheDir ?? DEFAULT_CACHE_DIR;
165
- this.filePath = (0, import_node_path.join)(cacheDir, fileName);
166
- this.maxEntries = options?.maxEntries ?? DEFAULT_MAX_ENTRIES;
167
- }
168
- async get(key) {
169
- const data = await this.readStore();
170
- const entry = data[key];
171
- return entry?.value ?? null;
172
- }
173
- async has(key) {
174
- const data = await this.readStore();
175
- return key in data;
176
- }
177
- set(key, value) {
178
- this.writeQueue = this.writeQueue.then(() => this.doSet(key, value)).catch(() => {
179
- });
180
- return this.writeQueue;
181
- }
182
- delete(key) {
183
- let deleted = false;
184
- this.writeQueue = this.writeQueue.then(async () => {
185
- const data = await this.readStore();
186
- if (key in data) {
187
- delete data[key];
188
- await this.writeStore(data);
189
- deleted = true;
190
- }
191
- }).catch(() => {
192
- });
193
- return this.writeQueue.then(() => deleted);
194
- }
195
- clear() {
196
- this.writeQueue = this.writeQueue.then(() => this.writeStore({})).catch(() => {
197
- });
198
- return this.writeQueue;
199
- }
200
- async size() {
201
- const data = await this.readStore();
202
- return Object.keys(data).length;
203
- }
204
- async doSet(key, value) {
205
- const data = await this.readStore();
206
- data[key] = { key, value, createdAt: Date.now() };
207
- const keys = Object.keys(data);
208
- if (keys.length > this.maxEntries) {
209
- const sorted = keys.sort((a, b) => data[a].createdAt - data[b].createdAt);
210
- const toRemove = sorted.slice(0, keys.length - this.maxEntries);
211
- for (const k of toRemove) {
212
- delete data[k];
213
- }
214
- }
215
- await this.writeStore(data);
216
- }
217
- async readStore() {
218
- try {
219
- const content = await (0, import_promises.readFile)(this.filePath, "utf-8");
220
- return JSON.parse(content);
221
- } catch (error) {
222
- if (error instanceof SyntaxError || error instanceof Error && "code" in error && error.code === "ERR_INVALID_JSON") {
223
- console.warn(
224
- `[line-lore] Cache file corrupted, resetting: ${this.filePath}`
225
- );
226
- await this.writeStore({});
227
- return {};
228
- }
229
- return {};
230
- }
231
- }
232
- async writeStore(data) {
233
- const dir = (0, import_node_path.join)(this.filePath, "..");
234
- await (0, import_promises.mkdir)(dir, { recursive: true });
235
- const tmpPath = `${this.filePath}.tmp`;
236
- await (0, import_promises.writeFile)(tmpPath, JSON.stringify(data), "utf-8");
237
- await (0, import_promises.rename)(tmpPath, this.filePath);
238
- }
239
- async destroy() {
240
- try {
241
- await (0, import_promises.unlink)(this.filePath);
242
- } catch {
243
- }
244
- }
245
- };
246
- }
247
- });
248
-
249
338
  // src/core/ancestry/ancestry.ts
250
339
  async function findMergeCommit(commitSha, options) {
251
340
  const ref = options?.ref ?? "HEAD";
341
+ const firstParentResult = await findMergeCommitWithArgs(
342
+ commitSha,
343
+ ref,
344
+ ["--first-parent"],
345
+ options
346
+ );
347
+ if (firstParentResult) return firstParentResult;
348
+ return findMergeCommitWithArgs(commitSha, ref, [], options);
349
+ }
350
+ async function findMergeCommitWithArgs(commitSha, ref, extraArgs, options) {
252
351
  try {
253
352
  const result = await gitExec(
254
353
  [
255
354
  "log",
256
355
  "--merges",
257
356
  "--ancestry-path",
357
+ ...extraArgs,
258
358
  `${commitSha}..${ref}`,
259
359
  "--topo-order",
260
360
  "--reverse",
@@ -262,28 +362,30 @@ async function findMergeCommit(commitSha, options) {
262
362
  ],
263
363
  { cwd: options?.cwd, timeout: options?.timeout }
264
364
  );
265
- const lines = result.stdout.trim().split("\n").filter(Boolean);
365
+ const lines = (0, import_common_utils9.filter)(result.stdout.trim().split("\n"), import_common_utils9.isTruthy);
266
366
  if (lines.length === 0) return null;
267
- const firstLine = lines[0];
268
- const parts = firstLine.split(" ");
269
- if (parts.length < 3) return null;
270
- const mergeCommitSha = parts[0];
271
- const parentShas = [];
272
- let subjectStart = 1;
273
- for (let i = 1; i < parts.length; i++) {
274
- if (/^[0-9a-f]{40}$/.test(parts[i])) {
275
- parentShas.push(parts[i]);
276
- subjectStart = i + 1;
277
- } else {
278
- break;
279
- }
280
- }
281
- const subject = parts.slice(subjectStart).join(" ");
282
- return { mergeCommitSha, parentShas, subject };
367
+ return parseMergeLogLine(lines[0]);
283
368
  } catch {
284
369
  return null;
285
370
  }
286
371
  }
372
+ function parseMergeLogLine(line) {
373
+ const parts = line.split(" ");
374
+ if (parts.length < 3) return null;
375
+ const mergeCommitSha = parts[0];
376
+ const parentShas = [];
377
+ let subjectStart = 1;
378
+ for (let i = 1; i < parts.length; i++) {
379
+ if (/^[0-9a-f]{40}$/.test(parts[i])) {
380
+ parentShas.push(parts[i]);
381
+ subjectStart = i + 1;
382
+ } else {
383
+ break;
384
+ }
385
+ }
386
+ const subject = parts.slice(subjectStart).join(" ");
387
+ return { mergeCommitSha, parentShas, subject };
388
+ }
287
389
  function extractPRFromMergeMessage(subject) {
288
390
  const ghMatch = /Merge pull request #(\d+)/.exec(subject);
289
391
  if (ghMatch) return parseInt(ghMatch[1], 10);
@@ -293,10 +395,12 @@ function extractPRFromMergeMessage(subject) {
293
395
  if (glMatch) return parseInt(glMatch[1], 10);
294
396
  return null;
295
397
  }
398
+ var import_common_utils9;
296
399
  var init_ancestry = __esm({
297
400
  "src/core/ancestry/ancestry.ts"() {
298
401
  "use strict";
299
402
  init_cjs_shims();
403
+ import_common_utils9 = require("@winglet/common-utils");
300
404
  init_executor();
301
405
  }
302
406
  });
@@ -311,14 +415,25 @@ var init_ancestry2 = __esm({
311
415
  });
312
416
 
313
417
  // src/core/patch-id/patch-id.ts
314
- function getCache() {
315
- if (!patchIdCache) {
316
- patchIdCache = new FileCache("sha-to-patch-id.json");
418
+ function repoKey(repoId) {
419
+ return `${repoId.host}/${repoId.owner}/${repoId.repo}`;
420
+ }
421
+ function getCache(repoId, noCache) {
422
+ if (noCache) {
423
+ return new ShardedCache("patch-id", { repoId, enabled: false });
317
424
  }
318
- return patchIdCache;
425
+ const key = repoKey(
426
+ repoId ?? { host: "_local", owner: "_", repo: "_default" }
427
+ );
428
+ let cache = cacheRegistry.get(key);
429
+ if (!cache) {
430
+ cache = new ShardedCache("patch-id", { repoId });
431
+ cacheRegistry.set(key, cache);
432
+ }
433
+ return cache;
319
434
  }
320
435
  async function computePatchId(commitSha, options) {
321
- const cache = getCache();
436
+ const cache = getCache(options?.repoId, options?.noCache);
322
437
  const cached = await cache.get(commitSha);
323
438
  if (cached) return cached;
324
439
  try {
@@ -349,7 +464,7 @@ async function findPatchIdMatch(commitSha, options) {
349
464
  ["log", "--format=%H", `-${scanDepth}`, ref],
350
465
  { cwd: options?.cwd, timeout: options?.timeout }
351
466
  );
352
- const candidates = logResult.stdout.trim().split("\n").filter(Boolean);
467
+ const candidates = (0, import_common_utils10.filter)(logResult.stdout.trim().split("\n"), import_common_utils10.isTruthy);
353
468
  for (const candidateSha of candidates) {
354
469
  if (candidateSha === commitSha) continue;
355
470
  const candidatePatchId = await computePatchId(candidateSha, options);
@@ -362,17 +477,18 @@ async function findPatchIdMatch(commitSha, options) {
362
477
  return null;
363
478
  }
364
479
  function resetPatchIdCache() {
365
- patchIdCache = null;
480
+ cacheRegistry.clear();
366
481
  }
367
- var DEFAULT_SCAN_DEPTH, patchIdCache;
482
+ var import_common_utils10, DEFAULT_SCAN_DEPTH, cacheRegistry;
368
483
  var init_patch_id = __esm({
369
484
  "src/core/patch-id/patch-id.ts"() {
370
485
  "use strict";
371
486
  init_cjs_shims();
372
- init_file_cache();
487
+ import_common_utils10 = require("@winglet/common-utils");
488
+ init_cache();
373
489
  init_executor();
374
490
  DEFAULT_SCAN_DEPTH = 500;
375
- patchIdCache = null;
491
+ cacheRegistry = /* @__PURE__ */ new Map();
376
492
  }
377
493
  });
378
494
 
@@ -392,40 +508,58 @@ var init_patch_id2 = __esm({
392
508
  });
393
509
 
394
510
  // src/core/pr-lookup/pr-lookup.ts
395
- function getCache2() {
396
- if (!prCache) {
397
- prCache = new FileCache("sha-to-pr.json");
511
+ function repoKey2(repoId) {
512
+ return `${repoId.host}/${repoId.owner}/${repoId.repo}`;
513
+ }
514
+ function getCache2(repoId, noCache) {
515
+ if (noCache) {
516
+ return new ShardedCache("pr", { repoId, enabled: false });
398
517
  }
399
- return prCache;
518
+ const key = repoKey2(
519
+ repoId ?? { host: "_local", owner: "_", repo: "_default" }
520
+ );
521
+ let cache = cacheRegistry2.get(key);
522
+ if (!cache) {
523
+ cache = new ShardedCache("pr", { repoId });
524
+ cacheRegistry2.set(key, cache);
525
+ }
526
+ return cache;
400
527
  }
401
528
  async function lookupPR(commitSha, adapter, options) {
402
- const cache = getCache2();
529
+ const cache = getCache2(options?.repoId, options?.noCache);
403
530
  const cached = await cache.get(commitSha);
404
531
  if (cached) return cached;
532
+ let mergeBasedPR = null;
405
533
  const mergeResult = await findMergeCommit(commitSha, options);
406
534
  if (mergeResult) {
407
535
  const prNumber = extractPRFromMergeMessage(mergeResult.subject);
408
536
  if (prNumber) {
409
537
  if (adapter) {
410
538
  const prInfo = await adapter.getPRForCommit(mergeResult.mergeCommitSha);
411
- if (prInfo) {
412
- await cache.set(commitSha, prInfo);
413
- return prInfo;
539
+ if (prInfo?.mergedAt) {
540
+ mergeBasedPR = prInfo;
414
541
  }
415
542
  }
416
- const minimalPR = {
417
- number: prNumber,
418
- title: mergeResult.subject,
419
- author: "",
420
- url: "",
421
- mergeCommit: mergeResult.mergeCommitSha,
422
- baseBranch: ""
423
- };
424
- await cache.set(commitSha, minimalPR);
425
- return minimalPR;
543
+ if (!mergeBasedPR) {
544
+ mergeBasedPR = {
545
+ number: prNumber,
546
+ title: mergeResult.subject,
547
+ author: "",
548
+ url: "",
549
+ mergeCommit: mergeResult.mergeCommitSha,
550
+ baseBranch: ""
551
+ };
552
+ }
553
+ if (!options?.deep || mergeBasedPR.mergedAt) {
554
+ await cache.set(commitSha, mergeBasedPR);
555
+ return mergeBasedPR;
556
+ }
426
557
  }
427
558
  }
428
- const patchIdMatch = await findPatchIdMatch(commitSha, options);
559
+ const patchIdMatch = await findPatchIdMatch(commitSha, {
560
+ ...options,
561
+ scanDepth: options?.deep ? DEEP_SCAN_DEPTH : void 0
562
+ });
429
563
  if (patchIdMatch) {
430
564
  const result = await lookupPR(patchIdMatch.matchedSha, adapter, options);
431
565
  if (result) {
@@ -433,9 +567,13 @@ async function lookupPR(commitSha, adapter, options) {
433
567
  return result;
434
568
  }
435
569
  }
570
+ if (mergeBasedPR) {
571
+ await cache.set(commitSha, mergeBasedPR);
572
+ return mergeBasedPR;
573
+ }
436
574
  if (adapter) {
437
575
  const prInfo = await adapter.getPRForCommit(commitSha);
438
- if (prInfo) {
576
+ if (prInfo?.mergedAt) {
439
577
  await cache.set(commitSha, prInfo);
440
578
  return prInfo;
441
579
  }
@@ -443,17 +581,18 @@ async function lookupPR(commitSha, adapter, options) {
443
581
  return null;
444
582
  }
445
583
  function resetPRCache() {
446
- prCache = null;
584
+ cacheRegistry2.clear();
447
585
  }
448
- var prCache;
586
+ var cacheRegistry2, DEEP_SCAN_DEPTH;
449
587
  var init_pr_lookup = __esm({
450
588
  "src/core/pr-lookup/pr-lookup.ts"() {
451
589
  "use strict";
452
590
  init_cjs_shims();
453
- init_file_cache();
591
+ init_cache();
454
592
  init_ancestry2();
455
593
  init_patch_id2();
456
- prCache = null;
594
+ cacheRegistry2 = /* @__PURE__ */ new Map();
595
+ DEEP_SCAN_DEPTH = 2e3;
457
596
  }
458
597
  });
459
598
 
@@ -477,6 +616,7 @@ __export(src_exports, {
477
616
  LineLoreError: () => LineLoreError,
478
617
  LineLoreErrorCode: () => LineLoreErrorCode,
479
618
  clearCache: () => clearCache,
619
+ graph: () => graph,
480
620
  health: () => health,
481
621
  trace: () => trace,
482
622
  traverseIssueGraph: () => traverseIssueGraph
@@ -487,12 +627,15 @@ init_errors();
487
627
 
488
628
  // src/core/core.ts
489
629
  init_cjs_shims();
630
+ var import_node_crypto2 = require("crypto");
631
+ var import_common_utils11 = require("@winglet/common-utils");
490
632
 
491
633
  // src/ast/index.ts
492
634
  init_cjs_shims();
493
635
 
494
636
  // src/ast/parser.ts
495
637
  init_cjs_shims();
638
+ var import_common_utils = require("@winglet/common-utils");
496
639
  var astGrep = null;
497
640
  var loadAttempted = false;
498
641
  var available = false;
@@ -543,7 +686,7 @@ async function findSymbols(source, lang) {
543
686
  try {
544
687
  const { parse, Lang } = astGrep;
545
688
  const langEnum = Lang[lang] ?? Lang[lang.charAt(0).toUpperCase() + lang.slice(1)];
546
- if (langEnum == null) return [];
689
+ if ((0, import_common_utils.isNil)(langEnum)) return [];
547
690
  const root = parse(langEnum, source).root();
548
691
  const symbols = [];
549
692
  const kindPatterns = [
@@ -695,8 +838,14 @@ function findPythonBlockEnd(lines, startIdx) {
695
838
  return lines.length - 1;
696
839
  }
697
840
 
841
+ // src/core/core.ts
842
+ init_cache();
843
+ init_errors();
844
+ init_executor();
845
+
698
846
  // src/git/health.ts
699
847
  init_cjs_shims();
848
+ var import_common_utils2 = require("@winglet/common-utils");
700
849
  init_executor();
701
850
  var GIT_VERSION_PATTERN = /git version (\d+\.\d+\.\d+)/;
702
851
  var BLOOM_FILTER_MIN_VERSION = [2, 27, 0];
@@ -705,7 +854,7 @@ function parseGitVersion(versionStr) {
705
854
  return match?.[1] ?? "0.0.0";
706
855
  }
707
856
  function isVersionAtLeast(version, minVersion) {
708
- const parts = version.split(".").map(Number);
857
+ const parts = (0, import_common_utils2.map)(version.split("."), Number);
709
858
  for (let i = 0; i < 3; i++) {
710
859
  if ((parts[i] ?? 0) > minVersion[i]) return true;
711
860
  if ((parts[i] ?? 0) < minVersion[i]) return false;
@@ -787,6 +936,7 @@ init_cjs_shims();
787
936
 
788
937
  // src/platform/github/github-adapter.ts
789
938
  init_cjs_shims();
939
+ var import_common_utils3 = require("@winglet/common-utils");
790
940
  init_executor();
791
941
 
792
942
  // src/platform/scheduler/index.ts
@@ -797,8 +947,6 @@ init_cjs_shims();
797
947
  var RequestScheduler = class {
798
948
  rateLimitInfo = null;
799
949
  threshold;
800
- etagCache = /* @__PURE__ */ new Map();
801
- responseCache = /* @__PURE__ */ new Map();
802
950
  constructor(options) {
803
951
  this.threshold = options?.rateLimitThreshold ?? 10;
804
952
  }
@@ -812,16 +960,6 @@ var RequestScheduler = class {
812
960
  getRateLimit() {
813
961
  return this.rateLimitInfo;
814
962
  }
815
- setEtag(url, etag, response) {
816
- this.etagCache.set(url, etag);
817
- this.responseCache.set(url, response);
818
- }
819
- getEtag(url) {
820
- return this.etagCache.get(url) ?? null;
821
- }
822
- getCachedResponse(url) {
823
- return this.responseCache.get(url) ?? null;
824
- }
825
963
  };
826
964
 
827
965
  // src/platform/github/github-adapter.ts
@@ -829,9 +967,14 @@ var GitHubAdapter = class {
829
967
  platform = "github";
830
968
  scheduler;
831
969
  hostname;
970
+ defaultBranchCache = null;
971
+ remoteName;
972
+ cwd;
832
973
  constructor(options) {
833
974
  this.hostname = options?.hostname ?? "github.com";
834
975
  this.scheduler = options?.scheduler ?? new RequestScheduler();
976
+ this.remoteName = options?.remoteName ?? "origin";
977
+ this.cwd = options?.cwd;
835
978
  }
836
979
  async checkAuth() {
837
980
  try {
@@ -839,6 +982,7 @@ var GitHubAdapter = class {
839
982
  "gh",
840
983
  ["auth", "token", "--hostname", this.hostname],
841
984
  {
985
+ cwd: this.cwd,
842
986
  allowExitCodes: [1]
843
987
  }
844
988
  );
@@ -854,64 +998,98 @@ var GitHubAdapter = class {
854
998
  async getPRForCommit(sha) {
855
999
  if (this.scheduler.isRateLimited()) return null;
856
1000
  try {
857
- const result = await shellExec("gh", [
858
- "api",
859
- `repos/{owner}/{repo}/commits/${sha}/pulls`,
860
- "--hostname",
861
- this.hostname,
862
- "--jq",
863
- ".[0] | {number, title, user: .user.login, html_url, merge_commit_sha, base: .base.ref, merged_at}"
864
- ]);
865
- const data = JSON.parse(result.stdout);
866
- if (!data?.number) return null;
1001
+ const result = await shellExec(
1002
+ "gh",
1003
+ [
1004
+ "api",
1005
+ `repos/{owner}/{repo}/commits/${sha}/pulls`,
1006
+ "--hostname",
1007
+ this.hostname,
1008
+ "--jq",
1009
+ "[.[] | select(.merged_at != null) | {number, title, user: .user.login, html_url, merge_commit_sha, base: .base.ref, merged_at}] | sort_by(.merged_at)"
1010
+ ],
1011
+ { cwd: this.cwd }
1012
+ );
1013
+ const prs = JSON.parse(result.stdout);
1014
+ if (!(0, import_common_utils3.isArray)(prs) || prs.length === 0) return null;
1015
+ const defaultBranch = await this.detectDefaultBranch();
1016
+ const defaultBranchPR = prs.find(
1017
+ (pr) => pr.base === defaultBranch
1018
+ );
1019
+ const data = defaultBranchPR ?? prs[0];
867
1020
  return {
868
1021
  number: data.number,
869
1022
  title: data.title ?? "",
870
1023
  author: data.user ?? "",
871
1024
  url: data.html_url ?? "",
872
1025
  mergeCommit: data.merge_commit_sha ?? sha,
873
- baseBranch: data.base ?? "main",
1026
+ baseBranch: data.base ?? defaultBranch,
874
1027
  mergedAt: data.merged_at
875
1028
  };
876
1029
  } catch {
877
1030
  return null;
878
1031
  }
879
1032
  }
1033
+ async detectDefaultBranch() {
1034
+ if (this.defaultBranchCache) return this.defaultBranchCache;
1035
+ try {
1036
+ const result = await gitExec(
1037
+ ["symbolic-ref", `refs/remotes/${this.remoteName}/HEAD`],
1038
+ { cwd: this.cwd }
1039
+ );
1040
+ const ref = result.stdout.trim();
1041
+ this.defaultBranchCache = ref.replace(`refs/remotes/${this.remoteName}/`, "") || "main";
1042
+ return this.defaultBranchCache;
1043
+ } catch {
1044
+ return "main";
1045
+ }
1046
+ }
880
1047
  async getPRCommits(prNumber) {
881
1048
  try {
882
- const result = await shellExec("gh", [
883
- "api",
884
- `repos/{owner}/{repo}/pulls/${prNumber}/commits`,
885
- "--hostname",
886
- this.hostname,
887
- "--jq",
888
- ".[].sha"
889
- ]);
890
- return result.stdout.trim().split("\n").filter(Boolean);
1049
+ const result = await shellExec(
1050
+ "gh",
1051
+ [
1052
+ "api",
1053
+ `repos/{owner}/{repo}/pulls/${prNumber}/commits`,
1054
+ "--hostname",
1055
+ this.hostname,
1056
+ "--jq",
1057
+ ".[].sha"
1058
+ ],
1059
+ { cwd: this.cwd }
1060
+ );
1061
+ return (0, import_common_utils3.filter)(result.stdout.trim().split("\n"), import_common_utils3.isTruthy);
891
1062
  } catch {
892
1063
  return [];
893
1064
  }
894
1065
  }
895
1066
  async getLinkedIssues(prNumber) {
896
1067
  try {
897
- const result = await shellExec("gh", [
898
- "api",
899
- "graphql",
900
- "--hostname",
901
- this.hostname,
902
- "-f",
903
- `query=query { repository(owner: "{owner}", name: "{repo}") { pullRequest(number: ${prNumber}) { closingIssuesReferences(first: 10) { nodes { number title url state labels(first: 5) { nodes { name } } } } } } }`,
904
- "--jq",
905
- ".data.repository.pullRequest.closingIssuesReferences.nodes"
906
- ]);
1068
+ const result = await shellExec(
1069
+ "gh",
1070
+ [
1071
+ "api",
1072
+ "graphql",
1073
+ "--hostname",
1074
+ this.hostname,
1075
+ "-f",
1076
+ `query=query { repository(owner: "{owner}", name: "{repo}") { pullRequest(number: ${prNumber}) { closingIssuesReferences(first: 10) { nodes { number title url state labels(first: 5) { nodes { name } } } } } } }`,
1077
+ "--jq",
1078
+ ".data.repository.pullRequest.closingIssuesReferences.nodes"
1079
+ ],
1080
+ { cwd: this.cwd }
1081
+ );
907
1082
  const nodes = JSON.parse(result.stdout);
908
- if (!Array.isArray(nodes)) return [];
909
- return nodes.map((node) => ({
1083
+ if (!(0, import_common_utils3.isArray)(nodes)) return [];
1084
+ return (0, import_common_utils3.map)(nodes, (node) => ({
910
1085
  number: node.number,
911
1086
  title: node.title ?? "",
912
1087
  url: node.url ?? "",
913
1088
  state: (node.state ?? "open").toLowerCase(),
914
- labels: (node.labels?.nodes ?? []).map((l) => l.name)
1089
+ labels: (0, import_common_utils3.map)(
1090
+ node.labels?.nodes ?? [],
1091
+ (l) => l.name
1092
+ )
915
1093
  }));
916
1094
  } catch {
917
1095
  return [];
@@ -919,23 +1097,28 @@ var GitHubAdapter = class {
919
1097
  }
920
1098
  async getLinkedPRs(issueNumber) {
921
1099
  try {
922
- const result = await shellExec("gh", [
923
- "api",
924
- `repos/{owner}/{repo}/issues/${issueNumber}/timeline`,
925
- "--hostname",
926
- this.hostname,
927
- "--jq",
928
- '[.[] | select(.source.issue.pull_request) | .source.issue] | map({number, title, user: .user.login, html_url, merge_commit_sha: .pull_request.merge_commit_sha, base: "main", merged_at: .pull_request.merged_at})'
929
- ]);
1100
+ const result = await shellExec(
1101
+ "gh",
1102
+ [
1103
+ "api",
1104
+ `repos/{owner}/{repo}/issues/${issueNumber}/timeline`,
1105
+ "--hostname",
1106
+ this.hostname,
1107
+ "--jq",
1108
+ "[.[] | select(.source.issue.pull_request) | .source.issue] | map({number, title, user: .user.login, html_url, merge_commit_sha: .pull_request.merge_commit_sha, merged_at: .pull_request.merged_at})"
1109
+ ],
1110
+ { cwd: this.cwd }
1111
+ );
930
1112
  const prs = JSON.parse(result.stdout);
931
- if (!Array.isArray(prs)) return [];
932
- return prs.map((pr) => ({
1113
+ if (!(0, import_common_utils3.isArray)(prs)) return [];
1114
+ const defaultBranch = await this.detectDefaultBranch();
1115
+ return (0, import_common_utils3.map)(prs, (pr) => ({
933
1116
  number: pr.number,
934
1117
  title: pr.title ?? "",
935
1118
  author: pr.user ?? "",
936
1119
  url: pr.html_url ?? "",
937
1120
  mergeCommit: pr.merge_commit_sha ?? "",
938
- baseBranch: pr.base ?? "main",
1121
+ baseBranch: defaultBranch,
939
1122
  mergedAt: pr.merged_at
940
1123
  }));
941
1124
  } catch {
@@ -944,14 +1127,18 @@ var GitHubAdapter = class {
944
1127
  }
945
1128
  async getRateLimit() {
946
1129
  try {
947
- const result = await shellExec("gh", [
948
- "api",
949
- "rate_limit",
950
- "--hostname",
951
- this.hostname,
952
- "--jq",
953
- ".rate | {limit, remaining, reset}"
954
- ]);
1130
+ const result = await shellExec(
1131
+ "gh",
1132
+ [
1133
+ "api",
1134
+ "rate_limit",
1135
+ "--hostname",
1136
+ this.hostname,
1137
+ "--jq",
1138
+ ".rate | {limit, remaining, reset}"
1139
+ ],
1140
+ { cwd: this.cwd }
1141
+ );
955
1142
  const data = JSON.parse(result.stdout);
956
1143
  const info = {
957
1144
  limit: data.limit ?? 5e3,
@@ -971,7 +1158,12 @@ init_cjs_shims();
971
1158
  var GitHubEnterpriseAdapter = class extends GitHubAdapter {
972
1159
  platform = "github-enterprise";
973
1160
  constructor(hostname, options) {
974
- super({ hostname, scheduler: options?.scheduler });
1161
+ super({
1162
+ hostname,
1163
+ scheduler: options?.scheduler,
1164
+ remoteName: options?.remoteName,
1165
+ cwd: options?.cwd
1166
+ });
975
1167
  }
976
1168
  };
977
1169
 
@@ -980,14 +1172,20 @@ init_cjs_shims();
980
1172
 
981
1173
  // src/platform/gitlab/gitlab-adapter.ts
982
1174
  init_cjs_shims();
1175
+ var import_common_utils4 = require("@winglet/common-utils");
983
1176
  init_executor();
984
1177
  var GitLabAdapter = class {
985
1178
  platform = "gitlab";
986
1179
  scheduler;
987
1180
  hostname;
1181
+ defaultBranchCache = null;
1182
+ remoteName;
1183
+ cwd;
988
1184
  constructor(options) {
989
1185
  this.hostname = options?.hostname ?? "gitlab.com";
990
1186
  this.scheduler = options?.scheduler ?? new RequestScheduler();
1187
+ this.remoteName = options?.remoteName ?? "origin";
1188
+ this.cwd = options?.cwd;
991
1189
  }
992
1190
  async checkAuth() {
993
1191
  if (process.env.GITLAB_TOKEN) {
@@ -997,7 +1195,7 @@ var GitLabAdapter = class {
997
1195
  const result = await shellExec(
998
1196
  "glab",
999
1197
  ["auth", "status", "--hostname", this.hostname],
1000
- { allowExitCodes: [1] }
1198
+ { cwd: this.cwd, allowExitCodes: [1] }
1001
1199
  );
1002
1200
  return {
1003
1201
  authenticated: result.exitCode === 0,
@@ -1010,54 +1208,93 @@ var GitLabAdapter = class {
1010
1208
  async getPRForCommit(sha) {
1011
1209
  if (this.scheduler.isRateLimited()) return null;
1012
1210
  try {
1013
- const result = await shellExec("glab", [
1014
- "api",
1015
- `projects/:id/repository/commits/${sha}/merge_requests`,
1016
- "--hostname",
1017
- this.hostname
1018
- ]);
1211
+ const result = await shellExec(
1212
+ "glab",
1213
+ [
1214
+ "api",
1215
+ `projects/:id/repository/commits/${sha}/merge_requests`,
1216
+ "--hostname",
1217
+ this.hostname
1218
+ ],
1219
+ { cwd: this.cwd }
1220
+ );
1019
1221
  const mrs = JSON.parse(result.stdout);
1020
- if (!Array.isArray(mrs) || mrs.length === 0) return null;
1021
- const mr = mrs[0];
1222
+ if (!(0, import_common_utils4.isArray)(mrs) || mrs.length === 0) return null;
1223
+ const mergedMRs = (0, import_common_utils4.filter)(
1224
+ mrs,
1225
+ (mr2) => mr2.state === "merged" && (0, import_common_utils4.isNotNil)(mr2.merged_at)
1226
+ ).sort((a, b) => {
1227
+ const aTime = new Date(a.merged_at).getTime();
1228
+ const bTime = new Date(b.merged_at).getTime();
1229
+ return aTime - bTime;
1230
+ });
1231
+ if (mergedMRs.length === 0) return null;
1232
+ const defaultBranch = await this.detectDefaultBranch();
1233
+ const defaultBranchMR = mergedMRs.find(
1234
+ (mr2) => mr2.target_branch === defaultBranch
1235
+ );
1236
+ const mr = defaultBranchMR ?? mergedMRs[0];
1022
1237
  return {
1023
1238
  number: mr.iid,
1024
1239
  title: mr.title ?? "",
1025
1240
  author: mr.author?.username ?? "",
1026
1241
  url: mr.web_url ?? "",
1027
1242
  mergeCommit: mr.merge_commit_sha ?? sha,
1028
- baseBranch: mr.target_branch ?? "main",
1243
+ baseBranch: mr.target_branch ?? defaultBranch,
1029
1244
  mergedAt: mr.merged_at
1030
1245
  };
1031
1246
  } catch {
1032
1247
  return null;
1033
1248
  }
1034
1249
  }
1250
+ async detectDefaultBranch() {
1251
+ if (this.defaultBranchCache) return this.defaultBranchCache;
1252
+ try {
1253
+ const result = await gitExec(
1254
+ ["symbolic-ref", `refs/remotes/${this.remoteName}/HEAD`],
1255
+ { cwd: this.cwd }
1256
+ );
1257
+ const ref = result.stdout.trim();
1258
+ this.defaultBranchCache = ref.replace(`refs/remotes/${this.remoteName}/`, "") || "main";
1259
+ return this.defaultBranchCache;
1260
+ } catch {
1261
+ return "main";
1262
+ }
1263
+ }
1035
1264
  async getPRCommits(prNumber) {
1036
1265
  try {
1037
- const result = await shellExec("glab", [
1038
- "api",
1039
- `projects/:id/merge_requests/${prNumber}/commits`,
1040
- "--hostname",
1041
- this.hostname
1042
- ]);
1266
+ const result = await shellExec(
1267
+ "glab",
1268
+ [
1269
+ "api",
1270
+ `projects/:id/merge_requests/${prNumber}/commits`,
1271
+ "--hostname",
1272
+ this.hostname
1273
+ ],
1274
+ { cwd: this.cwd }
1275
+ );
1043
1276
  const commits = JSON.parse(result.stdout);
1044
- if (!Array.isArray(commits)) return [];
1045
- return commits.map((c) => c.id);
1277
+ if (!(0, import_common_utils4.isArray)(commits)) return [];
1278
+ return (0, import_common_utils4.map)(commits, (c) => c.id);
1046
1279
  } catch {
1047
1280
  return [];
1048
1281
  }
1049
1282
  }
1050
1283
  async getLinkedIssues(prNumber) {
1051
1284
  try {
1052
- const result = await shellExec("glab", [
1053
- "api",
1054
- `projects/:id/merge_requests/${prNumber}/closes_issues`,
1055
- "--hostname",
1056
- this.hostname
1057
- ]);
1285
+ const result = await shellExec(
1286
+ "glab",
1287
+ [
1288
+ "api",
1289
+ `projects/:id/merge_requests/${prNumber}/closes_issues`,
1290
+ "--hostname",
1291
+ this.hostname
1292
+ ],
1293
+ { cwd: this.cwd }
1294
+ );
1058
1295
  const issues = JSON.parse(result.stdout);
1059
- if (!Array.isArray(issues)) return [];
1060
- return issues.map((issue) => ({
1296
+ if (!(0, import_common_utils4.isArray)(issues)) return [];
1297
+ return (0, import_common_utils4.map)(issues, (issue) => ({
1061
1298
  number: issue.iid,
1062
1299
  title: issue.title ?? "",
1063
1300
  url: issue.web_url ?? "",
@@ -1070,21 +1307,26 @@ var GitLabAdapter = class {
1070
1307
  }
1071
1308
  async getLinkedPRs(issueNumber) {
1072
1309
  try {
1073
- const result = await shellExec("glab", [
1074
- "api",
1075
- `projects/:id/issues/${issueNumber}/related_merge_requests`,
1076
- "--hostname",
1077
- this.hostname
1078
- ]);
1310
+ const result = await shellExec(
1311
+ "glab",
1312
+ [
1313
+ "api",
1314
+ `projects/:id/issues/${issueNumber}/related_merge_requests`,
1315
+ "--hostname",
1316
+ this.hostname
1317
+ ],
1318
+ { cwd: this.cwd }
1319
+ );
1079
1320
  const mrs = JSON.parse(result.stdout);
1080
- if (!Array.isArray(mrs)) return [];
1081
- return mrs.map((mr) => ({
1321
+ if (!(0, import_common_utils4.isArray)(mrs)) return [];
1322
+ const defaultBranch = await this.detectDefaultBranch();
1323
+ return (0, import_common_utils4.map)(mrs, (mr) => ({
1082
1324
  number: mr.iid,
1083
1325
  title: mr.title ?? "",
1084
1326
  author: mr.author?.username ?? "",
1085
1327
  url: mr.web_url ?? "",
1086
1328
  mergeCommit: mr.merge_commit_sha ?? "",
1087
- baseBranch: mr.target_branch ?? "main",
1329
+ baseBranch: mr.target_branch ?? defaultBranch,
1088
1330
  mergedAt: mr.merged_at
1089
1331
  }));
1090
1332
  } catch {
@@ -1101,7 +1343,12 @@ init_cjs_shims();
1101
1343
  var GitLabSelfHostedAdapter = class extends GitLabAdapter {
1102
1344
  platform = "gitlab-self-hosted";
1103
1345
  constructor(hostname, options) {
1104
- super({ hostname, scheduler: options?.scheduler });
1346
+ super({
1347
+ hostname,
1348
+ scheduler: options?.scheduler,
1349
+ remoteName: options?.remoteName,
1350
+ cwd: options?.cwd
1351
+ });
1105
1352
  }
1106
1353
  };
1107
1354
 
@@ -1110,21 +1357,21 @@ async function detectPlatformAdapter(options) {
1110
1357
  const remote = await getRemoteInfo(options?.remoteName, {
1111
1358
  cwd: options?.cwd
1112
1359
  });
1113
- const adapter = createAdapter(remote);
1360
+ const adapter = createAdapter(remote, options?.remoteName, options?.cwd);
1114
1361
  return { adapter, remote };
1115
1362
  }
1116
- function createAdapter(remote) {
1363
+ function createAdapter(remote, remoteName, cwd) {
1117
1364
  switch (remote.platform) {
1118
1365
  case "github":
1119
- return new GitHubAdapter({ hostname: remote.host });
1366
+ return new GitHubAdapter({ hostname: remote.host, remoteName, cwd });
1120
1367
  case "github-enterprise":
1121
- return new GitHubEnterpriseAdapter(remote.host);
1368
+ return new GitHubEnterpriseAdapter(remote.host, { remoteName, cwd });
1122
1369
  case "gitlab":
1123
- return new GitLabAdapter({ hostname: remote.host });
1370
+ return new GitLabAdapter({ hostname: remote.host, remoteName, cwd });
1124
1371
  case "gitlab-self-hosted":
1125
- return new GitLabSelfHostedAdapter(remote.host);
1372
+ return new GitLabSelfHostedAdapter(remote.host, { remoteName, cwd });
1126
1373
  case "unknown":
1127
- return new GitHubEnterpriseAdapter(remote.host);
1374
+ return new GitHubEnterpriseAdapter(remote.host, { remoteName, cwd });
1128
1375
  }
1129
1376
  }
1130
1377
 
@@ -1176,6 +1423,7 @@ init_cjs_shims();
1176
1423
 
1177
1424
  // src/core/ast-diff/ast-diff.ts
1178
1425
  init_cjs_shims();
1426
+ var import_common_utils5 = require("@winglet/common-utils");
1179
1427
  init_executor();
1180
1428
 
1181
1429
  // src/core/ast-diff/comparison/index.ts
@@ -1221,13 +1469,13 @@ function compareSymbolMaps(current, parent) {
1221
1469
  }
1222
1470
  return results;
1223
1471
  }
1224
- function findHashMatch(target, map) {
1225
- for (const [name, hash] of map) {
1472
+ function findHashMatch(target, map9) {
1473
+ for (const [name, hash] of map9) {
1226
1474
  if (hash.exact === target.exact) {
1227
1475
  return { name, confidence: "exact" };
1228
1476
  }
1229
1477
  }
1230
- for (const [name, hash] of map) {
1478
+ for (const [name, hash] of map9) {
1231
1479
  if (hash.structural === target.structural) {
1232
1480
  return { name, confidence: "structural" };
1233
1481
  }
@@ -1394,10 +1642,6 @@ var KEYWORDS = /* @__PURE__ */ new Set([
1394
1642
  // src/core/ast-diff/ast-diff.ts
1395
1643
  var MAX_TRAVERSAL_DEPTH = 50;
1396
1644
  async function traceByAst(file, line, startCommitSha, options) {
1397
- if (!isAstAvailable()) {
1398
- const lang2 = detectLanguage(file);
1399
- if (!lang2) return null;
1400
- }
1401
1645
  const lang = detectLanguage(file);
1402
1646
  if (!lang) return null;
1403
1647
  const maxDepth = options?.maxDepth ?? MAX_TRAVERSAL_DEPTH;
@@ -1421,10 +1665,10 @@ async function traceByAst(file, line, startCommitSha, options) {
1421
1665
  const parentContent = await getFileAtCommit(parentSha, file, options);
1422
1666
  const parentSymbols = await extractSymbols(parentContent, lang);
1423
1667
  const currentMap = new Map(
1424
- [currentSymbol].filter(Boolean).map((s) => [s.name, computeContentHash(s.bodyText)])
1668
+ [currentSymbol].filter(import_common_utils5.isTruthy).map((s) => [s.name, computeContentHash(s.bodyText)])
1425
1669
  );
1426
1670
  const parentMap = new Map(
1427
- parentSymbols.map((s) => [s.name, computeContentHash(s.bodyText)])
1671
+ (0, import_common_utils5.map)(parentSymbols, (s) => [s.name, computeContentHash(s.bodyText)])
1428
1672
  );
1429
1673
  const comparison = compareSymbolMaps(currentMap, parentMap);
1430
1674
  if (comparison.length > 0) {
@@ -1489,6 +1733,7 @@ init_cjs_shims();
1489
1733
 
1490
1734
  // src/core/blame/blame.ts
1491
1735
  init_cjs_shims();
1736
+ var import_common_utils7 = require("@winglet/common-utils");
1492
1737
  init_executor();
1493
1738
 
1494
1739
  // src/core/blame/detection/index.ts
@@ -1496,6 +1741,7 @@ init_cjs_shims();
1496
1741
 
1497
1742
  // src/core/blame/detection/cosmetic-detector.ts
1498
1743
  init_cjs_shims();
1744
+ var import_common_utils6 = require("@winglet/common-utils");
1499
1745
  init_executor();
1500
1746
  function isCosmeticDiff(diff) {
1501
1747
  const hunks = extractHunks(diff);
@@ -1543,23 +1789,33 @@ function normalize(line) {
1543
1789
  }
1544
1790
  function isWhitespaceOnly(hunks) {
1545
1791
  return hunks.every((hunk) => {
1546
- const removedNorm = hunk.removed.map(normalize).filter(Boolean).sort();
1547
- const addedNorm = hunk.added.map(normalize).filter(Boolean).sort();
1792
+ const removedNorm = [];
1793
+ (0, import_common_utils6.forEach)(hunk.removed, (line) => {
1794
+ const n = normalize(line);
1795
+ if ((0, import_common_utils6.isTruthy)(n)) removedNorm.push(n);
1796
+ });
1797
+ removedNorm.sort();
1798
+ const addedNorm = [];
1799
+ (0, import_common_utils6.forEach)(hunk.added, (line) => {
1800
+ const n = normalize(line);
1801
+ if ((0, import_common_utils6.isTruthy)(n)) addedNorm.push(n);
1802
+ });
1803
+ addedNorm.sort();
1548
1804
  if (removedNorm.length !== addedNorm.length) return false;
1549
1805
  return removedNorm.every((line, idx) => line === addedNorm[idx]);
1550
1806
  });
1551
1807
  }
1552
1808
  function isImportReorder(hunks) {
1553
1809
  return hunks.every((hunk) => {
1554
- const removedImports = hunk.removed.filter(isImportLine);
1555
- const addedImports = hunk.added.filter(isImportLine);
1810
+ const removedImports = (0, import_common_utils6.filter)(hunk.removed, isImportLine);
1811
+ const addedImports = (0, import_common_utils6.filter)(hunk.added, isImportLine);
1556
1812
  if (removedImports.length === 0) return false;
1557
- if (removedImports.length !== hunk.removed.filter((l) => l.trim()).length)
1813
+ if (removedImports.length !== (0, import_common_utils6.filter)(hunk.removed, (l) => (0, import_common_utils6.isTruthy)(l.trim())).length)
1558
1814
  return false;
1559
- if (addedImports.length !== hunk.added.filter((l) => l.trim()).length)
1815
+ if (addedImports.length !== (0, import_common_utils6.filter)(hunk.added, (l) => (0, import_common_utils6.isTruthy)(l.trim())).length)
1560
1816
  return false;
1561
- const removedSorted = removedImports.map(normalize).sort();
1562
- const addedSorted = addedImports.map(normalize).sort();
1817
+ const removedSorted = (0, import_common_utils6.map)(removedImports, normalize).sort();
1818
+ const addedSorted = (0, import_common_utils6.map)(addedImports, normalize).sort();
1563
1819
  if (removedSorted.length !== addedSorted.length) return false;
1564
1820
  return removedSorted.every((line, idx) => line === addedSorted[idx]);
1565
1821
  });
@@ -1655,31 +1911,36 @@ function parsePorcelainOutput(output) {
1655
1911
 
1656
1912
  // src/core/blame/blame.ts
1657
1913
  async function executeBlame(file, lineRange, options) {
1658
- const lineSpec = lineRange.start === lineRange.end ? `${lineRange.start},${lineRange.end}` : `${lineRange.start},${lineRange.end}`;
1914
+ const lineSpec = `${lineRange.start},${lineRange.end}`;
1659
1915
  const result = await gitExec(
1660
1916
  ["blame", "-w", "-C", "-C", "-M", "--porcelain", "-L", lineSpec, file],
1661
1917
  options
1662
1918
  );
1663
1919
  return parsePorcelainOutput(result.stdout);
1664
1920
  }
1665
- async function analyzeBlameResults(results, options) {
1666
- const uniqueShas = [...new Set(results.map((r) => r.commitHash))];
1921
+ async function analyzeBlameResults(results, filePath, options) {
1922
+ const uniqueShas = [...new Set((0, import_common_utils7.map)(results, (r) => r.commitHash))];
1667
1923
  const cosmeticMap = /* @__PURE__ */ new Map();
1668
1924
  const zeroSha = "0".repeat(40);
1669
- await Promise.all(
1670
- uniqueShas.filter((sha) => sha !== zeroSha).map(async (sha) => {
1671
- try {
1672
- const blameResult = results.find((r) => r.commitHash === sha);
1673
- if (!blameResult) return;
1674
- const file = blameResult.originalFile ?? results.find((r) => r.commitHash === sha)?.lineContent;
1675
- const diff = await getCosmeticDiff(sha, file ?? "", options);
1676
- cosmeticMap.set(sha, isCosmeticDiff(diff));
1677
- } catch {
1678
- cosmeticMap.set(sha, { isCosmetic: false });
1679
- }
1680
- })
1681
- );
1682
- return results.map((blame) => {
1925
+ const tasks = [];
1926
+ (0, import_common_utils7.forEach)(uniqueShas, (sha) => {
1927
+ if (sha === zeroSha) return;
1928
+ tasks.push(
1929
+ (async () => {
1930
+ try {
1931
+ const blameResult = results.find((r) => r.commitHash === sha);
1932
+ if (!blameResult) return;
1933
+ const file = blameResult.originalFile ?? filePath;
1934
+ const diff = await getCosmeticDiff(sha, file, options);
1935
+ cosmeticMap.set(sha, isCosmeticDiff(diff));
1936
+ } catch {
1937
+ cosmeticMap.set(sha, { isCosmetic: false });
1938
+ }
1939
+ })()
1940
+ );
1941
+ });
1942
+ await Promise.all(tasks);
1943
+ return (0, import_common_utils7.map)(results, (blame) => {
1683
1944
  const cosmetic = cosmeticMap.get(blame.commitHash);
1684
1945
  return {
1685
1946
  blame,
@@ -1689,6 +1950,102 @@ async function analyzeBlameResults(results, options) {
1689
1950
  });
1690
1951
  }
1691
1952
 
1953
+ // src/core/issue-graph/index.ts
1954
+ init_cjs_shims();
1955
+
1956
+ // src/core/issue-graph/issue-graph.ts
1957
+ init_cjs_shims();
1958
+ var import_common_utils8 = require("@winglet/common-utils");
1959
+ var DEFAULT_MAX_DEPTH = 2;
1960
+ async function traverseIssueGraph(adapter, startType, startNumber, options) {
1961
+ const maxDepth = options?.maxDepth ?? DEFAULT_MAX_DEPTH;
1962
+ const nodes = [];
1963
+ const edges = [];
1964
+ const visited = /* @__PURE__ */ new Set();
1965
+ await traverse(
1966
+ adapter,
1967
+ startType,
1968
+ startNumber,
1969
+ 0,
1970
+ maxDepth,
1971
+ nodes,
1972
+ edges,
1973
+ visited
1974
+ );
1975
+ return { nodes, edges };
1976
+ }
1977
+ async function traverse(adapter, type, number, depth, maxDepth, nodes, edges, visited) {
1978
+ const key = `${type}:${number}`;
1979
+ if (visited.has(key)) return;
1980
+ if (depth > maxDepth) return;
1981
+ visited.add(key);
1982
+ if (type === "pr") {
1983
+ nodes.push({
1984
+ type: "pull_request",
1985
+ trackingMethod: "issue-link",
1986
+ confidence: "exact",
1987
+ prNumber: number
1988
+ });
1989
+ if (depth < maxDepth) {
1990
+ const linkedIssues = await adapter.getLinkedIssues(number);
1991
+ for (const issue of linkedIssues) {
1992
+ edges.push({
1993
+ from: `pr:${number}`,
1994
+ to: `issue:${issue.number}`,
1995
+ relation: "closes"
1996
+ });
1997
+ }
1998
+ await Promise.all(
1999
+ (0, import_common_utils8.map)(
2000
+ linkedIssues,
2001
+ (issue) => traverse(
2002
+ adapter,
2003
+ "issue",
2004
+ issue.number,
2005
+ depth + 1,
2006
+ maxDepth,
2007
+ nodes,
2008
+ edges,
2009
+ visited
2010
+ )
2011
+ )
2012
+ );
2013
+ }
2014
+ } else {
2015
+ nodes.push({
2016
+ type: "issue",
2017
+ trackingMethod: "issue-link",
2018
+ confidence: "exact",
2019
+ issueNumber: number
2020
+ });
2021
+ if (depth < maxDepth) {
2022
+ const linkedPRs = await adapter.getLinkedPRs(number);
2023
+ for (const pr of linkedPRs) {
2024
+ edges.push({
2025
+ from: `issue:${number}`,
2026
+ to: `pr:${pr.number}`,
2027
+ relation: "referenced-by"
2028
+ });
2029
+ }
2030
+ await Promise.all(
2031
+ (0, import_common_utils8.map)(
2032
+ linkedPRs,
2033
+ (pr) => traverse(
2034
+ adapter,
2035
+ "pr",
2036
+ pr.number,
2037
+ depth + 1,
2038
+ maxDepth,
2039
+ nodes,
2040
+ edges,
2041
+ visited
2042
+ )
2043
+ )
2044
+ );
2045
+ }
2046
+ }
2047
+ }
2048
+
1692
2049
  // src/core/core.ts
1693
2050
  init_pr_lookup2();
1694
2051
  function computeFeatureFlags(operatingLevel, options) {
@@ -1696,24 +2053,35 @@ function computeFeatureFlags(operatingLevel, options) {
1696
2053
  astDiff: isAstAvailable() && !options.noAst,
1697
2054
  deepTrace: operatingLevel === 2 && (options.deep ?? false),
1698
2055
  commitGraph: false,
1699
- issueGraph: operatingLevel === 2 && (options.graphDepth ?? 0) > 0,
1700
2056
  graphql: operatingLevel === 2
1701
2057
  };
1702
2058
  }
2059
+ async function resolveRepoIdentity(cwd) {
2060
+ try {
2061
+ const result = await gitExec(["rev-parse", "--show-toplevel"], { cwd });
2062
+ const hash = (0, import_node_crypto2.createHash)("sha256").update(result.stdout.trim()).digest("hex").slice(0, 16);
2063
+ return { host: "_local", owner: "_", repo: hash };
2064
+ } catch {
2065
+ return { host: "_local", owner: "_", repo: "_unknown" };
2066
+ }
2067
+ }
1703
2068
  async function detectPlatform2(options) {
1704
2069
  const warnings = [];
1705
2070
  let adapter = null;
2071
+ let remote = null;
1706
2072
  let operatingLevel = 0;
1707
2073
  try {
1708
- const { adapter: detectedAdapter } = await detectPlatformAdapter({
1709
- remoteName: options.remote
2074
+ const detected = await detectPlatformAdapter({
2075
+ remoteName: options.remote,
2076
+ cwd: options.cwd
1710
2077
  });
1711
- adapter = detectedAdapter;
2078
+ adapter = detected.adapter;
2079
+ remote = detected.remote;
1712
2080
  } catch {
1713
2081
  operatingLevel = 0;
1714
2082
  warnings.push("Could not detect platform. Running in Level 0 (git only).");
1715
2083
  }
1716
- return { adapter, operatingLevel, warnings };
2084
+ return { adapter, remote, operatingLevel, warnings };
1717
2085
  }
1718
2086
  async function runBlameAndAuth(adapter, options, execOptions) {
1719
2087
  const warnings = [];
@@ -1721,7 +2089,7 @@ async function runBlameAndAuth(adapter, options, execOptions) {
1721
2089
  options.endLine ? `${options.line},${options.endLine}` : `${options.line}`
1722
2090
  );
1723
2091
  const blameChain = executeBlame(options.file, lineRange, execOptions).then(
1724
- (results) => analyzeBlameResults(results, execOptions)
2092
+ (results) => analyzeBlameResults(results, options.file, execOptions)
1725
2093
  );
1726
2094
  const [authResult, blameResult] = await Promise.allSettled([
1727
2095
  adapter ? adapter.checkAuth() : Promise.resolve({ authenticated: false }),
@@ -1743,55 +2111,83 @@ async function runBlameAndAuth(adapter, options, execOptions) {
1743
2111
  }
1744
2112
  return { analyzed: blameResult.value, operatingLevel, warnings };
1745
2113
  }
1746
- async function buildTraceNodes(analyzed, featureFlags, adapter, options, execOptions) {
2114
+ async function processEntry(entry, featureFlags, adapter, options, execOptions, repoId) {
1747
2115
  const nodes = [];
1748
- for (const entry of analyzed) {
1749
- const commitNode = {
1750
- type: entry.isCosmetic ? "cosmetic_commit" : "original_commit",
1751
- sha: entry.blame.commitHash,
1752
- trackingMethod: "blame-CMw",
1753
- confidence: "exact",
1754
- note: entry.cosmeticReason ? `Cosmetic change: ${entry.cosmeticReason}` : void 0
1755
- };
1756
- nodes.push(commitNode);
1757
- if (entry.isCosmetic && featureFlags.astDiff) {
1758
- const astResult = await traceByAst(
1759
- options.file,
1760
- options.line,
1761
- entry.blame.commitHash,
1762
- execOptions
1763
- );
1764
- if (astResult) {
1765
- nodes.push({
1766
- type: "original_commit",
1767
- sha: astResult.originSha,
1768
- trackingMethod: "ast-signature",
1769
- confidence: astResult.confidence
1770
- });
1771
- }
2116
+ const commitNode = {
2117
+ type: entry.isCosmetic ? "cosmetic_commit" : "original_commit",
2118
+ sha: entry.blame.commitHash,
2119
+ trackingMethod: "blame-CMw",
2120
+ confidence: "exact",
2121
+ note: entry.cosmeticReason ? `Cosmetic change: ${entry.cosmeticReason}` : void 0
2122
+ };
2123
+ nodes.push(commitNode);
2124
+ if (entry.isCosmetic && featureFlags.astDiff) {
2125
+ const astResult = await traceByAst(
2126
+ options.file,
2127
+ options.line,
2128
+ entry.blame.commitHash,
2129
+ execOptions
2130
+ );
2131
+ if (astResult) {
2132
+ nodes.push({
2133
+ type: "original_commit",
2134
+ sha: astResult.originSha,
2135
+ trackingMethod: "ast-signature",
2136
+ confidence: astResult.confidence
2137
+ });
1772
2138
  }
1773
- const targetSha = nodes[nodes.length - 1].sha;
1774
- if (targetSha) {
1775
- const prInfo = await lookupPR(targetSha, adapter, execOptions);
1776
- if (prInfo) {
1777
- nodes.push({
1778
- type: "pull_request",
1779
- sha: prInfo.mergeCommit,
1780
- trackingMethod: prInfo.url ? "api" : "message-parse",
1781
- confidence: prInfo.url ? "exact" : "heuristic",
1782
- prNumber: prInfo.number,
1783
- prUrl: prInfo.url || void 0,
1784
- prTitle: prInfo.title || void 0,
1785
- mergedAt: prInfo.mergedAt
1786
- });
1787
- }
2139
+ }
2140
+ const targetSha = nodes[nodes.length - 1].sha;
2141
+ if (targetSha) {
2142
+ const prInfo = await lookupPR(targetSha, adapter, {
2143
+ ...execOptions,
2144
+ noCache: options.noCache,
2145
+ deep: featureFlags.deepTrace,
2146
+ repoId
2147
+ });
2148
+ if (prInfo) {
2149
+ nodes.push({
2150
+ type: "pull_request",
2151
+ sha: prInfo.mergeCommit,
2152
+ trackingMethod: prInfo.url ? "api" : "message-parse",
2153
+ confidence: prInfo.url ? "exact" : "heuristic",
2154
+ prNumber: prInfo.number,
2155
+ prUrl: prInfo.url || void 0,
2156
+ prTitle: prInfo.title || void 0,
2157
+ mergedAt: prInfo.mergedAt
2158
+ });
1788
2159
  }
1789
2160
  }
1790
2161
  return nodes;
1791
2162
  }
2163
+ async function buildTraceNodes(analyzed, featureFlags, adapter, options, execOptions, repoId) {
2164
+ const results = await Promise.allSettled(
2165
+ (0, import_common_utils11.map)(
2166
+ analyzed,
2167
+ (entry) => processEntry(entry, featureFlags, adapter, options, execOptions, repoId)
2168
+ )
2169
+ );
2170
+ return results.flatMap((r) => r.status === "fulfilled" ? r.value : []);
2171
+ }
2172
+ var legacyCacheCleaned = false;
1792
2173
  async function trace(options) {
1793
- const execOptions = { cwd: void 0 };
2174
+ const execOptions = { cwd: options.cwd };
2175
+ if (!legacyCacheCleaned) {
2176
+ legacyCacheCleaned = true;
2177
+ cleanupLegacyCache().catch(() => {
2178
+ });
2179
+ }
1794
2180
  const platform = await detectPlatform2(options);
2181
+ let repoId;
2182
+ if (platform.remote) {
2183
+ repoId = {
2184
+ host: platform.remote.host,
2185
+ owner: platform.remote.owner,
2186
+ repo: platform.remote.repo
2187
+ };
2188
+ } else {
2189
+ repoId = await resolveRepoIdentity(options.cwd);
2190
+ }
1795
2191
  const blameAuth = await runBlameAndAuth(
1796
2192
  platform.adapter,
1797
2193
  options,
@@ -1805,10 +2201,26 @@ async function trace(options) {
1805
2201
  featureFlags,
1806
2202
  platform.adapter,
1807
2203
  options,
1808
- execOptions
2204
+ execOptions,
2205
+ repoId
1809
2206
  );
1810
2207
  return { nodes, operatingLevel, featureFlags, warnings };
1811
2208
  }
2209
+ async function graph(options) {
2210
+ const { adapter } = await detectPlatformAdapter({
2211
+ remoteName: options.remote
2212
+ });
2213
+ const auth = await adapter.checkAuth();
2214
+ if (!auth.authenticated) {
2215
+ throw new LineLoreError(
2216
+ LineLoreErrorCode.CLI_NOT_AUTHENTICATED,
2217
+ 'Platform CLI is not authenticated. Run "gh auth login" or set the appropriate token.'
2218
+ );
2219
+ }
2220
+ return traverseIssueGraph(adapter, options.type, options.number, {
2221
+ maxDepth: options.depth
2222
+ });
2223
+ }
1812
2224
  async function health(options) {
1813
2225
  const healthReport = await checkGitHealth(options);
1814
2226
  let operatingLevel = 0;
@@ -1822,94 +2234,19 @@ async function health(options) {
1822
2234
  return { ...healthReport, operatingLevel };
1823
2235
  }
1824
2236
  async function clearCache() {
2237
+ const { rm: rm2 } = await import("fs/promises");
2238
+ const { homedir: homedir2 } = await import("os");
2239
+ const { join: join2 } = await import("path");
1825
2240
  const { resetPRCache: resetPRCache2 } = await Promise.resolve().then(() => (init_pr_lookup2(), pr_lookup_exports));
1826
2241
  const { resetPatchIdCache: resetPatchIdCache2 } = await Promise.resolve().then(() => (init_patch_id2(), patch_id_exports));
1827
2242
  resetPRCache2();
1828
2243
  resetPatchIdCache2();
1829
- }
1830
-
1831
- // src/core/issue-graph/index.ts
1832
- init_cjs_shims();
1833
-
1834
- // src/core/issue-graph/issue-graph.ts
1835
- init_cjs_shims();
1836
- var DEFAULT_MAX_DEPTH = 2;
1837
- async function traverseIssueGraph(adapter, startType, startNumber, options) {
1838
- const maxDepth = options?.maxDepth ?? DEFAULT_MAX_DEPTH;
1839
- const nodes = [];
1840
- const edges = [];
1841
- const visited = /* @__PURE__ */ new Set();
1842
- await traverse(
1843
- adapter,
1844
- startType,
1845
- startNumber,
1846
- 0,
1847
- maxDepth,
1848
- nodes,
1849
- edges,
1850
- visited
1851
- );
1852
- return { nodes, edges };
1853
- }
1854
- async function traverse(adapter, type, number, depth, maxDepth, nodes, edges, visited) {
1855
- const key = `${type}:${number}`;
1856
- if (visited.has(key)) return;
1857
- if (depth > maxDepth) return;
1858
- visited.add(key);
1859
- if (type === "pr") {
1860
- nodes.push({
1861
- type: "pull_request",
1862
- trackingMethod: "issue-link",
1863
- confidence: "exact",
1864
- prNumber: number
1865
- });
1866
- if (depth < maxDepth) {
1867
- const linkedIssues = await adapter.getLinkedIssues(number);
1868
- for (const issue of linkedIssues) {
1869
- edges.push({
1870
- from: `pr:${number}`,
1871
- to: `issue:${issue.number}`,
1872
- relation: "closes"
1873
- });
1874
- await traverse(
1875
- adapter,
1876
- "issue",
1877
- issue.number,
1878
- depth + 1,
1879
- maxDepth,
1880
- nodes,
1881
- edges,
1882
- visited
1883
- );
1884
- }
1885
- }
1886
- } else {
1887
- nodes.push({
1888
- type: "issue",
1889
- trackingMethod: "issue-link",
1890
- confidence: "exact",
1891
- issueNumber: number
2244
+ try {
2245
+ await rm2(join2(homedir2(), ".line-lore", "cache"), {
2246
+ recursive: true,
2247
+ force: true
1892
2248
  });
1893
- if (depth < maxDepth) {
1894
- const linkedPRs = await adapter.getLinkedPRs(number);
1895
- for (const pr of linkedPRs) {
1896
- edges.push({
1897
- from: `issue:${number}`,
1898
- to: `pr:${pr.number}`,
1899
- relation: "referenced-by"
1900
- });
1901
- await traverse(
1902
- adapter,
1903
- "pr",
1904
- pr.number,
1905
- depth + 1,
1906
- maxDepth,
1907
- nodes,
1908
- edges,
1909
- visited
1910
- );
1911
- }
1912
- }
2249
+ } catch {
1913
2250
  }
1914
2251
  }
1915
2252
  // Annotate the CommonJS export names for ESM import in node:
@@ -1917,6 +2254,7 @@ async function traverse(adapter, type, number, depth, maxDepth, nodes, edges, vi
1917
2254
  LineLoreError,
1918
2255
  LineLoreErrorCode,
1919
2256
  clearCache,
2257
+ graph,
1920
2258
  health,
1921
2259
  trace,
1922
2260
  traverseIssueGraph