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.
- package/dist/args/commands/upload_ai_blame.d.mts +20 -1
- package/dist/args/commands/upload_ai_blame.mjs +54 -11
- package/dist/index.mjs +274 -113
- package/package.json +1 -1
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
9580
|
-
|
|
9581
|
-
|
|
9582
|
-
|
|
9583
|
-
|
|
9584
|
-
|
|
9585
|
-
|
|
9586
|
-
|
|
9587
|
-
|
|
9588
|
-
|
|
9589
|
-
|
|
9590
|
-
|
|
9591
|
-
|
|
9592
|
-
|
|
9593
|
-
|
|
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
|
-
|
|
9652
|
-
|
|
9653
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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 ??
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
|
21449
|
+
const { applicableFixes, skippedRuleIds } = this.mergeUserAndSystemFixes({
|
|
21312
21450
|
reportData: latestReport,
|
|
21313
21451
|
limit
|
|
21314
21452
|
});
|
|
21315
|
-
logDebug("[GraphQL] GetReportFixes response parsed", {
|
|
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
|
};
|