mobbdev 1.1.6 → 1.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/args/commands/upload_ai_blame.d.mts +46 -30
- package/dist/args/commands/upload_ai_blame.mjs +171 -5
- package/dist/index.mjs +549 -159
- package/package.json +3 -1
|
@@ -7,6 +7,16 @@ declare enum AiBlameInferenceType {
|
|
|
7
7
|
TabAutocomplete = "TAB_AUTOCOMPLETE"
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
type SanitizationCounts = {
|
|
11
|
+
pii: {
|
|
12
|
+
total: number;
|
|
13
|
+
high: number;
|
|
14
|
+
medium: number;
|
|
15
|
+
low: number;
|
|
16
|
+
};
|
|
17
|
+
secrets: number;
|
|
18
|
+
};
|
|
19
|
+
|
|
10
20
|
declare const PromptItemZ: z.ZodObject<{
|
|
11
21
|
type: z.ZodEnum<["USER_PROMPT", "AI_RESPONSE", "TOOL_EXECUTION", "AI_THINKING"]>;
|
|
12
22
|
attachedFiles: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
@@ -41,26 +51,18 @@ declare const PromptItemZ: z.ZodObject<{
|
|
|
41
51
|
name: string;
|
|
42
52
|
parameters: string;
|
|
43
53
|
result: string;
|
|
44
|
-
accepted?: boolean | undefined;
|
|
45
54
|
rawArguments?: string | undefined;
|
|
55
|
+
accepted?: boolean | undefined;
|
|
46
56
|
}, {
|
|
47
57
|
name: string;
|
|
48
58
|
parameters: string;
|
|
49
59
|
result: string;
|
|
50
|
-
accepted?: boolean | undefined;
|
|
51
60
|
rawArguments?: string | undefined;
|
|
61
|
+
accepted?: boolean | undefined;
|
|
52
62
|
}>>;
|
|
53
63
|
}, "strip", z.ZodTypeAny, {
|
|
54
64
|
type: "USER_PROMPT" | "AI_RESPONSE" | "TOOL_EXECUTION" | "AI_THINKING";
|
|
55
|
-
tool?: {
|
|
56
|
-
name: string;
|
|
57
|
-
parameters: string;
|
|
58
|
-
result: string;
|
|
59
|
-
accepted?: boolean | undefined;
|
|
60
|
-
rawArguments?: string | undefined;
|
|
61
|
-
} | undefined;
|
|
62
65
|
date?: Date | undefined;
|
|
63
|
-
text?: string | undefined;
|
|
64
66
|
attachedFiles?: {
|
|
65
67
|
relativePath: string;
|
|
66
68
|
startLine?: number | undefined;
|
|
@@ -69,17 +71,17 @@ declare const PromptItemZ: z.ZodObject<{
|
|
|
69
71
|
inputCount: number;
|
|
70
72
|
outputCount: number;
|
|
71
73
|
} | undefined;
|
|
72
|
-
|
|
73
|
-
type: "USER_PROMPT" | "AI_RESPONSE" | "TOOL_EXECUTION" | "AI_THINKING";
|
|
74
|
+
text?: string | undefined;
|
|
74
75
|
tool?: {
|
|
75
76
|
name: string;
|
|
76
77
|
parameters: string;
|
|
77
78
|
result: string;
|
|
78
|
-
accepted?: boolean | undefined;
|
|
79
79
|
rawArguments?: string | undefined;
|
|
80
|
+
accepted?: boolean | undefined;
|
|
80
81
|
} | undefined;
|
|
82
|
+
}, {
|
|
83
|
+
type: "USER_PROMPT" | "AI_RESPONSE" | "TOOL_EXECUTION" | "AI_THINKING";
|
|
81
84
|
date?: Date | undefined;
|
|
82
|
-
text?: string | undefined;
|
|
83
85
|
attachedFiles?: {
|
|
84
86
|
relativePath: string;
|
|
85
87
|
startLine?: number | undefined;
|
|
@@ -88,6 +90,14 @@ declare const PromptItemZ: z.ZodObject<{
|
|
|
88
90
|
inputCount: number;
|
|
89
91
|
outputCount: number;
|
|
90
92
|
} | undefined;
|
|
93
|
+
text?: string | undefined;
|
|
94
|
+
tool?: {
|
|
95
|
+
name: string;
|
|
96
|
+
parameters: string;
|
|
97
|
+
result: string;
|
|
98
|
+
rawArguments?: string | undefined;
|
|
99
|
+
accepted?: boolean | undefined;
|
|
100
|
+
} | undefined;
|
|
91
101
|
}>;
|
|
92
102
|
type PromptItem = z.infer<typeof PromptItemZ>;
|
|
93
103
|
declare const PromptItemArrayZ: z.ZodArray<z.ZodObject<{
|
|
@@ -124,26 +134,18 @@ declare const PromptItemArrayZ: z.ZodArray<z.ZodObject<{
|
|
|
124
134
|
name: string;
|
|
125
135
|
parameters: string;
|
|
126
136
|
result: string;
|
|
127
|
-
accepted?: boolean | undefined;
|
|
128
137
|
rawArguments?: string | undefined;
|
|
138
|
+
accepted?: boolean | undefined;
|
|
129
139
|
}, {
|
|
130
140
|
name: string;
|
|
131
141
|
parameters: string;
|
|
132
142
|
result: string;
|
|
133
|
-
accepted?: boolean | undefined;
|
|
134
143
|
rawArguments?: string | undefined;
|
|
144
|
+
accepted?: boolean | undefined;
|
|
135
145
|
}>>;
|
|
136
146
|
}, "strip", z.ZodTypeAny, {
|
|
137
147
|
type: "USER_PROMPT" | "AI_RESPONSE" | "TOOL_EXECUTION" | "AI_THINKING";
|
|
138
|
-
tool?: {
|
|
139
|
-
name: string;
|
|
140
|
-
parameters: string;
|
|
141
|
-
result: string;
|
|
142
|
-
accepted?: boolean | undefined;
|
|
143
|
-
rawArguments?: string | undefined;
|
|
144
|
-
} | undefined;
|
|
145
148
|
date?: Date | undefined;
|
|
146
|
-
text?: string | undefined;
|
|
147
149
|
attachedFiles?: {
|
|
148
150
|
relativePath: string;
|
|
149
151
|
startLine?: number | undefined;
|
|
@@ -152,17 +154,17 @@ declare const PromptItemArrayZ: z.ZodArray<z.ZodObject<{
|
|
|
152
154
|
inputCount: number;
|
|
153
155
|
outputCount: number;
|
|
154
156
|
} | undefined;
|
|
155
|
-
|
|
156
|
-
type: "USER_PROMPT" | "AI_RESPONSE" | "TOOL_EXECUTION" | "AI_THINKING";
|
|
157
|
+
text?: string | undefined;
|
|
157
158
|
tool?: {
|
|
158
159
|
name: string;
|
|
159
160
|
parameters: string;
|
|
160
161
|
result: string;
|
|
161
|
-
accepted?: boolean | undefined;
|
|
162
162
|
rawArguments?: string | undefined;
|
|
163
|
+
accepted?: boolean | undefined;
|
|
163
164
|
} | undefined;
|
|
165
|
+
}, {
|
|
166
|
+
type: "USER_PROMPT" | "AI_RESPONSE" | "TOOL_EXECUTION" | "AI_THINKING";
|
|
164
167
|
date?: Date | undefined;
|
|
165
|
-
text?: string | undefined;
|
|
166
168
|
attachedFiles?: {
|
|
167
169
|
relativePath: string;
|
|
168
170
|
startLine?: number | undefined;
|
|
@@ -171,6 +173,14 @@ declare const PromptItemArrayZ: z.ZodArray<z.ZodObject<{
|
|
|
171
173
|
inputCount: number;
|
|
172
174
|
outputCount: number;
|
|
173
175
|
} | undefined;
|
|
176
|
+
text?: string | undefined;
|
|
177
|
+
tool?: {
|
|
178
|
+
name: string;
|
|
179
|
+
parameters: string;
|
|
180
|
+
result: string;
|
|
181
|
+
rawArguments?: string | undefined;
|
|
182
|
+
accepted?: boolean | undefined;
|
|
183
|
+
} | undefined;
|
|
174
184
|
}>, "many">;
|
|
175
185
|
type PromptItemArray = z.infer<typeof PromptItemArrayZ>;
|
|
176
186
|
type UploadAiBlameOptions = {
|
|
@@ -185,6 +195,12 @@ type UploadAiBlameOptions = {
|
|
|
185
195
|
'blame-type'?: AiBlameInferenceType[];
|
|
186
196
|
};
|
|
187
197
|
declare function uploadAiBlameBuilder(args: Yargs.Argv<unknown>): Yargs.Argv<UploadAiBlameOptions>;
|
|
198
|
+
type UploadAiBlameResult = {
|
|
199
|
+
promptsCounts: SanitizationCounts;
|
|
200
|
+
inferenceCounts: SanitizationCounts;
|
|
201
|
+
promptsUUID?: string;
|
|
202
|
+
inferenceUUID?: string;
|
|
203
|
+
};
|
|
188
204
|
declare function uploadAiBlameHandlerFromExtension(args: {
|
|
189
205
|
prompts: PromptItemArray;
|
|
190
206
|
inference: string;
|
|
@@ -192,7 +208,7 @@ declare function uploadAiBlameHandlerFromExtension(args: {
|
|
|
192
208
|
tool: string;
|
|
193
209
|
responseTime: string;
|
|
194
210
|
blameType?: AiBlameInferenceType;
|
|
195
|
-
}): Promise<
|
|
211
|
+
}): Promise<UploadAiBlameResult>;
|
|
196
212
|
declare function uploadAiBlameHandler(args: UploadAiBlameOptions, exitOnError?: boolean): Promise<void>;
|
|
197
213
|
|
|
198
|
-
export { type PromptItem, type PromptItemArray, type UploadAiBlameOptions, uploadAiBlameBuilder, uploadAiBlameHandler, uploadAiBlameHandlerFromExtension };
|
|
214
|
+
export { type PromptItem, type PromptItemArray, type UploadAiBlameOptions, type UploadAiBlameResult, uploadAiBlameBuilder, uploadAiBlameHandler, uploadAiBlameHandlerFromExtension };
|
|
@@ -736,7 +736,7 @@ var GetVulByNodesMetadataDocument = `
|
|
|
736
736
|
where: {id: {_eq: $vulnerabilityReportId}}
|
|
737
737
|
) {
|
|
738
738
|
vulnerabilityReportIssues(
|
|
739
|
-
where: {fixId: {_is_null: true}, category: {_in: [Irrelevant, FalsePositive, Filtered]}}
|
|
739
|
+
where: {fixId: {_is_null: true}, category: {_in: [Irrelevant, FalsePositive, Filtered]}, _not: {vulnerabilityReportIssueTags: {vulnerability_report_issue_tag_value: {_eq: SUPPRESSED}}}}
|
|
740
740
|
) {
|
|
741
741
|
id
|
|
742
742
|
safeIssueType
|
|
@@ -5128,6 +5128,143 @@ async function uploadFile({
|
|
|
5128
5128
|
logInfo(`FileUpload: upload file done`);
|
|
5129
5129
|
}
|
|
5130
5130
|
|
|
5131
|
+
// src/utils/sanitize-sensitive-data.ts
|
|
5132
|
+
import { OpenRedaction } from "@openredaction/openredaction";
|
|
5133
|
+
import { spawn } from "child_process";
|
|
5134
|
+
import { installGitleaks } from "gitleaks-secret-scanner/lib/installer.js";
|
|
5135
|
+
var openRedaction = new OpenRedaction();
|
|
5136
|
+
var gitleaksBinaryPath = null;
|
|
5137
|
+
async function initializeGitleaks() {
|
|
5138
|
+
try {
|
|
5139
|
+
gitleaksBinaryPath = await installGitleaks({ version: "8.27.2" });
|
|
5140
|
+
return gitleaksBinaryPath;
|
|
5141
|
+
} catch {
|
|
5142
|
+
return null;
|
|
5143
|
+
}
|
|
5144
|
+
}
|
|
5145
|
+
var gitleaksInitPromise = initializeGitleaks();
|
|
5146
|
+
async function detectSecretsWithGitleaks(text) {
|
|
5147
|
+
const secrets = /* @__PURE__ */ new Set();
|
|
5148
|
+
const binaryPath = gitleaksBinaryPath || await gitleaksInitPromise;
|
|
5149
|
+
if (!binaryPath) {
|
|
5150
|
+
return secrets;
|
|
5151
|
+
}
|
|
5152
|
+
return new Promise((resolve) => {
|
|
5153
|
+
const gitleaks = spawn(
|
|
5154
|
+
binaryPath,
|
|
5155
|
+
[
|
|
5156
|
+
"detect",
|
|
5157
|
+
"--pipe",
|
|
5158
|
+
"--no-banner",
|
|
5159
|
+
"--exit-code",
|
|
5160
|
+
"0",
|
|
5161
|
+
"--report-format",
|
|
5162
|
+
"json"
|
|
5163
|
+
],
|
|
5164
|
+
{
|
|
5165
|
+
stdio: ["pipe", "pipe", "ignore"]
|
|
5166
|
+
}
|
|
5167
|
+
);
|
|
5168
|
+
let output = "";
|
|
5169
|
+
gitleaks.stdout.on("data", (data) => {
|
|
5170
|
+
output += data.toString();
|
|
5171
|
+
});
|
|
5172
|
+
gitleaks.on("close", () => {
|
|
5173
|
+
try {
|
|
5174
|
+
const findings = JSON.parse(output);
|
|
5175
|
+
if (Array.isArray(findings)) {
|
|
5176
|
+
for (const finding of findings) {
|
|
5177
|
+
if (finding.Secret) {
|
|
5178
|
+
secrets.add(finding.Secret);
|
|
5179
|
+
}
|
|
5180
|
+
}
|
|
5181
|
+
}
|
|
5182
|
+
} catch {
|
|
5183
|
+
}
|
|
5184
|
+
resolve(secrets);
|
|
5185
|
+
});
|
|
5186
|
+
gitleaks.on("error", () => {
|
|
5187
|
+
resolve(secrets);
|
|
5188
|
+
});
|
|
5189
|
+
gitleaks.stdin.write(text);
|
|
5190
|
+
gitleaks.stdin.end();
|
|
5191
|
+
});
|
|
5192
|
+
}
|
|
5193
|
+
function maskString(str, showStart = 2, showEnd = 2) {
|
|
5194
|
+
if (str.length <= showStart + showEnd) {
|
|
5195
|
+
return "*".repeat(str.length);
|
|
5196
|
+
}
|
|
5197
|
+
return str.slice(0, showStart) + "*".repeat(str.length - showStart - showEnd) + str.slice(-showEnd);
|
|
5198
|
+
}
|
|
5199
|
+
async function sanitizeDataWithCounts(obj) {
|
|
5200
|
+
const counts = {
|
|
5201
|
+
pii: { total: 0, high: 0, medium: 0, low: 0 },
|
|
5202
|
+
secrets: 0
|
|
5203
|
+
};
|
|
5204
|
+
const sanitizeString = async (str) => {
|
|
5205
|
+
let result = str;
|
|
5206
|
+
const piiDetections = openRedaction.scan(str);
|
|
5207
|
+
if (piiDetections && piiDetections.total > 0) {
|
|
5208
|
+
const allDetections = [
|
|
5209
|
+
...piiDetections.high,
|
|
5210
|
+
...piiDetections.medium,
|
|
5211
|
+
...piiDetections.low
|
|
5212
|
+
];
|
|
5213
|
+
const filteredDetections = allDetections.filter((detection) => {
|
|
5214
|
+
if (detection.type === "INSTAGRAM_USERNAME") {
|
|
5215
|
+
return false;
|
|
5216
|
+
}
|
|
5217
|
+
if (detection.value.length < 3 && detection.severity !== "high") {
|
|
5218
|
+
return false;
|
|
5219
|
+
}
|
|
5220
|
+
return true;
|
|
5221
|
+
});
|
|
5222
|
+
for (const detection of filteredDetections) {
|
|
5223
|
+
counts.pii.total++;
|
|
5224
|
+
if (detection.severity === "high") counts.pii.high++;
|
|
5225
|
+
else if (detection.severity === "medium") counts.pii.medium++;
|
|
5226
|
+
else if (detection.severity === "low") counts.pii.low++;
|
|
5227
|
+
const masked = maskString(detection.value);
|
|
5228
|
+
result = result.replaceAll(detection.value, masked);
|
|
5229
|
+
}
|
|
5230
|
+
}
|
|
5231
|
+
const secrets = await detectSecretsWithGitleaks(result);
|
|
5232
|
+
counts.secrets += secrets.size;
|
|
5233
|
+
for (const secret of secrets) {
|
|
5234
|
+
const masked = maskString(secret);
|
|
5235
|
+
result = result.replaceAll(secret, masked);
|
|
5236
|
+
}
|
|
5237
|
+
return result;
|
|
5238
|
+
};
|
|
5239
|
+
const sanitizeRecursive = async (data) => {
|
|
5240
|
+
if (typeof data === "string") {
|
|
5241
|
+
return sanitizeString(data);
|
|
5242
|
+
} else if (Array.isArray(data)) {
|
|
5243
|
+
return Promise.all(data.map((item) => sanitizeRecursive(item)));
|
|
5244
|
+
} else if (data instanceof Error) {
|
|
5245
|
+
return data;
|
|
5246
|
+
} else if (data instanceof Date) {
|
|
5247
|
+
return data;
|
|
5248
|
+
} else if (typeof data === "object" && data !== null) {
|
|
5249
|
+
const sanitized = {};
|
|
5250
|
+
const record = data;
|
|
5251
|
+
for (const key in record) {
|
|
5252
|
+
if (Object.prototype.hasOwnProperty.call(record, key)) {
|
|
5253
|
+
sanitized[key] = await sanitizeRecursive(record[key]);
|
|
5254
|
+
}
|
|
5255
|
+
}
|
|
5256
|
+
return sanitized;
|
|
5257
|
+
}
|
|
5258
|
+
return data;
|
|
5259
|
+
};
|
|
5260
|
+
const sanitizedData = await sanitizeRecursive(obj);
|
|
5261
|
+
return { sanitizedData, counts };
|
|
5262
|
+
}
|
|
5263
|
+
async function sanitizeData(obj) {
|
|
5264
|
+
const result = await sanitizeDataWithCounts(obj);
|
|
5265
|
+
return result.sanitizedData;
|
|
5266
|
+
}
|
|
5267
|
+
|
|
5131
5268
|
// src/args/commands/upload_ai_blame.ts
|
|
5132
5269
|
var PromptItemZ = z26.object({
|
|
5133
5270
|
type: z26.enum(["USER_PROMPT", "AI_RESPONSE", "TOOL_EXECUTION", "AI_THINKING"]),
|
|
@@ -5197,15 +5334,32 @@ async function uploadAiBlameHandlerFromExtension(args) {
|
|
|
5197
5334
|
aiResponseAt: [],
|
|
5198
5335
|
blameType: []
|
|
5199
5336
|
};
|
|
5337
|
+
let promptsCounts;
|
|
5338
|
+
let inferenceCounts;
|
|
5339
|
+
let promptsUUID;
|
|
5340
|
+
let inferenceUUID;
|
|
5200
5341
|
await withFile(async (promptFile) => {
|
|
5342
|
+
const promptsResult = await sanitizeDataWithCounts(args.prompts);
|
|
5343
|
+
promptsCounts = promptsResult.counts;
|
|
5344
|
+
promptsUUID = path6.basename(promptFile.path, path6.extname(promptFile.path));
|
|
5201
5345
|
await fsPromises2.writeFile(
|
|
5202
5346
|
promptFile.path,
|
|
5203
|
-
JSON.stringify(
|
|
5347
|
+
JSON.stringify(promptsResult.sanitizedData, null, 2),
|
|
5204
5348
|
"utf-8"
|
|
5205
5349
|
);
|
|
5206
5350
|
uploadArgs.prompt.push(promptFile.path);
|
|
5207
5351
|
await withFile(async (inferenceFile) => {
|
|
5208
|
-
await
|
|
5352
|
+
const inferenceResult = await sanitizeDataWithCounts(args.inference);
|
|
5353
|
+
inferenceCounts = inferenceResult.counts;
|
|
5354
|
+
inferenceUUID = path6.basename(
|
|
5355
|
+
inferenceFile.path,
|
|
5356
|
+
path6.extname(inferenceFile.path)
|
|
5357
|
+
);
|
|
5358
|
+
await fsPromises2.writeFile(
|
|
5359
|
+
inferenceFile.path,
|
|
5360
|
+
inferenceResult.sanitizedData,
|
|
5361
|
+
"utf-8"
|
|
5362
|
+
);
|
|
5209
5363
|
uploadArgs.inference.push(inferenceFile.path);
|
|
5210
5364
|
uploadArgs.model.push(args.model);
|
|
5211
5365
|
uploadArgs.toolName.push(args.tool);
|
|
@@ -5214,6 +5368,12 @@ async function uploadAiBlameHandlerFromExtension(args) {
|
|
|
5214
5368
|
await uploadAiBlameHandler(uploadArgs, false);
|
|
5215
5369
|
});
|
|
5216
5370
|
});
|
|
5371
|
+
return {
|
|
5372
|
+
promptsCounts,
|
|
5373
|
+
inferenceCounts,
|
|
5374
|
+
promptsUUID,
|
|
5375
|
+
inferenceUUID
|
|
5376
|
+
};
|
|
5217
5377
|
}
|
|
5218
5378
|
async function uploadAiBlameHandler(args, exitOnError = true) {
|
|
5219
5379
|
const prompts = args.prompt || [];
|
|
@@ -5260,8 +5420,11 @@ async function uploadAiBlameHandler(args, exitOnError = true) {
|
|
|
5260
5420
|
const authenticatedClient = await getAuthenticatedGQLClient({
|
|
5261
5421
|
isSkipPrompts: true
|
|
5262
5422
|
});
|
|
5263
|
-
const
|
|
5423
|
+
const sanitizedSessions = await sanitizeData(
|
|
5264
5424
|
sessions
|
|
5425
|
+
);
|
|
5426
|
+
const initRes = await authenticatedClient.uploadAIBlameInferencesInitRaw({
|
|
5427
|
+
sessions: sanitizedSessions
|
|
5265
5428
|
});
|
|
5266
5429
|
const uploadSessions = initRes.uploadAIBlameInferencesInit?.uploadSessions ?? [];
|
|
5267
5430
|
if (uploadSessions.length !== sessions.length) {
|
|
@@ -5305,8 +5468,11 @@ async function uploadAiBlameHandler(args, exitOnError = true) {
|
|
|
5305
5468
|
blameType: s.blameType
|
|
5306
5469
|
};
|
|
5307
5470
|
});
|
|
5471
|
+
const sanitizedFinalizeSessions = await sanitizeData(
|
|
5472
|
+
finalizeSessions
|
|
5473
|
+
);
|
|
5308
5474
|
const finRes = await authenticatedClient.finalizeAIBlameInferencesUploadRaw({
|
|
5309
|
-
sessions:
|
|
5475
|
+
sessions: sanitizedFinalizeSessions
|
|
5310
5476
|
});
|
|
5311
5477
|
const status = finRes?.finalizeAIBlameInferencesUpload?.status;
|
|
5312
5478
|
if (status !== "OK") {
|
package/dist/index.mjs
CHANGED
|
@@ -1996,7 +1996,7 @@ var GetVulByNodesMetadataDocument = `
|
|
|
1996
1996
|
where: {id: {_eq: $vulnerabilityReportId}}
|
|
1997
1997
|
) {
|
|
1998
1998
|
vulnerabilityReportIssues(
|
|
1999
|
-
where: {fixId: {_is_null: true}, category: {_in: [Irrelevant, FalsePositive, Filtered]}}
|
|
1999
|
+
where: {fixId: {_is_null: true}, category: {_in: [Irrelevant, FalsePositive, Filtered]}, _not: {vulnerabilityReportIssueTags: {vulnerability_report_issue_tag_value: {_eq: SUPPRESSED}}}}
|
|
2000
2000
|
) {
|
|
2001
2001
|
id
|
|
2002
2002
|
safeIssueType
|
|
@@ -7512,6 +7512,61 @@ var GET_BLAME_DOCUMENT = `
|
|
|
7512
7512
|
}
|
|
7513
7513
|
}
|
|
7514
7514
|
`;
|
|
7515
|
+
var GITHUB_GRAPHQL_FRAGMENTS = {
|
|
7516
|
+
/**
|
|
7517
|
+
* Fragment for fetching PR additions/deletions.
|
|
7518
|
+
* Use with pullRequest(number: $n) alias.
|
|
7519
|
+
*/
|
|
7520
|
+
PR_CHANGES: `
|
|
7521
|
+
additions
|
|
7522
|
+
deletions
|
|
7523
|
+
`,
|
|
7524
|
+
/**
|
|
7525
|
+
* Fragment for fetching PR comments.
|
|
7526
|
+
* Returns first 100 comments with author info.
|
|
7527
|
+
*/
|
|
7528
|
+
PR_COMMENTS: `
|
|
7529
|
+
comments(first: 100) {
|
|
7530
|
+
nodes {
|
|
7531
|
+
author {
|
|
7532
|
+
login
|
|
7533
|
+
__typename
|
|
7534
|
+
}
|
|
7535
|
+
body
|
|
7536
|
+
}
|
|
7537
|
+
}
|
|
7538
|
+
`,
|
|
7539
|
+
/**
|
|
7540
|
+
* Fragment for fetching blame data.
|
|
7541
|
+
* Use with object(expression: $ref) on Commit type.
|
|
7542
|
+
*/
|
|
7543
|
+
BLAME_RANGES: `
|
|
7544
|
+
blame(path: "$path") {
|
|
7545
|
+
ranges {
|
|
7546
|
+
startingLine
|
|
7547
|
+
endingLine
|
|
7548
|
+
commit {
|
|
7549
|
+
oid
|
|
7550
|
+
author {
|
|
7551
|
+
user {
|
|
7552
|
+
name
|
|
7553
|
+
login
|
|
7554
|
+
email
|
|
7555
|
+
}
|
|
7556
|
+
}
|
|
7557
|
+
}
|
|
7558
|
+
}
|
|
7559
|
+
}
|
|
7560
|
+
`,
|
|
7561
|
+
/**
|
|
7562
|
+
* Fragment for fetching commit timestamp.
|
|
7563
|
+
* Use with object(oid: $sha) on Commit type.
|
|
7564
|
+
*/
|
|
7565
|
+
COMMIT_TIMESTAMP: `
|
|
7566
|
+
oid
|
|
7567
|
+
committedDate
|
|
7568
|
+
`
|
|
7569
|
+
};
|
|
7515
7570
|
|
|
7516
7571
|
// src/features/analysis/scm/github/utils/encrypt_secret.ts
|
|
7517
7572
|
import sodium from "libsodium-wrappers";
|
|
@@ -7649,6 +7704,36 @@ async function githubValidateParams(url, accessToken) {
|
|
|
7649
7704
|
|
|
7650
7705
|
// src/features/analysis/scm/github/github.ts
|
|
7651
7706
|
var MAX_GH_PR_BODY_LENGTH = 65536;
|
|
7707
|
+
async function executeBatchGraphQL(octokit, owner, repo, config2) {
|
|
7708
|
+
const { items, aliasPrefix, buildFragment, extractResult } = config2;
|
|
7709
|
+
if (items.length === 0) {
|
|
7710
|
+
return /* @__PURE__ */ new Map();
|
|
7711
|
+
}
|
|
7712
|
+
const fragments = items.map((item, index) => buildFragment(item, index)).join("\n");
|
|
7713
|
+
const query = `
|
|
7714
|
+
query Batch${aliasPrefix}($owner: String!, $repo: String!) {
|
|
7715
|
+
repository(owner: $owner, name: $repo) {
|
|
7716
|
+
${fragments}
|
|
7717
|
+
}
|
|
7718
|
+
}
|
|
7719
|
+
`;
|
|
7720
|
+
const response = await octokit.graphql(query, { owner, repo });
|
|
7721
|
+
const result = /* @__PURE__ */ new Map();
|
|
7722
|
+
items.forEach((item, index) => {
|
|
7723
|
+
const data = response.repository[`${aliasPrefix}${index}`];
|
|
7724
|
+
if (data) {
|
|
7725
|
+
const extracted = extractResult(
|
|
7726
|
+
data,
|
|
7727
|
+
item,
|
|
7728
|
+
index
|
|
7729
|
+
);
|
|
7730
|
+
if (extracted !== void 0) {
|
|
7731
|
+
result.set(item, extracted);
|
|
7732
|
+
}
|
|
7733
|
+
}
|
|
7734
|
+
});
|
|
7735
|
+
return result;
|
|
7736
|
+
}
|
|
7652
7737
|
function getGithubSdk(params = {}) {
|
|
7653
7738
|
const octokit = getOctoKit(params);
|
|
7654
7739
|
return {
|
|
@@ -8114,6 +8199,125 @@ function getGithubSdk(params = {}) {
|
|
|
8114
8199
|
pull_number: params2.pull_number
|
|
8115
8200
|
});
|
|
8116
8201
|
return { data };
|
|
8202
|
+
},
|
|
8203
|
+
/**
|
|
8204
|
+
* Batch fetch additions/deletions for multiple PRs via GraphQL.
|
|
8205
|
+
* Uses GITHUB_GRAPHQL_FRAGMENTS.PR_CHANGES for the field selection.
|
|
8206
|
+
*/
|
|
8207
|
+
async getPrAdditionsDeletionsBatch(params2) {
|
|
8208
|
+
return executeBatchGraphQL(octokit, params2.owner, params2.repo, {
|
|
8209
|
+
items: params2.prNumbers,
|
|
8210
|
+
aliasPrefix: "pr",
|
|
8211
|
+
buildFragment: (prNumber, index) => `
|
|
8212
|
+
pr${index}: pullRequest(number: ${prNumber}) {
|
|
8213
|
+
${GITHUB_GRAPHQL_FRAGMENTS.PR_CHANGES}
|
|
8214
|
+
}`,
|
|
8215
|
+
extractResult: (data) => {
|
|
8216
|
+
const prData = data;
|
|
8217
|
+
if (prData.additions !== void 0 && prData.deletions !== void 0) {
|
|
8218
|
+
return {
|
|
8219
|
+
additions: prData.additions,
|
|
8220
|
+
deletions: prData.deletions
|
|
8221
|
+
};
|
|
8222
|
+
}
|
|
8223
|
+
return void 0;
|
|
8224
|
+
}
|
|
8225
|
+
});
|
|
8226
|
+
},
|
|
8227
|
+
/**
|
|
8228
|
+
* Batch fetch comments for multiple PRs via GraphQL.
|
|
8229
|
+
* Uses GITHUB_GRAPHQL_FRAGMENTS.PR_COMMENTS for the field selection.
|
|
8230
|
+
*/
|
|
8231
|
+
async getPrCommentsBatch(params2) {
|
|
8232
|
+
return executeBatchGraphQL(octokit, params2.owner, params2.repo, {
|
|
8233
|
+
items: params2.prNumbers,
|
|
8234
|
+
aliasPrefix: "pr",
|
|
8235
|
+
buildFragment: (prNumber, index) => `
|
|
8236
|
+
pr${index}: pullRequest(number: ${prNumber}) {
|
|
8237
|
+
${GITHUB_GRAPHQL_FRAGMENTS.PR_COMMENTS}
|
|
8238
|
+
}`,
|
|
8239
|
+
extractResult: (data) => {
|
|
8240
|
+
const prData = data;
|
|
8241
|
+
if (prData.comments?.nodes) {
|
|
8242
|
+
return prData.comments.nodes.map((node) => ({
|
|
8243
|
+
author: node.author ? { login: node.author.login, type: node.author.__typename } : null,
|
|
8244
|
+
body: node.body
|
|
8245
|
+
}));
|
|
8246
|
+
}
|
|
8247
|
+
return void 0;
|
|
8248
|
+
}
|
|
8249
|
+
});
|
|
8250
|
+
},
|
|
8251
|
+
/**
|
|
8252
|
+
* Batch fetch blame data for multiple files via GraphQL.
|
|
8253
|
+
* Field selection matches GITHUB_GRAPHQL_FRAGMENTS.BLAME_RANGES pattern.
|
|
8254
|
+
*/
|
|
8255
|
+
async getBlameBatch(params2) {
|
|
8256
|
+
return executeBatchGraphQL(octokit, params2.owner, params2.repo, {
|
|
8257
|
+
items: params2.filePaths,
|
|
8258
|
+
aliasPrefix: "file",
|
|
8259
|
+
buildFragment: (path22, index) => `
|
|
8260
|
+
file${index}: object(expression: "${params2.ref}") {
|
|
8261
|
+
... on Commit {
|
|
8262
|
+
blame(path: "${path22}") {
|
|
8263
|
+
ranges {
|
|
8264
|
+
startingLine
|
|
8265
|
+
endingLine
|
|
8266
|
+
commit {
|
|
8267
|
+
oid
|
|
8268
|
+
author {
|
|
8269
|
+
user {
|
|
8270
|
+
name
|
|
8271
|
+
login
|
|
8272
|
+
email
|
|
8273
|
+
}
|
|
8274
|
+
}
|
|
8275
|
+
}
|
|
8276
|
+
}
|
|
8277
|
+
}
|
|
8278
|
+
}
|
|
8279
|
+
}`,
|
|
8280
|
+
extractResult: (data) => {
|
|
8281
|
+
const fileData = data;
|
|
8282
|
+
if (fileData.blame?.ranges) {
|
|
8283
|
+
return fileData.blame.ranges.map((range) => ({
|
|
8284
|
+
startingLine: range.startingLine,
|
|
8285
|
+
endingLine: range.endingLine,
|
|
8286
|
+
commitSha: range.commit.oid,
|
|
8287
|
+
email: range.commit.author.user?.email || "",
|
|
8288
|
+
name: range.commit.author.user?.name || "",
|
|
8289
|
+
login: range.commit.author.user?.login || ""
|
|
8290
|
+
}));
|
|
8291
|
+
}
|
|
8292
|
+
return void 0;
|
|
8293
|
+
}
|
|
8294
|
+
});
|
|
8295
|
+
},
|
|
8296
|
+
/**
|
|
8297
|
+
* Batch fetch commit timestamps for multiple commits via GraphQL.
|
|
8298
|
+
* Uses GITHUB_GRAPHQL_FRAGMENTS.COMMIT_TIMESTAMP for the field selection.
|
|
8299
|
+
*/
|
|
8300
|
+
async getCommitsBatch(params2) {
|
|
8301
|
+
return executeBatchGraphQL(octokit, params2.owner, params2.repo, {
|
|
8302
|
+
items: params2.commitShas,
|
|
8303
|
+
aliasPrefix: "commit",
|
|
8304
|
+
buildFragment: (sha, index) => `
|
|
8305
|
+
commit${index}: object(oid: "${sha}") {
|
|
8306
|
+
... on Commit {
|
|
8307
|
+
${GITHUB_GRAPHQL_FRAGMENTS.COMMIT_TIMESTAMP}
|
|
8308
|
+
}
|
|
8309
|
+
}`,
|
|
8310
|
+
extractResult: (data) => {
|
|
8311
|
+
const commitData = data;
|
|
8312
|
+
if (commitData.oid && commitData.committedDate) {
|
|
8313
|
+
return {
|
|
8314
|
+
sha: commitData.oid,
|
|
8315
|
+
timestamp: new Date(commitData.committedDate)
|
|
8316
|
+
};
|
|
8317
|
+
}
|
|
8318
|
+
return void 0;
|
|
8319
|
+
}
|
|
8320
|
+
});
|
|
8117
8321
|
}
|
|
8118
8322
|
};
|
|
8119
8323
|
}
|
|
@@ -8382,7 +8586,7 @@ var GithubSCMLib = class extends SCMLib {
|
|
|
8382
8586
|
comment_id: commentId
|
|
8383
8587
|
});
|
|
8384
8588
|
}
|
|
8385
|
-
async getCommitDiff(commitSha) {
|
|
8589
|
+
async getCommitDiff(commitSha, options) {
|
|
8386
8590
|
this._validateAccessTokenAndUrl();
|
|
8387
8591
|
const { owner, repo } = parseGithubOwnerAndRepo(this.url);
|
|
8388
8592
|
const { commit, diff } = await this.githubSdk.getCommitWithDiff({
|
|
@@ -8393,43 +8597,49 @@ var GithubSCMLib = class extends SCMLib {
|
|
|
8393
8597
|
const commitTimestamp = commit.commit.committer?.date ? new Date(commit.commit.committer.date) : new Date(commit.commit.author?.date || Date.now());
|
|
8394
8598
|
let parentCommits;
|
|
8395
8599
|
if (commit.parents && commit.parents.length > 0) {
|
|
8600
|
+
if (options?.parentCommitTimestamps) {
|
|
8601
|
+
parentCommits = commit.parents.map((p) => options.parentCommitTimestamps.get(p.sha)).filter((p) => p !== void 0);
|
|
8602
|
+
} else {
|
|
8603
|
+
try {
|
|
8604
|
+
parentCommits = await Promise.all(
|
|
8605
|
+
commit.parents.map(async (parent) => {
|
|
8606
|
+
const parentCommit = await this.githubSdk.getCommit({
|
|
8607
|
+
owner,
|
|
8608
|
+
repo,
|
|
8609
|
+
commitSha: parent.sha
|
|
8610
|
+
});
|
|
8611
|
+
const parentTimestamp = parentCommit.data.committer?.date ? new Date(parentCommit.data.committer.date) : new Date(Date.now());
|
|
8612
|
+
return {
|
|
8613
|
+
sha: parent.sha,
|
|
8614
|
+
timestamp: parentTimestamp
|
|
8615
|
+
};
|
|
8616
|
+
})
|
|
8617
|
+
);
|
|
8618
|
+
} catch (error) {
|
|
8619
|
+
console.error("Failed to fetch parent commit timestamps", {
|
|
8620
|
+
error,
|
|
8621
|
+
commitSha,
|
|
8622
|
+
owner,
|
|
8623
|
+
repo
|
|
8624
|
+
});
|
|
8625
|
+
parentCommits = void 0;
|
|
8626
|
+
}
|
|
8627
|
+
}
|
|
8628
|
+
}
|
|
8629
|
+
let repositoryCreatedAt = options?.repositoryCreatedAt;
|
|
8630
|
+
if (repositoryCreatedAt === void 0) {
|
|
8396
8631
|
try {
|
|
8397
|
-
|
|
8398
|
-
|
|
8399
|
-
const parentCommit = await this.githubSdk.getCommit({
|
|
8400
|
-
owner,
|
|
8401
|
-
repo,
|
|
8402
|
-
commitSha: parent.sha
|
|
8403
|
-
});
|
|
8404
|
-
const parentTimestamp = parentCommit.data.committer?.date ? new Date(parentCommit.data.committer.date) : new Date(Date.now());
|
|
8405
|
-
return {
|
|
8406
|
-
sha: parent.sha,
|
|
8407
|
-
timestamp: parentTimestamp
|
|
8408
|
-
};
|
|
8409
|
-
})
|
|
8410
|
-
);
|
|
8632
|
+
const repoData = await this.githubSdk.getRepository({ owner, repo });
|
|
8633
|
+
repositoryCreatedAt = repoData.data.created_at ? new Date(repoData.data.created_at) : void 0;
|
|
8411
8634
|
} catch (error) {
|
|
8412
|
-
console.error("Failed to fetch
|
|
8635
|
+
console.error("Failed to fetch repository creation date", {
|
|
8413
8636
|
error,
|
|
8414
|
-
commitSha,
|
|
8415
8637
|
owner,
|
|
8416
8638
|
repo
|
|
8417
8639
|
});
|
|
8418
|
-
|
|
8640
|
+
repositoryCreatedAt = void 0;
|
|
8419
8641
|
}
|
|
8420
8642
|
}
|
|
8421
|
-
let repositoryCreatedAt;
|
|
8422
|
-
try {
|
|
8423
|
-
const repoData = await this.githubSdk.getRepository({ owner, repo });
|
|
8424
|
-
repositoryCreatedAt = repoData.data.created_at ? new Date(repoData.data.created_at) : void 0;
|
|
8425
|
-
} catch (error) {
|
|
8426
|
-
console.error("Failed to fetch repository creation date", {
|
|
8427
|
-
error,
|
|
8428
|
-
owner,
|
|
8429
|
-
repo
|
|
8430
|
-
});
|
|
8431
|
-
repositoryCreatedAt = void 0;
|
|
8432
|
-
}
|
|
8433
8643
|
return {
|
|
8434
8644
|
diff,
|
|
8435
8645
|
commitTimestamp,
|
|
@@ -8445,15 +8655,37 @@ var GithubSCMLib = class extends SCMLib {
|
|
|
8445
8655
|
this._validateAccessTokenAndUrl();
|
|
8446
8656
|
const { owner, repo } = parseGithubOwnerAndRepo(this.url);
|
|
8447
8657
|
const prNumber = Number(submitRequestId);
|
|
8448
|
-
const [prRes, commitsRes, filesRes] = await Promise.all([
|
|
8658
|
+
const [prRes, commitsRes, filesRes, repoData] = await Promise.all([
|
|
8449
8659
|
this.githubSdk.getPr({ owner, repo, pull_number: prNumber }),
|
|
8450
8660
|
this.githubSdk.getPrCommits({ owner, repo, pull_number: prNumber }),
|
|
8451
|
-
this.githubSdk.listPRFiles({ owner, repo, pull_number: prNumber })
|
|
8661
|
+
this.githubSdk.listPRFiles({ owner, repo, pull_number: prNumber }),
|
|
8662
|
+
this.githubSdk.getRepository({ owner, repo })
|
|
8452
8663
|
]);
|
|
8453
8664
|
const pr = prRes.data;
|
|
8454
|
-
const
|
|
8665
|
+
const repositoryCreatedAt = repoData.data.created_at ? new Date(repoData.data.created_at) : void 0;
|
|
8666
|
+
const allParentShas = /* @__PURE__ */ new Set();
|
|
8667
|
+
for (const commit of commitsRes.data) {
|
|
8668
|
+
if (commit.parents) {
|
|
8669
|
+
for (const parent of commit.parents) {
|
|
8670
|
+
allParentShas.add(parent.sha);
|
|
8671
|
+
}
|
|
8672
|
+
}
|
|
8673
|
+
}
|
|
8674
|
+
const [parentCommitTimestamps, prDiff] = await Promise.all([
|
|
8675
|
+
this.githubSdk.getCommitsBatch({
|
|
8676
|
+
owner,
|
|
8677
|
+
repo,
|
|
8678
|
+
commitShas: Array.from(allParentShas)
|
|
8679
|
+
}),
|
|
8680
|
+
this.getPrDiff({ pull_number: prNumber })
|
|
8681
|
+
]);
|
|
8455
8682
|
const commits = await Promise.all(
|
|
8456
|
-
commitsRes.data.map(
|
|
8683
|
+
commitsRes.data.map(
|
|
8684
|
+
(commit) => this.getCommitDiff(commit.sha, {
|
|
8685
|
+
repositoryCreatedAt,
|
|
8686
|
+
parentCommitTimestamps
|
|
8687
|
+
})
|
|
8688
|
+
)
|
|
8457
8689
|
);
|
|
8458
8690
|
const diffLines = await this._attributeLinesViaBlame(
|
|
8459
8691
|
pr.head.ref,
|
|
@@ -8480,104 +8712,83 @@ var GithubSCMLib = class extends SCMLib {
|
|
|
8480
8712
|
this._validateAccessToken();
|
|
8481
8713
|
const { owner, repo } = parseGithubOwnerAndRepo(repoUrl);
|
|
8482
8714
|
const pullsRes = await this.githubSdk.getRepoPullRequests({ owner, repo });
|
|
8483
|
-
const
|
|
8484
|
-
|
|
8485
|
-
|
|
8486
|
-
|
|
8487
|
-
|
|
8488
|
-
|
|
8489
|
-
|
|
8490
|
-
|
|
8491
|
-
|
|
8492
|
-
|
|
8493
|
-
|
|
8494
|
-
|
|
8495
|
-
|
|
8496
|
-
|
|
8497
|
-
|
|
8498
|
-
|
|
8499
|
-
|
|
8500
|
-
|
|
8501
|
-
|
|
8502
|
-
|
|
8503
|
-
|
|
8504
|
-
|
|
8505
|
-
|
|
8506
|
-
|
|
8507
|
-
|
|
8508
|
-
|
|
8509
|
-
|
|
8510
|
-
|
|
8511
|
-
|
|
8715
|
+
const prNumbers = pullsRes.data.map((pr) => pr.number);
|
|
8716
|
+
const [additionsDeletionsMap, commentsMap] = await Promise.all([
|
|
8717
|
+
this.githubSdk.getPrAdditionsDeletionsBatch({ owner, repo, prNumbers }),
|
|
8718
|
+
this.githubSdk.getPrCommentsBatch({ owner, repo, prNumbers })
|
|
8719
|
+
]);
|
|
8720
|
+
const submitRequests = pullsRes.data.map((pr) => {
|
|
8721
|
+
let status = "open";
|
|
8722
|
+
if (pr.state === "closed") {
|
|
8723
|
+
status = pr.merged_at ? "merged" : "closed";
|
|
8724
|
+
} else if (pr.draft) {
|
|
8725
|
+
status = "draft";
|
|
8726
|
+
}
|
|
8727
|
+
const changedLinesData = additionsDeletionsMap.get(pr.number);
|
|
8728
|
+
const changedLines = changedLinesData ? {
|
|
8729
|
+
added: changedLinesData.additions,
|
|
8730
|
+
removed: changedLinesData.deletions
|
|
8731
|
+
} : { added: 0, removed: 0 };
|
|
8732
|
+
const comments = commentsMap.get(pr.number) || [];
|
|
8733
|
+
const tickets = this._extractLinearTicketsFromComments(comments);
|
|
8734
|
+
return {
|
|
8735
|
+
submitRequestId: String(pr.number),
|
|
8736
|
+
submitRequestNumber: pr.number,
|
|
8737
|
+
title: pr.title,
|
|
8738
|
+
status,
|
|
8739
|
+
sourceBranch: pr.head.ref,
|
|
8740
|
+
targetBranch: pr.base.ref,
|
|
8741
|
+
authorName: pr.user?.name || pr.user?.login,
|
|
8742
|
+
authorEmail: pr.user?.email || void 0,
|
|
8743
|
+
createdAt: new Date(pr.created_at),
|
|
8744
|
+
updatedAt: new Date(pr.updated_at),
|
|
8745
|
+
description: pr.body || void 0,
|
|
8746
|
+
tickets,
|
|
8747
|
+
changedLines
|
|
8748
|
+
};
|
|
8749
|
+
});
|
|
8512
8750
|
return submitRequests;
|
|
8513
8751
|
}
|
|
8514
|
-
|
|
8515
|
-
|
|
8516
|
-
|
|
8517
|
-
|
|
8518
|
-
|
|
8519
|
-
|
|
8520
|
-
|
|
8521
|
-
|
|
8522
|
-
|
|
8523
|
-
|
|
8524
|
-
|
|
8525
|
-
|
|
8526
|
-
|
|
8527
|
-
|
|
8528
|
-
|
|
8529
|
-
|
|
8530
|
-
|
|
8531
|
-
|
|
8532
|
-
|
|
8533
|
-
|
|
8534
|
-
|
|
8535
|
-
|
|
8536
|
-
|
|
8537
|
-
const
|
|
8538
|
-
|
|
8539
|
-
|
|
8540
|
-
|
|
8541
|
-
if (tickets.some((t) => t.name === name && t.url === url)) {
|
|
8542
|
-
continue;
|
|
8543
|
-
}
|
|
8544
|
-
if (!name || !url) {
|
|
8545
|
-
continue;
|
|
8546
|
-
}
|
|
8547
|
-
const urlParts = url.split("/");
|
|
8548
|
-
const titleSlug = urlParts[urlParts.length - 1] || "";
|
|
8549
|
-
const title = titleSlug.replace(/-/g, " ");
|
|
8550
|
-
tickets.push({ name, title, url });
|
|
8752
|
+
/**
|
|
8753
|
+
* Parse a Linear ticket from URL and name
|
|
8754
|
+
* Returns null if invalid or missing data
|
|
8755
|
+
*/
|
|
8756
|
+
_parseLinearTicket(url, name) {
|
|
8757
|
+
if (!name || !url) return null;
|
|
8758
|
+
const urlParts = url.split("/");
|
|
8759
|
+
const titleSlug = urlParts[urlParts.length - 1] || "";
|
|
8760
|
+
const title = titleSlug.replace(/-/g, " ");
|
|
8761
|
+
return { name, title, url };
|
|
8762
|
+
}
|
|
8763
|
+
/**
|
|
8764
|
+
* Extract Linear ticket links from pre-fetched comments (pure function, no API calls)
|
|
8765
|
+
*/
|
|
8766
|
+
_extractLinearTicketsFromComments(comments) {
|
|
8767
|
+
const tickets = [];
|
|
8768
|
+
const seen = /* @__PURE__ */ new Set();
|
|
8769
|
+
for (const comment of comments) {
|
|
8770
|
+
if (comment.author?.login === "linear[bot]" || comment.author?.type === "Bot") {
|
|
8771
|
+
const body = comment.body || "";
|
|
8772
|
+
const htmlPattern = /<a href="(https:\/\/linear\.app\/[^"]+)">([A-Z]+-\d+)<\/a>/g;
|
|
8773
|
+
let match;
|
|
8774
|
+
while ((match = htmlPattern.exec(body)) !== null) {
|
|
8775
|
+
const ticket = this._parseLinearTicket(match[1], match[2]);
|
|
8776
|
+
if (ticket && !seen.has(`${ticket.name}|${ticket.url}`)) {
|
|
8777
|
+
seen.add(`${ticket.name}|${ticket.url}`);
|
|
8778
|
+
tickets.push(ticket);
|
|
8551
8779
|
}
|
|
8552
8780
|
}
|
|
8553
|
-
|
|
8554
|
-
|
|
8555
|
-
|
|
8556
|
-
|
|
8557
|
-
|
|
8558
|
-
|
|
8559
|
-
|
|
8560
|
-
try {
|
|
8561
|
-
const diffRes = await this.githubSdk.getPrDiff({
|
|
8562
|
-
owner,
|
|
8563
|
-
repo,
|
|
8564
|
-
pull_number: prNumber
|
|
8565
|
-
});
|
|
8566
|
-
const diff = z21.string().parse(diffRes.data);
|
|
8567
|
-
let added = 0;
|
|
8568
|
-
let removed = 0;
|
|
8569
|
-
const lines = diff.split("\n");
|
|
8570
|
-
for (const line of lines) {
|
|
8571
|
-
if (line.startsWith("+") && !line.startsWith("+++")) {
|
|
8572
|
-
added++;
|
|
8573
|
-
} else if (line.startsWith("-") && !line.startsWith("---")) {
|
|
8574
|
-
removed++;
|
|
8781
|
+
const markdownPattern = /\[([A-Z]+-\d+)\]\((https:\/\/linear\.app\/[^)]+)\)/g;
|
|
8782
|
+
while ((match = markdownPattern.exec(body)) !== null) {
|
|
8783
|
+
const ticket = this._parseLinearTicket(match[2], match[1]);
|
|
8784
|
+
if (ticket && !seen.has(`${ticket.name}|${ticket.url}`)) {
|
|
8785
|
+
seen.add(`${ticket.name}|${ticket.url}`);
|
|
8786
|
+
tickets.push(ticket);
|
|
8787
|
+
}
|
|
8575
8788
|
}
|
|
8576
8789
|
}
|
|
8577
|
-
return { added, removed };
|
|
8578
|
-
} catch (error) {
|
|
8579
|
-
return { added: 0, removed: 0 };
|
|
8580
8790
|
}
|
|
8791
|
+
return tickets;
|
|
8581
8792
|
}
|
|
8582
8793
|
/**
|
|
8583
8794
|
* Optimized helper to parse added line numbers from a unified diff patch
|
|
@@ -8605,48 +8816,59 @@ var GithubSCMLib = class extends SCMLib {
|
|
|
8605
8816
|
return addedLines;
|
|
8606
8817
|
}
|
|
8607
8818
|
/**
|
|
8608
|
-
*
|
|
8819
|
+
* Process blame data for a single file to attribute lines to commits
|
|
8820
|
+
* Uses pre-fetched blame data instead of making API calls
|
|
8609
8821
|
*/
|
|
8610
|
-
|
|
8611
|
-
|
|
8612
|
-
|
|
8613
|
-
|
|
8614
|
-
|
|
8615
|
-
|
|
8616
|
-
|
|
8617
|
-
|
|
8618
|
-
|
|
8619
|
-
|
|
8620
|
-
|
|
8621
|
-
|
|
8622
|
-
|
|
8623
|
-
|
|
8624
|
-
|
|
8625
|
-
commitSha: blameRange.commitSha
|
|
8626
|
-
});
|
|
8627
|
-
}
|
|
8822
|
+
_processFileBlameSafe(file, blameData, prCommitShas) {
|
|
8823
|
+
const addedLines = this._parseAddedLinesFromPatch(file.patch);
|
|
8824
|
+
const addedLinesSet = new Set(addedLines);
|
|
8825
|
+
const fileAttributions = [];
|
|
8826
|
+
for (const blameRange of blameData) {
|
|
8827
|
+
if (!prCommitShas.has(blameRange.commitSha)) {
|
|
8828
|
+
continue;
|
|
8829
|
+
}
|
|
8830
|
+
for (let lineNum = blameRange.startingLine; lineNum <= blameRange.endingLine; lineNum++) {
|
|
8831
|
+
if (addedLinesSet.has(lineNum)) {
|
|
8832
|
+
fileAttributions.push({
|
|
8833
|
+
file: file.filename,
|
|
8834
|
+
line: lineNum,
|
|
8835
|
+
commitSha: blameRange.commitSha
|
|
8836
|
+
});
|
|
8628
8837
|
}
|
|
8629
8838
|
}
|
|
8630
|
-
return fileAttributions;
|
|
8631
|
-
} catch (error) {
|
|
8632
|
-
return [];
|
|
8633
8839
|
}
|
|
8840
|
+
return fileAttributions;
|
|
8634
8841
|
}
|
|
8635
8842
|
/**
|
|
8636
8843
|
* Optimized helper to attribute PR lines to commits using blame API
|
|
8637
|
-
*
|
|
8844
|
+
* Batch blame queries for minimal API call time (1 call instead of M calls)
|
|
8638
8845
|
*/
|
|
8639
8846
|
async _attributeLinesViaBlame(headRef, changedFiles, prCommits) {
|
|
8640
8847
|
const prCommitShas = new Set(prCommits.map((c) => c.commitSha));
|
|
8641
8848
|
const filesWithAdditions = changedFiles.filter(
|
|
8642
8849
|
(file) => file.patch?.includes("\n+")
|
|
8643
8850
|
);
|
|
8644
|
-
|
|
8645
|
-
|
|
8646
|
-
|
|
8647
|
-
|
|
8648
|
-
|
|
8649
|
-
|
|
8851
|
+
if (filesWithAdditions.length === 0) {
|
|
8852
|
+
return [];
|
|
8853
|
+
}
|
|
8854
|
+
const { owner, repo } = parseGithubOwnerAndRepo(this.url);
|
|
8855
|
+
const blameMap = await this.githubSdk.getBlameBatch({
|
|
8856
|
+
owner,
|
|
8857
|
+
repo,
|
|
8858
|
+
ref: headRef,
|
|
8859
|
+
filePaths: filesWithAdditions.map((f) => f.filename)
|
|
8860
|
+
});
|
|
8861
|
+
const allAttributions = [];
|
|
8862
|
+
for (const file of filesWithAdditions) {
|
|
8863
|
+
const blameData = blameMap.get(file.filename) || [];
|
|
8864
|
+
const fileAttributions = this._processFileBlameSafe(
|
|
8865
|
+
file,
|
|
8866
|
+
blameData,
|
|
8867
|
+
prCommitShas
|
|
8868
|
+
);
|
|
8869
|
+
allAttributions.push(...fileAttributions);
|
|
8870
|
+
}
|
|
8871
|
+
return allAttributions;
|
|
8650
8872
|
}
|
|
8651
8873
|
};
|
|
8652
8874
|
|
|
@@ -13046,6 +13268,145 @@ import path11 from "path";
|
|
|
13046
13268
|
import chalk9 from "chalk";
|
|
13047
13269
|
import { withFile } from "tmp-promise";
|
|
13048
13270
|
import z31 from "zod";
|
|
13271
|
+
|
|
13272
|
+
// src/utils/sanitize-sensitive-data.ts
|
|
13273
|
+
import { OpenRedaction } from "@openredaction/openredaction";
|
|
13274
|
+
import { spawn } from "child_process";
|
|
13275
|
+
import { installGitleaks } from "gitleaks-secret-scanner/lib/installer.js";
|
|
13276
|
+
var openRedaction = new OpenRedaction();
|
|
13277
|
+
var gitleaksBinaryPath = null;
|
|
13278
|
+
async function initializeGitleaks() {
|
|
13279
|
+
try {
|
|
13280
|
+
gitleaksBinaryPath = await installGitleaks({ version: "8.27.2" });
|
|
13281
|
+
return gitleaksBinaryPath;
|
|
13282
|
+
} catch {
|
|
13283
|
+
return null;
|
|
13284
|
+
}
|
|
13285
|
+
}
|
|
13286
|
+
var gitleaksInitPromise = initializeGitleaks();
|
|
13287
|
+
async function detectSecretsWithGitleaks(text) {
|
|
13288
|
+
const secrets = /* @__PURE__ */ new Set();
|
|
13289
|
+
const binaryPath = gitleaksBinaryPath || await gitleaksInitPromise;
|
|
13290
|
+
if (!binaryPath) {
|
|
13291
|
+
return secrets;
|
|
13292
|
+
}
|
|
13293
|
+
return new Promise((resolve) => {
|
|
13294
|
+
const gitleaks = spawn(
|
|
13295
|
+
binaryPath,
|
|
13296
|
+
[
|
|
13297
|
+
"detect",
|
|
13298
|
+
"--pipe",
|
|
13299
|
+
"--no-banner",
|
|
13300
|
+
"--exit-code",
|
|
13301
|
+
"0",
|
|
13302
|
+
"--report-format",
|
|
13303
|
+
"json"
|
|
13304
|
+
],
|
|
13305
|
+
{
|
|
13306
|
+
stdio: ["pipe", "pipe", "ignore"]
|
|
13307
|
+
}
|
|
13308
|
+
);
|
|
13309
|
+
let output = "";
|
|
13310
|
+
gitleaks.stdout.on("data", (data) => {
|
|
13311
|
+
output += data.toString();
|
|
13312
|
+
});
|
|
13313
|
+
gitleaks.on("close", () => {
|
|
13314
|
+
try {
|
|
13315
|
+
const findings = JSON.parse(output);
|
|
13316
|
+
if (Array.isArray(findings)) {
|
|
13317
|
+
for (const finding of findings) {
|
|
13318
|
+
if (finding.Secret) {
|
|
13319
|
+
secrets.add(finding.Secret);
|
|
13320
|
+
}
|
|
13321
|
+
}
|
|
13322
|
+
}
|
|
13323
|
+
} catch {
|
|
13324
|
+
}
|
|
13325
|
+
resolve(secrets);
|
|
13326
|
+
});
|
|
13327
|
+
gitleaks.on("error", () => {
|
|
13328
|
+
resolve(secrets);
|
|
13329
|
+
});
|
|
13330
|
+
gitleaks.stdin.write(text);
|
|
13331
|
+
gitleaks.stdin.end();
|
|
13332
|
+
});
|
|
13333
|
+
}
|
|
13334
|
+
function maskString(str, showStart = 2, showEnd = 2) {
|
|
13335
|
+
if (str.length <= showStart + showEnd) {
|
|
13336
|
+
return "*".repeat(str.length);
|
|
13337
|
+
}
|
|
13338
|
+
return str.slice(0, showStart) + "*".repeat(str.length - showStart - showEnd) + str.slice(-showEnd);
|
|
13339
|
+
}
|
|
13340
|
+
async function sanitizeDataWithCounts(obj) {
|
|
13341
|
+
const counts = {
|
|
13342
|
+
pii: { total: 0, high: 0, medium: 0, low: 0 },
|
|
13343
|
+
secrets: 0
|
|
13344
|
+
};
|
|
13345
|
+
const sanitizeString = async (str) => {
|
|
13346
|
+
let result = str;
|
|
13347
|
+
const piiDetections = openRedaction.scan(str);
|
|
13348
|
+
if (piiDetections && piiDetections.total > 0) {
|
|
13349
|
+
const allDetections = [
|
|
13350
|
+
...piiDetections.high,
|
|
13351
|
+
...piiDetections.medium,
|
|
13352
|
+
...piiDetections.low
|
|
13353
|
+
];
|
|
13354
|
+
const filteredDetections = allDetections.filter((detection) => {
|
|
13355
|
+
if (detection.type === "INSTAGRAM_USERNAME") {
|
|
13356
|
+
return false;
|
|
13357
|
+
}
|
|
13358
|
+
if (detection.value.length < 3 && detection.severity !== "high") {
|
|
13359
|
+
return false;
|
|
13360
|
+
}
|
|
13361
|
+
return true;
|
|
13362
|
+
});
|
|
13363
|
+
for (const detection of filteredDetections) {
|
|
13364
|
+
counts.pii.total++;
|
|
13365
|
+
if (detection.severity === "high") counts.pii.high++;
|
|
13366
|
+
else if (detection.severity === "medium") counts.pii.medium++;
|
|
13367
|
+
else if (detection.severity === "low") counts.pii.low++;
|
|
13368
|
+
const masked = maskString(detection.value);
|
|
13369
|
+
result = result.replaceAll(detection.value, masked);
|
|
13370
|
+
}
|
|
13371
|
+
}
|
|
13372
|
+
const secrets = await detectSecretsWithGitleaks(result);
|
|
13373
|
+
counts.secrets += secrets.size;
|
|
13374
|
+
for (const secret of secrets) {
|
|
13375
|
+
const masked = maskString(secret);
|
|
13376
|
+
result = result.replaceAll(secret, masked);
|
|
13377
|
+
}
|
|
13378
|
+
return result;
|
|
13379
|
+
};
|
|
13380
|
+
const sanitizeRecursive = async (data) => {
|
|
13381
|
+
if (typeof data === "string") {
|
|
13382
|
+
return sanitizeString(data);
|
|
13383
|
+
} else if (Array.isArray(data)) {
|
|
13384
|
+
return Promise.all(data.map((item) => sanitizeRecursive(item)));
|
|
13385
|
+
} else if (data instanceof Error) {
|
|
13386
|
+
return data;
|
|
13387
|
+
} else if (data instanceof Date) {
|
|
13388
|
+
return data;
|
|
13389
|
+
} else if (typeof data === "object" && data !== null) {
|
|
13390
|
+
const sanitized = {};
|
|
13391
|
+
const record = data;
|
|
13392
|
+
for (const key in record) {
|
|
13393
|
+
if (Object.prototype.hasOwnProperty.call(record, key)) {
|
|
13394
|
+
sanitized[key] = await sanitizeRecursive(record[key]);
|
|
13395
|
+
}
|
|
13396
|
+
}
|
|
13397
|
+
return sanitized;
|
|
13398
|
+
}
|
|
13399
|
+
return data;
|
|
13400
|
+
};
|
|
13401
|
+
const sanitizedData = await sanitizeRecursive(obj);
|
|
13402
|
+
return { sanitizedData, counts };
|
|
13403
|
+
}
|
|
13404
|
+
async function sanitizeData(obj) {
|
|
13405
|
+
const result = await sanitizeDataWithCounts(obj);
|
|
13406
|
+
return result.sanitizedData;
|
|
13407
|
+
}
|
|
13408
|
+
|
|
13409
|
+
// src/args/commands/upload_ai_blame.ts
|
|
13049
13410
|
var PromptItemZ = z31.object({
|
|
13050
13411
|
type: z31.enum(["USER_PROMPT", "AI_RESPONSE", "TOOL_EXECUTION", "AI_THINKING"]),
|
|
13051
13412
|
attachedFiles: z31.array(
|
|
@@ -13114,15 +13475,32 @@ async function uploadAiBlameHandlerFromExtension(args) {
|
|
|
13114
13475
|
aiResponseAt: [],
|
|
13115
13476
|
blameType: []
|
|
13116
13477
|
};
|
|
13478
|
+
let promptsCounts;
|
|
13479
|
+
let inferenceCounts;
|
|
13480
|
+
let promptsUUID;
|
|
13481
|
+
let inferenceUUID;
|
|
13117
13482
|
await withFile(async (promptFile) => {
|
|
13483
|
+
const promptsResult = await sanitizeDataWithCounts(args.prompts);
|
|
13484
|
+
promptsCounts = promptsResult.counts;
|
|
13485
|
+
promptsUUID = path11.basename(promptFile.path, path11.extname(promptFile.path));
|
|
13118
13486
|
await fsPromises3.writeFile(
|
|
13119
13487
|
promptFile.path,
|
|
13120
|
-
JSON.stringify(
|
|
13488
|
+
JSON.stringify(promptsResult.sanitizedData, null, 2),
|
|
13121
13489
|
"utf-8"
|
|
13122
13490
|
);
|
|
13123
13491
|
uploadArgs.prompt.push(promptFile.path);
|
|
13124
13492
|
await withFile(async (inferenceFile) => {
|
|
13125
|
-
await
|
|
13493
|
+
const inferenceResult = await sanitizeDataWithCounts(args.inference);
|
|
13494
|
+
inferenceCounts = inferenceResult.counts;
|
|
13495
|
+
inferenceUUID = path11.basename(
|
|
13496
|
+
inferenceFile.path,
|
|
13497
|
+
path11.extname(inferenceFile.path)
|
|
13498
|
+
);
|
|
13499
|
+
await fsPromises3.writeFile(
|
|
13500
|
+
inferenceFile.path,
|
|
13501
|
+
inferenceResult.sanitizedData,
|
|
13502
|
+
"utf-8"
|
|
13503
|
+
);
|
|
13126
13504
|
uploadArgs.inference.push(inferenceFile.path);
|
|
13127
13505
|
uploadArgs.model.push(args.model);
|
|
13128
13506
|
uploadArgs.toolName.push(args.tool);
|
|
@@ -13131,6 +13509,12 @@ async function uploadAiBlameHandlerFromExtension(args) {
|
|
|
13131
13509
|
await uploadAiBlameHandler(uploadArgs, false);
|
|
13132
13510
|
});
|
|
13133
13511
|
});
|
|
13512
|
+
return {
|
|
13513
|
+
promptsCounts,
|
|
13514
|
+
inferenceCounts,
|
|
13515
|
+
promptsUUID,
|
|
13516
|
+
inferenceUUID
|
|
13517
|
+
};
|
|
13134
13518
|
}
|
|
13135
13519
|
async function uploadAiBlameHandler(args, exitOnError = true) {
|
|
13136
13520
|
const prompts = args.prompt || [];
|
|
@@ -13177,8 +13561,11 @@ async function uploadAiBlameHandler(args, exitOnError = true) {
|
|
|
13177
13561
|
const authenticatedClient = await getAuthenticatedGQLClient({
|
|
13178
13562
|
isSkipPrompts: true
|
|
13179
13563
|
});
|
|
13180
|
-
const
|
|
13564
|
+
const sanitizedSessions = await sanitizeData(
|
|
13181
13565
|
sessions
|
|
13566
|
+
);
|
|
13567
|
+
const initRes = await authenticatedClient.uploadAIBlameInferencesInitRaw({
|
|
13568
|
+
sessions: sanitizedSessions
|
|
13182
13569
|
});
|
|
13183
13570
|
const uploadSessions = initRes.uploadAIBlameInferencesInit?.uploadSessions ?? [];
|
|
13184
13571
|
if (uploadSessions.length !== sessions.length) {
|
|
@@ -13222,8 +13609,11 @@ async function uploadAiBlameHandler(args, exitOnError = true) {
|
|
|
13222
13609
|
blameType: s.blameType
|
|
13223
13610
|
};
|
|
13224
13611
|
});
|
|
13612
|
+
const sanitizedFinalizeSessions = await sanitizeData(
|
|
13613
|
+
finalizeSessions
|
|
13614
|
+
);
|
|
13225
13615
|
const finRes = await authenticatedClient.finalizeAIBlameInferencesUploadRaw({
|
|
13226
|
-
sessions:
|
|
13616
|
+
sessions: sanitizedFinalizeSessions
|
|
13227
13617
|
});
|
|
13228
13618
|
const status = finRes?.finalizeAIBlameInferencesUpload?.status;
|
|
13229
13619
|
if (status !== "OK") {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mobbdev",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.8",
|
|
4
4
|
"description": "Automated secure code remediation tool",
|
|
5
5
|
"repository": "git+https://github.com/mobb-dev/bugsy.git",
|
|
6
6
|
"main": "dist/index.mjs",
|
|
@@ -56,6 +56,7 @@
|
|
|
56
56
|
"@modelcontextprotocol/sdk": "1.21.1",
|
|
57
57
|
"@octokit/core": "5.2.0",
|
|
58
58
|
"@octokit/request-error": "5.1.1",
|
|
59
|
+
"@openredaction/openredaction": "1.0.4",
|
|
59
60
|
"adm-zip": "0.5.16",
|
|
60
61
|
"axios": "1.13.2",
|
|
61
62
|
"azure-devops-node-api": "15.1.1",
|
|
@@ -67,6 +68,7 @@
|
|
|
67
68
|
"debug": "4.4.3",
|
|
68
69
|
"dotenv": "16.6.1",
|
|
69
70
|
"extract-zip": "2.0.1",
|
|
71
|
+
"gitleaks-secret-scanner": "1.2.2",
|
|
70
72
|
"globby": "14.1.0",
|
|
71
73
|
"graphql": "16.12.0",
|
|
72
74
|
"graphql-request": "6.1.0",
|