@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.
- package/LICENSE +21 -0
- package/README.md +188 -0
- package/dist/ast/index.d.ts +1 -0
- package/dist/ast/parser.d.ts +6 -0
- package/dist/cache/file-cache.d.ts +19 -0
- package/dist/cache/index.d.ts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.mjs +2288 -0
- package/dist/commands/cache.d.ts +2 -0
- package/dist/commands/graph.d.ts +2 -0
- package/dist/commands/health.d.ts +2 -0
- package/dist/commands/index.d.ts +4 -0
- package/dist/commands/trace.d.ts +2 -0
- package/dist/components/TraceProgress.d.ts +7 -0
- package/dist/components/TraceResult.d.ts +8 -0
- package/dist/components/index.d.ts +2 -0
- package/dist/core/ancestry/ancestry.d.ts +10 -0
- package/dist/core/ancestry/index.d.ts +2 -0
- package/dist/core/ast-diff/ast-diff.d.ts +4 -0
- package/dist/core/ast-diff/comparison/index.d.ts +2 -0
- package/dist/core/ast-diff/comparison/structure-comparator.d.ts +4 -0
- package/dist/core/ast-diff/extraction/index.d.ts +2 -0
- package/dist/core/ast-diff/extraction/signature-hasher.d.ts +4 -0
- package/dist/core/ast-diff/extraction/symbol-extractor.d.ts +3 -0
- package/dist/core/ast-diff/index.d.ts +4 -0
- package/dist/core/blame/blame.d.ts +3 -0
- package/dist/core/blame/detection/cosmetic-detector.d.ts +7 -0
- package/dist/core/blame/detection/index.d.ts +2 -0
- package/dist/core/blame/index.d.ts +4 -0
- package/dist/core/blame/parsing/blame-parser.d.ts +2 -0
- package/dist/core/blame/parsing/index.d.ts +1 -0
- package/dist/core/core.d.ts +14 -0
- package/dist/core/index.d.ts +11 -0
- package/dist/core/issue-graph/index.d.ts +2 -0
- package/dist/core/issue-graph/issue-graph.d.ts +5 -0
- package/dist/core/patch-id/index.d.ts +2 -0
- package/dist/core/patch-id/patch-id.d.ts +11 -0
- package/dist/core/pr-lookup/index.d.ts +1 -0
- package/dist/core/pr-lookup/pr-lookup.d.ts +3 -0
- package/dist/errors.d.ts +32 -0
- package/dist/git/executor.d.ts +2 -0
- package/dist/git/health.d.ts +4 -0
- package/dist/git/index.d.ts +3 -0
- package/dist/git/remote.d.ts +6 -0
- package/dist/index.cjs +1904 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.mjs +1872 -0
- package/dist/output/formats.d.ts +5 -0
- package/dist/output/index.d.ts +2 -0
- package/dist/output/normalizer.d.ts +11 -0
- package/dist/platform/github/github-adapter.d.ts +17 -0
- package/dist/platform/github/github-enterprise-adapter.d.ts +8 -0
- package/dist/platform/github/index.d.ts +2 -0
- package/dist/platform/gitlab/gitlab-adapter.d.ts +17 -0
- package/dist/platform/gitlab/gitlab-self-hosted-adapter.d.ts +8 -0
- package/dist/platform/gitlab/index.d.ts +2 -0
- package/dist/platform/index.d.ts +6 -0
- package/dist/platform/platform.d.ts +9 -0
- package/dist/platform/scheduler/index.d.ts +2 -0
- package/dist/platform/scheduler/request-scheduler.d.ts +17 -0
- package/dist/types/ast.d.ts +28 -0
- package/dist/types/blame.d.ts +34 -0
- package/dist/types/cache.d.ts +5 -0
- package/dist/types/git.d.ts +23 -0
- package/dist/types/graph.d.ts +15 -0
- package/dist/types/index.d.ts +11 -0
- package/dist/types/output.d.ts +24 -0
- package/dist/types/pipeline.d.ts +28 -0
- package/dist/types/platform.d.ts +44 -0
- package/dist/types/stage.d.ts +14 -0
- package/dist/types/trace.d.ts +38 -0
- package/dist/types/util.d.ts +4 -0
- package/dist/utils/command-registry.d.ts +29 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/line-range.d.ts +2 -0
- package/dist/version.d.ts +1 -0
- 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
|
+
};
|