mobbdev 1.0.204 → 1.0.206

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.
@@ -1,5 +1,172 @@
1
1
  import * as Yargs from 'yargs';
2
+ import z from 'zod';
2
3
 
4
+ declare const PromptItemZ: z.ZodObject<{
5
+ type: z.ZodEnum<["USER_PROMPT", "AI_RESPONSE", "TOOL_EXECUTION", "AI_THINKING"]>;
6
+ attachedFiles: z.ZodOptional<z.ZodArray<z.ZodObject<{
7
+ relativePath: z.ZodString;
8
+ startLine: z.ZodOptional<z.ZodNumber>;
9
+ }, "strip", z.ZodTypeAny, {
10
+ relativePath: string;
11
+ startLine?: number | undefined;
12
+ }, {
13
+ relativePath: string;
14
+ startLine?: number | undefined;
15
+ }>, "many">>;
16
+ tokens: z.ZodOptional<z.ZodObject<{
17
+ inputCount: z.ZodNumber;
18
+ outputCount: z.ZodNumber;
19
+ }, "strip", z.ZodTypeAny, {
20
+ inputCount: number;
21
+ outputCount: number;
22
+ }, {
23
+ inputCount: number;
24
+ outputCount: number;
25
+ }>>;
26
+ text: z.ZodOptional<z.ZodString>;
27
+ date: z.ZodOptional<z.ZodDate>;
28
+ tool: z.ZodOptional<z.ZodObject<{
29
+ name: z.ZodString;
30
+ parameters: z.ZodString;
31
+ result: z.ZodString;
32
+ rawArguments: z.ZodOptional<z.ZodString>;
33
+ accepted: z.ZodOptional<z.ZodBoolean>;
34
+ }, "strip", z.ZodTypeAny, {
35
+ name: string;
36
+ parameters: string;
37
+ result: string;
38
+ accepted?: boolean | undefined;
39
+ rawArguments?: string | undefined;
40
+ }, {
41
+ name: string;
42
+ parameters: string;
43
+ result: string;
44
+ accepted?: boolean | undefined;
45
+ rawArguments?: string | undefined;
46
+ }>>;
47
+ }, "strip", z.ZodTypeAny, {
48
+ type: "USER_PROMPT" | "AI_RESPONSE" | "TOOL_EXECUTION" | "AI_THINKING";
49
+ tool?: {
50
+ name: string;
51
+ parameters: string;
52
+ result: string;
53
+ accepted?: boolean | undefined;
54
+ rawArguments?: string | undefined;
55
+ } | undefined;
56
+ date?: Date | undefined;
57
+ text?: string | undefined;
58
+ attachedFiles?: {
59
+ relativePath: string;
60
+ startLine?: number | undefined;
61
+ }[] | undefined;
62
+ tokens?: {
63
+ inputCount: number;
64
+ outputCount: number;
65
+ } | undefined;
66
+ }, {
67
+ type: "USER_PROMPT" | "AI_RESPONSE" | "TOOL_EXECUTION" | "AI_THINKING";
68
+ tool?: {
69
+ name: string;
70
+ parameters: string;
71
+ result: string;
72
+ accepted?: boolean | undefined;
73
+ rawArguments?: string | undefined;
74
+ } | undefined;
75
+ date?: Date | undefined;
76
+ text?: string | undefined;
77
+ attachedFiles?: {
78
+ relativePath: string;
79
+ startLine?: number | undefined;
80
+ }[] | undefined;
81
+ tokens?: {
82
+ inputCount: number;
83
+ outputCount: number;
84
+ } | undefined;
85
+ }>;
86
+ type PromptItem = z.infer<typeof PromptItemZ>;
87
+ declare const PromptItemArrayZ: z.ZodArray<z.ZodObject<{
88
+ type: z.ZodEnum<["USER_PROMPT", "AI_RESPONSE", "TOOL_EXECUTION", "AI_THINKING"]>;
89
+ attachedFiles: z.ZodOptional<z.ZodArray<z.ZodObject<{
90
+ relativePath: z.ZodString;
91
+ startLine: z.ZodOptional<z.ZodNumber>;
92
+ }, "strip", z.ZodTypeAny, {
93
+ relativePath: string;
94
+ startLine?: number | undefined;
95
+ }, {
96
+ relativePath: string;
97
+ startLine?: number | undefined;
98
+ }>, "many">>;
99
+ tokens: z.ZodOptional<z.ZodObject<{
100
+ inputCount: z.ZodNumber;
101
+ outputCount: z.ZodNumber;
102
+ }, "strip", z.ZodTypeAny, {
103
+ inputCount: number;
104
+ outputCount: number;
105
+ }, {
106
+ inputCount: number;
107
+ outputCount: number;
108
+ }>>;
109
+ text: z.ZodOptional<z.ZodString>;
110
+ date: z.ZodOptional<z.ZodDate>;
111
+ tool: z.ZodOptional<z.ZodObject<{
112
+ name: z.ZodString;
113
+ parameters: z.ZodString;
114
+ result: z.ZodString;
115
+ rawArguments: z.ZodOptional<z.ZodString>;
116
+ accepted: z.ZodOptional<z.ZodBoolean>;
117
+ }, "strip", z.ZodTypeAny, {
118
+ name: string;
119
+ parameters: string;
120
+ result: string;
121
+ accepted?: boolean | undefined;
122
+ rawArguments?: string | undefined;
123
+ }, {
124
+ name: string;
125
+ parameters: string;
126
+ result: string;
127
+ accepted?: boolean | undefined;
128
+ rawArguments?: string | undefined;
129
+ }>>;
130
+ }, "strip", z.ZodTypeAny, {
131
+ type: "USER_PROMPT" | "AI_RESPONSE" | "TOOL_EXECUTION" | "AI_THINKING";
132
+ tool?: {
133
+ name: string;
134
+ parameters: string;
135
+ result: string;
136
+ accepted?: boolean | undefined;
137
+ rawArguments?: string | undefined;
138
+ } | undefined;
139
+ date?: Date | undefined;
140
+ text?: string | undefined;
141
+ attachedFiles?: {
142
+ relativePath: string;
143
+ startLine?: number | undefined;
144
+ }[] | undefined;
145
+ tokens?: {
146
+ inputCount: number;
147
+ outputCount: number;
148
+ } | undefined;
149
+ }, {
150
+ type: "USER_PROMPT" | "AI_RESPONSE" | "TOOL_EXECUTION" | "AI_THINKING";
151
+ tool?: {
152
+ name: string;
153
+ parameters: string;
154
+ result: string;
155
+ accepted?: boolean | undefined;
156
+ rawArguments?: string | undefined;
157
+ } | undefined;
158
+ date?: Date | undefined;
159
+ text?: string | undefined;
160
+ attachedFiles?: {
161
+ relativePath: string;
162
+ startLine?: number | undefined;
163
+ }[] | undefined;
164
+ tokens?: {
165
+ inputCount: number;
166
+ outputCount: number;
167
+ } | undefined;
168
+ }>, "many">;
169
+ type PromptItemArray = z.infer<typeof PromptItemArrayZ>;
3
170
  type UploadAiBlameOptions = {
4
171
  prompt: string[];
5
172
  inference: string[];
@@ -10,6 +177,13 @@ type UploadAiBlameOptions = {
10
177
  'tool-name'?: string[];
11
178
  };
12
179
  declare function uploadAiBlameBuilder(args: Yargs.Argv<unknown>): Yargs.Argv<UploadAiBlameOptions>;
13
- declare function uploadAiBlameHandler(args: UploadAiBlameOptions): Promise<void>;
180
+ declare function uploadAiBlameHandlerFromExtension(args: {
181
+ prompts: PromptItemArray;
182
+ inference: string;
183
+ model: string;
184
+ tool: string;
185
+ responseTime: string;
186
+ }): Promise<void>;
187
+ declare function uploadAiBlameHandler(args: UploadAiBlameOptions, exitOnError?: boolean): Promise<void>;
14
188
 
15
- export { type UploadAiBlameOptions, uploadAiBlameBuilder, uploadAiBlameHandler };
189
+ export { type PromptItem, type PromptItemArray, type UploadAiBlameOptions, uploadAiBlameBuilder, uploadAiBlameHandler, uploadAiBlameHandlerFromExtension };
@@ -92,9 +92,11 @@ var init_GitService = __esm({
92
92
  });
93
93
 
94
94
  // src/args/commands/upload_ai_blame.ts
95
- import fs5 from "fs/promises";
95
+ import fsPromises2 from "fs/promises";
96
96
  import path6 from "path";
97
97
  import chalk2 from "chalk";
98
+ import { withFile } from "tmp-promise";
99
+ import z27 from "zod";
98
100
 
99
101
  // src/features/analysis/upload-file.ts
100
102
  import Debug7 from "debug";
@@ -5560,6 +5562,29 @@ async function createAuthenticatedMcpGQLClient({
5560
5562
  }
5561
5563
 
5562
5564
  // src/args/commands/upload_ai_blame.ts
5565
+ var PromptItemZ = z27.object({
5566
+ type: z27.enum(["USER_PROMPT", "AI_RESPONSE", "TOOL_EXECUTION", "AI_THINKING"]),
5567
+ attachedFiles: z27.array(
5568
+ z27.object({
5569
+ relativePath: z27.string(),
5570
+ startLine: z27.number().optional()
5571
+ })
5572
+ ).optional(),
5573
+ tokens: z27.object({
5574
+ inputCount: z27.number(),
5575
+ outputCount: z27.number()
5576
+ }).optional(),
5577
+ text: z27.string().optional(),
5578
+ date: z27.date().optional(),
5579
+ tool: z27.object({
5580
+ name: z27.string(),
5581
+ parameters: z27.string(),
5582
+ result: z27.string(),
5583
+ rawArguments: z27.string().optional(),
5584
+ accepted: z27.boolean().optional()
5585
+ }).optional()
5586
+ });
5587
+ var PromptItemArrayZ = z27.array(PromptItemZ);
5563
5588
  function uploadAiBlameBuilder(args) {
5564
5589
  return args.option("prompt", {
5565
5590
  type: "string",
@@ -5589,17 +5614,44 @@ function uploadAiBlameBuilder(args) {
5589
5614
  describe: chalk2.bold("Tool/IDE name(s) (optional, one per session)")
5590
5615
  }).strict();
5591
5616
  }
5592
- async function uploadAiBlameHandler(args) {
5617
+ async function uploadAiBlameHandlerFromExtension(args) {
5618
+ const uploadArgs = {
5619
+ prompt: [],
5620
+ inference: [],
5621
+ model: [],
5622
+ toolName: [],
5623
+ aiResponseAt: []
5624
+ };
5625
+ await withFile(async (promptFile) => {
5626
+ await fsPromises2.writeFile(
5627
+ promptFile.path,
5628
+ JSON.stringify(args.prompts, null, 2),
5629
+ "utf-8"
5630
+ );
5631
+ uploadArgs.prompt.push(promptFile.path);
5632
+ await withFile(async (inferenceFile) => {
5633
+ await fsPromises2.writeFile(inferenceFile.path, args.inference, "utf-8");
5634
+ uploadArgs.inference.push(inferenceFile.path);
5635
+ uploadArgs.model.push(args.model);
5636
+ uploadArgs.toolName.push(args.tool);
5637
+ uploadArgs.aiResponseAt.push(args.responseTime);
5638
+ await uploadAiBlameHandler(uploadArgs, false);
5639
+ });
5640
+ });
5641
+ }
5642
+ async function uploadAiBlameHandler(args, exitOnError = true) {
5593
5643
  const prompts = args.prompt || [];
5594
5644
  const inferences = args.inference || [];
5595
5645
  const models = args.model || [];
5596
5646
  const tools = args.toolName || args["tool-name"] || [];
5597
5647
  const responseTimes = args.aiResponseAt || args["ai-response-at"] || [];
5598
5648
  if (prompts.length !== inferences.length) {
5599
- console.error(
5600
- chalk2.red("prompt and inference must have the same number of entries")
5601
- );
5602
- process.exit(1);
5649
+ const errorMsg = "prompt and inference must have the same number of entries";
5650
+ console.error(chalk2.red(errorMsg));
5651
+ if (exitOnError) {
5652
+ process.exit(1);
5653
+ }
5654
+ throw new Error(errorMsg);
5603
5655
  }
5604
5656
  const nowIso = (/* @__PURE__ */ new Date()).toISOString();
5605
5657
  const sessions = [];
@@ -5607,10 +5659,17 @@ async function uploadAiBlameHandler(args) {
5607
5659
  const promptPath = String(prompts[i]);
5608
5660
  const inferencePath = String(inferences[i]);
5609
5661
  try {
5610
- await Promise.all([fs5.access(promptPath), fs5.access(inferencePath)]);
5662
+ await Promise.all([
5663
+ fsPromises2.access(promptPath),
5664
+ fsPromises2.access(inferencePath)
5665
+ ]);
5611
5666
  } catch {
5612
- console.error(chalk2.red(`File not found for session ${i + 1}`));
5613
- process.exit(1);
5667
+ const errorMsg = `File not found for session ${i + 1}`;
5668
+ console.error(chalk2.red(errorMsg));
5669
+ if (exitOnError) {
5670
+ process.exit(1);
5671
+ }
5672
+ throw new Error(errorMsg);
5614
5673
  }
5615
5674
  sessions.push({
5616
5675
  promptFileName: path6.basename(promptPath),
@@ -5624,10 +5683,12 @@ async function uploadAiBlameHandler(args) {
5624
5683
  const initRes = await gqlClient.uploadAIBlameInferencesInitRaw({ sessions });
5625
5684
  const uploadSessions = initRes.uploadAIBlameInferencesInit?.uploadSessions ?? [];
5626
5685
  if (uploadSessions.length !== sessions.length) {
5627
- console.error(
5628
- chalk2.red("Init failed to return expected number of sessions")
5629
- );
5630
- process.exit(1);
5686
+ const errorMsg = "Init failed to return expected number of sessions";
5687
+ console.error(chalk2.red(errorMsg));
5688
+ if (exitOnError) {
5689
+ process.exit(1);
5690
+ }
5691
+ throw new Error(errorMsg);
5631
5692
  }
5632
5693
  for (let i = 0; i < uploadSessions.length; i++) {
5633
5694
  const us = uploadSessions[i];
@@ -5662,16 +5723,17 @@ async function uploadAiBlameHandler(args) {
5662
5723
  });
5663
5724
  const status = finRes?.finalizeAIBlameInferencesUpload?.status;
5664
5725
  if (status !== "OK") {
5665
- console.error(
5666
- chalk2.red(
5667
- `Finalize failed: ${finRes?.finalizeAIBlameInferencesUpload?.error || "unknown error"}`
5668
- )
5669
- );
5670
- process.exit(1);
5726
+ const errorMsg = finRes?.finalizeAIBlameInferencesUpload?.error || "unknown error";
5727
+ console.error(chalk2.red(errorMsg));
5728
+ if (exitOnError) {
5729
+ process.exit(1);
5730
+ }
5731
+ throw new Error(errorMsg);
5671
5732
  }
5672
5733
  console.log(chalk2.green("AI Blame uploads finalized successfully"));
5673
5734
  }
5674
5735
  export {
5675
5736
  uploadAiBlameBuilder,
5676
- uploadAiBlameHandler
5737
+ uploadAiBlameHandler,
5738
+ uploadAiBlameHandlerFromExtension
5677
5739
  };
package/dist/index.mjs CHANGED
@@ -7354,6 +7354,7 @@ var GET_BLAME_DOCUMENT = `
7354
7354
  blame(path: $path) {
7355
7355
  ranges {
7356
7356
  commit {
7357
+ oid
7357
7358
  author {
7358
7359
  user {
7359
7360
  name
@@ -7368,7 +7369,7 @@ var GET_BLAME_DOCUMENT = `
7368
7369
  }
7369
7370
  }
7370
7371
  }
7371
-
7372
+
7372
7373
  }
7373
7374
  }
7374
7375
  }
@@ -7776,6 +7777,7 @@ function getGithubSdk(params = {}) {
7776
7777
  (range) => ({
7777
7778
  startingLine: range.startingLine,
7778
7779
  endingLine: range.endingLine,
7780
+ commitSha: range.commit.oid,
7779
7781
  email: range.commit.author.user?.email || "",
7780
7782
  name: range.commit.author.user?.name || "",
7781
7783
  login: range.commit.author.user?.login || ""
@@ -7925,6 +7927,14 @@ function getGithubSdk(params = {}) {
7925
7927
  direction: "desc",
7926
7928
  per_page: 100
7927
7929
  });
7930
+ },
7931
+ async listPRFiles(params2) {
7932
+ return octokit.rest.pulls.listFiles({
7933
+ owner: params2.owner,
7934
+ repo: params2.repo,
7935
+ pull_number: params2.pull_number,
7936
+ per_page: 100
7937
+ });
7928
7938
  }
7929
7939
  };
7930
7940
  }
@@ -8207,24 +8217,21 @@ var GithubSCMLib = class extends SCMLib {
8207
8217
  this._validateAccessTokenAndUrl();
8208
8218
  const { owner, repo } = parseGithubOwnerAndRepo(this.url);
8209
8219
  const prNumber = Number(submitRequestId);
8210
- const prRes = await this.githubSdk.getPr({
8211
- owner,
8212
- repo,
8213
- pull_number: prNumber
8214
- });
8215
- const prDiff = await this.getPrDiff({ pull_number: prNumber });
8216
- const commitsRes = await this.githubSdk.getPrCommits({
8217
- owner,
8218
- repo,
8219
- pull_number: prNumber
8220
- });
8221
- const commits = [];
8222
- for (const commit of commitsRes.data) {
8223
- const commitDiff = await this.getCommitDiff(commit.sha);
8224
- commits.push(commitDiff);
8225
- }
8220
+ const [prRes, commitsRes, filesRes] = await Promise.all([
8221
+ this.githubSdk.getPr({ owner, repo, pull_number: prNumber }),
8222
+ this.githubSdk.getPrCommits({ owner, repo, pull_number: prNumber }),
8223
+ this.githubSdk.listPRFiles({ owner, repo, pull_number: prNumber })
8224
+ ]);
8226
8225
  const pr = prRes.data;
8227
- const diffLines = this._calculateDiffLineAttributions(prDiff, commits);
8226
+ const prDiff = await this.getPrDiff({ pull_number: prNumber });
8227
+ const commits = await Promise.all(
8228
+ commitsRes.data.map((commit) => this.getCommitDiff(commit.sha))
8229
+ );
8230
+ const diffLines = await this._attributeLinesViaBlame(
8231
+ pr.head.ref,
8232
+ filesRes.data,
8233
+ commits
8234
+ );
8228
8235
  return {
8229
8236
  diff: prDiff,
8230
8237
  createdAt: new Date(pr.created_at),
@@ -8344,18 +8351,15 @@ var GithubSCMLib = class extends SCMLib {
8344
8351
  return { added: 0, removed: 0 };
8345
8352
  }
8346
8353
  }
8347
- _calculateDiffLineAttributions(prDiff, commits) {
8348
- const attributions = [];
8349
- const prDiffLines = prDiff.split("\n");
8350
- let currentFile = "";
8354
+ /**
8355
+ * Optimized helper to parse added line numbers from a unified diff patch
8356
+ * Single-pass parsing for minimal CPU usage
8357
+ */
8358
+ _parseAddedLinesFromPatch(patch) {
8359
+ const addedLines = [];
8360
+ const lines = patch.split("\n");
8351
8361
  let currentLineNumber = 0;
8352
- for (const line of prDiffLines) {
8353
- if (line.startsWith("+++")) {
8354
- const match = line.match(/^\+\+\+ b\/(.+)$/);
8355
- currentFile = match?.[1] || "";
8356
- currentLineNumber = 0;
8357
- continue;
8358
- }
8362
+ for (const line of lines) {
8359
8363
  if (line.startsWith("@@")) {
8360
8364
  const match = line.match(/^@@ -\d+(?:,\d+)? \+(\d+)/);
8361
8365
  if (match?.[1]) {
@@ -8364,122 +8368,57 @@ var GithubSCMLib = class extends SCMLib {
8364
8368
  continue;
8365
8369
  }
8366
8370
  if (line.startsWith("+") && !line.startsWith("+++")) {
8367
- const commitSha = this._findCommitForLine(
8368
- currentFile,
8369
- currentLineNumber,
8370
- line.substring(1),
8371
- // Remove the '+' prefix
8372
- commits
8373
- );
8374
- if (commitSha && currentFile) {
8375
- attributions.push({
8376
- file: currentFile,
8377
- line: currentLineNumber,
8378
- commitSha
8379
- });
8380
- }
8371
+ addedLines.push(currentLineNumber);
8381
8372
  currentLineNumber++;
8382
8373
  } else if (!line.startsWith("-")) {
8383
8374
  currentLineNumber++;
8384
8375
  }
8385
8376
  }
8386
- return attributions;
8377
+ return addedLines;
8387
8378
  }
8388
- _findCommitForLine(file, lineNumber, lineContent, commits) {
8389
- const normalizedContent = lineContent.trim();
8390
- for (let i = commits.length - 1; i >= 0; i--) {
8391
- const commit = commits[i];
8392
- if (!commit) {
8393
- continue;
8394
- }
8395
- const commitLines = commit.diff.split("\n");
8396
- let commitCurrentFile = "";
8397
- for (const commitLine of commitLines) {
8398
- if (commitLine.startsWith("+++")) {
8399
- const match = commitLine.match(/^\+\+\+ b\/(.+)$/);
8400
- commitCurrentFile = match?.[1] || "";
8401
- continue;
8402
- }
8403
- if (commitLine.startsWith("@@")) {
8379
+ /**
8380
+ * Attribute lines in a single file to their commits using blame
8381
+ */
8382
+ async _attributeFileLines(file, headRef, prCommitShas) {
8383
+ try {
8384
+ const blame = await this.getRepoBlameRanges(headRef, file.filename);
8385
+ const addedLines = this._parseAddedLinesFromPatch(file.patch);
8386
+ const addedLinesSet = new Set(addedLines);
8387
+ const fileAttributions = [];
8388
+ for (const blameRange of blame) {
8389
+ if (!prCommitShas.has(blameRange.commitSha)) {
8404
8390
  continue;
8405
8391
  }
8406
- if (commitCurrentFile === file && commitLine.startsWith("+") && !commitLine.startsWith("+++")) {
8407
- const commitLineContent = commitLine.substring(1).trim();
8408
- if (commitLineContent === normalizedContent) {
8409
- return commit.commitSha;
8392
+ for (let lineNum = blameRange.startingLine; lineNum <= blameRange.endingLine; lineNum++) {
8393
+ if (addedLinesSet.has(lineNum)) {
8394
+ fileAttributions.push({
8395
+ file: file.filename,
8396
+ line: lineNum,
8397
+ commitSha: blameRange.commitSha
8398
+ });
8410
8399
  }
8411
8400
  }
8412
8401
  }
8402
+ return fileAttributions;
8403
+ } catch (error) {
8404
+ return [];
8413
8405
  }
8414
- for (let i = commits.length - 1; i >= 0; i--) {
8415
- const commit = commits[i];
8416
- if (!commit) {
8417
- continue;
8418
- }
8419
- const addedLinesInFile = this._getAddedLinesFromCommit(commit, file);
8420
- for (const { lineNum, content } of addedLinesInFile) {
8421
- if (Math.abs(lineNum - lineNumber) <= 10 && this._contentSimilarity(content, normalizedContent) > 0.9) {
8422
- return commit.commitSha;
8423
- }
8424
- }
8425
- }
8426
- const lastCommit = commits[commits.length - 1];
8427
- return lastCommit ? lastCommit.commitSha : null;
8428
- }
8429
- _getAddedLinesFromCommit(commit, targetFile) {
8430
- const addedLines = [];
8431
- const commitLines = commit.diff.split("\n");
8432
- let currentFile = "";
8433
- let currentLineNumber = 0;
8434
- for (const line of commitLines) {
8435
- if (line.startsWith("+++")) {
8436
- const match = line.match(/^\+\+\+ b\/(.+)$/);
8437
- currentFile = match?.[1] || "";
8438
- currentLineNumber = 0;
8439
- continue;
8440
- }
8441
- if (line.startsWith("@@")) {
8442
- const match = line.match(/^@@ -\d+(?:,\d+)? \+(\d+)/);
8443
- if (match?.[1]) {
8444
- currentLineNumber = parseInt(match[1], 10);
8445
- }
8446
- continue;
8447
- }
8448
- if (currentFile === targetFile && line.startsWith("+") && !line.startsWith("+++")) {
8449
- addedLines.push({
8450
- lineNum: currentLineNumber,
8451
- content: line.substring(1).trim()
8452
- });
8453
- currentLineNumber++;
8454
- } else if (!line.startsWith("-")) {
8455
- if (currentFile === targetFile) {
8456
- currentLineNumber++;
8457
- }
8458
- }
8459
- }
8460
- return addedLines;
8461
8406
  }
8462
- _contentSimilarity(content1, content2) {
8463
- if (content1 === content2) {
8464
- return 1;
8465
- }
8466
- const normalized1 = content1.replace(/\s+/g, " ");
8467
- const normalized2 = content2.replace(/\s+/g, " ");
8468
- if (normalized1 === normalized2) {
8469
- return 0.95;
8470
- }
8471
- const longer = normalized1.length > normalized2.length ? normalized1 : normalized2;
8472
- const shorter = normalized1.length > normalized2.length ? normalized2 : normalized1;
8473
- if (longer.length === 0) {
8474
- return 1;
8475
- }
8476
- if (longer.includes(shorter)) {
8477
- return shorter.length / longer.length;
8478
- }
8479
- const set1 = new Set(normalized1.split(""));
8480
- const set2 = new Set(normalized2.split(""));
8481
- const intersection = new Set([...set1].filter((x) => set2.has(x)));
8482
- return intersection.size / Math.max(set1.size, set2.size);
8407
+ /**
8408
+ * Optimized helper to attribute PR lines to commits using blame API
8409
+ * Parallel blame queries for minimal API call time
8410
+ */
8411
+ async _attributeLinesViaBlame(headRef, changedFiles, prCommits) {
8412
+ const prCommitShas = new Set(prCommits.map((c) => c.commitSha));
8413
+ const filesWithAdditions = changedFiles.filter(
8414
+ (file) => file.patch?.includes("\n+")
8415
+ );
8416
+ const attributions = await Promise.all(
8417
+ filesWithAdditions.map(
8418
+ (file) => this._attributeFileLines(file, headRef, prCommitShas)
8419
+ )
8420
+ );
8421
+ return attributions.flat();
8483
8422
  }
8484
8423
  };
8485
8424
 
@@ -8828,6 +8767,7 @@ async function getGitlabBlameRanges({ ref, gitlabUrl, path: path17 }, options) {
8828
8767
  return {
8829
8768
  startingLine: oldLineNumber,
8830
8769
  endingLine: lineNumber - 1,
8770
+ commitSha: range.commit.id,
8831
8771
  login: range.commit.author_email,
8832
8772
  email: range.commit.author_email,
8833
8773
  name: range.commit.author_name
@@ -19226,9 +19166,34 @@ async function addScmTokenHandler(args) {
19226
19166
  }
19227
19167
 
19228
19168
  // src/args/commands/upload_ai_blame.ts
19229
- import fs18 from "fs/promises";
19169
+ import fsPromises3 from "fs/promises";
19230
19170
  import path16 from "path";
19231
19171
  import chalk10 from "chalk";
19172
+ import { withFile } from "tmp-promise";
19173
+ import z38 from "zod";
19174
+ var PromptItemZ = z38.object({
19175
+ type: z38.enum(["USER_PROMPT", "AI_RESPONSE", "TOOL_EXECUTION", "AI_THINKING"]),
19176
+ attachedFiles: z38.array(
19177
+ z38.object({
19178
+ relativePath: z38.string(),
19179
+ startLine: z38.number().optional()
19180
+ })
19181
+ ).optional(),
19182
+ tokens: z38.object({
19183
+ inputCount: z38.number(),
19184
+ outputCount: z38.number()
19185
+ }).optional(),
19186
+ text: z38.string().optional(),
19187
+ date: z38.date().optional(),
19188
+ tool: z38.object({
19189
+ name: z38.string(),
19190
+ parameters: z38.string(),
19191
+ result: z38.string(),
19192
+ rawArguments: z38.string().optional(),
19193
+ accepted: z38.boolean().optional()
19194
+ }).optional()
19195
+ });
19196
+ var PromptItemArrayZ = z38.array(PromptItemZ);
19232
19197
  function uploadAiBlameBuilder(args) {
19233
19198
  return args.option("prompt", {
19234
19199
  type: "string",
@@ -19258,17 +19223,19 @@ function uploadAiBlameBuilder(args) {
19258
19223
  describe: chalk10.bold("Tool/IDE name(s) (optional, one per session)")
19259
19224
  }).strict();
19260
19225
  }
19261
- async function uploadAiBlameHandler(args) {
19226
+ async function uploadAiBlameHandler(args, exitOnError = true) {
19262
19227
  const prompts = args.prompt || [];
19263
19228
  const inferences = args.inference || [];
19264
19229
  const models = args.model || [];
19265
19230
  const tools = args.toolName || args["tool-name"] || [];
19266
19231
  const responseTimes = args.aiResponseAt || args["ai-response-at"] || [];
19267
19232
  if (prompts.length !== inferences.length) {
19268
- console.error(
19269
- chalk10.red("prompt and inference must have the same number of entries")
19270
- );
19271
- process.exit(1);
19233
+ const errorMsg = "prompt and inference must have the same number of entries";
19234
+ console.error(chalk10.red(errorMsg));
19235
+ if (exitOnError) {
19236
+ process.exit(1);
19237
+ }
19238
+ throw new Error(errorMsg);
19272
19239
  }
19273
19240
  const nowIso = (/* @__PURE__ */ new Date()).toISOString();
19274
19241
  const sessions = [];
@@ -19276,10 +19243,17 @@ async function uploadAiBlameHandler(args) {
19276
19243
  const promptPath = String(prompts[i]);
19277
19244
  const inferencePath = String(inferences[i]);
19278
19245
  try {
19279
- await Promise.all([fs18.access(promptPath), fs18.access(inferencePath)]);
19246
+ await Promise.all([
19247
+ fsPromises3.access(promptPath),
19248
+ fsPromises3.access(inferencePath)
19249
+ ]);
19280
19250
  } catch {
19281
- console.error(chalk10.red(`File not found for session ${i + 1}`));
19282
- process.exit(1);
19251
+ const errorMsg = `File not found for session ${i + 1}`;
19252
+ console.error(chalk10.red(errorMsg));
19253
+ if (exitOnError) {
19254
+ process.exit(1);
19255
+ }
19256
+ throw new Error(errorMsg);
19283
19257
  }
19284
19258
  sessions.push({
19285
19259
  promptFileName: path16.basename(promptPath),
@@ -19293,10 +19267,12 @@ async function uploadAiBlameHandler(args) {
19293
19267
  const initRes = await gqlClient.uploadAIBlameInferencesInitRaw({ sessions });
19294
19268
  const uploadSessions = initRes.uploadAIBlameInferencesInit?.uploadSessions ?? [];
19295
19269
  if (uploadSessions.length !== sessions.length) {
19296
- console.error(
19297
- chalk10.red("Init failed to return expected number of sessions")
19298
- );
19299
- process.exit(1);
19270
+ const errorMsg = "Init failed to return expected number of sessions";
19271
+ console.error(chalk10.red(errorMsg));
19272
+ if (exitOnError) {
19273
+ process.exit(1);
19274
+ }
19275
+ throw new Error(errorMsg);
19300
19276
  }
19301
19277
  for (let i = 0; i < uploadSessions.length; i++) {
19302
19278
  const us = uploadSessions[i];
@@ -19331,12 +19307,12 @@ async function uploadAiBlameHandler(args) {
19331
19307
  });
19332
19308
  const status = finRes?.finalizeAIBlameInferencesUpload?.status;
19333
19309
  if (status !== "OK") {
19334
- console.error(
19335
- chalk10.red(
19336
- `Finalize failed: ${finRes?.finalizeAIBlameInferencesUpload?.error || "unknown error"}`
19337
- )
19338
- );
19339
- process.exit(1);
19310
+ const errorMsg = finRes?.finalizeAIBlameInferencesUpload?.error || "unknown error";
19311
+ console.error(chalk10.red(errorMsg));
19312
+ if (exitOnError) {
19313
+ process.exit(1);
19314
+ }
19315
+ throw new Error(errorMsg);
19340
19316
  }
19341
19317
  console.log(chalk10.green("AI Blame uploads finalized successfully"));
19342
19318
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mobbdev",
3
- "version": "1.0.204",
3
+ "version": "1.0.206",
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",
@@ -92,6 +92,7 @@
92
92
  "snyk": "1.1300.0",
93
93
  "tar": "6.2.1",
94
94
  "tmp": "0.2.5",
95
+ "tmp-promise": "3.0.3",
95
96
  "undici": "6.21.3",
96
97
  "uuid": "11.1.0",
97
98
  "ws": "8.18.3",