@lumy-pack/line-lore 0.0.1

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 (77) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +188 -0
  3. package/dist/ast/index.d.ts +1 -0
  4. package/dist/ast/parser.d.ts +6 -0
  5. package/dist/cache/file-cache.d.ts +19 -0
  6. package/dist/cache/index.d.ts +1 -0
  7. package/dist/cli.d.ts +1 -0
  8. package/dist/cli.mjs +2288 -0
  9. package/dist/commands/cache.d.ts +2 -0
  10. package/dist/commands/graph.d.ts +2 -0
  11. package/dist/commands/health.d.ts +2 -0
  12. package/dist/commands/index.d.ts +4 -0
  13. package/dist/commands/trace.d.ts +2 -0
  14. package/dist/components/TraceProgress.d.ts +7 -0
  15. package/dist/components/TraceResult.d.ts +8 -0
  16. package/dist/components/index.d.ts +2 -0
  17. package/dist/core/ancestry/ancestry.d.ts +10 -0
  18. package/dist/core/ancestry/index.d.ts +2 -0
  19. package/dist/core/ast-diff/ast-diff.d.ts +4 -0
  20. package/dist/core/ast-diff/comparison/index.d.ts +2 -0
  21. package/dist/core/ast-diff/comparison/structure-comparator.d.ts +4 -0
  22. package/dist/core/ast-diff/extraction/index.d.ts +2 -0
  23. package/dist/core/ast-diff/extraction/signature-hasher.d.ts +4 -0
  24. package/dist/core/ast-diff/extraction/symbol-extractor.d.ts +3 -0
  25. package/dist/core/ast-diff/index.d.ts +4 -0
  26. package/dist/core/blame/blame.d.ts +3 -0
  27. package/dist/core/blame/detection/cosmetic-detector.d.ts +7 -0
  28. package/dist/core/blame/detection/index.d.ts +2 -0
  29. package/dist/core/blame/index.d.ts +4 -0
  30. package/dist/core/blame/parsing/blame-parser.d.ts +2 -0
  31. package/dist/core/blame/parsing/index.d.ts +1 -0
  32. package/dist/core/core.d.ts +14 -0
  33. package/dist/core/index.d.ts +11 -0
  34. package/dist/core/issue-graph/index.d.ts +2 -0
  35. package/dist/core/issue-graph/issue-graph.d.ts +5 -0
  36. package/dist/core/patch-id/index.d.ts +2 -0
  37. package/dist/core/patch-id/patch-id.d.ts +11 -0
  38. package/dist/core/pr-lookup/index.d.ts +1 -0
  39. package/dist/core/pr-lookup/pr-lookup.d.ts +3 -0
  40. package/dist/errors.d.ts +32 -0
  41. package/dist/git/executor.d.ts +2 -0
  42. package/dist/git/health.d.ts +4 -0
  43. package/dist/git/index.d.ts +3 -0
  44. package/dist/git/remote.d.ts +6 -0
  45. package/dist/index.cjs +1904 -0
  46. package/dist/index.d.ts +6 -0
  47. package/dist/index.mjs +1872 -0
  48. package/dist/output/formats.d.ts +5 -0
  49. package/dist/output/index.d.ts +2 -0
  50. package/dist/output/normalizer.d.ts +11 -0
  51. package/dist/platform/github/github-adapter.d.ts +17 -0
  52. package/dist/platform/github/github-enterprise-adapter.d.ts +8 -0
  53. package/dist/platform/github/index.d.ts +2 -0
  54. package/dist/platform/gitlab/gitlab-adapter.d.ts +17 -0
  55. package/dist/platform/gitlab/gitlab-self-hosted-adapter.d.ts +8 -0
  56. package/dist/platform/gitlab/index.d.ts +2 -0
  57. package/dist/platform/index.d.ts +6 -0
  58. package/dist/platform/platform.d.ts +9 -0
  59. package/dist/platform/scheduler/index.d.ts +2 -0
  60. package/dist/platform/scheduler/request-scheduler.d.ts +17 -0
  61. package/dist/types/ast.d.ts +28 -0
  62. package/dist/types/blame.d.ts +34 -0
  63. package/dist/types/cache.d.ts +5 -0
  64. package/dist/types/git.d.ts +23 -0
  65. package/dist/types/graph.d.ts +15 -0
  66. package/dist/types/index.d.ts +11 -0
  67. package/dist/types/output.d.ts +24 -0
  68. package/dist/types/pipeline.d.ts +28 -0
  69. package/dist/types/platform.d.ts +44 -0
  70. package/dist/types/stage.d.ts +14 -0
  71. package/dist/types/trace.d.ts +38 -0
  72. package/dist/types/util.d.ts +4 -0
  73. package/dist/utils/command-registry.d.ts +29 -0
  74. package/dist/utils/index.d.ts +1 -0
  75. package/dist/utils/line-range.d.ts +2 -0
  76. package/dist/version.d.ts +1 -0
  77. package/package.json +71 -0
