mobbdev 1.4.9 → 1.4.11

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.
@@ -206,6 +206,25 @@ declare const PromptItemArrayZ: z.ZodArray<z.ZodObject<{
206
206
  }[] | undefined;
207
207
  }>, "many">;
208
208
  type PromptItemArray = z.infer<typeof PromptItemArrayZ>;
209
+ type RepoState = {
210
+ repositoryUrl: string | null;
211
+ branch: string | null;
212
+ commitSha: string | null;
213
+ };
214
+ /**
215
+ * Reads git state for tracy event attribution: repo URL, current branch, and
216
+ * HEAD commit SHA. Each field is read fresh — no caching across calls. Detached
217
+ * HEAD (rebase, bisect) returns `branch: null` rather than the literal string
218
+ * "HEAD" that `git rev-parse --abbrev-ref HEAD` would produce.
219
+ *
220
+ * Both the CLI daemon and the VS Code extension flow through the shared
221
+ * `GitService.getCurrentRepoState()` so detached-HEAD handling and SHA
222
+ * normalization stay in lockstep across the two clients.
223
+ *
224
+ * Never throws — non-existent dirs, missing git binaries, and unrecognized
225
+ * remotes all resolve to nulls so the daemon hot path can rely on a value.
226
+ */
227
+ declare function readRepoState(workingDir?: string): Promise<RepoState>;
209
228
  /**
210
229
  * Gets the normalized GitHub repository URL from the current working directory.
211
230
  * Returns null if not in a git repository or if not a GitHub repository.
@@ -265,4 +284,4 @@ type UploadAiBlameHandlerOptions = {
265
284
  declare function uploadAiBlameHandler(options: UploadAiBlameHandlerOptions): Promise<void>;
266
285
  declare function uploadAiBlameCommandHandler(args: UploadAiBlameOptions): Promise<void>;
267
286
 
268
- export { type PromptItem, type PromptItemArray, type UploadAiBlameOptions, type UploadAiBlameResult, getRepositoryUrl, getSystemInfo, uploadAiBlameBuilder, uploadAiBlameCommandHandler, uploadAiBlameHandler, uploadAiBlameHandlerFromExtension };
287
+ export { type PromptItem, type PromptItemArray, type RepoState, type UploadAiBlameOptions, type UploadAiBlameResult, getRepositoryUrl, getSystemInfo, readRepoState, uploadAiBlameBuilder, uploadAiBlameCommandHandler, uploadAiBlameHandler, uploadAiBlameHandlerFromExtension };
@@ -464,6 +464,9 @@ var init_client_generates = __esm({
464
464
  ... on FixData {
465
465
  patch
466
466
  patchOriginalEncodingBase64
467
+ questions {
468
+ name
469
+ }
467
470
  extraContext {
468
471
  extraContext {
469
472
  key
@@ -1342,7 +1345,7 @@ var init_shared = __esm({
1342
1345
 
1343
1346
  // src/features/analysis/scm/shared/src/types/fix.ts
1344
1347
  import { z as z2 } from "zod";
1345
- var PackageInfoZ, ManifestActionRequiredZ, ExtraContextInternalZ, FixExtraContextZ, PatchAndQuestionsZ, FixRatingZ, IssueSharedStateZ, FixSharedStateZ, FixQueryZ, FixPartsForFixScreenZ;
1348
+ var PackageInfoZ, ManifestActionRequiredZ, ExtraContextInternalZ, FixExtraContextZ, PatchAndQuestionsZ, FixRatingZ, IssueRatingZ, IssueSharedStateZ, FixSharedStateZ, FixQueryZ, FixPartsForFixScreenZ;
1346
1349
  var init_fix = __esm({
1347
1350
  "src/features/analysis/scm/shared/src/types/fix.ts"() {
1348
1351
  "use strict";
@@ -1403,6 +1406,15 @@ var init_fix = __esm({
1403
1406
  name: z2.string()
1404
1407
  })
1405
1408
  });
1409
+ IssueRatingZ = z2.object({
1410
+ voteScore: z2.number(),
1411
+ comment: z2.string().nullable().default(null),
1412
+ updatedDate: z2.string().nullable(),
1413
+ user: z2.object({
1414
+ email: z2.string(),
1415
+ name: z2.string()
1416
+ })
1417
+ });
1406
1418
  IssueSharedStateZ = z2.object({
1407
1419
  id: z2.string(),
1408
1420
  createdAt: z2.string(),
@@ -1412,7 +1424,8 @@ var init_fix = __esm({
1412
1424
  z2.object({
1413
1425
  url: z2.string()
1414
1426
  })
1415
- )
1427
+ ),
1428
+ issueRatings: z2.array(IssueRatingZ).default([])
1416
1429
  }).nullable();
1417
1430
  FixSharedStateZ = z2.object({
1418
1431
  state: z2.nativeEnum(Fix_State_Enum),
@@ -1518,7 +1531,7 @@ var init_analysis = __esm({
1518
1531
 
1519
1532
  // src/features/analysis/scm/shared/src/types/issue.ts
1520
1533
  import { z as z4 } from "zod";
1521
- var MAX_SOURCE_CODE_FILE_SIZE_IN_BYTES, VulnerabilityReportIssueSharedStateZ, BaseIssuePartsZ, FalsePositivePartsZ, IssuePartsWithFixZ, IssuePartsFpZ, GeneralIssueZ, IssuePartsZ, GetIssueIndexesZ, GetIssueScreenDataZ, IssueBucketZ, mapBucketTypeToCategory;
1534
+ var MAX_SOURCE_CODE_FILE_SIZE_IN_BYTES, VulnerabilityReportIssueRatingZ, VulnerabilityReportIssueSharedStateZ, BaseIssuePartsZ, FalsePositivePartsZ, IssuePartsWithFixZ, IssuePartsFpZ, GeneralIssueZ, IssuePartsZ, GetIssueIndexesZ, GetIssueScreenDataZ, IssueBucketZ, mapBucketTypeToCategory;
1522
1535
  var init_issue = __esm({
1523
1536
  "src/features/analysis/scm/shared/src/types/issue.ts"() {
1524
1537
  "use strict";
@@ -1527,6 +1540,15 @@ var init_issue = __esm({
1527
1540
  init_fix();
1528
1541
  init_shared();
1529
1542
  MAX_SOURCE_CODE_FILE_SIZE_IN_BYTES = 1e5;
1543
+ VulnerabilityReportIssueRatingZ = z4.object({
1544
+ voteScore: z4.number(),
1545
+ comment: z4.string().nullable().default(null),
1546
+ updatedDate: z4.string().nullable(),
1547
+ user: z4.object({
1548
+ email: z4.string(),
1549
+ name: z4.string()
1550
+ })
1551
+ });
1530
1552
  VulnerabilityReportIssueSharedStateZ = z4.object({
1531
1553
  id: z4.string().uuid(),
1532
1554
  createdAt: z4.string(),
@@ -1536,7 +1558,8 @@ var init_issue = __esm({
1536
1558
  z4.object({
1537
1559
  url: z4.string()
1538
1560
  })
1539
- )
1561
+ ),
1562
+ issueRatings: z4.array(VulnerabilityReportIssueRatingZ).default([])
1540
1563
  }).nullish();
1541
1564
  BaseIssuePartsZ = z4.object({
1542
1565
  id: z4.string().uuid(),
@@ -3845,6 +3868,31 @@ var init_GitService = __esm({
3845
3868
  throw new Error(errorMessage);
3846
3869
  }
3847
3870
  }
3871
+ /**
3872
+ * Reads `{ branch, commitSha }` for tracy event attribution. Detached-HEAD
3873
+ * (rebase, bisect, "open this commit") returns `branch: null` rather than
3874
+ * the literal string `"HEAD"` that `getCurrentBranch()` produces — that
3875
+ * literal would silently corrupt downstream branch dashboards.
3876
+ *
3877
+ * The two reads run in parallel so the wall-time cost is one `git`
3878
+ * round-trip rather than two. Never throws — failures resolve to nulls so
3879
+ * the daemon hot path can rely on a value, not an exception.
3880
+ */
3881
+ async getCurrentRepoState() {
3882
+ const branchPromise = this.git.raw(["symbolic-ref", "--short", "-q", "HEAD"]).then((s) => {
3883
+ const trimmed = s.trim();
3884
+ return trimmed.length > 0 ? trimmed : null;
3885
+ }).catch(() => null);
3886
+ const commitShaPromise = this.git.raw(["rev-parse", "HEAD"]).then((s) => {
3887
+ const trimmed = s.trim().toLowerCase();
3888
+ return /^[0-9a-f]{40}$/.test(trimmed) ? trimmed : null;
3889
+ }).catch(() => null);
3890
+ const [branch, commitSha] = await Promise.all([
3891
+ branchPromise,
3892
+ commitShaPromise
3893
+ ]);
3894
+ return { branch, commitSha };
3895
+ }
3848
3896
  /**
3849
3897
  * Gets both the current commit hash and current branch name
3850
3898
  */
