mobbdev 1.4.10 → 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
@@ -3865,6 +3868,31 @@ var init_GitService = __esm({
3865
3868
  throw new Error(errorMessage);
3866
3869
  }
3867
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
+ }
3868
3896
  /**
3869
3897
  * Gets both the current commit hash and current branch name
3870
3898
  */
@@ -8115,19 +8143,33 @@ var PromptItemZ = z27.object({
8115
8143
  }).optional()
8116
8144
  });
8117
8145
  var PromptItemArrayZ = z27.array(PromptItemZ);
8118
- 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;
8119
8154
  try {
8120
- const gitService = new GitService(workingDir ?? process.cwd());
8121
- const isRepo = await gitService.isGitRepository();
8122
- if (!isRepo) {
8123
- return null;
8124
- }
8125
- const remoteUrl = await gitService.getRemoteUrl();
8126
- const parsed = parseScmURL(remoteUrl);
8127
- return parsed?.scmType && parsed.scmType !== "Unknown" ? remoteUrl : null;
8155
+ gitService = new GitService(dir);
8128
8156
  } catch {
8129
- return null;
8130
- }
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;
8131
8173
  }
8132
8174
  function getSystemInfo() {
8133
8175
  let userName;
@@ -8409,6 +8451,7 @@ async function uploadAiBlameCommandHandler(args) {
8409
8451
  export {
8410
8452
  getRepositoryUrl,
8411
8453
  getSystemInfo,
8454
+ readRepoState,
8412
8455
  uploadAiBlameBuilder,
8413
8456
  uploadAiBlameCommandHandler,
8414
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
@@ -3917,6 +3920,31 @@ var init_GitService = __esm({
3917
3920
  throw new Error(errorMessage);
3918
3921
  }
3919
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
+ }
3920
3948
  /**
3921
3949
  * Gets both the current commit hash and current branch name
3922
3950
  */
@@ -9280,6 +9308,52 @@ async function executeBatchGraphQL(octokit, owner, repo, config2) {
9280
9308
  }
9281
9309
  function getGithubSdk(params = {}) {
9282
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
+ }
9283
9357
  return {
9284
9358
  async postPrComment(params2) {
9285
9359
  return octokit.request(POST_COMMENT_PATH, params2);
@@ -9576,86 +9650,26 @@ function getGithubSdk(params = {}) {
9576
9650
  async createPr(params2) {
9577
9651
  const { sourceRepoUrl, filesPaths, userRepoUrl, title, body } = params2;
9578
9652
  const { owner: sourceOwner, repo: sourceRepo } = parseGithubOwnerAndRepo(sourceRepoUrl);
9579
- const { owner, repo } = parseGithubOwnerAndRepo(userRepoUrl);
9580
- const [sourceFilePath, secondFilePath] = filesPaths;
9581
- const sourceFileContentResponse = await octokit.rest.repos.getContent({
9582
- owner: sourceOwner,
9583
- repo: sourceRepo,
9584
- path: `/${sourceFilePath}`
9585
- });
9586
- const { data: repository } = await octokit.rest.repos.get({ owner, repo });
9587
- const defaultBranch = repository.default_branch;
9588
- const newBranchName = `mobb/workflow-${Date.now()}`;
9589
- await octokit.rest.git.createRef({
9590
- owner,
9591
- repo,
9592
- ref: `refs/heads/${newBranchName}`,
9593
- sha: await octokit.rest.git.getRef({ owner, repo, ref: `heads/${defaultBranch}` }).then((response) => response.data.object.sha)
9594
- });
9595
- const decodedContent = Buffer.from(
9596
- // Check if file content exists and handle different response types
9597
- typeof sourceFileContentResponse.data === "object" && !Array.isArray(sourceFileContentResponse.data) && "content" in sourceFileContentResponse.data && typeof sourceFileContentResponse.data.content === "string" ? sourceFileContentResponse.data.content : "",
9598
- "base64"
9599
- ).toString("utf-8");
9600
- const tree = [
9601
- {
9602
- path: sourceFilePath,
9603
- mode: "100644",
9604
- type: "blob",
9605
- content: decodedContent
9606
- }
9607
- ];
9608
- if (secondFilePath) {
9609
- const secondFileContentResponse = await octokit.rest.repos.getContent({
9610
- owner: sourceOwner,
9611
- repo: sourceRepo,
9612
- path: `/${secondFilePath}`
9613
- });
9614
- const secondDecodedContent = Buffer.from(
9615
- // Check if file content exists and handle different response types
9616
- typeof secondFileContentResponse.data === "object" && !Array.isArray(secondFileContentResponse.data) && "content" in secondFileContentResponse.data && typeof secondFileContentResponse.data.content === "string" ? secondFileContentResponse.data.content : "",
9617
- "base64"
9618
- ).toString("utf-8");
9619
- tree.push({
9620
- path: secondFilePath,
9621
- mode: "100644",
9622
- type: "blob",
9623
- content: secondDecodedContent
9624
- });
9625
- }
9626
- const createTreeResponse = await octokit.rest.git.createTree({
9627
- owner,
9628
- repo,
9629
- base_tree: await octokit.rest.git.getRef({ owner, repo, ref: `heads/${defaultBranch}` }).then((response) => response.data.object.sha),
9630
- tree
9631
- });
9632
- const createCommitResponse = await octokit.rest.git.createCommit({
9633
- owner,
9634
- repo,
9635
- message: "Add new yaml file",
9636
- tree: createTreeResponse.data.sha,
9637
- parents: [
9638
- await octokit.rest.git.getRef({ owner, repo, ref: `heads/${defaultBranch}` }).then((response) => response.data.object.sha)
9639
- ]
9640
- });
9641
- await octokit.rest.git.updateRef({
9642
- owner,
9643
- repo,
9644
- ref: `heads/${newBranchName}`,
9645
- sha: createCommitResponse.data.sha
9646
- });
9647
- const createPRResponse = await octokit.rest.pulls.create({
9648
- owner,
9649
- 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()}`,
9650
9668
  title,
9651
- head: newBranchName,
9652
- head_repo: sourceRepo,
9653
- body: safeBody(body, MAX_GH_PR_BODY_LENGTH),
9654
- base: defaultBranch
9669
+ body,
9670
+ commitMessage: "Add new yaml file",
9671
+ headRepo: sourceRepo
9655
9672
  });
9656
- return {
9657
- pull_request_url: createPRResponse.data.html_url
9658
- };
9659
9673
  },
9660
9674
  async getGithubBranchList(repoUrl) {
9661
9675
  const { owner, repo } = parseGithubOwnerAndRepo(repoUrl);
@@ -9666,6 +9680,35 @@ function getGithubSdk(params = {}) {
9666
9680
  page: 1
9667
9681
  });
9668
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
+ },
9669
9712
  async createPullRequest(options) {
9670
9713
  const { owner, repo } = parseGithubOwnerAndRepo(options.repoUrl);
9671
9714
  return octokit.rest.pulls.create({
@@ -10036,6 +10079,20 @@ var GithubSCMLib = class extends SCMLib {
10036
10079
  });
10037
10080
  return { pull_request_url };
10038
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
+ }
10039
10096
  async validateParams() {
10040
10097
  return await githubValidateParams(this.url, this.accessToken);
10041
10098
  }
@@ -14257,19 +14314,33 @@ var PromptItemZ = z27.object({
14257
14314
  }).optional()
14258
14315
  });
14259
14316
  var PromptItemArrayZ = z27.array(PromptItemZ);
14260
- 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;
14261
14325
  try {
14262
- const gitService = new GitService(workingDir ?? process.cwd());
14263
- const isRepo = await gitService.isGitRepository();
14264
- if (!isRepo) {
14265
- return null;
14266
- }
14267
- const remoteUrl = await gitService.getRemoteUrl();
14268
- const parsed = parseScmURL(remoteUrl);
14269
- return parsed?.scmType && parsed.scmType !== "Unknown" ? remoteUrl : null;
14326
+ gitService = new GitService(dir);
14270
14327
  } catch {
14271
- return null;
14272
- }
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;
14273
14344
  }
14274
14345
  function getSystemInfo() {
14275
14346
  let userName;
@@ -14602,7 +14673,7 @@ async function prepareAndSendTracyRecords(client, rawRecords, workingDir, option
14602
14673
  const { computerName, userName } = getSystemInfo();
14603
14674
  const defaultClientVersion = packageJson.version;
14604
14675
  const shouldSanitize = options?.sanitize ?? true;
14605
- 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 };
14606
14677
  debug10(
14607
14678
  "[step:sanitize] %s %d records",
14608
14679
  shouldSanitize ? "Sanitizing" : "Serializing",
@@ -14622,7 +14693,9 @@ async function prepareAndSendTracyRecords(client, rawRecords, workingDir, option
14622
14693
  const { rawData: _rawData, ...rest } = record;
14623
14694
  results.push({
14624
14695
  ...rest,
14625
- 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,
14626
14699
  computerName,
14627
14700
  userName,
14628
14701
  clientVersion: record.clientVersion ?? defaultClientVersion
@@ -18760,6 +18833,8 @@ async function uploadContextRecords(opts) {
18760
18833
  now,
18761
18834
  platform: platform2,
18762
18835
  repositoryUrl,
18836
+ branch,
18837
+ commitSha,
18763
18838
  clientVersion,
18764
18839
  onFileError,
18765
18840
  onSkillError
@@ -18770,6 +18845,8 @@ async function uploadContextRecords(opts) {
18770
18845
  const limit = pLimit7(UPLOAD_CONCURRENCY);
18771
18846
  const extraFields = {
18772
18847
  ...repositoryUrl !== void 0 && { repositoryUrl },
18848
+ ...branch !== void 0 && { branch },
18849
+ ...commitSha !== void 0 && { commitSha },
18773
18850
  ...clientVersion !== void 0 && { clientVersion }
18774
18851
  };
18775
18852
  const tasks = [
@@ -18855,6 +18932,8 @@ async function runContextFileUploadPipeline(opts) {
18855
18932
  uploadFieldsJSON,
18856
18933
  keyPrefix,
18857
18934
  repositoryUrl,
18935
+ branch,
18936
+ commitSha,
18858
18937
  clientVersion,
18859
18938
  submitRecords,
18860
18939
  onFileError,
@@ -18877,6 +18956,8 @@ async function runContextFileUploadPipeline(opts) {
18877
18956
  now,
18878
18957
  platform: platform2,
18879
18958
  repositoryUrl,
18959
+ branch,
18960
+ commitSha,
18880
18961
  clientVersion,
18881
18962
  onFileError,
18882
18963
  onSkillError
@@ -19232,7 +19313,7 @@ function createLogger(config2) {
19232
19313
 
19233
19314
  // src/features/claude_code/hook_logger.ts
19234
19315
  var DD_RUM_TOKEN = true ? "pubf59c0182545bfb4c299175119f1abf9b" : "";
19235
- var CLI_VERSION = true ? "1.4.10" : "unknown";
19316
+ var CLI_VERSION = true ? "1.4.11" : "unknown";
19236
19317
  var NAMESPACE = "mobbdev-claude-code-hook-logs";
19237
19318
  var claudeCodeVersion;
19238
19319
  function buildDdTags() {
@@ -19699,6 +19780,7 @@ async function processTranscript(input, sessionStore, log2, maxEntries = DAEMON_
19699
19780
  }
19700
19781
  const cursorForModel = sessionStore.get(cursorKey);
19701
19782
  let lastSeenModel = cursorForModel?.lastModel ?? null;
19783
+ const sampledRepoState = await readRepoState(input.cwd);
19702
19784
  const records = entries.map((entry) => {
19703
19785
  const { _recordId, ...rawEntry } = entry;
19704
19786
  const message = rawEntry["message"];
@@ -19720,7 +19802,10 @@ async function processTranscript(input, sessionStore, log2, maxEntries = DAEMON_
19720
19802
  recordId: _recordId,
19721
19803
  recordTimestamp: entry.timestamp ?? (/* @__PURE__ */ new Date()).toISOString(),
19722
19804
  blameType: "CHAT" /* Chat */,
19723
- rawData: rawEntry
19805
+ rawData: rawEntry,
19806
+ repositoryUrl: sampledRepoState.repositoryUrl ?? void 0,
19807
+ branch: sampledRepoState.branch,
19808
+ commitSha: sampledRepoState.commitSha
19724
19809
  };
19725
19810
  });
19726
19811
  let totalRawDataBytes = 0;
@@ -20714,10 +20799,15 @@ var FixExtraContextResponseSchema = z33.object({
20714
20799
  extraContext: z33.array(UnstructuredFixExtraContextSchema),
20715
20800
  fixDescription: z33.string()
20716
20801
  });
20802
+ var FixQuestionSchema = z33.object({
20803
+ __typename: z33.literal("FixQuestion").optional(),
20804
+ name: z33.string()
20805
+ });
20717
20806
  var FixDataSchema = z33.object({
20718
20807
  __typename: z33.literal("FixData"),
20719
20808
  patch: z33.string(),
20720
20809
  patchOriginalEncodingBase64: z33.string(),
20810
+ questions: z33.array(FixQuestionSchema),
20721
20811
  extraContext: FixExtraContextResponseSchema
20722
20812
  });
20723
20813
  var GetFixNoFixErrorSchema = z33.object({
@@ -20809,6 +20899,48 @@ var GetLatestReportByRepoUrlResponseSchema = z33.object({
20809
20899
  expiredReport: z33.array(ExpiredReportSchema)
20810
20900
  });
20811
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
+
20812
20944
  // src/mcp/services/McpGQLClient.ts
20813
20945
  var McpGQLClient = class extends GQLClient {
20814
20946
  constructor(args) {
@@ -21091,7 +21223,7 @@ var McpGQLClient = class extends GQLClient {
21091
21223
  reportData,
21092
21224
  limit
21093
21225
  }) {
21094
- if (!reportData) return [];
21226
+ if (!reportData) return { applicableFixes: [], skippedRuleIds: [] };
21095
21227
  const reportMetadata = {
21096
21228
  id: reportData.id,
21097
21229
  organizationId: reportData.vulnerabilityReport?.project?.organizationId,
@@ -21127,7 +21259,12 @@ var McpGQLClient = class extends GQLClient {
21127
21259
  fixMap.set(fix.id, fixWithUrl);
21128
21260
  }
21129
21261
  }
21130
- 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
+ };
21131
21268
  }
21132
21269
  async updateFixesDownloadStatus(fixIds) {
21133
21270
  if (fixIds.length > 0) {
@@ -21231,14 +21368,15 @@ var McpGQLClient = class extends GQLClient {
21231
21368
  reportCount: resp.fixReport?.length || 0
21232
21369
  });
21233
21370
  const latestReport = resp.fixReport?.[0] && FixReportSummarySchema.parse(resp.fixReport?.[0]);
21234
- const fixes = this.mergeUserAndSystemFixes({
21371
+ const { applicableFixes, skippedRuleIds } = this.mergeUserAndSystemFixes({
21235
21372
  reportData: latestReport,
21236
21373
  limit
21237
21374
  });
21238
21375
  return {
21239
21376
  fixReport: latestReport ? {
21240
21377
  ...latestReport,
21241
- fixes
21378
+ fixes: applicableFixes,
21379
+ skippedRuleIds
21242
21380
  } : null,
21243
21381
  expiredReport: resp.expiredReport?.[0] || null
21244
21382
  };
@@ -21308,13 +21446,17 @@ var McpGQLClient = class extends GQLClient {
21308
21446
  return null;
21309
21447
  }
21310
21448
  const latestReport = FixReportSummarySchema.parse(res.fixReport?.[0]);
21311
- const fixes = this.mergeUserAndSystemFixes({
21449
+ const { applicableFixes, skippedRuleIds } = this.mergeUserAndSystemFixes({
21312
21450
  reportData: latestReport,
21313
21451
  limit
21314
21452
  });
21315
- logDebug("[GraphQL] GetReportFixes response parsed", { fixes });
21453
+ logDebug("[GraphQL] GetReportFixes response parsed", {
21454
+ fixes: applicableFixes,
21455
+ skippedCount: skippedRuleIds.length
21456
+ });
21316
21457
  return {
21317
- fixes,
21458
+ fixes: applicableFixes,
21459
+ skippedRuleIds,
21318
21460
  totalCount: res.fixReport?.[0]?.filteredFixesCount?.aggregate?.count || 0,
21319
21461
  expiredReport: res.expiredReport?.[0] || null,
21320
21462
  fixReport: res.fixReport?.[0] ? {
@@ -24382,6 +24524,17 @@ function friendlyType(s) {
24382
24524
  }
24383
24525
  var noFixesReturnedForParameters = `No fixes returned for the given offset and limit parameters.
24384
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
+ };
24385
24538
  var noFixesReturnedForParametersWithGuidance = ({
24386
24539
  offset,
24387
24540
  limit,
@@ -24628,11 +24781,12 @@ var fixesFoundPrompt = ({
24628
24781
  fixReport,
24629
24782
  offset,
24630
24783
  limit,
24631
- gqlClient
24784
+ gqlClient,
24785
+ skippedInteractiveCount = 0
24632
24786
  }) => {
24633
24787
  const totalFixes = fixReport.filteredFixesCount.aggregate?.count || 0;
24634
24788
  if (totalFixes === 0) {
24635
- return noFixesAvailablePrompt;
24789
+ return noFixesAvailablePrompt + skippedInteractiveFixesNotice(skippedInteractiveCount);
24636
24790
  }
24637
24791
  const criticalFixes = fixReport.CRITICAL?.aggregate?.count || 0;
24638
24792
  const highFixes = fixReport.HIGH?.aggregate?.count || 0;
@@ -24675,7 +24829,7 @@ ${applyFixesPrompt({
24675
24829
  offset,
24676
24830
  limit,
24677
24831
  gqlClient
24678
- })}`;
24832
+ })}${skippedInteractiveFixesNotice(skippedInteractiveCount)}`;
24679
24833
  };
24680
24834
  var nextStepsPrompt = ({ scannedFiles }) => `
24681
24835
  ### \u{1F4C1} Scanned Files
@@ -24721,10 +24875,11 @@ var fixesPrompt = ({
24721
24875
  offset,
24722
24876
  scannedFiles,
24723
24877
  limit,
24724
- gqlClient
24878
+ gqlClient,
24879
+ skippedInteractiveCount = 0
24725
24880
  }) => {
24726
24881
  if (totalCount === 0) {
24727
- return noFixesFoundPrompt({ scannedFiles });
24882
+ return noFixesFoundPrompt({ scannedFiles }) + skippedInteractiveFixesNotice(skippedInteractiveCount);
24728
24883
  }
24729
24884
  const shownCount = fixes.length;
24730
24885
  const nextOffset = offset + shownCount;
@@ -24742,7 +24897,7 @@ ${applyFixesPrompt({
24742
24897
  limit,
24743
24898
  gqlClient
24744
24899
  })}
24745
-
24900
+ ${skippedInteractiveFixesNotice(skippedInteractiveCount)}
24746
24901
  ${nextStepsPrompt({ scannedFiles })}
24747
24902
  `;
24748
24903
  };
@@ -24815,7 +24970,8 @@ For assistance:
24815
24970
  var freshFixesPrompt = ({
24816
24971
  fixes,
24817
24972
  limit,
24818
- gqlClient
24973
+ gqlClient,
24974
+ skippedInteractiveCount = 0
24819
24975
  }) => {
24820
24976
  return `Here are the fresh fixes to the vulnerabilities discovered by Mobb MCP
24821
24977
 
@@ -24830,6 +24986,7 @@ ${applyFixesPrompt({
24830
24986
  limit,
24831
24987
  gqlClient
24832
24988
  })}
24989
+ ${skippedInteractiveFixesNotice(skippedInteractiveCount)}
24833
24990
  `;
24834
24991
  };
24835
24992
  function extractTargetFileFromPatch(patch) {
@@ -24848,7 +25005,8 @@ function formatSeverity(severityText, severityValue) {
24848
25005
  }
24849
25006
  var appliedFixesSummaryPrompt = ({
24850
25007
  fixes,
24851
- gqlClient
25008
+ gqlClient,
25009
+ skippedInteractiveCount = 0
24852
25010
  }) => {
24853
25011
  const fixIds = fixes.map((fix) => fix.id);
24854
25012
  void gqlClient.updateFixesDownloadStatus(fixIds);
@@ -24883,11 +25041,11 @@ ${fixes.map((fix, index) => {
24883
25041
  ${continuousMonitoringSection}
24884
25042
 
24885
25043
  ${autoFixSettingsSection}
24886
-
25044
+ ${skippedInteractiveFixesNotice(skippedInteractiveCount)}
24887
25045
  ## \u{1F4CB} Next Steps
24888
25046
 
24889
25047
  1. **Review the changes** - Check the modified files to understand what was fixed
24890
- 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
24891
25049
  3. **Commit the changes** - Add and commit the security fixes to your repository
24892
25050
  4. **Continue coding** - Mobb will keep protecting your code automatically
24893
25051
 
@@ -27861,7 +28019,8 @@ var _FetchAvailableFixesService = class _FetchAvailableFixesService {
27861
28019
  fixReport,
27862
28020
  offset: effectiveOffset,
27863
28021
  limit,
27864
- gqlClient
28022
+ gqlClient,
28023
+ skippedInteractiveCount: fixReport.skippedRuleIds?.length ?? 0
27865
28024
  });
27866
28025
  this.currentOffset = effectiveOffset + (fixReport.fixes?.length || 0);
27867
28026
  return prompt;
@@ -28196,7 +28355,8 @@ var _ScanAndFixVulnerabilitiesService = class _ScanAndFixVulnerabilitiesService
28196
28355
  offset: effectiveOffset,
28197
28356
  scannedFiles: [...fileList],
28198
28357
  limit: effectiveLimit,
28199
- gqlClient: this.gqlClient
28358
+ gqlClient: this.gqlClient,
28359
+ skippedInteractiveCount: fixes.skippedRuleIds.length
28200
28360
  });
28201
28361
  this.currentOffset = effectiveOffset + (fixes.fixes?.length || 0);
28202
28362
  return prompt;
@@ -28240,7 +28400,8 @@ var _ScanAndFixVulnerabilitiesService = class _ScanAndFixVulnerabilitiesService
28240
28400
  logDebug(`${fixes?.fixes?.length} fixes retrieved`);
28241
28401
  return {
28242
28402
  fixes: fixes?.fixes || [],
28243
- totalCount: fixes?.totalCount || 0
28403
+ totalCount: fixes?.totalCount || 0,
28404
+ skippedRuleIds: fixes?.skippedRuleIds || []
28244
28405
  };
28245
28406
  }
28246
28407
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mobbdev",
3
- "version": "1.4.10",
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",