package/dist/index.mjs ADDED
@@ -0,0 +1,1872 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropNames = Object.getOwnPropertyNames;
3
+ var __esm = (fn, res) => function __init() {
4
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
5
+ };
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+
11
+ // ../../node_modules/tsup/assets/esm_shims.js
12
+ import path from "path";
13
+ import { fileURLToPath } from "url";
14
+ var init_esm_shims = __esm({
15
+ "../../node_modules/tsup/assets/esm_shims.js"() {
16
+ "use strict";
17
+ }
18
+ });
19
+
20
+ // src/errors.ts
21
+ var LineLoreErrorCode, LineLoreError;
22
+ var init_errors = __esm({
23
+ "src/errors.ts"() {
24
+ "use strict";
25
+ init_esm_shims();
26
+ LineLoreErrorCode = {
27
+ // Existing
28
+ NOT_GIT_REPO: "NOT_GIT_REPO",
29
+ FILE_NOT_FOUND: "FILE_NOT_FOUND",
30
+ INVALID_LINE: "INVALID_LINE",
31
+ GIT_BLAME_FAILED: "GIT_BLAME_FAILED",
32
+ PR_NOT_FOUND: "PR_NOT_FOUND",
33
+ UNKNOWN: "UNKNOWN",
34
+ // Git executor
35
+ GIT_COMMAND_FAILED: "GIT_COMMAND_FAILED",
36
+ GIT_TIMEOUT: "GIT_TIMEOUT",
37
+ // Pipeline stages
38
+ ANCESTRY_PATH_FAILED: "ANCESTRY_PATH_FAILED",
39
+ PATCH_ID_NO_MATCH: "PATCH_ID_NO_MATCH",
40
+ AST_PARSE_FAILED: "AST_PARSE_FAILED",
41
+ AST_ENGINE_UNAVAILABLE: "AST_ENGINE_UNAVAILABLE",
42
+ // Platform
43
+ PLATFORM_UNKNOWN: "PLATFORM_UNKNOWN",
44
+ CLI_NOT_INSTALLED: "CLI_NOT_INSTALLED",
45
+ CLI_NOT_AUTHENTICATED: "CLI_NOT_AUTHENTICATED",
46
+ API_RATE_LIMITED: "API_RATE_LIMITED",
47
+ API_REQUEST_FAILED: "API_REQUEST_FAILED",
48
+ GRAPHQL_NOT_SUPPORTED: "GRAPHQL_NOT_SUPPORTED",
49
+ ENTERPRISE_VERSION_UNSUPPORTED: "ENTERPRISE_VERSION_UNSUPPORTED",
50
+ // Issue graph
51
+ ISSUE_NOT_FOUND: "ISSUE_NOT_FOUND",
52
+ GRAPH_DEPTH_EXCEEDED: "GRAPH_DEPTH_EXCEEDED",
53
+ GRAPH_CYCLE_DETECTED: "GRAPH_CYCLE_DETECTED",
54
+ // Cache
55
+ CACHE_CORRUPTED: "CACHE_CORRUPTED",
56
+ // Remote
57
+ INVALID_REMOTE_URL: "INVALID_REMOTE_URL"
58
+ };
59
+ LineLoreError = class extends Error {
60
+ code;
61
+ context;
62
+ constructor(code, message, context) {
63
+ super(message);
64
+ this.code = code;
65
+ this.context = context;
66
+ Object.setPrototypeOf(this, new.target.prototype);
67
+ }
68
+ };
69
+ }
70
+ });
71
+
72
+ // src/git/executor.ts
73
+ import { execa } from "execa";
74
+ async function gitExec(args, options) {
75
+ const { cwd, timeout, allowExitCodes = [] } = options ?? {};
76
+ try {
77
+ const result = await execa("git", args, {
78
+ cwd,
79
+ timeout,
80
+ reject: false
81
+ });
82
+ const exitCode = result.exitCode ?? 0;
83
+ if (exitCode !== 0 && !allowExitCodes.includes(exitCode)) {
84
+ throw new LineLoreError(
85
+ LineLoreErrorCode.GIT_COMMAND_FAILED,
86
+ `git ${args[0]} failed with exit code ${exitCode}: ${result.stderr}`,
87
+ { args, exitCode, stderr: result.stderr, cwd }
88
+ );
89
+ }
90
+ return {
91
+ stdout: result.stdout,
92
+ stderr: result.stderr,
93
+ exitCode
94
+ };
95
+ } catch (error) {
96
+ if (error instanceof LineLoreError) throw error;
97
+ const isTimeout = error instanceof Error && "isTerminated" in error && error.timedOut === true;
98
+ if (isTimeout) {
99
+ throw new LineLoreError(
100
+ LineLoreErrorCode.GIT_TIMEOUT,
101
+ `git ${args[0]} timed out after ${timeout}ms`,
102
+ { args, timeout, cwd }
103
+ );
104
+ }
105
+ throw new LineLoreError(
106
+ LineLoreErrorCode.GIT_COMMAND_FAILED,
107
+ `git ${args[0]} failed: ${error instanceof Error ? error.message : String(error)}`,
108
+ { args, cwd }
109
+ );
110
+ }
111
+ }
112
+ var init_executor = __esm({
113
+ "src/git/executor.ts"() {
114
+ "use strict";
115
+ init_esm_shims();
116
+ init_errors();
117
+ }
118
+ });
119
+
120
+ // src/cache/file-cache.ts
121
+ import { mkdir, readFile, rename, unlink, writeFile } from "fs/promises";
122
+ import { homedir } from "os";
123
+ import { join } from "path";
124
+ var DEFAULT_CACHE_DIR, DEFAULT_MAX_ENTRIES, FileCache;
125
+ var init_file_cache = __esm({
126
+ "src/cache/file-cache.ts"() {
127
+ "use strict";
128
+ init_esm_shims();
129
+ DEFAULT_CACHE_DIR = join(homedir(), ".line-lore", "cache");
130
+ DEFAULT_MAX_ENTRIES = 1e4;
131
+ FileCache = class {
132
+ filePath;
133
+ maxEntries;
134
+ writeQueue = Promise.resolve();
135
+ constructor(fileName, options) {
136
+ const cacheDir = options?.cacheDir ?? DEFAULT_CACHE_DIR;
137
+ this.filePath = join(cacheDir, fileName);
138
+ this.maxEntries = options?.maxEntries ?? DEFAULT_MAX_ENTRIES;
139
+ }
140
+ async get(key) {
141
+ const data = await this.readStore();
142
+ const entry = data[key];
143
+ return entry?.value ?? null;
144
+ }
145
+ async has(key) {
146
+ const data = await this.readStore();
147
+ return key in data;
148
+ }
149
+ set(key, value) {
150
+ this.writeQueue = this.writeQueue.then(() => this.doSet(key, value)).catch(() => {
151
+ });
152
+ return this.writeQueue;
153
+ }
154
+ delete(key) {
155
+ let deleted = false;
156
+ this.writeQueue = this.writeQueue.then(async () => {
157
+ const data = await this.readStore();
158
+ if (key in data) {
159
+ delete data[key];
160
+ await this.writeStore(data);
161
+ deleted = true;
162
+ }
163
+ }).catch(() => {
164
+ });
165
+ return this.writeQueue.then(() => deleted);
166
+ }
167
+ clear() {
168
+ this.writeQueue = this.writeQueue.then(() => this.writeStore({})).catch(() => {
169
+ });
170
+ return this.writeQueue;
171
+ }
172
+ async size() {
173
+ const data = await this.readStore();
174
+ return Object.keys(data).length;
175
+ }
176
+ async doSet(key, value) {
177
+ const data = await this.readStore();
178
+ data[key] = { key, value, createdAt: Date.now() };
179
+ const keys = Object.keys(data);
180
+ if (keys.length > this.maxEntries) {
181
+ const sorted = keys.sort((a, b) => data[a].createdAt - data[b].createdAt);
182
+ const toRemove = sorted.slice(0, keys.length - this.maxEntries);
183
+ for (const k of toRemove) {
184
+ delete data[k];
185
+ }
186
+ }
187
+ await this.writeStore(data);
188
+ }
189
+ async readStore() {
190
+ try {
191
+ const content = await readFile(this.filePath, "utf-8");
192
+ return JSON.parse(content);
193
+ } catch (error) {
194
+ if (error instanceof SyntaxError || error instanceof Error && "code" in error && error.code === "ERR_INVALID_JSON") {
195
+ console.warn(
196
+ `[line-lore] Cache file corrupted, resetting: ${this.filePath}`
197
+ );
198
+ await this.writeStore({});
199
+ return {};
200
+ }
201
+ return {};
202
+ }
203
+ }
204
+ async writeStore(data) {
205
+ const dir = join(this.filePath, "..");
206
+ await mkdir(dir, { recursive: true });
207
+ const tmpPath = `${this.filePath}.tmp`;
208
+ await writeFile(tmpPath, JSON.stringify(data), "utf-8");
209
+ await rename(tmpPath, this.filePath);
210
+ }
211
+ async destroy() {
212
+ try {
213
+ await unlink(this.filePath);
214
+ } catch {
215
+ }
216
+ }
217
+ };
218
+ }
219
+ });
220
+
221
+ // src/core/ancestry/ancestry.ts
222
+ async function findMergeCommit(commitSha, options) {
223
+ const ref = options?.ref ?? "HEAD";
224
+ try {
225
+ const result = await gitExec(
226
+ [
227
+ "log",
228
+ "--merges",
229
+ "--ancestry-path",
230
+ `${commitSha}..${ref}`,
231
+ "--topo-order",
232
+ "--reverse",
233
+ "--format=%H %P %s"
234
+ ],
235
+ { cwd: options?.cwd, timeout: options?.timeout }
236
+ );
237
+ const lines = result.stdout.trim().split("\n").filter(Boolean);
238
+ if (lines.length === 0) return null;
239
+ const firstLine = lines[0];
240
+ const parts = firstLine.split(" ");
241
+ if (parts.length < 3) return null;
242
+ const mergeCommitSha = parts[0];
243
+ const parentShas = [];
244
+ let subjectStart = 1;
245
+ for (let i = 1; i < parts.length; i++) {
246
+ if (/^[0-9a-f]{40}$/.test(parts[i])) {
247
+ parentShas.push(parts[i]);
248
+ subjectStart = i + 1;
249
+ } else {
250
+ break;
251
+ }
252
+ }
253
+ const subject = parts.slice(subjectStart).join(" ");
254
+ return { mergeCommitSha, parentShas, subject };
255
+ } catch {
256
+ return null;
257
+ }
258
+ }
259
+ function extractPRFromMergeMessage(subject) {
260
+ const ghMatch = /Merge pull request #(\d+)/.exec(subject);
261
+ if (ghMatch) return parseInt(ghMatch[1], 10);
262
+ const squashMatch = /\(#(\d+)\)\s*$/.exec(subject);
263
+ if (squashMatch) return parseInt(squashMatch[1], 10);
264
+ const glMatch = /!(\d+)\s*$/.exec(subject);
265
+ if (glMatch) return parseInt(glMatch[1], 10);
266
+ return null;
267
+ }
268
+ var init_ancestry = __esm({
269
+ "src/core/ancestry/ancestry.ts"() {
270
+ "use strict";
271
+ init_esm_shims();
272
+ init_executor();
273
+ }
274
+ });
275
+
276
+ // src/core/ancestry/index.ts
277
+ var init_ancestry2 = __esm({
278
+ "src/core/ancestry/index.ts"() {
279
+ "use strict";
280
+ init_esm_shims();
281
+ init_ancestry();
282
+ }
283
+ });
284
+
285
+ // src/core/patch-id/patch-id.ts
286
+ function getCache() {
287
+ if (!patchIdCache) {
288
+ patchIdCache = new FileCache("sha-to-patch-id.json");
289
+ }
290
+ return patchIdCache;
291
+ }
292
+ async function computePatchId(commitSha, options) {
293
+ const cache = getCache();
294
+ const cached = await cache.get(commitSha);
295
+ if (cached) return cached;
296
+ try {
297
+ const { execa: execaFn } = await import("execa");
298
+ const cwd = options?.cwd ?? ".";
299
+ const result = await execaFn(
300
+ "bash",
301
+ [
302
+ "-c",
303
+ `git -C "${cwd}" diff "${commitSha}^..${commitSha}" | git patch-id --stable`
304
+ ],
305
+ { timeout: options?.timeout }
306
+ );
307
+ const patchId = result.stdout.trim().split(/\s+/)[0];
308
+ if (!patchId) return null;
309
+ await cache.set(commitSha, patchId);
310
+ return patchId;
311
+ } catch {
312
+ return null;
313
+ }
314
+ }
315
+ async function findPatchIdMatch(commitSha, options) {
316
+ const scanDepth = options?.scanDepth ?? DEFAULT_SCAN_DEPTH;
317
+ const ref = options?.ref ?? "HEAD";
318
+ const targetPatchId = await computePatchId(commitSha, options);
319
+ if (!targetPatchId) return null;
320
+ try {
321
+ const logResult = await gitExec(
322
+ ["log", "--format=%H", `-${scanDepth}`, ref],
323
+ { cwd: options?.cwd, timeout: options?.timeout }
324
+ );
325
+ const candidates = logResult.stdout.trim().split("\n").filter(Boolean);
326
+ for (const candidateSha of candidates) {
327
+ if (candidateSha === commitSha) continue;
328
+ const candidatePatchId = await computePatchId(candidateSha, options);
329
+ if (candidatePatchId && candidatePatchId === targetPatchId) {
330
+ return { matchedSha: candidateSha, patchId: targetPatchId };
331
+ }
332
+ }
333
+ } catch {
334
+ }
335
+ return null;
336
+ }
337
+ function resetPatchIdCache() {
338
+ patchIdCache = null;
339
+ }
340
+ var DEFAULT_SCAN_DEPTH, patchIdCache;
341
+ var init_patch_id = __esm({
342
+ "src/core/patch-id/patch-id.ts"() {
343
+ "use strict";
344
+ init_esm_shims();
345
+ init_file_cache();
346
+ init_executor();
347
+ DEFAULT_SCAN_DEPTH = 500;
348
+ patchIdCache = null;
349
+ }
350
+ });
351
+
352
+ // src/core/patch-id/index.ts
353
+ var patch_id_exports = {};
354
+ __export(patch_id_exports, {
355
+ computePatchId: () => computePatchId,
356
+ findPatchIdMatch: () => findPatchIdMatch,
357
+ resetPatchIdCache: () => resetPatchIdCache
358
+ });
359
+ var init_patch_id2 = __esm({
360
+ "src/core/patch-id/index.ts"() {
361
+ "use strict";
362
+ init_esm_shims();
363
+ init_patch_id();
364
+ }
365
+ });
366
+
367
+ // src/core/pr-lookup/pr-lookup.ts
368
+ function getCache2() {
369
+ if (!prCache) {
370
+ prCache = new FileCache("sha-to-pr.json");
371
+ }
372
+ return prCache;
373
+ }
374
+ async function lookupPR(commitSha, adapter, options) {
375
+ const cache = getCache2();
376
+ const cached = await cache.get(commitSha);
377
+ if (cached) return cached;
378
+ const mergeResult = await findMergeCommit(commitSha, options);
379
+ if (mergeResult) {
380
+ const prNumber = extractPRFromMergeMessage(mergeResult.subject);
381
+ if (prNumber) {
382
+ if (adapter) {
383
+ const prInfo = await adapter.getPRForCommit(mergeResult.mergeCommitSha);
384
+ if (prInfo) {
385
+ await cache.set(commitSha, prInfo);
386
+ return prInfo;
387
+ }
388
+ }
389
+ const minimalPR = {
390
+ number: prNumber,
391
+ title: mergeResult.subject,
392
+ author: "",
393
+ url: "",
394
+ mergeCommit: mergeResult.mergeCommitSha,
395
+ baseBranch: ""
396
+ };
397
+ await cache.set(commitSha, minimalPR);
398
+ return minimalPR;
399
+ }
400
+ }
401
+ const patchIdMatch = await findPatchIdMatch(commitSha, options);
402
+ if (patchIdMatch) {
403
+ const result = await lookupPR(patchIdMatch.matchedSha, adapter, options);
404
+ if (result) {
405
+ await cache.set(commitSha, result);
406
+ return result;
407
+ }
408
+ }
409
+ if (adapter) {
410
+ const prInfo = await adapter.getPRForCommit(commitSha);
411
+ if (prInfo) {
412
+ await cache.set(commitSha, prInfo);
413
+ return prInfo;
414
+ }
415
+ }
416
+ return null;
417
+ }
418
+ function resetPRCache() {
419
+ prCache = null;
420
+ }
421
+ var prCache;
422
+ var init_pr_lookup = __esm({
423
+ "src/core/pr-lookup/pr-lookup.ts"() {
424
+ "use strict";
425
+ init_esm_shims();
426
+ init_file_cache();
427
+ init_ancestry2();
428
+ init_patch_id2();
429
+ prCache = null;
430
+ }
431
+ });
432
+
433
+ // src/core/pr-lookup/index.ts
434
+ var pr_lookup_exports = {};
435
+ __export(pr_lookup_exports, {
436
+ lookupPR: () => lookupPR,
437
+ resetPRCache: () => resetPRCache
438
+ });
439
+ var init_pr_lookup2 = __esm({
440
+ "src/core/pr-lookup/index.ts"() {
441
+ "use strict";
442
+ init_esm_shims();
443
+ init_pr_lookup();
444
+ }
445
+ });
446
+
447
+ // src/index.ts
448
+ init_esm_shims();
449
+ init_errors();
450
+
451
+ // src/core/core.ts
452
+ init_esm_shims();
453
+
454
+ // src/ast/index.ts
455
+ init_esm_shims();
456
+
457
+ // src/ast/parser.ts
458
+ init_esm_shims();
459
+ var astGrep = null;
460
+ var loadAttempted = false;
461
+ var available = false;
462
+ var EXTENSION_TO_LANG = {
463
+ ".ts": "typescript",
464
+ ".tsx": "typescript",
465
+ ".js": "javascript",
466
+ ".jsx": "javascript",
467
+ ".py": "python",
468
+ ".go": "go",
469
+ ".rs": "rust",
470
+ ".java": "java",
471
+ ".c": "c",
472
+ ".cpp": "cpp",
473
+ ".rb": "ruby",
474
+ ".swift": "swift",
475
+ ".kt": "kotlin",
476
+ ".cs": "c_sharp"
477
+ };
478
+ var AST_GREP_MODULE = "@ast-grep/napi";
479
+ async function loadAstGrep() {
480
+ if (loadAttempted) return available;
481
+ loadAttempted = true;
482
+ try {
483
+ astGrep = await import(
484
+ /* webpackIgnore: true */
485
+ AST_GREP_MODULE
486
+ );
487
+ available = true;
488
+ } catch {
489
+ console.warn(
490
+ "[line-lore] @ast-grep/napi not available. AST diff features disabled."
491
+ );
492
+ available = false;
493
+ }
494
+ return available;
495
+ }
496
+ function isAstAvailable() {
497
+ return available;
498
+ }
499
+ function detectLanguage(filePath) {
500
+ const ext = filePath.slice(filePath.lastIndexOf("."));
501
+ return EXTENSION_TO_LANG[ext] ?? null;
502
+ }
503
+ async function findSymbols(source, lang) {
504
+ await loadAstGrep();
505
+ if (!astGrep) return [];
506
+ try {
507
+ const { parse, Lang } = astGrep;
508
+ const langEnum = Lang[lang] ?? Lang[lang.charAt(0).toUpperCase() + lang.slice(1)];
509
+ if (langEnum == null) return [];
510
+ const root = parse(langEnum, source).root();
511
+ const symbols = [];
512
+ const kindPatterns = [
513
+ { rule: { kind: "function_declaration" }, symbolKind: "function" },
514
+ { rule: { kind: "arrow_function" }, symbolKind: "arrow_function" },
515
+ { rule: { kind: "method_definition" }, symbolKind: "method" },
516
+ { rule: { kind: "class_declaration" }, symbolKind: "class" },
517
+ { rule: { kind: "function_item" }, symbolKind: "function" },
518
+ { rule: { kind: "impl_item" }, symbolKind: "class" }
519
+ ];
520
+ for (const { rule, symbolKind } of kindPatterns) {
521
+ const nodes = root.findAll({ rule });
522
+ for (const node of nodes) {
523
+ const range = node.range();
524
+ const nameNode = root.findAll({
525
+ rule: { kind: "identifier", inside: { kind: rule.kind } }
526
+ });
527
+ const name = nameNode.length > 0 ? nameNode[0].text() : "anonymous";
528
+ symbols.push({
529
+ name,
530
+ kind: symbolKind,
531
+ startLine: range.start.line + 1,
532
+ endLine: range.end.line + 1,
533
+ bodyText: node.text()
534
+ });
535
+ }
536
+ }
537
+ return symbols;
538
+ } catch {
539
+ return [];
540
+ }
541
+ }
542
+ function extractSymbolsFromText(source, lang) {
543
+ const symbols = [];
544
+ const lines = source.split("\n");
545
+ if (lang === "typescript" || lang === "javascript") {
546
+ extractJsSymbols(lines, symbols);
547
+ } else if (lang === "python") {
548
+ extractPythonSymbols(lines, symbols);
549
+ }
550
+ return symbols;
551
+ }
552
+ function extractJsSymbols(lines, symbols) {
553
+ for (let i = 0; i < lines.length; i++) {
554
+ const line = lines[i];
555
+ const funcMatch = /^(?:export\s+)?(?:async\s+)?function\s+(\w+)/.exec(line);
556
+ if (funcMatch) {
557
+ const endLine = findBlockEnd(lines, i);
558
+ symbols.push({
559
+ name: funcMatch[1],
560
+ kind: "function",
561
+ startLine: i + 1,
562
+ endLine: endLine + 1,
563
+ bodyText: lines.slice(i, endLine + 1).join("\n")
564
+ });
565
+ continue;
566
+ }
567
+ const arrowMatch = /^(?:export\s+)?(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?\(/.exec(
568
+ line
569
+ );
570
+ if (arrowMatch && (line.includes("=>") || lines[i + 1]?.includes("=>"))) {
571
+ const endLine = findBlockEnd(lines, i);
572
+ symbols.push({
573
+ name: arrowMatch[1],
574
+ kind: "arrow_function",
575
+ startLine: i + 1,
576
+ endLine: endLine + 1,
577
+ bodyText: lines.slice(i, endLine + 1).join("\n")
578
+ });
579
+ continue;
580
+ }
581
+ const classMatch = /^(?:export\s+)?class\s+(\w+)/.exec(line);
582
+ if (classMatch) {
583
+ const endLine = findBlockEnd(lines, i);
584
+ symbols.push({
585
+ name: classMatch[1],
586
+ kind: "class",
587
+ startLine: i + 1,
588
+ endLine: endLine + 1,
589
+ bodyText: lines.slice(i, endLine + 1).join("\n")
590
+ });
591
+ continue;
592
+ }
593
+ const methodMatch = /^\s+(?:async\s+)?(\w+)\s*\(/.exec(line);
594
+ if (methodMatch && !line.includes("if") && !line.includes("for") && !line.includes("while")) {
595
+ const endLine = findBlockEnd(lines, i);
596
+ if (endLine > i) {
597
+ symbols.push({
598
+ name: methodMatch[1],
599
+ kind: "method",
600
+ startLine: i + 1,
601
+ endLine: endLine + 1,
602
+ bodyText: lines.slice(i, endLine + 1).join("\n")
603
+ });
604
+ }
605
+ }
606
+ }
607
+ }
608
+ function extractPythonSymbols(lines, symbols) {
609
+ for (let i = 0; i < lines.length; i++) {
610
+ const line = lines[i];
611
+ const funcMatch = /^(?:async\s+)?def\s+(\w+)/.exec(line);
612
+ if (funcMatch) {
613
+ const endLine = findPythonBlockEnd(lines, i);
614
+ symbols.push({
615
+ name: funcMatch[1],
616
+ kind: "function",
617
+ startLine: i + 1,
618
+ endLine: endLine + 1,
619
+ bodyText: lines.slice(i, endLine + 1).join("\n")
620
+ });
621
+ continue;
622
+ }
623
+ const classMatch = /^class\s+(\w+)/.exec(line);
624
+ if (classMatch) {
625
+ const endLine = findPythonBlockEnd(lines, i);
626
+ symbols.push({
627
+ name: classMatch[1],
628
+ kind: "class",
629
+ startLine: i + 1,
630
+ endLine: endLine + 1,
631
+ bodyText: lines.slice(i, endLine + 1).join("\n")
632
+ });
633
+ }
634
+ }
635
+ }
636
+ function findBlockEnd(lines, startIdx) {
637
+ let depth = 0;
638
+ let started = false;
639
+ for (let i = startIdx; i < lines.length; i++) {
640
+ for (const ch of lines[i]) {
641
+ if (ch === "{") {
642
+ depth++;
643
+ started = true;
644
+ }
645
+ if (ch === "}") depth--;
646
+ }
647
+ if (started && depth === 0) return i;
648
+ }
649
+ return startIdx;
650
+ }
651
+ function findPythonBlockEnd(lines, startIdx) {
652
+ const indent = lines[startIdx].search(/\S/);
653
+ for (let i = startIdx + 1; i < lines.length; i++) {
654
+ const line = lines[i];
655
+ if (line.trim() === "") continue;
656
+ if (line.search(/\S/) <= indent) return i - 1;
657
+ }
658
+ return lines.length - 1;
659
+ }
660
+
661
+ // src/git/health.ts
662
+ init_esm_shims();
663
+ init_executor();
664
+ var GIT_VERSION_PATTERN = /git version (\d+\.\d+\.\d+)/;
665
+ var BLOOM_FILTER_MIN_VERSION = [2, 27, 0];
666
+ function parseGitVersion(versionStr) {
667
+ const match = GIT_VERSION_PATTERN.exec(versionStr);
668
+ return match?.[1] ?? "0.0.0";
669
+ }
670
+ function isVersionAtLeast(version, minVersion) {
671
+ const parts = version.split(".").map(Number);
672
+ for (let i = 0; i < 3; i++) {
673
+ if ((parts[i] ?? 0) > minVersion[i]) return true;
674
+ if ((parts[i] ?? 0) < minVersion[i]) return false;
675
+ }
676
+ return true;
677
+ }
678
+ async function checkGitHealth(options) {
679
+ const hints = [];
680
+ let gitVersion = "0.0.0";
681
+ let commitGraph = false;
682
+ let bloomFilter = false;
683
+ try {
684
+ const versionResult = await gitExec(["version"], { cwd: options?.cwd });
685
+ gitVersion = parseGitVersion(versionResult.stdout);
686
+ } catch {
687
+ hints.push("Could not determine git version.");
688
+ }
689
+ try {
690
+ await gitExec(["commit-graph", "verify"], { cwd: options?.cwd });
691
+ commitGraph = true;
692
+ } catch {
693
+ commitGraph = false;
694
+ hints.push(
695
+ "Run `git commit-graph write --reachable` to enable commit-graph acceleration."
696
+ );
697
+ }
698
+ bloomFilter = isVersionAtLeast(gitVersion, BLOOM_FILTER_MIN_VERSION);
699
+ if (!bloomFilter) {
700
+ hints.push(
701
+ `Upgrade git to ${BLOOM_FILTER_MIN_VERSION.join(".")}+ for bloom filter support (current: ${gitVersion}).`
702
+ );
703
+ }
704
+ return { commitGraph, bloomFilter, gitVersion, hints };
705
+ }
706
+
707
+ // src/platform/index.ts
708
+ init_esm_shims();
709
+
710
+ // src/platform/platform.ts
711
+ init_esm_shims();
712
+
713
+ // src/git/remote.ts
714
+ init_esm_shims();
715
+ init_errors();
716
+ init_executor();
717
+ var SSH_PATTERN = /^(?:ssh:\/\/)?git@([^:/]+)[:/]([^/]+)\/(.+?)(?:\.git)?$/;
718
+ var HTTPS_PATTERN = /^https?:\/\/([^/]+)\/([^/]+)\/(.+?)(?:\.git)?$/;
719
+ function parseRemoteUrl(url) {
720
+ let match = SSH_PATTERN.exec(url);
721
+ if (match) {
722
+ const [, host, owner, repo] = match;
723
+ return { host, owner, repo, platform: detectPlatform(host) };
724
+ }
725
+ match = HTTPS_PATTERN.exec(url);
726
+ if (match) {
727
+ const [, host, owner, repo] = match;
728
+ return { host, owner, repo, platform: detectPlatform(host) };
729
+ }
730
+ throw new LineLoreError(
731
+ LineLoreErrorCode.INVALID_REMOTE_URL,
732
+ `Cannot parse remote URL: ${url}`,
733
+ { url }
734
+ );
735
+ }
736
+ function detectPlatform(host) {
737
+ if (host === "github.com") return "github";
738
+ if (host === "gitlab.com") return "gitlab";
739
+ return "unknown";
740
+ }
741
+ async function getRemoteInfo(remoteName = "origin", options) {
742
+ const result = await gitExec(["remote", "get-url", remoteName], {
743
+ cwd: options?.cwd
744
+ });
745
+ return parseRemoteUrl(result.stdout.trim());
746
+ }
747
+
748
+ // src/platform/github/index.ts
749
+ init_esm_shims();
750
+
751
+ // src/platform/github/github-adapter.ts
752
+ init_esm_shims();
753
+ init_executor();
754
+
755
+ // src/platform/scheduler/index.ts
756
+ init_esm_shims();
757
+
758
+ // src/platform/scheduler/request-scheduler.ts
759
+ init_esm_shims();
760
+ var RequestScheduler = class {
761
+ rateLimitInfo = null;
762
+ threshold;
763
+ etagCache = /* @__PURE__ */ new Map();
764
+ responseCache = /* @__PURE__ */ new Map();
765
+ constructor(options) {
766
+ this.threshold = options?.rateLimitThreshold ?? 10;
767
+ }
768
+ updateRateLimit(info) {
769
+ this.rateLimitInfo = info;
770
+ }
771
+ isRateLimited() {
772
+ if (!this.rateLimitInfo) return false;
773
+ return this.rateLimitInfo.remaining < this.threshold;
774
+ }
775
+ getRateLimit() {
776
+ return this.rateLimitInfo;
777
+ }
778
+ setEtag(url, etag, response) {
779
+ this.etagCache.set(url, etag);
780
+ this.responseCache.set(url, response);
781
+ }
782
+ getEtag(url) {
783
+ return this.etagCache.get(url) ?? null;
784
+ }
785
+ getCachedResponse(url) {
786
+ return this.responseCache.get(url) ?? null;
787
+ }
788
+ };
789
+
790
+ // src/platform/github/github-adapter.ts
791
+ var GitHubAdapter = class {
792
+ platform = "github";
793
+ scheduler;
794
+ hostname;
795
+ constructor(options) {
796
+ this.hostname = options?.hostname ?? "github.com";
797
+ this.scheduler = options?.scheduler ?? new RequestScheduler();
798
+ }
799
+ async checkAuth() {
800
+ try {
801
+ const result = await gitExec(
802
+ ["gh", "auth", "status", "--hostname", this.hostname].slice(1),
803
+ {
804
+ allowExitCodes: [1]
805
+ }
806
+ );
807
+ const output = result.stdout + result.stderr;
808
+ const usernameMatch = /Logged in to .+ as (\S+)/.exec(output);
809
+ return {
810
+ authenticated: result.exitCode === 0,
811
+ username: usernameMatch?.[1],
812
+ hostname: this.hostname
813
+ };
814
+ } catch {
815
+ return { authenticated: false, hostname: this.hostname };
816
+ }
817
+ }
818
+ async getPRForCommit(sha) {
819
+ if (this.scheduler.isRateLimited()) return null;
820
+ try {
821
+ const result = await gitExec(
822
+ [
823
+ "gh",
824
+ "api",
825
+ `repos/{owner}/{repo}/commits/${sha}/pulls`,
826
+ "--hostname",
827
+ this.hostname,
828
+ "--jq",
829
+ ".[0] | {number, title, user: .user.login, html_url, merge_commit_sha, base: .base.ref, merged_at}"
830
+ ].slice(1)
831
+ );
832
+ const data = JSON.parse(result.stdout);
833
+ if (!data?.number) return null;
834
+ return {
835
+ number: data.number,
836
+ title: data.title ?? "",
837
+ author: data.user ?? "",
838
+ url: data.html_url ?? "",
839
+ mergeCommit: data.merge_commit_sha ?? sha,
840
+ baseBranch: data.base ?? "main",
841
+ mergedAt: data.merged_at
842
+ };
843
+ } catch {
844
+ return null;
845
+ }
846
+ }
847
+ async getPRCommits(prNumber) {
848
+ try {
849
+ const result = await gitExec(
850
+ [
851
+ "gh",
852
+ "api",
853
+ `repos/{owner}/{repo}/pulls/${prNumber}/commits`,
854
+ "--hostname",
855
+ this.hostname,
856
+ "--jq",
857
+ ".[].sha"
858
+ ].slice(1)
859
+ );
860
+ return result.stdout.trim().split("\n").filter(Boolean);
861
+ } catch {
862
+ return [];
863
+ }
864
+ }
865
+ async getLinkedIssues(prNumber) {
866
+ try {
867
+ const result = await gitExec(
868
+ [
869
+ "gh",
870
+ "api",
871
+ "graphql",
872
+ "--hostname",
873
+ this.hostname,
874
+ "-f",
875
+ `query=query { repository(owner: "{owner}", name: "{repo}") { pullRequest(number: ${prNumber}) { closingIssuesReferences(first: 10) { nodes { number title url state labels(first: 5) { nodes { name } } } } } } }`,
876
+ "--jq",
877
+ ".data.repository.pullRequest.closingIssuesReferences.nodes"
878
+ ].slice(1)
879
+ );
880
+ const nodes = JSON.parse(result.stdout);
881
+ if (!Array.isArray(nodes)) return [];
882
+ return nodes.map((node) => ({
883
+ number: node.number,
884
+ title: node.title ?? "",
885
+ url: node.url ?? "",
886
+ state: (node.state ?? "open").toLowerCase(),
887
+ labels: (node.labels?.nodes ?? []).map((l) => l.name)
888
+ }));
889
+ } catch {
890
+ return [];
891
+ }
892
+ }
893
+ async getLinkedPRs(issueNumber) {
894
+ try {
895
+ const result = await gitExec(
896
+ [
897
+ "gh",
898
+ "api",
899
+ `repos/{owner}/{repo}/issues/${issueNumber}/timeline`,
900
+ "--hostname",
901
+ this.hostname,
902
+ "--jq",
903
+ '[.[] | 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})'
904
+ ].slice(1)
905
+ );
906
+ const prs = JSON.parse(result.stdout);
907
+ if (!Array.isArray(prs)) return [];
908
+ return prs.map((pr) => ({
909
+ number: pr.number,
910
+ title: pr.title ?? "",
911
+ author: pr.user ?? "",
912
+ url: pr.html_url ?? "",
913
+ mergeCommit: pr.merge_commit_sha ?? "",
914
+ baseBranch: pr.base ?? "main",
915
+ mergedAt: pr.merged_at
916
+ }));
917
+ } catch {
918
+ return [];
919
+ }
920
+ }
921
+ async getRateLimit() {
922
+ try {
923
+ const result = await gitExec(
924
+ [
925
+ "gh",
926
+ "api",
927
+ "rate_limit",
928
+ "--hostname",
929
+ this.hostname,
930
+ "--jq",
931
+ ".rate | {limit, remaining, reset}"
932
+ ].slice(1)
933
+ );
934
+ const data = JSON.parse(result.stdout);
935
+ const info = {
936
+ limit: data.limit ?? 5e3,
937
+ remaining: data.remaining ?? 5e3,
938
+ resetAt: new Date((data.reset ?? 0) * 1e3).toISOString()
939
+ };
940
+ this.scheduler.updateRateLimit(info);
941
+ return info;
942
+ } catch {
943
+ return { limit: 0, remaining: 0, resetAt: (/* @__PURE__ */ new Date()).toISOString() };
944
+ }
945
+ }
946
+ };
947
+
948
+ // src/platform/github/github-enterprise-adapter.ts
949
+ init_esm_shims();
950
+ var GitHubEnterpriseAdapter = class extends GitHubAdapter {
951
+ platform = "github-enterprise";
952
+ constructor(hostname, options) {
953
+ super({ hostname, scheduler: options?.scheduler });
954
+ }
955
+ };
956
+
957
+ // src/platform/gitlab/index.ts
958
+ init_esm_shims();
959
+
960
+ // src/platform/gitlab/gitlab-adapter.ts
961
+ init_esm_shims();
962
+ init_executor();
963
+ var GitLabAdapter = class {
964
+ platform = "gitlab";
965
+ scheduler;
966
+ hostname;
967
+ constructor(options) {
968
+ this.hostname = options?.hostname ?? "gitlab.com";
969
+ this.scheduler = options?.scheduler ?? new RequestScheduler();
970
+ }
971
+ async checkAuth() {
972
+ try {
973
+ const result = await gitExec(
974
+ ["glab", "auth", "status", "--hostname", this.hostname].slice(1),
975
+ { allowExitCodes: [1] }
976
+ );
977
+ const output = result.stdout + result.stderr;
978
+ const usernameMatch = /Logged in to .+ as (\S+)/.exec(output);
979
+ return {
980
+ authenticated: result.exitCode === 0,
981
+ username: usernameMatch?.[1],
982
+ hostname: this.hostname
983
+ };
984
+ } catch {
985
+ return { authenticated: false, hostname: this.hostname };
986
+ }
987
+ }
988
+ async getPRForCommit(sha) {
989
+ if (this.scheduler.isRateLimited()) return null;
990
+ try {
991
+ const result = await gitExec(
992
+ [
993
+ "glab",
994
+ "api",
995
+ `projects/:id/repository/commits/${sha}/merge_requests`,
996
+ "--hostname",
997
+ this.hostname
998
+ ].slice(1)
999
+ );
1000
+ const mrs = JSON.parse(result.stdout);
1001
+ if (!Array.isArray(mrs) || mrs.length === 0) return null;
1002
+ const mr = mrs[0];
1003
+ return {
1004
+ number: mr.iid,
1005
+ title: mr.title ?? "",
1006
+ author: mr.author?.username ?? "",
1007
+ url: mr.web_url ?? "",
1008
+ mergeCommit: mr.merge_commit_sha ?? sha,
1009
+ baseBranch: mr.target_branch ?? "main",
1010
+ mergedAt: mr.merged_at
1011
+ };
1012
+ } catch {
1013
+ return null;
1014
+ }
1015
+ }
1016
+ async getPRCommits(prNumber) {
1017
+ try {
1018
+ const result = await gitExec(
1019
+ [
1020
+ "glab",
1021
+ "api",
1022
+ `projects/:id/merge_requests/${prNumber}/commits`,
1023
+ "--hostname",
1024
+ this.hostname
1025
+ ].slice(1)
1026
+ );
1027
+ const commits = JSON.parse(result.stdout);
1028
+ if (!Array.isArray(commits)) return [];
1029
+ return commits.map((c) => c.id);
1030
+ } catch {
1031
+ return [];
1032
+ }
1033
+ }
1034
+ async getLinkedIssues(prNumber) {
1035
+ try {
1036
+ const result = await gitExec(
1037
+ [
1038
+ "glab",
1039
+ "api",
1040
+ `projects/:id/merge_requests/${prNumber}/closes_issues`,
1041
+ "--hostname",
1042
+ this.hostname
1043
+ ].slice(1)
1044
+ );
1045
+ const issues = JSON.parse(result.stdout);
1046
+ if (!Array.isArray(issues)) return [];
1047
+ return issues.map((issue) => ({
1048
+ number: issue.iid,
1049
+ title: issue.title ?? "",
1050
+ url: issue.web_url ?? "",
1051
+ state: (issue.state ?? "opened") === "closed" ? "closed" : "open",
1052
+ labels: issue.labels ?? []
1053
+ }));
1054
+ } catch {
1055
+ return [];
1056
+ }
1057
+ }
1058
+ async getLinkedPRs(issueNumber) {
1059
+ try {
1060
+ const result = await gitExec(
1061
+ [
1062
+ "glab",
1063
+ "api",
1064
+ `projects/:id/issues/${issueNumber}/related_merge_requests`,
1065
+ "--hostname",
1066
+ this.hostname
1067
+ ].slice(1)
1068
+ );
1069
+ const mrs = JSON.parse(result.stdout);
1070
+ if (!Array.isArray(mrs)) return [];
1071
+ return mrs.map((mr) => ({
1072
+ number: mr.iid,
1073
+ title: mr.title ?? "",
1074
+ author: mr.author?.username ?? "",
1075
+ url: mr.web_url ?? "",
1076
+ mergeCommit: mr.merge_commit_sha ?? "",
1077
+ baseBranch: mr.target_branch ?? "main",
1078
+ mergedAt: mr.merged_at
1079
+ }));
1080
+ } catch {
1081
+ return [];
1082
+ }
1083
+ }
1084
+ async getRateLimit() {
1085
+ return { limit: 0, remaining: 0, resetAt: (/* @__PURE__ */ new Date()).toISOString() };
1086
+ }
1087
+ };
1088
+
1089
+ // src/platform/gitlab/gitlab-self-hosted-adapter.ts
1090
+ init_esm_shims();
1091
+ var GitLabSelfHostedAdapter = class extends GitLabAdapter {
1092
+ platform = "gitlab-self-hosted";
1093
+ constructor(hostname, options) {
1094
+ super({ hostname, scheduler: options?.scheduler });
1095
+ }
1096
+ };
1097
+
1098
+ // src/platform/platform.ts
1099
+ async function detectPlatformAdapter(options) {
1100
+ const remote = await getRemoteInfo(options?.remoteName, {
1101
+ cwd: options?.cwd
1102
+ });
1103
+ const adapter = createAdapter(remote);
1104
+ return { adapter, remote };
1105
+ }
1106
+ function createAdapter(remote) {
1107
+ switch (remote.platform) {
1108
+ case "github":
1109
+ return new GitHubAdapter({ hostname: remote.host });
1110
+ case "github-enterprise":
1111
+ return new GitHubEnterpriseAdapter(remote.host);
1112
+ case "gitlab":
1113
+ return new GitLabAdapter({ hostname: remote.host });
1114
+ case "gitlab-self-hosted":
1115
+ return new GitLabSelfHostedAdapter(remote.host);
1116
+ case "unknown":
1117
+ return new GitHubEnterpriseAdapter(remote.host);
1118
+ }
1119
+ }
1120
+
1121
+ // src/utils/line-range.ts
1122
+ init_esm_shims();
1123
+ init_errors();
1124
+ function parseLineRange(input) {
1125
+ const parts = input.split(",");
1126
+ if (parts.length > 2) {
1127
+ throw new LineLoreError(
1128
+ LineLoreErrorCode.INVALID_LINE,
1129
+ `Invalid line range format: "${input}". Expected "line" or "start,end".`,
1130
+ { input }
1131
+ );
1132
+ }
1133
+ const start = parseLineNumber(parts[0], input);
1134
+ const end = parts.length === 2 ? parseLineNumber(parts[1], input) : start;
1135
+ if (start > end) {
1136
+ throw new LineLoreError(
1137
+ LineLoreErrorCode.INVALID_LINE,
1138
+ `Invalid line range: start (${start}) must be <= end (${end}).`,
1139
+ { input, start, end }
1140
+ );
1141
+ }
1142
+ return { start, end };
1143
+ }
1144
+ function parseLineNumber(value, originalInput) {
1145
+ const trimmed = value.trim();
1146
+ if (trimmed === "" || !/^\d+$/.test(trimmed)) {
1147
+ throw new LineLoreError(
1148
+ LineLoreErrorCode.INVALID_LINE,
1149
+ `Invalid line number: "${trimmed}". Must be a positive integer.`,
1150
+ { input: originalInput }
1151
+ );
1152
+ }
1153
+ const num = Number(trimmed);
1154
+ if (num <= 0) {
1155
+ throw new LineLoreError(
1156
+ LineLoreErrorCode.INVALID_LINE,
1157
+ `Invalid line number: ${num}. Must be a positive integer.`,
1158
+ { input: originalInput }
1159
+ );
1160
+ }
1161
+ return num;
1162
+ }
1163
+
1164
+ // src/core/ast-diff/index.ts
1165
+ init_esm_shims();
1166
+
1167
+ // src/core/ast-diff/ast-diff.ts
1168
+ init_esm_shims();
1169
+ init_executor();
1170
+
1171
+ // src/core/ast-diff/comparison/index.ts
1172
+ init_esm_shims();
1173
+
1174
+ // src/core/ast-diff/comparison/structure-comparator.ts
1175
+ init_esm_shims();
1176
+ function compareSymbolMaps(current, parent) {
1177
+ const results = [];
1178
+ for (const [name, hash] of current) {
1179
+ const parentHash = parent.get(name);
1180
+ if (parentHash) {
1181
+ if (parentHash.exact === hash.exact) {
1182
+ results.push({ change: "identical", confidence: "exact" });
1183
+ } else if (parentHash.structural === hash.structural) {
1184
+ results.push({
1185
+ change: "modified",
1186
+ fromName: name,
1187
+ toName: name,
1188
+ confidence: "structural"
1189
+ });
1190
+ } else {
1191
+ results.push({
1192
+ change: "modified",
1193
+ fromName: name,
1194
+ toName: name,
1195
+ confidence: "heuristic"
1196
+ });
1197
+ }
1198
+ continue;
1199
+ }
1200
+ const renameMatch = findHashMatch(hash, parent);
1201
+ if (renameMatch) {
1202
+ results.push({
1203
+ change: "rename",
1204
+ fromName: renameMatch.name,
1205
+ toName: name,
1206
+ confidence: renameMatch.confidence
1207
+ });
1208
+ } else {
1209
+ results.push({ change: "new", toName: name, confidence: "exact" });
1210
+ }
1211
+ }
1212
+ return results;
1213
+ }
1214
+ function findHashMatch(target, map) {
1215
+ for (const [name, hash] of map) {
1216
+ if (hash.exact === target.exact) {
1217
+ return { name, confidence: "exact" };
1218
+ }
1219
+ }
1220
+ for (const [name, hash] of map) {
1221
+ if (hash.structural === target.structural) {
1222
+ return { name, confidence: "structural" };
1223
+ }
1224
+ }
1225
+ return null;
1226
+ }
1227
+
1228
+ // src/core/ast-diff/extraction/index.ts
1229
+ init_esm_shims();
1230
+
1231
+ // src/core/ast-diff/extraction/symbol-extractor.ts
1232
+ init_esm_shims();
1233
+ async function extractSymbols(source, lang) {
1234
+ if (isAstAvailable()) {
1235
+ const symbols = await findSymbols(source, lang);
1236
+ if (symbols.length > 0) return symbols;
1237
+ }
1238
+ return extractSymbolsFromText(source, lang);
1239
+ }
1240
+ function findContainingSymbol(symbols, line) {
1241
+ let best = null;
1242
+ for (const symbol of symbols) {
1243
+ if (line >= symbol.startLine && line <= symbol.endLine) {
1244
+ if (!best || symbol.endLine - symbol.startLine < best.endLine - best.startLine) {
1245
+ best = symbol;
1246
+ }
1247
+ }
1248
+ }
1249
+ return best;
1250
+ }
1251
+
1252
+ // src/core/ast-diff/extraction/signature-hasher.ts
1253
+ init_esm_shims();
1254
+ import { createHash } from "crypto";
1255
+ function computeExactHash(bodyText) {
1256
+ const normalized = stripWhitespaceAndComments(bodyText);
1257
+ return sha256(normalized);
1258
+ }
1259
+ function computeStructuralHash(bodyText) {
1260
+ const stripped = stripWhitespaceAndComments(bodyText);
1261
+ const normalized = normalizeIdentifiers(stripped);
1262
+ return sha256(normalized);
1263
+ }
1264
+ function computeContentHash(bodyText) {
1265
+ return {
1266
+ exact: computeExactHash(bodyText),
1267
+ structural: computeStructuralHash(bodyText)
1268
+ };
1269
+ }
1270
+ function stripWhitespaceAndComments(text) {
1271
+ let result = text;
1272
+ result = result.replace(/\/\/.*$/gm, "");
1273
+ result = result.replace(/\/\*[\s\S]*?\*\//g, "");
1274
+ result = result.replace(/#.*$/gm, "");
1275
+ result = result.replace(/"(?:[^"\\]|\\.)*"/g, '""');
1276
+ result = result.replace(/'(?:[^'\\]|\\.)*'/g, "''");
1277
+ result = result.replace(/`(?:[^`\\]|\\.)*`/g, "``");
1278
+ result = result.replace(/\s+/g, "");
1279
+ return result;
1280
+ }
1281
+ function normalizeIdentifiers(text) {
1282
+ const identifiers = /* @__PURE__ */ new Map();
1283
+ let counter = 0;
1284
+ return text.replace(/[a-zA-Z_]\w*/g, (match) => {
1285
+ if (KEYWORDS.has(match)) return match;
1286
+ let normalized = identifiers.get(match);
1287
+ if (!normalized) {
1288
+ normalized = `$${counter++}`;
1289
+ identifiers.set(match, normalized);
1290
+ }
1291
+ return normalized;
1292
+ });
1293
+ }
1294
+ function sha256(input) {
1295
+ return createHash("sha256").update(input).digest("hex");
1296
+ }
1297
+ var KEYWORDS = /* @__PURE__ */ new Set([
1298
+ // JavaScript/TypeScript
1299
+ "async",
1300
+ "await",
1301
+ "break",
1302
+ "case",
1303
+ "catch",
1304
+ "class",
1305
+ "const",
1306
+ "continue",
1307
+ "debugger",
1308
+ "default",
1309
+ "delete",
1310
+ "do",
1311
+ "else",
1312
+ "export",
1313
+ "extends",
1314
+ "false",
1315
+ "finally",
1316
+ "for",
1317
+ "function",
1318
+ "if",
1319
+ "import",
1320
+ "in",
1321
+ "instanceof",
1322
+ "let",
1323
+ "new",
1324
+ "null",
1325
+ "of",
1326
+ "return",
1327
+ "static",
1328
+ "super",
1329
+ "switch",
1330
+ "this",
1331
+ "throw",
1332
+ "true",
1333
+ "try",
1334
+ "typeof",
1335
+ "undefined",
1336
+ "var",
1337
+ "void",
1338
+ "while",
1339
+ "with",
1340
+ "yield",
1341
+ "type",
1342
+ "interface",
1343
+ "enum",
1344
+ "implements",
1345
+ "public",
1346
+ "private",
1347
+ "protected",
1348
+ "abstract",
1349
+ "readonly",
1350
+ // Python
1351
+ "def",
1352
+ "lambda",
1353
+ "pass",
1354
+ "raise",
1355
+ "and",
1356
+ "or",
1357
+ "not",
1358
+ "is",
1359
+ "None",
1360
+ "True",
1361
+ "False",
1362
+ "nonlocal",
1363
+ "global",
1364
+ "assert",
1365
+ "elif",
1366
+ "except",
1367
+ "from",
1368
+ "as",
1369
+ "with",
1370
+ // Go
1371
+ "func",
1372
+ "package",
1373
+ "range",
1374
+ "struct",
1375
+ "map",
1376
+ "chan",
1377
+ "go",
1378
+ "select",
1379
+ "defer",
1380
+ "fallthrough",
1381
+ "goto"
1382
+ ]);
1383
+
1384
+ // src/core/ast-diff/ast-diff.ts
1385
+ var MAX_TRAVERSAL_DEPTH = 50;
1386
+ async function traceByAst(file, line, startCommitSha, options) {
1387
+ if (!isAstAvailable()) {
1388
+ const lang2 = detectLanguage(file);
1389
+ if (!lang2) return null;
1390
+ }
1391
+ const lang = detectLanguage(file);
1392
+ if (!lang) return null;
1393
+ const maxDepth = options?.maxDepth ?? MAX_TRAVERSAL_DEPTH;
1394
+ const changes = [];
1395
+ let currentSha = startCommitSha;
1396
+ let currentSymbol = null;
1397
+ try {
1398
+ const content = await getFileAtCommit(currentSha, file, options);
1399
+ const symbols = await extractSymbols(content, lang);
1400
+ currentSymbol = findContainingSymbol(symbols, line);
1401
+ if (!currentSymbol) return null;
1402
+ } catch {
1403
+ return null;
1404
+ }
1405
+ let originSha = currentSha;
1406
+ let originSymbol = currentSymbol;
1407
+ for (let depth = 0; depth < maxDepth; depth++) {
1408
+ const parentSha = await getParentCommit(currentSha, options);
1409
+ if (!parentSha) break;
1410
+ try {
1411
+ const parentContent = await getFileAtCommit(parentSha, file, options);
1412
+ const parentSymbols = await extractSymbols(parentContent, lang);
1413
+ const currentMap = new Map(
1414
+ [currentSymbol].filter(Boolean).map((s) => [s.name, computeContentHash(s.bodyText)])
1415
+ );
1416
+ const parentMap = new Map(
1417
+ parentSymbols.map((s) => [s.name, computeContentHash(s.bodyText)])
1418
+ );
1419
+ const comparison = compareSymbolMaps(currentMap, parentMap);
1420
+ if (comparison.length > 0) {
1421
+ const result = comparison[0];
1422
+ changes.push(result);
1423
+ if (result.change === "identical") {
1424
+ originSha = parentSha;
1425
+ const parentSymbol = parentSymbols.find(
1426
+ (s) => s.name === currentSymbol.name
1427
+ );
1428
+ if (parentSymbol) {
1429
+ originSymbol = parentSymbol;
1430
+ currentSymbol = parentSymbol;
1431
+ }
1432
+ } else if (result.change === "rename" && result.fromName) {
1433
+ originSha = parentSha;
1434
+ const parentSymbol = parentSymbols.find(
1435
+ (s) => s.name === result.fromName
1436
+ );
1437
+ if (parentSymbol) {
1438
+ originSymbol = parentSymbol;
1439
+ currentSymbol = parentSymbol;
1440
+ }
1441
+ } else if (result.change === "new") {
1442
+ break;
1443
+ } else {
1444
+ break;
1445
+ }
1446
+ } else {
1447
+ break;
1448
+ }
1449
+ currentSha = parentSha;
1450
+ } catch {
1451
+ break;
1452
+ }
1453
+ }
1454
+ const lastChange = changes[changes.length - 1];
1455
+ return {
1456
+ originSha,
1457
+ originSymbol,
1458
+ trackingMethod: "ast-signature",
1459
+ confidence: lastChange?.confidence ?? "exact",
1460
+ changes
1461
+ };
1462
+ }
1463
+ async function getFileAtCommit(sha, file, options) {
1464
+ const result = await gitExec(["show", `${sha}:${file}`], options);
1465
+ return result.stdout;
1466
+ }
1467
+ async function getParentCommit(sha, options) {
1468
+ try {
1469
+ const result = await gitExec(["log", "-1", "--format=%P", sha], options);
1470
+ const parents = result.stdout.trim().split(/\s+/);
1471
+ return parents[0] || null;
1472
+ } catch {
1473
+ return null;
1474
+ }
1475
+ }
1476
+
1477
+ // src/core/blame/index.ts
1478
+ init_esm_shims();
1479
+
1480
+ // src/core/blame/blame.ts
1481
+ init_esm_shims();
1482
+ init_executor();
1483
+
1484
+ // src/core/blame/detection/index.ts
1485
+ init_esm_shims();
1486
+
1487
+ // src/core/blame/detection/cosmetic-detector.ts
1488
+ init_esm_shims();
1489
+ init_executor();
1490
+ function isCosmeticDiff(diff) {
1491
+ const hunks = extractHunks(diff);
1492
+ if (hunks.length === 0) return { isCosmetic: false };
1493
+ if (isImportReorder(hunks)) {
1494
+ return { isCosmetic: true, reason: "import-order" };
1495
+ }
1496
+ if (isWhitespaceOnly(hunks)) {
1497
+ return { isCosmetic: true, reason: "whitespace" };
1498
+ }
1499
+ if (isFormattingOnly(hunks)) {
1500
+ return { isCosmetic: true, reason: "formatting" };
1501
+ }
1502
+ return { isCosmetic: false };
1503
+ }
1504
+ async function getCosmeticDiff(commitSha, filePath, options) {
1505
+ const result = await gitExec(
1506
+ ["diff", `${commitSha}^..${commitSha}`, "--", filePath],
1507
+ options
1508
+ );
1509
+ return result.stdout;
1510
+ }
1511
+ function extractHunks(diff) {
1512
+ const lines = diff.split("\n");
1513
+ const hunks = [];
1514
+ let current = null;
1515
+ for (const line of lines) {
1516
+ if (line.startsWith("@@")) {
1517
+ if (current) hunks.push(current);
1518
+ current = { removed: [], added: [] };
1519
+ continue;
1520
+ }
1521
+ if (!current) continue;
1522
+ if (line.startsWith("-") && !line.startsWith("---")) {
1523
+ current.removed.push(line.slice(1));
1524
+ } else if (line.startsWith("+") && !line.startsWith("+++")) {
1525
+ current.added.push(line.slice(1));
1526
+ }
1527
+ }
1528
+ if (current) hunks.push(current);
1529
+ return hunks;
1530
+ }
1531
+ function normalize(line) {
1532
+ return line.replace(/\s+/g, "").trim();
1533
+ }
1534
+ function isWhitespaceOnly(hunks) {
1535
+ return hunks.every((hunk) => {
1536
+ const removedNorm = hunk.removed.map(normalize).filter(Boolean).sort();
1537
+ const addedNorm = hunk.added.map(normalize).filter(Boolean).sort();
1538
+ if (removedNorm.length !== addedNorm.length) return false;
1539
+ return removedNorm.every((line, idx) => line === addedNorm[idx]);
1540
+ });
1541
+ }
1542
+ function isImportReorder(hunks) {
1543
+ return hunks.every((hunk) => {
1544
+ const removedImports = hunk.removed.filter(isImportLine);
1545
+ const addedImports = hunk.added.filter(isImportLine);
1546
+ if (removedImports.length === 0) return false;
1547
+ if (removedImports.length !== hunk.removed.filter((l) => l.trim()).length)
1548
+ return false;
1549
+ if (addedImports.length !== hunk.added.filter((l) => l.trim()).length)
1550
+ return false;
1551
+ const removedSorted = removedImports.map(normalize).sort();
1552
+ const addedSorted = addedImports.map(normalize).sort();
1553
+ if (removedSorted.length !== addedSorted.length) return false;
1554
+ return removedSorted.every((line, idx) => line === addedSorted[idx]);
1555
+ });
1556
+ }
1557
+ function isImportLine(line) {
1558
+ const trimmed = line.trim();
1559
+ return trimmed.startsWith("import ") || trimmed.startsWith("from ") || trimmed.startsWith("require(") || trimmed.startsWith("const ") && trimmed.includes("require(");
1560
+ }
1561
+ function isFormattingOnly(hunks) {
1562
+ return hunks.every((hunk) => {
1563
+ const removedTokens = extractAlphanumericTokens(hunk.removed.join(" "));
1564
+ const addedTokens = extractAlphanumericTokens(hunk.added.join(" "));
1565
+ return removedTokens === addedTokens;
1566
+ });
1567
+ }
1568
+ function extractAlphanumericTokens(line) {
1569
+ return (line.match(/[a-zA-Z0-9_]+/g) ?? []).join(" ");
1570
+ }
1571
+
1572
+ // src/core/blame/parsing/index.ts
1573
+ init_esm_shims();
1574
+
1575
+ // src/core/blame/parsing/blame-parser.ts
1576
+ init_esm_shims();
1577
+ function parsePorcelainOutput(output) {
1578
+ const results = [];
1579
+ const lines = output.split("\n");
1580
+ let i = 0;
1581
+ while (i < lines.length) {
1582
+ const headerLine = lines[i];
1583
+ if (!headerLine || headerLine.trim() === "") {
1584
+ i++;
1585
+ continue;
1586
+ }
1587
+ const headerMatch = /^([0-9a-f]{40}|[\^][0-9a-f]{39})\s+(\d+)\s+(\d+)(?:\s+(\d+))?$/.exec(
1588
+ headerLine
1589
+ );
1590
+ if (!headerMatch) {
1591
+ i++;
1592
+ continue;
1593
+ }
1594
+ let commitHash = headerMatch[1];
1595
+ const originalLine = parseInt(headerMatch[2], 10);
1596
+ const isBoundary = commitHash.startsWith("^");
1597
+ if (isBoundary) {
1598
+ commitHash = commitHash.slice(1).padStart(40, "0");
1599
+ }
1600
+ const headers = {};
1601
+ i++;
1602
+ while (i < lines.length && !lines[i].startsWith(" ")) {
1603
+ const line = lines[i];
1604
+ const spaceIdx = line.indexOf(" ");
1605
+ if (spaceIdx > 0) {
1606
+ const key = line.slice(0, spaceIdx);
1607
+ const value = line.slice(spaceIdx + 1);
1608
+ headers[key] = value;
1609
+ }
1610
+ i++;
1611
+ }
1612
+ let lineContent = "";
1613
+ if (i < lines.length && lines[i].startsWith(" ")) {
1614
+ lineContent = lines[i].slice(1);
1615
+ i++;
1616
+ }
1617
+ const authorTime = headers["author-time"];
1618
+ const date = authorTime ? new Date(parseInt(authorTime, 10) * 1e3).toISOString() : "";
1619
+ const authorEmail = headers["author-mail"] ?? "";
1620
+ const cleanEmail = authorEmail.replace(/^<|>$/g, "");
1621
+ const currentFilename = headers["filename"];
1622
+ const previousHeader = headers["previous"];
1623
+ let originalFile;
1624
+ if (previousHeader) {
1625
+ const prevParts = previousHeader.split(" ");
1626
+ if (prevParts.length >= 2) {
1627
+ const prevFilename = prevParts.slice(1).join(" ");
1628
+ if (currentFilename && prevFilename !== currentFilename) {
1629
+ originalFile = prevFilename;
1630
+ }
1631
+ }
1632
+ }
1633
+ results.push({
1634
+ commitHash,
1635
+ author: headers["author"] ?? "",
1636
+ authorEmail: cleanEmail,
1637
+ date,
1638
+ lineContent,
1639
+ originalFile,
1640
+ originalLine: originalFile ? originalLine : void 0
1641
+ });
1642
+ }
1643
+ return results;
1644
+ }
1645
+
1646
+ // src/core/blame/blame.ts
1647
+ async function executeBlame(file, lineRange, options) {
1648
+ const lineSpec = lineRange.start === lineRange.end ? `${lineRange.start},${lineRange.end}` : `${lineRange.start},${lineRange.end}`;
1649
+ const result = await gitExec(
1650
+ ["blame", "-w", "-C", "-C", "-M", "--porcelain", "-L", lineSpec, file],
1651
+ options
1652
+ );
1653
+ return parsePorcelainOutput(result.stdout);
1654
+ }
1655
+ async function analyzeBlameResults(results, options) {
1656
+ const uniqueShas = [...new Set(results.map((r) => r.commitHash))];
1657
+ const cosmeticMap = /* @__PURE__ */ new Map();
1658
+ for (const sha of uniqueShas) {
1659
+ if (sha === "0".repeat(40)) continue;
1660
+ try {
1661
+ const blameResult = results.find((r) => r.commitHash === sha);
1662
+ if (!blameResult) continue;
1663
+ const file = blameResult.originalFile ?? results.find((r) => r.commitHash === sha)?.lineContent;
1664
+ const diff = await getCosmeticDiff(sha, file ?? "", options);
1665
+ cosmeticMap.set(sha, isCosmeticDiff(diff));
1666
+ } catch {
1667
+ cosmeticMap.set(sha, { isCosmetic: false });
1668
+ }
1669
+ }
1670
+ return results.map((blame) => {
1671
+ const cosmetic = cosmeticMap.get(blame.commitHash);
1672
+ return {
1673
+ blame,
1674
+ isCosmetic: cosmetic?.isCosmetic ?? false,
1675
+ cosmeticReason: cosmetic?.reason
1676
+ };
1677
+ });
1678
+ }
1679
+
1680
+ // src/core/core.ts
1681
+ init_pr_lookup2();
1682
+ async function trace(options) {
1683
+ const warnings = [];
1684
+ const nodes = [];
1685
+ const execOptions = { cwd: void 0 };
1686
+ let adapter = null;
1687
+ let operatingLevel = 0;
1688
+ try {
1689
+ const { adapter: detectedAdapter } = await detectPlatformAdapter({
1690
+ remoteName: options.remote
1691
+ });
1692
+ adapter = detectedAdapter;
1693
+ const authStatus = await adapter.checkAuth();
1694
+ if (authStatus.authenticated) {
1695
+ operatingLevel = 2;
1696
+ } else {
1697
+ operatingLevel = 1;
1698
+ warnings.push(
1699
+ "Platform CLI not authenticated. Running in Level 1 (local only)."
1700
+ );
1701
+ }
1702
+ } catch {
1703
+ operatingLevel = 0;
1704
+ warnings.push("Could not detect platform. Running in Level 0 (git only).");
1705
+ }
1706
+ const featureFlags = {
1707
+ astDiff: isAstAvailable() && !options.noAst,
1708
+ deepTrace: operatingLevel === 2 && (options.deep ?? false),
1709
+ commitGraph: false,
1710
+ issueGraph: operatingLevel === 2 && (options.graphDepth ?? 0) > 0,
1711
+ graphql: operatingLevel === 2
1712
+ };
1713
+ const lineRange = parseLineRange(
1714
+ options.endLine ? `${options.line},${options.endLine}` : `${options.line}`
1715
+ );
1716
+ const blameResults = await executeBlame(options.file, lineRange, execOptions);
1717
+ const analyzed = await analyzeBlameResults(blameResults, execOptions);
1718
+ for (const entry of analyzed) {
1719
+ const commitNode = {
1720
+ type: entry.isCosmetic ? "cosmetic_commit" : "original_commit",
1721
+ sha: entry.blame.commitHash,
1722
+ trackingMethod: "blame-CMw",
1723
+ confidence: "exact",
1724
+ note: entry.cosmeticReason ? `Cosmetic change: ${entry.cosmeticReason}` : void 0
1725
+ };
1726
+ nodes.push(commitNode);
1727
+ if (entry.isCosmetic && featureFlags.astDiff) {
1728
+ const astResult = await traceByAst(
1729
+ options.file,
1730
+ options.line,
1731
+ entry.blame.commitHash,
1732
+ execOptions
1733
+ );
1734
+ if (astResult) {
1735
+ nodes.push({
1736
+ type: "original_commit",
1737
+ sha: astResult.originSha,
1738
+ trackingMethod: "ast-signature",
1739
+ confidence: astResult.confidence
1740
+ });
1741
+ }
1742
+ }
1743
+ const targetSha = nodes[nodes.length - 1].sha;
1744
+ if (targetSha) {
1745
+ const prInfo = await lookupPR(targetSha, adapter, execOptions);
1746
+ if (prInfo) {
1747
+ nodes.push({
1748
+ type: "pull_request",
1749
+ sha: prInfo.mergeCommit,
1750
+ trackingMethod: prInfo.url ? "api" : "message-parse",
1751
+ confidence: prInfo.url ? "exact" : "heuristic",
1752
+ prNumber: prInfo.number,
1753
+ prUrl: prInfo.url || void 0,
1754
+ prTitle: prInfo.title || void 0,
1755
+ mergedAt: prInfo.mergedAt
1756
+ });
1757
+ }
1758
+ }
1759
+ }
1760
+ return { nodes, operatingLevel, featureFlags, warnings };
1761
+ }
1762
+ async function health(options) {
1763
+ const healthReport = await checkGitHealth(options);
1764
+ let operatingLevel = 0;
1765
+ try {
1766
+ const { adapter } = await detectPlatformAdapter({ cwd: options?.cwd });
1767
+ const auth = await adapter.checkAuth();
1768
+ operatingLevel = auth.authenticated ? 2 : 1;
1769
+ } catch {
1770
+ operatingLevel = 0;
1771
+ }
1772
+ return { ...healthReport, operatingLevel };
1773
+ }
1774
+ async function clearCache() {
1775
+ const { resetPRCache: resetPRCache2 } = await Promise.resolve().then(() => (init_pr_lookup2(), pr_lookup_exports));
1776
+ const { resetPatchIdCache: resetPatchIdCache2 } = await Promise.resolve().then(() => (init_patch_id2(), patch_id_exports));
1777
+ resetPRCache2();
1778
+ resetPatchIdCache2();
1779
+ }
1780
+
1781
+ // src/core/issue-graph/index.ts
1782
+ init_esm_shims();
1783
+
1784
+ // src/core/issue-graph/issue-graph.ts
1785
+ init_esm_shims();
1786
+ var DEFAULT_MAX_DEPTH = 2;
1787
+ async function traverseIssueGraph(adapter, startType, startNumber, options) {
1788
+ const maxDepth = options?.maxDepth ?? DEFAULT_MAX_DEPTH;
1789
+ const nodes = [];
1790
+ const edges = [];
1791
+ const visited = /* @__PURE__ */ new Set();
1792
+ await traverse(
1793
+ adapter,
1794
+ startType,
1795
+ startNumber,
1796
+ 0,
1797
+ maxDepth,
1798
+ nodes,
1799
+ edges,
1800
+ visited
1801
+ );
1802
+ return { nodes, edges };
1803
+ }
1804
+ async function traverse(adapter, type, number, depth, maxDepth, nodes, edges, visited) {
1805
+ const key = `${type}:${number}`;
1806
+ if (visited.has(key)) return;
1807
+ if (depth > maxDepth) return;
1808
+ visited.add(key);
1809
+ if (type === "pr") {
1810
+ nodes.push({
1811
+ type: "pull_request",
1812
+ trackingMethod: "issue-link",
1813
+ confidence: "exact",
1814
+ prNumber: number
1815
+ });
1816
+ if (depth < maxDepth) {
1817
+ const linkedIssues = await adapter.getLinkedIssues(number);
1818
+ for (const issue of linkedIssues) {
1819
+ edges.push({
1820
+ from: `pr:${number}`,
1821
+ to: `issue:${issue.number}`,
1822
+ relation: "closes"
1823
+ });
1824
+ await traverse(
1825
+ adapter,
1826
+ "issue",
1827
+ issue.number,
1828
+ depth + 1,
1829
+ maxDepth,
1830
+ nodes,
1831
+ edges,
1832
+ visited
1833
+ );
1834
+ }
1835
+ }
1836
+ } else {
1837
+ nodes.push({
1838
+ type: "issue",
1839
+ trackingMethod: "issue-link",
1840
+ confidence: "exact",
1841
+ issueNumber: number
1842
+ });
1843
+ if (depth < maxDepth) {
1844
+ const linkedPRs = await adapter.getLinkedPRs(number);
1845
+ for (const pr of linkedPRs) {
1846
+ edges.push({
1847
+ from: `issue:${number}`,
1848
+ to: `pr:${pr.number}`,
1849
+ relation: "referenced-by"
1850
+ });
1851
+ await traverse(
1852
+ adapter,
1853
+ "pr",
1854
+ pr.number,
1855
+ depth + 1,
1856
+ maxDepth,
1857
+ nodes,
1858
+ edges,
1859
+ visited
1860
+ );
1861
+ }
1862
+ }
1863
+ }
1864
+ }
1865
+ export {
1866
+ LineLoreError,
1867
+ LineLoreErrorCode,
1868
+ clearCache,
1869
+ health,
1870
+ trace,
1871
+ traverseIssueGraph
1872
+ };