@@ -8095,19 +8143,33 @@ var PromptItemZ = z27.object({
8095
8143
  }).optional()
8096
8144
  });
8097
8145
  var PromptItemArrayZ = z27.array(PromptItemZ);
8098
- async function getRepositoryUrl(workingDir) {
8146
+ var NULL_REPO_STATE = {
8147
+ repositoryUrl: null,
8148
+ branch: null,
8149
+ commitSha: null
8150
+ };
8151
+ async function readRepoState(workingDir) {
8152
+ const dir = workingDir ?? process.cwd();
8153
+ let gitService;
8099
8154
  try {
8100
- const gitService = new GitService(workingDir ?? process.cwd());
8101
- const isRepo = await gitService.isGitRepository();
8102
- if (!isRepo) {
8103
- return null;
8104
- }
8105
- const remoteUrl = await gitService.getRemoteUrl();
8106
- const parsed = parseScmURL(remoteUrl);
8107
- return parsed?.scmType && parsed.scmType !== "Unknown" ? remoteUrl : null;
8155
+ gitService = new GitService(dir);
8108
8156
  } catch {
8109
- return null;
8110
- }
8157
+ return NULL_REPO_STATE;
8158
+ }
8159
+ const repoStatePromise = gitService.getCurrentRepoState().catch(() => ({ branch: null, commitSha: null }));
8160
+ const repositoryUrlPromise = gitService.getRemoteUrl().then((url) => {
8161
+ if (!url) return null;
8162
+ const parsed = parseScmURL(url);
8163
+ return parsed?.scmType && parsed.scmType !== "Unknown" ? url : null;
8164
+ }).catch(() => null);
8165
+ const [{ branch, commitSha }, repositoryUrl] = await Promise.all([
8166
+ repoStatePromise,
8167
+ repositoryUrlPromise
8168
+ ]);
8169
+ return { repositoryUrl, branch, commitSha };
8170
+ }
8171
+ async function getRepositoryUrl(workingDir) {
8172
+ return (await readRepoState(workingDir)).repositoryUrl;
8111
8173
  }
8112
8174
  function getSystemInfo() {
8113
8175
  let userName;
@@ -8389,6 +8451,7 @@ async function uploadAiBlameCommandHandler(args) {
8389
8451
  export {
8390
8452
  getRepositoryUrl,
8391
8453
  getSystemInfo,
8454
+ readRepoState,
8392
8455
  uploadAiBlameBuilder,
8393
8456
  uploadAiBlameCommandHandler,
8394
8457
  uploadAiBlameHandler,
package/dist/index.mjs CHANGED
@@ -464,6 +464,9 @@ var init_client_generates = __esm({
464
464
  ... on FixData {
465
465
  patch
466
466
  patchOriginalEncodingBase64
467
+ questions {
468
+ name
469
+ }
467
470
  extraContext {
468
471
  extraContext {
469
472
  key
@@ -1563,7 +1566,7 @@ var init_shared = __esm({
1563
1566
 
1564
1567
  // src/features/analysis/scm/shared/src/types/fix.ts
1565
1568
  import { z as z7 } from "zod";
1566
- var PackageInfoZ, ManifestActionRequiredZ, ExtraContextInternalZ, FixExtraContextZ, PatchAndQuestionsZ, FixRatingZ, IssueSharedStateZ, FixSharedStateZ, FixQueryZ, FixPartsForFixScreenZ;
1569
+ var PackageInfoZ, ManifestActionRequiredZ, ExtraContextInternalZ, FixExtraContextZ, PatchAndQuestionsZ, FixRatingZ, IssueRatingZ, IssueSharedStateZ, FixSharedStateZ, FixQueryZ, FixPartsForFixScreenZ;
1567
1570
  var init_fix = __esm({
1568
1571
  "src/features/analysis/scm/shared/src/types/fix.ts"() {
1569
1572
  "use strict";
@@ -1624,6 +1627,15 @@ var init_fix = __esm({
1624
1627
  name: z7.string()
1625
1628
  })
1626
1629
  });
1630
+ IssueRatingZ = z7.object({
1631
+ voteScore: z7.number(),
1632
+ comment: z7.string().nullable().default(null),
1633
+ updatedDate: z7.string().nullable(),
1634
+ user: z7.object({
1635
+ email: z7.string(),
1636
+ name: z7.string()
1637
+ })
1638
+ });
1627
1639
  IssueSharedStateZ = z7.object({
1628
1640
  id: z7.string(),
1629
1641
  createdAt: z7.string(),
@@ -1633,7 +1645,8 @@ var init_fix = __esm({
1633
1645
  z7.object({
1634
1646
  url: z7.string()
1635
1647
  })
1636
- )
1648
+ ),
1649
+ issueRatings: z7.array(IssueRatingZ).default([])
1637
1650
  }).nullable();
1638
1651
  FixSharedStateZ = z7.object({
1639
1652
  state: z7.nativeEnum(Fix_State_Enum),
@@ -1739,7 +1752,7 @@ var init_analysis = __esm({
1739
1752
 
1740
1753
  // src/features/analysis/scm/shared/src/types/issue.ts
1741
1754
  import { z as z9 } from "zod";
1742
- var MAX_SOURCE_CODE_FILE_SIZE_IN_BYTES, VulnerabilityReportIssueSharedStateZ, BaseIssuePartsZ, FalsePositivePartsZ, IssuePartsWithFixZ, IssuePartsFpZ, GeneralIssueZ, IssuePartsZ, GetIssueIndexesZ, GetIssueScreenDataZ, IssueBucketZ, mapCategoryToBucket, mapBucketTypeToCategory;
1755
+ var MAX_SOURCE_CODE_FILE_SIZE_IN_BYTES, VulnerabilityReportIssueRatingZ, VulnerabilityReportIssueSharedStateZ, BaseIssuePartsZ, FalsePositivePartsZ, IssuePartsWithFixZ, IssuePartsFpZ, GeneralIssueZ, IssuePartsZ, GetIssueIndexesZ, GetIssueScreenDataZ, IssueBucketZ, mapCategoryToBucket, mapBucketTypeToCategory;
1743
1756
  var init_issue = __esm({
1744
1757
  "src/features/analysis/scm/shared/src/types/issue.ts"() {
1745
1758
  "use strict";
@@ -1748,6 +1761,15 @@ var init_issue = __esm({
1748
1761
  init_fix();
1749
1762
  init_shared();
1750
1763
  MAX_SOURCE_CODE_FILE_SIZE_IN_BYTES = 1e5;
1764
+ VulnerabilityReportIssueRatingZ = z9.object({
1765
+ voteScore: z9.number(),
1766
+ comment: z9.string().nullable().default(null),
1767
+ updatedDate: z9.string().nullable(),
1768
+ user: z9.object({
1769
+ email: z9.string(),
1770
+ name: z9.string()
1771
+ })
1772
+ });
1751
1773
  VulnerabilityReportIssueSharedStateZ = z9.object({
1752
1774
  id: z9.string().uuid(),
1753
1775
  createdAt: z9.string(),
@@ -1757,7 +1779,8 @@ var init_issue = __esm({
1757
1779
  z9.object({
1758
1780
  url: z9.string()
1759
1781
  })
1760
- )
1782
+ ),
1783
+ issueRatings: z9.array(VulnerabilityReportIssueRatingZ).default([])
1761
1784
  }).nullish();
1762
1785
  BaseIssuePartsZ = z9.object({
1763
1786
  id: z9.string().uuid(),
@@ -3897,6 +3920,31 @@ var init_GitService = __esm({
3897
3920
  throw new Error(errorMessage);
3898
3921
  }
3899
3922
  }
3923
+ /**
3924
+ * Reads `{ branch, commitSha }` for tracy event attribution. Detached-HEAD
3925
+ * (rebase, bisect, "open this commit") returns `branch: null` rather than
3926
+ * the literal string `"HEAD"` that `getCurrentBranch()` produces — that
3927
+ * literal would silently corrupt downstream branch dashboards.
3928
+ *
3929
+ * The two reads run in parallel so the wall-time cost is one `git`
3930
+ * round-trip rather than two. Never throws — failures resolve to nulls so
3931
+ * the daemon hot path can rely on a value, not an exception.
3932
+ */
3933
+ async getCurrentRepoState() {
3934
+ const branchPromise = this.git.raw(["symbolic-ref", "--short", "-q", "HEAD"]).then((s) => {
3935
+ const trimmed = s.trim();
3936
+ return trimmed.length > 0 ? trimmed : null;
3937
+ }).catch(() => null);
3938
+ const commitShaPromise = this.git.raw(["rev-parse", "HEAD"]).then((s) => {
3939
+ const trimmed = s.trim().toLowerCase();
3940
+ return /^[0-9a-f]{40}$/.test(trimmed) ? trimmed : null;
3941
+ }).catch(() => null);
3942
+ const [branch, commitSha] = await Promise.all([
3943
+ branchPromise,
3944
+ commitShaPromise
3945
+ ]);
3946
+ return { branch, commitSha };
3947
+ }
3900
3948
  /**
3901
3949
  * Gets both the current commit hash and current branch name
3902
3950
  */
@@ -9260,6 +9308,52 @@ async function executeBatchGraphQL(octokit, owner, repo, config2) {
9260
9308
  }
9261
9309
  function getGithubSdk(params = {}) {
9262
9310
  const octokit = getOctoKit(params);
9311
+ async function openPrWithFiles(params2) {
9312
+ const { owner, repo } = parseGithubOwnerAndRepo(params2.userRepoUrl);
9313
+ const { data: repository } = await octokit.rest.repos.get({ owner, repo });
9314
+ const defaultBranch = repository.default_branch;
9315
+ const baseSha = await octokit.rest.git.getRef({ owner, repo, ref: `heads/${defaultBranch}` }).then((r) => r.data.object.sha);
9316
+ await octokit.rest.git.createRef({
9317
+ owner,
9318
+ repo,
9319
+ ref: `refs/heads/${params2.branch}`,
9320
+ sha: baseSha
9321
+ });
9322
+ const tree = await octokit.rest.git.createTree({
9323
+ owner,
9324
+ repo,
9325
+ base_tree: baseSha,
9326
+ tree: params2.files.map((f) => ({
9327
+ path: f.path,
9328
+ mode: "100644",
9329
+ type: "blob",
9330
+ content: f.content
9331
+ }))
9332
+ });
9333
+ const commit = await octokit.rest.git.createCommit({
9334
+ owner,
9335
+ repo,
9336
+ message: params2.commitMessage ?? params2.title,
9337
+ tree: tree.data.sha,
9338
+ parents: [baseSha]
9339
+ });
9340
+ await octokit.rest.git.updateRef({
9341
+ owner,
9342
+ repo,
9343
+ ref: `heads/${params2.branch}`,
9344
+ sha: commit.data.sha
9345
+ });
9346
+ const pr = await octokit.rest.pulls.create({
9347
+ owner,
9348
+ repo,
9349
+ title: params2.title,
9350
+ head: params2.branch,
9351
+ ...params2.headRepo ? { head_repo: params2.headRepo } : {},
9352
+ body: safeBody(params2.body, MAX_GH_PR_BODY_LENGTH),
9353
+ base: defaultBranch
9354
+ });
9355
+ return { pull_request_url: pr.data.html_url };
9356
+ }
9263
9357
  return {
9264
9358
  async postPrComment(params2) {
9265
9359
  return octokit.request(POST_COMMENT_PATH, params2);
@@ -9556,86 +9650,26 @@ function getGithubSdk(params = {}) {
9556
9650
  async createPr(params2) {
9557
9651
  const { sourceRepoUrl, filesPaths, userRepoUrl, title, body } = params2;
9558
9652
  const { owner: sourceOwner, repo: sourceRepo } = parseGithubOwnerAndRepo(sourceRepoUrl);
9559
- const { owner, repo } = parseGithubOwnerAndRepo(userRepoUrl);
9560
- const [sourceFilePath, secondFilePath] = filesPaths;
9561
- const sourceFileContentResponse = await octokit.rest.repos.getContent({
9562
- owner: sourceOwner,
9563
- repo: sourceRepo,
9564
- path: `/${sourceFilePath}`
9565
- });
9566
- const { data: repository } = await octokit.rest.repos.get({ owner, repo });
9567
- const defaultBranch = repository.default_branch;
9568
- const newBranchName = `mobb/workflow-${Date.now()}`;
9569
- await octokit.rest.git.createRef({
9570
- owner,
9571
- repo,
9572
- ref: `refs/heads/${newBranchName}`,
9573
- sha: await octokit.rest.git.getRef({ owner, repo, ref: `heads/${defaultBranch}` }).then((response) => response.data.object.sha)
9574
- });
9575
- const decodedContent = Buffer.from(
9576
- // Check if file content exists and handle different response types
9577
- typeof sourceFileContentResponse.data === "object" && !Array.isArray(sourceFileContentResponse.data) && "content" in sourceFileContentResponse.data && typeof sourceFileContentResponse.data.content === "string" ? sourceFileContentResponse.data.content : "",
9578
- "base64"
9579
- ).toString("utf-8");
9580
- const tree = [
9581
- {
9582
- path: sourceFilePath,
9583
- mode: "100644",
9584
- type: "blob",
9585
- content: decodedContent
9586
- }
9587
- ];
9588
- if (secondFilePath) {
9589
- const secondFileContentResponse = await octokit.rest.repos.getContent({
9590
- owner: sourceOwner,
9591
- repo: sourceRepo,
9592
- path: `/${secondFilePath}`
9593
- });
9594
- const secondDecodedContent = Buffer.from(
9595
- // Check if file content exists and handle different response types
9596
- typeof secondFileContentResponse.data === "object" && !Array.isArray(secondFileContentResponse.data) && "content" in secondFileContentResponse.data && typeof secondFileContentResponse.data.content === "string" ? secondFileContentResponse.data.content : "",
9597
- "base64"
9598
- ).toString("utf-8");
9599
- tree.push({
9600
- path: secondFilePath,
9601
- mode: "100644",
9602
- type: "blob",
9603
- content: secondDecodedContent
9604
- });
9605
- }
9606
- const createTreeResponse = await octokit.rest.git.createTree({
9607
- owner,
9608
- repo,
9609
- base_tree: await octokit.rest.git.getRef({ owner, repo, ref: `heads/${defaultBranch}` }).then((response) => response.data.object.sha),
9610
- tree
9611
- });
9612
- const createCommitResponse = await octokit.rest.git.createCommit({
9613
- owner,
9614
- repo,
9615
- message: "Add new yaml file",
9616
- tree: createTreeResponse.data.sha,
9617
- parents: [
9618
- await octokit.rest.git.getRef({ owner, repo, ref: `heads/${defaultBranch}` }).then((response) => response.data.object.sha)
9619
- ]
9620
- });
9621
- await octokit.rest.git.updateRef({
9622
- owner,
9623
- repo,
9624
- ref: `heads/${newBranchName}`,
9625
- sha: createCommitResponse.data.sha
9626
- });
9627
- const createPRResponse = await octokit.rest.pulls.create({
9628
- owner,
9629
- repo,
9653
+ const files = await Promise.all(
9654
+ filesPaths.map(async (filePath) => {
9655
+ const response = await octokit.rest.repos.getContent({
9656
+ owner: sourceOwner,
9657
+ repo: sourceRepo,
9658
+ path: `/${filePath}`
9659
+ });
9660
+ const content = typeof response.data === "object" && !Array.isArray(response.data) && "content" in response.data && typeof response.data.content === "string" ? Buffer.from(response.data.content, "base64").toString("utf-8") : "";
9661
+ return { path: filePath, content };
9662
+ })
9663
+ );
9664
+ return openPrWithFiles({
9665
+ userRepoUrl,
9666
+ files,
9667
+ branch: `mobb/workflow-${Date.now()}`,
9630
9668
  title,
9631
- head: newBranchName,
9632
- head_repo: sourceRepo,
9633
- body: safeBody(body, MAX_GH_PR_BODY_LENGTH),
9634
- base: defaultBranch
9669
+ body,
9670
+ commitMessage: "Add new yaml file",
9671
+ headRepo: sourceRepo
9635
9672
  });
9636
- return {
9637
- pull_request_url: createPRResponse.data.html_url
9638
- };
9639
9673
  },
9640
9674
  async getGithubBranchList(repoUrl) {
9641
9675
  const { owner, repo } = parseGithubOwnerAndRepo(repoUrl);
@@ -9646,6 +9680,35 @@ function getGithubSdk(params = {}) {
9646
9680
  page: 1
9647
9681
  });
9648
9682
  },
9683
+ // T-500 — open a PR adding a single inline file. Used by the
9684
+ // openSecuritySkillPR resolver to deliver `.claude/skills/<slug>/SKILL.md`.
9685
+ async createPrWithContent(params2) {
9686
+ return openPrWithFiles({
9687
+ userRepoUrl: params2.userRepoUrl,
9688
+ files: [{ path: params2.filePath, content: params2.content }],
9689
+ branch: params2.branch,
9690
+ title: params2.title,
9691
+ body: params2.body
9692
+ });
9693
+ },
9694
+ // T-500 — best-effort branch cleanup for openSecuritySkillPR retry.
9695
+ // Swallows 422/404 so callers can call it unconditionally before
9696
+ // a fresh PR-creation attempt.
9697
+ async deleteBranchIfExists(params2) {
9698
+ const { owner, repo } = parseGithubOwnerAndRepo(params2.userRepoUrl);
9699
+ try {
9700
+ await octokit.rest.git.deleteRef({
9701
+ owner,
9702
+ repo,
9703
+ ref: `heads/${params2.branch}`
9704
+ });
9705
+ } catch (err) {
9706
+ if (err instanceof RequestError && (err.status === 422 || err.status === 404)) {
9707
+ return;
9708
+ }
9709
+ throw err;
9710
+ }
9711
+ },
9649
9712
  async createPullRequest(options) {
9650
9713
  const { owner, repo } = parseGithubOwnerAndRepo(options.repoUrl);
9651
9714
  return octokit.rest.pulls.create({
@@ -10016,6 +10079,20 @@ var GithubSCMLib = class extends SCMLib {
10016
10079
  });
10017
10080
  return { pull_request_url };
10018
10081
  }
10082
+ // T-500 — sibling of `createPullRequestWithNewFile` that takes inline
10083
+ // content rather than reading from a source repo. Used by
10084
+ // openSecuritySkillPR to deliver `.claude/skills/<slug>/SKILL.md`.
10085
+ async createPullRequestWithInlineFile(params) {
10086
+ const { pull_request_url } = await this.githubSdk.createPrWithContent(params);
10087
+ return { pull_request_url };
10088
+ }
10089
+ // T-500 — used by the openSecuritySkillPR resolver to clean up a
10090
+ // branch left behind by a prior failed PR-creation attempt before
10091
+ // retrying. Swallows missing-branch responses; only real network
10092
+ // errors propagate.
10093
+ async deleteBranchIfExists(params) {
10094
+ return this.githubSdk.deleteBranchIfExists(params);
10095
+ }
10019
10096
  async validateParams() {
10020
10097
  return await githubValidateParams(this.url, this.accessToken);
10021
10098
  }
@@ -14237,19 +14314,33 @@ var PromptItemZ = z27.object({
14237
14314
  }).optional()
14238
14315
  });
14239
14316
  var PromptItemArrayZ = z27.array(PromptItemZ);
14240
- async function getRepositoryUrl(workingDir) {
14317
+ var NULL_REPO_STATE = {
14318
+ repositoryUrl: null,
14319
+ branch: null,
14320
+ commitSha: null
14321
+ };
14322
+ async function readRepoState(workingDir) {
14323
+ const dir = workingDir ?? process.cwd();
14324
+ let gitService;
14241
14325
  try {
14242
- const gitService = new GitService(workingDir ?? process.cwd());
14243
- const isRepo = await gitService.isGitRepository();
14244
- if (!isRepo) {
14245
- return null;
14246
- }
14247
- const remoteUrl = await gitService.getRemoteUrl();
14248
- const parsed = parseScmURL(remoteUrl);
14249
- return parsed?.scmType && parsed.scmType !== "Unknown" ? remoteUrl : null;
14326
+ gitService = new GitService(dir);
14250
14327
  } catch {
14251
- return null;
14252
- }
14328
+ return NULL_REPO_STATE;
14329
+ }
14330
+ const repoStatePromise = gitService.getCurrentRepoState().catch(() => ({ branch: null, commitSha: null }));
14331
+ const repositoryUrlPromise = gitService.getRemoteUrl().then((url) => {
14332
+ if (!url) return null;
14333
+ const parsed = parseScmURL(url);
14334
+ return parsed?.scmType && parsed.scmType !== "Unknown" ? url : null;
14335
+ }).catch(() => null);
14336
+ const [{ branch, commitSha }, repositoryUrl] = await Promise.all([
14337
+ repoStatePromise,
14338
+ repositoryUrlPromise
14339
+ ]);
14340
+ return { repositoryUrl, branch, commitSha };
14341
+ }
14342
+ async function getRepositoryUrl(workingDir) {
14343
+ return (await readRepoState(workingDir)).repositoryUrl;
14253
14344
  }
14254
14345
  function getSystemInfo() {
14255
14346
  let userName;
@@ -14582,7 +14673,7 @@ async function prepareAndSendTracyRecords(client, rawRecords, workingDir, option
14582
14673
  const { computerName, userName } = getSystemInfo();
14583
14674
  const defaultClientVersion = packageJson.version;
14584
14675
  const shouldSanitize = options?.sanitize ?? true;
14585
- const defaultRepoUrl = rawRecords[0]?.repositoryUrl ? void 0 : await getRepositoryUrl(workingDir) ?? void 0;
14676
+ const defaults = workingDir != null ? await readRepoState(workingDir) : { repositoryUrl: null, branch: null, commitSha: null };
14586
14677
  debug10(
14587
14678
  "[step:sanitize] %s %d records",
14588
14679
  shouldSanitize ? "Sanitizing" : "Serializing",
@@ -14602,7 +14693,9 @@ async function prepareAndSendTracyRecords(client, rawRecords, workingDir, option
14602
14693
  const { rawData: _rawData, ...rest } = record;
14603
14694
  results.push({
14604
14695
  ...rest,
14605
- repositoryUrl: record.repositoryUrl ?? defaultRepoUrl,
14696
+ repositoryUrl: record.repositoryUrl ?? defaults.repositoryUrl ?? void 0,
14697
+ branch: record.branch ?? defaults.branch ?? void 0,
14698
+ commitSha: record.commitSha ?? defaults.commitSha ?? void 0,
14606
14699
  computerName,
14607
14700
  userName,
14608
14701
  clientVersion: record.clientVersion ?? defaultClientVersion
@@ -18740,6 +18833,8 @@ async function uploadContextRecords(opts) {
18740
18833
  now,
18741
18834
  platform: platform2,
18742
18835
  repositoryUrl,
18836
+ branch,
18837
+ commitSha,
18743
18838
  clientVersion,
18744
18839
  onFileError,
18745
18840
  onSkillError
@@ -18750,6 +18845,8 @@ async function uploadContextRecords(opts) {
18750
18845
  const limit = pLimit7(UPLOAD_CONCURRENCY);
18751
18846
  const extraFields = {
18752
18847
  ...repositoryUrl !== void 0 && { repositoryUrl },
18848
+ ...branch !== void 0 && { branch },
18849
+ ...commitSha !== void 0 && { commitSha },
18753
18850
  ...clientVersion !== void 0 && { clientVersion }
18754
18851
  };
18755
18852
  const tasks = [
@@ -18835,6 +18932,8 @@ async function runContextFileUploadPipeline(opts) {
18835
18932
  uploadFieldsJSON,
18836
18933
  keyPrefix,
18837
18934
  repositoryUrl,
18935
+ branch,
18936
+ commitSha,
18838
18937
  clientVersion,
18839
18938
  submitRecords,
18840
18939
  onFileError,
@@ -18857,6 +18956,8 @@ async function runContextFileUploadPipeline(opts) {
18857
18956
  now,
18858
18957
  platform: platform2,
18859
18958
  repositoryUrl,
18959
+ branch,
18960
+ commitSha,
18860
18961
  clientVersion,
18861
18962
  onFileError,
18862
18963
  onSkillError
@@ -19212,7 +19313,7 @@ function createLogger(config2) {
19212
19313
 
19213
19314
  // src/features/claude_code/hook_logger.ts
19214
19315
  var DD_RUM_TOKEN = true ? "pubf59c0182545bfb4c299175119f1abf9b" : "";
19215
- var CLI_VERSION = true ? "1.4.9" : "unknown";
19316
+ var CLI_VERSION = true ? "1.4.11" : "unknown";
19216
19317
  var NAMESPACE = "mobbdev-claude-code-hook-logs";
19217
19318
  var claudeCodeVersion;
19218
19319
  function buildDdTags() {
@@ -19679,6 +19780,7 @@ async function processTranscript(input, sessionStore, log2, maxEntries = DAEMON_
19679
19780
  }
19680
19781
  const cursorForModel = sessionStore.get(cursorKey);
19681
19782
  let lastSeenModel = cursorForModel?.lastModel ?? null;
19783
+ const sampledRepoState = await readRepoState(input.cwd);
19682
19784
  const records = entries.map((entry) => {
19683
19785
  const { _recordId, ...rawEntry } = entry;
19684
19786
  const message = rawEntry["message"];
@@ -19700,7 +19802,10 @@ async function processTranscript(input, sessionStore, log2, maxEntries = DAEMON_
19700
19802
  recordId: _recordId,
19701
19803
  recordTimestamp: entry.timestamp ?? (/* @__PURE__ */ new Date()).toISOString(),
19702
19804
  blameType: "CHAT" /* Chat */,
19703
- rawData: rawEntry
19805
+ rawData: rawEntry,
19806
+ repositoryUrl: sampledRepoState.repositoryUrl ?? void 0,
19807
+ branch: sampledRepoState.branch,
19808
+ commitSha: sampledRepoState.commitSha
19704
19809
  };
19705
19810
  });
19706
19811
  let totalRawDataBytes = 0;
@@ -20694,10 +20799,15 @@ var FixExtraContextResponseSchema = z33.object({
20694
20799
  extraContext: z33.array(UnstructuredFixExtraContextSchema),
20695
20800
  fixDescription: z33.string()
20696
20801
  });
20802
+ var FixQuestionSchema = z33.object({
20803
+ __typename: z33.literal("FixQuestion").optional(),
20804
+ name: z33.string()
20805
+ });
20697
20806
  var FixDataSchema = z33.object({
20698
20807
  __typename: z33.literal("FixData"),
20699
20808
  patch: z33.string(),
20700
20809
  patchOriginalEncodingBase64: z33.string(),
20810
+ questions: z33.array(FixQuestionSchema),
20701
20811
  extraContext: FixExtraContextResponseSchema
20702
20812
  });
20703
20813
  var GetFixNoFixErrorSchema = z33.object({
@@ -20789,6 +20899,48 @@ var GetLatestReportByRepoUrlResponseSchema = z33.object({
20789
20899
  expiredReport: z33.array(ExpiredReportSchema)
20790
20900
  });
20791
20901
 
20902
+ // src/mcp/services/InteractiveFixFilter.ts
20903
+ var isFilterDisabled = () => {
20904
+ const raw = process.env["MOBB_MCP_DISABLE_INTERACTIVE_FILTER"];
20905
+ return raw === "1" || raw === "true";
20906
+ };
20907
+ var isInteractiveFix = (fix) => {
20908
+ if (fix.patchAndQuestions.__typename !== "FixData") {
20909
+ return false;
20910
+ }
20911
+ return fix.patchAndQuestions.questions.length > 0;
20912
+ };
20913
+ var ruleIdFor = (fix) => fix.safeIssueType ?? "UNKNOWN";
20914
+ var countByRule = (ruleIds) => {
20915
+ const counts = {};
20916
+ for (const ruleId of ruleIds) {
20917
+ counts[ruleId] = (counts[ruleId] ?? 0) + 1;
20918
+ }
20919
+ return counts;
20920
+ };
20921
+ var partitionInteractiveFixes = (fixes) => {
20922
+ if (isFilterDisabled()) {
20923
+ return { applicableFixes: fixes, skippedRuleIds: [] };
20924
+ }
20925
+ const applicableFixes = [];
20926
+ const skippedRuleIds = [];
20927
+ for (const fix of fixes) {
20928
+ if (isInteractiveFix(fix)) {
20929
+ skippedRuleIds.push(ruleIdFor(fix));
20930
+ } else {
20931
+ applicableFixes.push(fix);
20932
+ }
20933
+ }
20934
+ if (skippedRuleIds.length > 0) {
20935
+ logInfo("[InteractiveFixFilter] Skipped interactive fixes", {
20936
+ totalFixes: fixes.length,
20937
+ skippedCount: skippedRuleIds.length,
20938
+ skippedByRule: countByRule(skippedRuleIds)
20939
+ });
20940
+ }
20941
+ return { applicableFixes, skippedRuleIds };
20942
+ };
20943
+
20792
20944
  // src/mcp/services/McpGQLClient.ts
20793
20945
  var McpGQLClient = class extends GQLClient {
20794
20946
  constructor(args) {
@@ -21071,7 +21223,7 @@ var McpGQLClient = class extends GQLClient {
21071
21223
  reportData,
21072
21224
  limit
21073
21225
  }) {
21074
- if (!reportData) return [];
21226
+ if (!reportData) return { applicableFixes: [], skippedRuleIds: [] };
21075
21227
  const reportMetadata = {
21076
21228
  id: reportData.id,
21077
21229
  organizationId: reportData.vulnerabilityReport?.project?.organizationId,
@@ -21107,7 +21259,12 @@ var McpGQLClient = class extends GQLClient {
21107
21259
  fixMap.set(fix.id, fixWithUrl);
21108
21260
  }
21109
21261
  }
21110
- return Array.from(fixMap.values()).slice(0, limit);
21262
+ const merged = Array.from(fixMap.values());
21263
+ const { applicableFixes, skippedRuleIds } = partitionInteractiveFixes(merged);
21264
+ return {
21265
+ applicableFixes: applicableFixes.slice(0, limit),
21266
+ skippedRuleIds
21267
+ };
21111
21268
  }
21112
21269
  async updateFixesDownloadStatus(fixIds) {
21113
21270
  if (fixIds.length > 0) {
@@ -21211,14 +21368,15 @@ var McpGQLClient = class extends GQLClient {
21211
21368
  reportCount: resp.fixReport?.length || 0
21212
21369
  });
21213
21370
  const latestReport = resp.fixReport?.[0] && FixReportSummarySchema.parse(resp.fixReport?.[0]);
21214
- const fixes = this.mergeUserAndSystemFixes({
21371
+ const { applicableFixes, skippedRuleIds } = this.mergeUserAndSystemFixes({
21215
21372
  reportData: latestReport,
21216
21373
  limit
21217
21374
  });
21218
21375
  return {
21219
21376
  fixReport: latestReport ? {
21220
21377
  ...latestReport,
21221
- fixes
21378
+ fixes: applicableFixes,
21379
+ skippedRuleIds
21222
21380
  } : null,
21223
21381
  expiredReport: resp.expiredReport?.[0] || null
21224
21382
  };
@@ -21288,13 +21446,17 @@ var McpGQLClient = class extends GQLClient {
21288
21446
  return null;
21289
21447
  }
21290
21448
  const latestReport = FixReportSummarySchema.parse(res.fixReport?.[0]);
21291
- const fixes = this.mergeUserAndSystemFixes({
21449
+ const { applicableFixes, skippedRuleIds } = this.mergeUserAndSystemFixes({
21292
21450
  reportData: latestReport,
21293
21451
  limit
21294
21452
  });
21295
- logDebug("[GraphQL] GetReportFixes response parsed", { fixes });
21453
+ logDebug("[GraphQL] GetReportFixes response parsed", {
21454
+ fixes: applicableFixes,
21455
+ skippedCount: skippedRuleIds.length
21456
+ });
21296
21457
  return {
21297
- fixes,
21458
+ fixes: applicableFixes,
21459
+ skippedRuleIds,
21298
21460
  totalCount: res.fixReport?.[0]?.filteredFixesCount?.aggregate?.count || 0,
21299
21461
  expiredReport: res.expiredReport?.[0] || null,
21300
21462
  fixReport: res.fixReport?.[0] ? {
@@ -24362,6 +24524,17 @@ function friendlyType(s) {
24362
24524
  }
24363
24525
  var noFixesReturnedForParameters = `No fixes returned for the given offset and limit parameters.
24364
24526
  `;
24527
+ var skippedInteractiveFixesNotice = (skippedCount) => {
24528
+ if (skippedCount <= 0) return "";
24529
+ const s = skippedCount === 1 ? "" : "es";
24530
+ const verb = skippedCount === 1 ? "requires" : "require";
24531
+ const wasWere = skippedCount === 1 ? "was" : "were";
24532
+ return `
24533
+ ## Skipped fixes
24534
+
24535
+ ${skippedCount} fix${s} ${verb} user input that is not available over MCP and ${wasWere} skipped. Mention this to the user when summarizing results.
24536
+ `;
24537
+ };
24365
24538
  var noFixesReturnedForParametersWithGuidance = ({
24366
24539
  offset,
24367
24540
  limit,
@@ -24608,11 +24781,12 @@ var fixesFoundPrompt = ({
24608
24781
  fixReport,
24609
24782
  offset,
24610
24783
  limit,
24611
- gqlClient
24784
+ gqlClient,
24785
+ skippedInteractiveCount = 0
24612
24786
  }) => {
24613
24787
  const totalFixes = fixReport.filteredFixesCount.aggregate?.count || 0;
24614
24788
  if (totalFixes === 0) {
24615
- return noFixesAvailablePrompt;
24789
+ return noFixesAvailablePrompt + skippedInteractiveFixesNotice(skippedInteractiveCount);
24616
24790
  }
24617
24791
  const criticalFixes = fixReport.CRITICAL?.aggregate?.count || 0;
24618
24792
  const highFixes = fixReport.HIGH?.aggregate?.count || 0;
@@ -24655,7 +24829,7 @@ ${applyFixesPrompt({
24655
24829
  offset,
24656
24830
  limit,
24657
24831
  gqlClient
24658
- })}`;
24832
+ })}${skippedInteractiveFixesNotice(skippedInteractiveCount)}`;
24659
24833
  };
24660
24834
  var nextStepsPrompt = ({ scannedFiles }) => `
24661
24835
  ### \u{1F4C1} Scanned Files
@@ -24701,10 +24875,11 @@ var fixesPrompt = ({
24701
24875
  offset,
24702
24876
  scannedFiles,
24703
24877
  limit,
24704
- gqlClient
24878
+ gqlClient,
24879
+ skippedInteractiveCount = 0
24705
24880
  }) => {
24706
24881
  if (totalCount === 0) {
24707
- return noFixesFoundPrompt({ scannedFiles });
24882
+ return noFixesFoundPrompt({ scannedFiles }) + skippedInteractiveFixesNotice(skippedInteractiveCount);
24708
24883
  }
24709
24884
  const shownCount = fixes.length;
24710
24885
  const nextOffset = offset + shownCount;
@@ -24722,7 +24897,7 @@ ${applyFixesPrompt({
24722
24897
  limit,
24723
24898
  gqlClient
24724
24899
  })}
24725
-
24900
+ ${skippedInteractiveFixesNotice(skippedInteractiveCount)}
24726
24901
  ${nextStepsPrompt({ scannedFiles })}
24727
24902
  `;
24728
24903
  };
@@ -24795,7 +24970,8 @@ For assistance:
24795
24970
  var freshFixesPrompt = ({
24796
24971
  fixes,
24797
24972
  limit,
24798
- gqlClient
24973
+ gqlClient,
24974
+ skippedInteractiveCount = 0
24799
24975
  }) => {
24800
24976
  return `Here are the fresh fixes to the vulnerabilities discovered by Mobb MCP
24801
24977
 
@@ -24810,6 +24986,7 @@ ${applyFixesPrompt({
24810
24986
  limit,
24811
24987
  gqlClient
24812
24988
  })}
24989
+ ${skippedInteractiveFixesNotice(skippedInteractiveCount)}
24813
24990
  `;
24814
24991
  };
24815
24992
  function extractTargetFileFromPatch(patch) {
@@ -24828,7 +25005,8 @@ function formatSeverity(severityText, severityValue) {
24828
25005
  }
24829
25006
  var appliedFixesSummaryPrompt = ({
24830
25007
  fixes,
24831
- gqlClient
25008
+ gqlClient,
25009
+ skippedInteractiveCount = 0
24832
25010
  }) => {
24833
25011
  const fixIds = fixes.map((fix) => fix.id);
24834
25012
  void gqlClient.updateFixesDownloadStatus(fixIds);
@@ -24863,11 +25041,11 @@ ${fixes.map((fix, index) => {
24863
25041
  ${continuousMonitoringSection}
24864
25042
 
24865
25043
  ${autoFixSettingsSection}
24866
-
25044
+ ${skippedInteractiveFixesNotice(skippedInteractiveCount)}
24867
25045
  ## \u{1F4CB} Next Steps
24868
25046
 
24869
25047
  1. **Review the changes** - Check the modified files to understand what was fixed
24870
- 2. **Test your application** - Ensure the fixes don't break existing functionality
25048
+ 2. **Test your application** - Ensure the fixes don't break existing functionality
24871
25049
  3. **Commit the changes** - Add and commit the security fixes to your repository
24872
25050
  4. **Continue coding** - Mobb will keep protecting your code automatically
24873
25051
 
@@ -27841,7 +28019,8 @@ var _FetchAvailableFixesService = class _FetchAvailableFixesService {
27841
28019
  fixReport,
27842
28020
  offset: effectiveOffset,
27843
28021
  limit,
27844
- gqlClient
28022
+ gqlClient,
28023
+ skippedInteractiveCount: fixReport.skippedRuleIds?.length ?? 0
27845
28024
  });
27846
28025
  this.currentOffset = effectiveOffset + (fixReport.fixes?.length || 0);
27847
28026
  return prompt;
@@ -28176,7 +28355,8 @@ var _ScanAndFixVulnerabilitiesService = class _ScanAndFixVulnerabilitiesService
28176
28355
  offset: effectiveOffset,
28177
28356
  scannedFiles: [...fileList],
28178
28357
  limit: effectiveLimit,
28179
- gqlClient: this.gqlClient
28358
+ gqlClient: this.gqlClient,
28359
+ skippedInteractiveCount: fixes.skippedRuleIds.length
28180
28360
  });
28181
28361
  this.currentOffset = effectiveOffset + (fixes.fixes?.length || 0);
28182
28362
  return prompt;
@@ -28220,7 +28400,8 @@ var _ScanAndFixVulnerabilitiesService = class _ScanAndFixVulnerabilitiesService
28220
28400
  logDebug(`${fixes?.fixes?.length} fixes retrieved`);
28221
28401
  return {
28222
28402
  fixes: fixes?.fixes || [],
28223
- totalCount: fixes?.totalCount || 0
28403
+ totalCount: fixes?.totalCount || 0,
28404
+ skippedRuleIds: fixes?.skippedRuleIds || []
28224
28405
  };
28225
28406
  }
28226
28407
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mobbdev",
3
- "version": "1.4.9",
3
+ "version": "1.4.11",
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",