mobbdev 1.1.32 → 1.1.35

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.
@@ -4,6 +4,10 @@ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { en
4
4
  var __esm = (fn, res) => function __init() {
5
5
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
6
6
  };
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
7
11
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
8
12
 
9
13
  // src/features/analysis/scm/env.ts
@@ -28,7 +32,7 @@ var init_env = __esm({
28
32
  });
29
33
 
30
34
  // src/mcp/core/configs.ts
31
- var MCP_LOGIN_MAX_WAIT, MCP_LOGIN_CHECK_DELAY, MCP_VUL_REPORT_DIGEST_TIMEOUT_MS, MCP_MAX_FILE_SIZE, MCP_PERIODIC_CHECK_INTERVAL, MCP_REPORT_ID_EXPIRATION_MS, MCP_TOOLS_BROWSER_COOLDOWN_MS, isAutoScan, MVS_AUTO_FIX_OVERRIDE, MCP_PERIODIC_TRACK_INTERVAL, MCP_SYSTEM_FIND_TIMEOUT_MS;
35
+ var MCP_LOGIN_MAX_WAIT, MCP_LOGIN_CHECK_DELAY, MCP_VUL_REPORT_DIGEST_TIMEOUT_MS, MCP_MAX_FILE_SIZE, MCP_PERIODIC_CHECK_INTERVAL, MCP_DEFAULT_MAX_FILES_TO_SCAN, MCP_REPORT_ID_EXPIRATION_MS, MCP_TOOLS_BROWSER_COOLDOWN_MS, isAutoScan, MVS_AUTO_FIX_OVERRIDE, MCP_PERIODIC_TRACK_INTERVAL, MCP_SYSTEM_FIND_TIMEOUT_MS;
32
36
  var init_configs = __esm({
33
37
  "src/mcp/core/configs.ts"() {
34
38
  "use strict";
@@ -38,6 +42,7 @@ var init_configs = __esm({
38
42
  MCP_VUL_REPORT_DIGEST_TIMEOUT_MS = 30 * 60 * 1e3;
39
43
  MCP_MAX_FILE_SIZE = MAX_UPLOAD_FILE_SIZE_MB * 1024 * 1024;
40
44
  MCP_PERIODIC_CHECK_INTERVAL = 15 * 60 * 1e3;
45
+ MCP_DEFAULT_MAX_FILES_TO_SCAN = 10;
41
46
  MCP_REPORT_ID_EXPIRATION_MS = 2 * 60 * 60 * 1e3;
42
47
  MCP_TOOLS_BROWSER_COOLDOWN_MS = 24 * 60 * 60 * 1e3;
43
48
  isAutoScan = process.env["AUTO_SCAN"] !== "false";
@@ -48,16 +53,513 @@ var init_configs = __esm({
48
53
  });
49
54
 
50
55
  // src/features/analysis/scm/services/ExcludedDirs.ts
56
+ var EXCLUDED_DIRS;
51
57
  var init_ExcludedDirs = __esm({
52
58
  "src/features/analysis/scm/services/ExcludedDirs.ts"() {
53
59
  "use strict";
60
+ EXCLUDED_DIRS = [
61
+ "$RECYCLE.BIN",
62
+ ".7z",
63
+ ".AppleDouble",
64
+ ".DS_Store",
65
+ ".Rproj.user",
66
+ ".Spotlight-V100",
67
+ ".Trashes",
68
+ ".adoc",
69
+ ".android",
70
+ ".angular",
71
+ ".atom",
72
+ ".aws-sam",
73
+ ".azure",
74
+ ".azure-pipelines",
75
+ ".babelrc",
76
+ ".babelrc.js",
77
+ ".bmp",
78
+ ".brackets.json",
79
+ ".browserslistrc",
80
+ ".build",
81
+ ".bundle",
82
+ ".bundle.js",
83
+ ".bzr",
84
+ ".c8rc",
85
+ ".c9",
86
+ ".cabal",
87
+ ".cabal-sandbox",
88
+ ".cache",
89
+ ".cache-loader",
90
+ ".cargo",
91
+ ".chunk.js",
92
+ ".circleci",
93
+ ".class",
94
+ ".classpath",
95
+ ".composer",
96
+ ".conf",
97
+ ".config",
98
+ ".cpanm",
99
+ ".crt",
100
+ ".cvs",
101
+ ".d.ts",
102
+ ".dart_tool",
103
+ ".db",
104
+ ".devcontainer",
105
+ ".dll",
106
+ ".docker",
107
+ ".dockerignore",
108
+ ".docusaurus",
109
+ ".dylib",
110
+ ".ebextensions",
111
+ ".eclipse",
112
+ ".editorconfig",
113
+ ".eggs",
114
+ ".ember-cli",
115
+ ".ensime_cache",
116
+ ".env",
117
+ ".env.vault",
118
+ ".eot",
119
+ ".eslintcache",
120
+ ".eslintrc",
121
+ ".eslintrc.js",
122
+ ".exe",
123
+ ".expo",
124
+ ".expo-shared",
125
+ ".flutter-plugins",
126
+ ".fseventsd",
127
+ ".gem",
128
+ ".gif",
129
+ ".git",
130
+ ".gitattributes",
131
+ ".github",
132
+ ".gitignore",
133
+ ".gitkeep",
134
+ ".gitlab",
135
+ ".gitlab-ci",
136
+ ".gitlab-ci.yml",
137
+ ".gitmodules",
138
+ ".gradle",
139
+ ".gvmrc",
140
+ ".gz",
141
+ ".hbuilder",
142
+ ".helm",
143
+ ".hg",
144
+ ".hgignore",
145
+ ".history",
146
+ ".htaccess",
147
+ ".husky",
148
+ ".ico",
149
+ ".idea",
150
+ ".ini",
151
+ ".ionic",
152
+ ".ipynb_checkpoints",
153
+ ".ivy2",
154
+ ".jekyll-cache",
155
+ ".jest-cache",
156
+ ".jpeg",
157
+ ".jpg",
158
+ ".jscsrc",
159
+ ".jshintrc",
160
+ ".json",
161
+ ".k8s",
162
+ ".keep",
163
+ ".key",
164
+ ".kubernetes",
165
+ ".lcov",
166
+ ".lock",
167
+ ".log",
168
+ ".logs",
169
+ ".m2",
170
+ ".mailmap",
171
+ ".md",
172
+ ".metadata",
173
+ ".metals",
174
+ ".min.css",
175
+ ".min.html",
176
+ ".min.js",
177
+ ".mvn",
178
+ ".mypy_cache",
179
+ ".nbproject",
180
+ ".netbeans",
181
+ ".netlify",
182
+ ".next",
183
+ ".node-version",
184
+ ".node_modules",
185
+ ".npmrc",
186
+ ".nuget",
187
+ ".nunit",
188
+ ".nuxt",
189
+ ".nvm",
190
+ ".nvmrc",
191
+ ".nx",
192
+ ".nyc_output",
193
+ ".nycrc",
194
+ ".o",
195
+ ".obj",
196
+ ".otf",
197
+ ".output",
198
+ ".p12",
199
+ ".parcel-cache",
200
+ ".pem",
201
+ ".pfx",
202
+ ".phpunit.result.cache",
203
+ ".png",
204
+ ".pnp",
205
+ ".pnp.cjs",
206
+ ".pnp.js",
207
+ ".pnpm",
208
+ ".pnpm-state",
209
+ ".pnpm-store",
210
+ ".pnpmfile.cjs",
211
+ ".prettierrc",
212
+ ".prettierrc.js",
213
+ ".project",
214
+ ".project.vim",
215
+ ".pub-cache",
216
+ ".pulumi",
217
+ ".pyc",
218
+ ".pyenv",
219
+ ".pyo",
220
+ ".pytest_cache",
221
+ ".python-version",
222
+ ".pythonrc",
223
+ ".quasar",
224
+ ".rar",
225
+ ".rbenv-version",
226
+ ".react-native",
227
+ ".rebar3",
228
+ ".rollup.cache",
229
+ ".rst",
230
+ ".ruby-version",
231
+ ".ruff_cache",
232
+ ".rush",
233
+ ".rvm",
234
+ ".rvmrc",
235
+ ".sass-cache",
236
+ ".sbt",
237
+ ".serverless",
238
+ ".settings",
239
+ ".snap",
240
+ ".so",
241
+ ".spec.js",
242
+ ".spec.jsx",
243
+ ".spec.ts",
244
+ ".spec.tsx",
245
+ ".sql",
246
+ ".sqlite",
247
+ ".stack-work",
248
+ ".storybook",
249
+ ".stylelintcache",
250
+ ".stylelintrc",
251
+ ".stylelintrc.js",
252
+ ".sublime-project",
253
+ ".sublime-workspace",
254
+ ".svelte-kit",
255
+ ".svg",
256
+ ".svn",
257
+ ".swcrc",
258
+ ".tar",
259
+ ".temp",
260
+ ".terraform",
261
+ ".test.js",
262
+ ".test.jsx",
263
+ ".test.ts",
264
+ ".test.tsx",
265
+ ".tiff",
266
+ ".tmp",
267
+ ".toml",
268
+ ".tox",
269
+ ".travis",
270
+ ".travis.yml",
271
+ ".ttf",
272
+ ".turbo",
273
+ ".txt",
274
+ ".vagrant",
275
+ ".venv",
276
+ ".vite",
277
+ ".vs",
278
+ ".vscode",
279
+ ".webp",
280
+ ".webpack",
281
+ ".woff",
282
+ ".woff2",
283
+ ".wrangler",
284
+ ".xml",
285
+ ".yaml",
286
+ ".yarn",
287
+ ".yarnrc",
288
+ ".yml",
289
+ ".zip",
290
+ "Carthage",
291
+ "Debug",
292
+ "DerivedData",
293
+ "Godeps",
294
+ "Release",
295
+ "TestResults",
296
+ "__pycache__",
297
+ "_build",
298
+ "allure-results",
299
+ "bazel",
300
+ "benchmark",
301
+ "benchmarks",
302
+ "bin",
303
+ "bower_components",
304
+ "buck-out",
305
+ "build",
306
+ "build-cache",
307
+ "build_tools",
308
+ "builds",
309
+ "cache",
310
+ "changelog",
311
+ "changelogs",
312
+ "compiled",
313
+ "copyright",
314
+ "coverage",
315
+ "debug",
316
+ "deploy",
317
+ "deployment",
318
+ "dist",
319
+ "dist-newstyle",
320
+ "dist-packages",
321
+ "docker",
322
+ "dockerfile",
323
+ "egg-info",
324
+ "elm-stuff",
325
+ "fonts",
326
+ "gemfile",
327
+ "generated",
328
+ "go.mod",
329
+ "go.sum",
330
+ "jars",
331
+ "jenkinsfile",
332
+ "jspm_packages",
333
+ "junit-reports",
334
+ "makefile",
335
+ "mock_data",
336
+ "nbproject",
337
+ "node_modules",
338
+ "notice",
339
+ "obj",
340
+ "out",
341
+ "out-tsc",
342
+ "output",
343
+ "packrat",
344
+ "pipfile",
345
+ "pycache",
346
+ "readme",
347
+ "sdist",
348
+ "snapshots",
349
+ "target",
350
+ "debug",
351
+ "temp",
352
+ "tempfiles",
353
+ "test-reports",
354
+ "test-results",
355
+ "test_output",
356
+ "third-party",
357
+ "third_party",
358
+ "thirdparty",
359
+ "tmp",
360
+ "vendor",
361
+ "bundle",
362
+ "venv",
363
+ "virtualenv",
364
+ "webpack-cache",
365
+ "www",
366
+ "wwwroot",
367
+ "xunit"
368
+ ];
54
369
  }
55
370
  });
56
371
 
57
372
  // src/features/analysis/scm/services/FilePatterns.ts
373
+ var EXCLUDED_FILE_PATTERNS, SUPPORTED_EXTENSIONS, IMPORTANT_PROJECT_FILES;
58
374
  var init_FilePatterns = __esm({
59
375
  "src/features/analysis/scm/services/FilePatterns.ts"() {
60
376
  "use strict";
377
+ EXCLUDED_FILE_PATTERNS = [
378
+ // Minified and bundled files (have supported extensions but should be excluded)
379
+ ".min.js",
380
+ ".min.html",
381
+ ".bundle.js",
382
+ ".chunk.js",
383
+ // Test files (have supported extensions but should be excluded)
384
+ ".test.js",
385
+ ".test.ts",
386
+ ".test.jsx",
387
+ ".test.tsx",
388
+ ".spec.js",
389
+ ".spec.ts",
390
+ ".spec.jsx",
391
+ ".spec.tsx",
392
+ // TypeScript declaration files
393
+ ".d.ts",
394
+ // Runtime version files that have supported extensions
395
+ ".pnpmfile.cjs",
396
+ // Language-specific files with supported extensions that should be excluded
397
+ "go.sum",
398
+ "project.clj",
399
+ "setup.py",
400
+ "setup.cfg",
401
+ "manifest.in",
402
+ // Build tool configuration files (have supported extensions but should be excluded)
403
+ "gulpfile.js",
404
+ "gruntfile.js",
405
+ "webpack.config.js",
406
+ "webpack.config.ts",
407
+ "rollup.config.js",
408
+ "vite.config.js",
409
+ "vite.config.ts",
410
+ "next.config.js",
411
+ "nuxt.config.js",
412
+ "tailwind.config.js",
413
+ "postcss.config.js",
414
+ // Linter and formatter config files (with supported extensions)
415
+ ".babelrc.js",
416
+ ".eslintrc.js",
417
+ ".prettierrc.js",
418
+ ".stylelintrc.js",
419
+ // Test framework config files (with supported extensions)
420
+ "jest.config.js",
421
+ "jest.config.ts",
422
+ "vitest.config.js",
423
+ "karma.conf.js",
424
+ "protractor.conf.js",
425
+ "cypress.config.js",
426
+ "playwright.config.js"
427
+ ];
428
+ SUPPORTED_EXTENSIONS = [
429
+ // Apex
430
+ ".cls",
431
+ // Bash
432
+ ".bash",
433
+ ".sh",
434
+ // C
435
+ ".c",
436
+ ".h",
437
+ // Cairo
438
+ ".cairo",
439
+ // Circom
440
+ ".circom",
441
+ // Clojure
442
+ ".clj",
443
+ ".cljs",
444
+ ".cljc",
445
+ ".edn",
446
+ // C++
447
+ ".cc",
448
+ ".cpp",
449
+ ".cxx",
450
+ ".c++",
451
+ ".pcc",
452
+ ".tpp",
453
+ ".C",
454
+ ".hh",
455
+ ".hpp",
456
+ ".hxx",
457
+ ".inl",
458
+ ".ipp",
459
+ // C#
460
+ ".cs",
461
+ // Dart
462
+ ".dart",
463
+ // Dockerfile
464
+ ".dockerfile",
465
+ ".Dockerfile",
466
+ "Dockerfile",
467
+ "dockerfile",
468
+ // Elixir
469
+ ".ex",
470
+ ".exs",
471
+ // Go
472
+ ".go",
473
+ // Hack
474
+ ".hack",
475
+ ".hck",
476
+ ".hh",
477
+ // HTML
478
+ ".htm",
479
+ ".html",
480
+ // Java
481
+ ".java",
482
+ // JavaScript
483
+ ".cjs",
484
+ ".js",
485
+ ".jsx",
486
+ ".mjs",
487
+ // JSON
488
+ ".json",
489
+ ".ipynb",
490
+ // Jsonnet
491
+ ".jsonnet",
492
+ ".libsonnet",
493
+ // Julia
494
+ ".jl",
495
+ // Kotlin
496
+ ".kt",
497
+ ".kts",
498
+ ".ktm",
499
+ // Lisp
500
+ ".lisp",
501
+ ".cl",
502
+ ".el",
503
+ // Lua
504
+ ".lua",
505
+ // Move (both Sui and Aptos)
506
+ ".move",
507
+ // OCaml
508
+ ".ml",
509
+ ".mli",
510
+ // PHP
511
+ ".php",
512
+ ".tpl",
513
+ ".phtml",
514
+ // PromQL
515
+ ".promql",
516
+ // Protocol Buffers
517
+ ".proto",
518
+ // Python
519
+ ".py",
520
+ ".pyi",
521
+ // QL
522
+ ".ql",
523
+ ".qll",
524
+ // R
525
+ ".r",
526
+ ".R",
527
+ // Ruby
528
+ ".rb",
529
+ // Rust
530
+ ".rs",
531
+ // Scala
532
+ ".scala",
533
+ // Scheme
534
+ ".scm",
535
+ ".ss",
536
+ // Solidity
537
+ ".sol",
538
+ // Swift
539
+ ".swift",
540
+ // Terraform
541
+ ".tf",
542
+ ".hcl",
543
+ ".tfvars",
544
+ // TypeScript
545
+ ".ts",
546
+ ".tsx",
547
+ // Vue
548
+ ".vue",
549
+ // XML
550
+ ".xml",
551
+ ".plist",
552
+ // YAML
553
+ ".yml",
554
+ ".yaml"
555
+ ];
556
+ IMPORTANT_PROJECT_FILES = [
557
+ "package.json",
558
+ "package-lock.json",
559
+ "pnpm-lock.yaml",
560
+ "yarn.lock",
561
+ "pom.xml"
562
+ ];
61
563
  }
62
564
  });
63
565
 
@@ -66,27 +568,795 @@ import fs4 from "fs";
66
568
  import { promises as fsPromises } from "fs";
67
569
  import { isBinary } from "istextorbinary";
68
570
  import path4 from "path";
571
+ var FileUtils;
69
572
  var init_FileUtils = __esm({
70
573
  "src/features/analysis/scm/services/FileUtils.ts"() {
71
574
  "use strict";
72
575
  init_configs();
73
576
  init_ExcludedDirs();
74
577
  init_FilePatterns();
578
+ FileUtils = class {
579
+ // Important project configuration files that should always be included
580
+ static isExcludedFileType(filepath) {
581
+ const basename2 = path4.basename(filepath).toLowerCase();
582
+ if (IMPORTANT_PROJECT_FILES.includes(basename2)) {
583
+ return false;
584
+ }
585
+ const ext = path4.extname(filepath).toLowerCase();
586
+ const isSupported = SUPPORTED_EXTENSIONS.includes(ext) || SUPPORTED_EXTENSIONS.includes(basename2);
587
+ if (!isSupported) {
588
+ return true;
589
+ }
590
+ if (EXCLUDED_FILE_PATTERNS.some((pattern) => basename2.endsWith(pattern))) {
591
+ return true;
592
+ }
593
+ return false;
594
+ }
595
+ static shouldPackFile(filepath, maxFileSize = MCP_MAX_FILE_SIZE) {
596
+ const absoluteFilepath = path4.resolve(filepath);
597
+ if (this.isExcludedFileType(filepath)) {
598
+ return false;
599
+ }
600
+ try {
601
+ const stats = fs4.statSync(absoluteFilepath);
602
+ if (stats.size > maxFileSize) {
603
+ return false;
604
+ }
605
+ const data = fs4.readFileSync(absoluteFilepath);
606
+ if (isBinary(null, data)) {
607
+ return false;
608
+ }
609
+ return true;
610
+ } catch {
611
+ return false;
612
+ }
613
+ }
614
+ // Process directory at repository root level with special handling for excluded root directories
615
+ static async processRootDirectory(dir, excludedRootDirectories) {
616
+ const visitedDirs = /* @__PURE__ */ new Set();
617
+ return this.processDirectory(
618
+ dir,
619
+ dir,
620
+ excludedRootDirectories,
621
+ 0,
622
+ visitedDirs,
623
+ true
624
+ );
625
+ }
626
+ // Process directories with tracking to prevent circular symlink recursion
627
+ static async processDirectory(dir, rootDir, excludedRootDirectories = [], depth = 0, visitedDirs = /* @__PURE__ */ new Set(), isRootLevel = false) {
628
+ if (depth > 20) {
629
+ return [];
630
+ }
631
+ let canonicalPath;
632
+ try {
633
+ canonicalPath = await fsPromises.realpath(dir);
634
+ if (visitedDirs.has(canonicalPath)) {
635
+ return [];
636
+ }
637
+ visitedDirs.add(canonicalPath);
638
+ } catch {
639
+ return [];
640
+ }
641
+ try {
642
+ await fsPromises.access(dir, fs4.constants.R_OK);
643
+ } catch {
644
+ return [];
645
+ }
646
+ const items = await fsPromises.readdir(dir);
647
+ const results = [];
648
+ const filePromises = [];
649
+ for (const item of items) {
650
+ const fullPath = path4.join(dir, item);
651
+ try {
652
+ await fsPromises.access(fullPath, fs4.constants.R_OK);
653
+ const stat = await fsPromises.stat(fullPath);
654
+ if (stat.isDirectory()) {
655
+ if (isRootLevel && excludedRootDirectories.includes(item)) {
656
+ continue;
657
+ }
658
+ filePromises.push(
659
+ this.processDirectory(fullPath, rootDir, [], depth + 1, visitedDirs)
660
+ );
661
+ } else {
662
+ results.push({
663
+ name: item,
664
+ fullPath,
665
+ relativePath: path4.relative(rootDir, fullPath),
666
+ time: stat.mtime.getTime(),
667
+ isFile: true
668
+ });
669
+ }
670
+ } catch {
671
+ continue;
672
+ }
673
+ }
674
+ const subdirResults = await Promise.all(filePromises);
675
+ for (const subdirResult of subdirResults) {
676
+ results.push(...subdirResult);
677
+ }
678
+ return results;
679
+ }
680
+ static async getLastChangedFiles({
681
+ dir,
682
+ maxFileSize,
683
+ maxFiles = MCP_DEFAULT_MAX_FILES_TO_SCAN,
684
+ isAllFilesScan
685
+ }) {
686
+ try {
687
+ const stats = fs4.statSync(dir);
688
+ if (!stats.isDirectory()) return [];
689
+ } catch {
690
+ return [];
691
+ }
692
+ let gitMatcher = null;
693
+ try {
694
+ const { GitService: GitService2 } = await Promise.resolve().then(() => (init_GitService(), GitService_exports));
695
+ const gitService = new GitService2(dir);
696
+ gitMatcher = await gitService.getGitignoreMatcher();
697
+ } catch (e) {
698
+ }
699
+ const allFiles = await this.processRootDirectory(dir, EXCLUDED_DIRS);
700
+ const filteredFiles = allFiles.filter(
701
+ (file) => this.shouldPackFile(file.fullPath, maxFileSize) && !gitMatcher?.ignores(file.relativePath)
702
+ ).sort((a, b) => b.time - a.time).map((file) => file.relativePath);
703
+ if (isAllFilesScan) {
704
+ return filteredFiles;
705
+ } else {
706
+ return filteredFiles.slice(0, maxFiles);
707
+ }
708
+ }
709
+ };
75
710
  }
76
711
  });
77
712
 
78
713
  // src/features/analysis/scm/services/GitService.ts
714
+ var GitService_exports = {};
715
+ __export(GitService_exports, {
716
+ GitService: () => GitService,
717
+ isGitHubUrl: () => isGitHubUrl,
718
+ normalizeGitUrl: () => normalizeGitUrl
719
+ });
79
720
  import fs5 from "fs";
80
721
  import ignore from "ignore";
81
722
  import * as path5 from "path";
82
723
  import { simpleGit as simpleGit2 } from "simple-git";
83
- var MAX_COMMIT_DIFF_SIZE_BYTES;
724
+ function normalizeGitUrl(url) {
725
+ let normalizedUrl = url;
726
+ if (normalizedUrl.endsWith(".git")) {
727
+ normalizedUrl = normalizedUrl.slice(0, -".git".length);
728
+ }
729
+ const sshToHttpsMappings = [
730
+ // GitHub
731
+ { pattern: "git@github.com:", replacement: "https://github.com/" },
732
+ // GitLab
733
+ { pattern: "git@gitlab.com:", replacement: "https://gitlab.com/" },
734
+ // Bitbucket
735
+ { pattern: "git@bitbucket.org:", replacement: "https://bitbucket.org/" },
736
+ // Azure DevOps (SSH format)
737
+ {
738
+ pattern: "git@ssh.dev.azure.com:",
739
+ replacement: "https://dev.azure.com/"
740
+ },
741
+ // Azure DevOps (alternative SSH format)
742
+ {
743
+ pattern: /git@([^:]+):v3\/([^/]+)\/([^/]+)\/([^/]+)/,
744
+ replacement: "https://$1/$2/_git/$4"
745
+ }
746
+ ];
747
+ for (const mapping of sshToHttpsMappings) {
748
+ if (typeof mapping.pattern === "string") {
749
+ if (normalizedUrl.startsWith(mapping.pattern)) {
750
+ normalizedUrl = normalizedUrl.replace(
751
+ mapping.pattern,
752
+ mapping.replacement
753
+ );
754
+ break;
755
+ }
756
+ } else {
757
+ const match = normalizedUrl.match(mapping.pattern);
758
+ if (match) {
759
+ normalizedUrl = normalizedUrl.replace(
760
+ mapping.pattern,
761
+ mapping.replacement
762
+ );
763
+ break;
764
+ }
765
+ }
766
+ }
767
+ if (normalizedUrl.startsWith("https://") || normalizedUrl.startsWith("http://")) {
768
+ normalizedUrl = normalizedUrl.replace(/^(https?:\/\/)([^@/]+@)/, "$1");
769
+ }
770
+ return normalizedUrl;
771
+ }
772
+ function isGitHubUrl(normalizedUrl) {
773
+ try {
774
+ const url = new URL(normalizedUrl);
775
+ const host = url.host.toLowerCase();
776
+ return host === "github.com" || host.endsWith(".github.com");
777
+ } catch {
778
+ return false;
779
+ }
780
+ }
781
+ var MAX_COMMIT_DIFF_SIZE_BYTES, GitService;
84
782
  var init_GitService = __esm({
85
783
  "src/features/analysis/scm/services/GitService.ts"() {
86
784
  "use strict";
87
785
  init_configs();
88
786
  init_FileUtils();
89
787
  MAX_COMMIT_DIFF_SIZE_BYTES = 3 * 1024 * 1024;
788
+ GitService = class {
789
+ constructor(repositoryPath, log) {
790
+ __publicField(this, "git");
791
+ __publicField(this, "repositoryPath");
792
+ __publicField(this, "log");
793
+ const noopLog = (_message, _level, _data) => {
794
+ };
795
+ this.log = log || noopLog;
796
+ this.git = simpleGit2(repositoryPath, { binary: "git" });
797
+ this.repositoryPath = repositoryPath;
798
+ this.log("[GitService] Git service initialized", "debug", {
799
+ repositoryPath
800
+ });
801
+ }
802
+ /**
803
+ * Checks if the current path is within a git repository
804
+ * @returns Promise<boolean> True if it's a git repository, false otherwise
805
+ */
806
+ async isGitRepository() {
807
+ try {
808
+ const isRepo = await this.git.checkIsRepo();
809
+ if (!isRepo) {
810
+ this.log("[GitService] Not a git repository", "debug");
811
+ return false;
812
+ }
813
+ await this.git.revparse(["--show-toplevel"]);
814
+ return true;
815
+ } catch (error) {
816
+ this.log("[GitService] Not a git repository", "debug", {
817
+ error: String(error)
818
+ });
819
+ return false;
820
+ }
821
+ }
822
+ /**
823
+ * Validates that the path is a valid git repository
824
+ */
825
+ async validateRepository() {
826
+ this.log("[GitService] Validating git repository", "debug");
827
+ try {
828
+ const isRepo = await this.git.checkIsRepo();
829
+ if (!isRepo) {
830
+ const error = "[GitService] Path is not a valid git repository";
831
+ this.log(error, "error");
832
+ return { isValid: false, error };
833
+ }
834
+ this.log("[GitService] Git repository validation successful", "debug");
835
+ return { isValid: true };
836
+ } catch (error) {
837
+ const errorMessage = `Failed to verify git repository: ${error.message}`;
838
+ this.log(`[GitService] ${errorMessage}`, "error", { error });
839
+ return { isValid: false, error: errorMessage };
840
+ }
841
+ }
842
+ /**
843
+ * Gets the current git status and returns changed files
844
+ */
845
+ async getChangedFiles() {
846
+ this.log("[GitService] Getting git status", "debug");
847
+ try {
848
+ const status = await this.git.status();
849
+ const gitRoot = await this.git.revparse(["--show-toplevel"]);
850
+ const relativePathFromGitRoot = path5.relative(
851
+ gitRoot,
852
+ this.repositoryPath
853
+ );
854
+ const deletedFiles = status.files.filter((file) => file.index === "D" || file.working_dir === "D").map((file) => file.path);
855
+ const files = status.files.filter((file) => {
856
+ return !(file.index === "D" || file.working_dir === "D");
857
+ }).map((file) => {
858
+ const gitRelativePath = file.path;
859
+ if (relativePathFromGitRoot === "") {
860
+ return gitRelativePath;
861
+ }
862
+ if (gitRelativePath.startsWith(relativePathFromGitRoot + "/")) {
863
+ return gitRelativePath.substring(relativePathFromGitRoot.length + 1);
864
+ }
865
+ const safeInput = path5.basename(
866
+ String(gitRelativePath || "").replace("\0", "").replace(/^(\.\.(\/|\\$))+/, "")
867
+ );
868
+ return path5.relative(
869
+ this.repositoryPath,
870
+ path5.join(gitRoot, safeInput)
871
+ );
872
+ });
873
+ this.log("[GitService] Git status retrieved", "info", {
874
+ fileCount: files.length,
875
+ files: files.slice(0, 10),
876
+ // Log first 10 files to avoid spam
877
+ deletedFileCount: deletedFiles.length,
878
+ deletedFiles: deletedFiles.slice(0, 10),
879
+ gitRoot,
880
+ workingDir: this.repositoryPath,
881
+ relativePathFromGitRoot
882
+ });
883
+ return { files, deletedFiles, status };
884
+ } catch (error) {
885
+ const errorMessage = `Failed to get git status: ${error.message}`;
886
+ this.log(`[GitService] ${errorMessage}`, "error", { error });
887
+ throw new Error(errorMessage);
888
+ }
889
+ }
890
+ /**
891
+ * Gets git repository information including remote URL, current commit hash, and branch name
892
+ */
893
+ async getGitInfo() {
894
+ this.log("[GitService] Getting git repository information", "debug");
895
+ try {
896
+ const [repoUrl, hash, reference] = await Promise.all([
897
+ this.git.getConfig("remote.origin.url"),
898
+ this.git.revparse(["HEAD"]),
899
+ this.git.revparse(["--abbrev-ref", "HEAD"])
900
+ ]);
901
+ const normalizedRepoUrl = repoUrl.value ? normalizeGitUrl(repoUrl.value) : "";
902
+ this.log("[GitService] Git repository information retrieved", "debug", {
903
+ repoUrl: normalizedRepoUrl,
904
+ hash,
905
+ reference
906
+ });
907
+ return {
908
+ repoUrl: normalizedRepoUrl,
909
+ hash,
910
+ reference
911
+ };
912
+ } catch (error) {
913
+ const errorMessage = `Failed to get git repository information: ${error.message}`;
914
+ this.log(`[GitService] ${errorMessage}`, "error", { error });
915
+ throw new Error(errorMessage);
916
+ }
917
+ }
918
+ /**
919
+ * Validates if a branch name is valid according to git's rules
920
+ */
921
+ async isValidBranchName(branchName) {
922
+ this.log("[GitService] Validating branch name", "debug", { branchName });
923
+ try {
924
+ const result = await this.git.raw([
925
+ "check-ref-format",
926
+ "--branch",
927
+ branchName
928
+ ]);
929
+ const isValid = Boolean(result);
930
+ this.log("[GitService] Branch name validation result", "debug", {
931
+ branchName,
932
+ isValid
933
+ });
934
+ return isValid;
935
+ } catch (error) {
936
+ this.log("[GitService] Branch name validation failed", "debug", {
937
+ branchName,
938
+ error
939
+ });
940
+ return false;
941
+ }
942
+ }
943
+ /**
944
+ * Gets the current branch name
945
+ */
946
+ async getCurrentBranch() {
947
+ this.log("[GitService] Getting current branch name", "debug");
948
+ try {
949
+ const branch = await this.git.revparse(["--abbrev-ref", "HEAD"]);
950
+ this.log("[GitService] Current branch retrieved", "debug", { branch });
951
+ return branch;
952
+ } catch (error) {
953
+ const errorMessage = `Failed to get current branch: ${error.message}`;
954
+ this.log(`[GitService] ${errorMessage}`, "error", { error });
955
+ throw new Error(errorMessage);
956
+ }
957
+ }
958
+ /**
959
+ * Gets the current commit hash
960
+ */
961
+ async getCurrentCommitHash() {
962
+ this.log("[GitService] Getting current commit hash", "debug");
963
+ try {
964
+ const hash = await this.git.revparse(["HEAD"]);
965
+ this.log("[GitService] Current commit hash retrieved", "debug", { hash });
966
+ return hash;
967
+ } catch (error) {
968
+ const errorMessage = `Failed to get current commit hash: ${error.message}`;
969
+ this.log(`[GitService] ${errorMessage}`, "error", { error });
970
+ throw new Error(errorMessage);
971
+ }
972
+ }
973
+ /**
974
+ * Gets both the current commit hash and current branch name
975
+ */
976
+ async getCurrentCommitAndBranch() {
977
+ this.log("[GitService] Getting current commit hash and branch", "debug");
978
+ try {
979
+ const [hash, branch] = await Promise.all([
980
+ this.git.revparse(["HEAD"]),
981
+ this.git.revparse(["--abbrev-ref", "HEAD"])
982
+ ]);
983
+ this.log(
984
+ "[GitService] Current commit hash and branch retrieved",
985
+ "debug",
986
+ {
987
+ hash,
988
+ branch
989
+ }
990
+ );
991
+ return { hash, branch };
992
+ } catch (error) {
993
+ const errorMessage = `Failed to get current commit hash and branch: ${error.message}`;
994
+ this.log(`[GitService] ${errorMessage}`, "error", { error });
995
+ return { hash: "", branch: "" };
996
+ }
997
+ }
998
+ /**
999
+ * Gets the remote repository URL (origin)
1000
+ */
1001
+ async getRemoteUrl() {
1002
+ this.log("[GitService] Getting remote repository URL", "debug");
1003
+ try {
1004
+ const remoteUrl = await this.git.getConfig("remote.origin.url");
1005
+ const url = remoteUrl.value || "";
1006
+ const normalizedUrl = normalizeGitUrl(url);
1007
+ this.log("[GitService] Remote repository URL retrieved", "debug", {
1008
+ url: normalizedUrl
1009
+ });
1010
+ return normalizedUrl;
1011
+ } catch (error) {
1012
+ const errorMessage = `Failed to get remote repository URL: ${error.message}`;
1013
+ this.log(`[GitService] ${errorMessage}`, "error", { error });
1014
+ throw new Error(errorMessage);
1015
+ }
1016
+ }
1017
+ /**
1018
+ * Gets the maxFiles most recently changed files, starting with current changes and then from commit history
1019
+ */
1020
+ async getRecentlyChangedFiles({
1021
+ maxFiles = MCP_DEFAULT_MAX_FILES_TO_SCAN
1022
+ }) {
1023
+ this.log(
1024
+ `[GitService] Getting the ${maxFiles} most recently changed files, starting with current changes`,
1025
+ "debug"
1026
+ );
1027
+ try {
1028
+ const currentChanges = await this.getChangedFiles();
1029
+ const gitRoot = await this.git.revparse(["--show-toplevel"]);
1030
+ const relativePathFromGitRoot = path5.relative(
1031
+ gitRoot,
1032
+ this.repositoryPath
1033
+ );
1034
+ const fileSet = /* @__PURE__ */ new Set();
1035
+ let commitsProcessed = 0;
1036
+ const consideredFiles = [];
1037
+ for (const file of currentChanges.files) {
1038
+ if (fileSet.size >= maxFiles) {
1039
+ break;
1040
+ }
1041
+ const fullPath = path5.join(this.repositoryPath, file);
1042
+ if (await FileUtils.shouldPackFile(fullPath) && !file.startsWith("..")) {
1043
+ fileSet.add(file);
1044
+ }
1045
+ }
1046
+ this.log(
1047
+ `[GitService] Added ${fileSet.size} files from current changes`,
1048
+ "debug",
1049
+ {
1050
+ filesFromCurrentChanges: fileSet.size,
1051
+ currentChangesTotal: currentChanges.files.length
1052
+ }
1053
+ );
1054
+ const logResult = await this.git.log({
1055
+ maxCount: maxFiles * 5,
1056
+ // 5 times the max files to scan to ensure we find enough files
1057
+ format: {
1058
+ hash: "%H",
1059
+ date: "%ai",
1060
+ message: "%s",
1061
+ //the field name author_name can't follow the naming convention as we are using the git log command
1062
+ author_name: "%an"
1063
+ }
1064
+ });
1065
+ for (const commit of logResult.all) {
1066
+ if (fileSet.size >= maxFiles) {
1067
+ break;
1068
+ }
1069
+ commitsProcessed++;
1070
+ try {
1071
+ const filesOutput = await this.git.show([
1072
+ "--name-only",
1073
+ "--pretty=format:",
1074
+ commit.hash
1075
+ ]);
1076
+ const commitFiles = filesOutput.split("\n").filter((file) => file.trim() !== "");
1077
+ for (const file of commitFiles) {
1078
+ if (fileSet.size >= maxFiles) {
1079
+ break;
1080
+ }
1081
+ const gitRelativePath = file.trim();
1082
+ let adjustedPath;
1083
+ if (relativePathFromGitRoot === "") {
1084
+ adjustedPath = gitRelativePath;
1085
+ } else if (gitRelativePath.startsWith(relativePathFromGitRoot + "/")) {
1086
+ adjustedPath = gitRelativePath.substring(
1087
+ relativePathFromGitRoot.length + 1
1088
+ );
1089
+ } else {
1090
+ adjustedPath = path5.relative(
1091
+ this.repositoryPath,
1092
+ path5.join(gitRoot, gitRelativePath)
1093
+ );
1094
+ }
1095
+ consideredFiles.push(adjustedPath);
1096
+ if (!fileSet.has(adjustedPath) && await FileUtils.shouldPackFile(
1097
+ path5.join(gitRoot, gitRelativePath)
1098
+ ) && !adjustedPath.startsWith("..")) {
1099
+ fileSet.add(adjustedPath);
1100
+ }
1101
+ }
1102
+ } catch (showError) {
1103
+ this.log(
1104
+ `[GitService] Could not get files for commit ${commit.hash}`,
1105
+ "debug",
1106
+ {
1107
+ error: showError
1108
+ }
1109
+ );
1110
+ }
1111
+ }
1112
+ const files = Array.from(fileSet);
1113
+ if (consideredFiles.length > 0) {
1114
+ this.log(
1115
+ `[GitService] Considered ${consideredFiles.length} files during recent file search`,
1116
+ "debug",
1117
+ { consideredFiles }
1118
+ );
1119
+ }
1120
+ this.log("[GitService] Recently changed files retrieved", "info", {
1121
+ fileCount: files.length,
1122
+ commitsProcessed,
1123
+ totalCommitsAvailable: logResult.all.length,
1124
+ files: files.slice(0, maxFiles),
1125
+ // Log the files (should be all of them since we limit to maxFiles)
1126
+ gitRoot,
1127
+ workingDir: this.repositoryPath,
1128
+ relativePathFromGitRoot
1129
+ });
1130
+ return {
1131
+ files,
1132
+ commitCount: commitsProcessed
1133
+ };
1134
+ } catch (error) {
1135
+ const errorMessage = `Failed to get recently changed files: ${error.message}`;
1136
+ this.log(`[GitService] ${errorMessage}`, "error", { error });
1137
+ throw new Error(errorMessage);
1138
+ }
1139
+ }
1140
+ /**
1141
+ * Fetches the contents of the .gitignore file from the repository
1142
+ * @returns The contents of the .gitignore file as a string, or null if the file doesn't exist
1143
+ */
1144
+ async getGitignoreContent() {
1145
+ this.log("[GitService] Getting .gitignore contents", "debug");
1146
+ try {
1147
+ let combinedContent = "";
1148
+ const localGitignorePath = path5.join(this.repositoryPath, ".gitignore");
1149
+ if (fs5.existsSync(localGitignorePath)) {
1150
+ const localContent = fs5.readFileSync(localGitignorePath, "utf8");
1151
+ combinedContent += `${localContent}
1152
+ `;
1153
+ }
1154
+ try {
1155
+ const gitRoot = await this.git.revparse(["--show-toplevel"]);
1156
+ const rootGitignorePath = path5.join(gitRoot, ".gitignore");
1157
+ if (fs5.existsSync(rootGitignorePath)) {
1158
+ const rootContent = fs5.readFileSync(rootGitignorePath, "utf8");
1159
+ if (rootContent.trim() !== combinedContent.trim()) {
1160
+ combinedContent += `
1161
+ ${rootContent}`;
1162
+ }
1163
+ }
1164
+ } catch (rootErr) {
1165
+ this.log(
1166
+ "[GitService] Unable to resolve git root while reading .gitignore",
1167
+ "debug",
1168
+ { error: rootErr }
1169
+ );
1170
+ }
1171
+ if (combinedContent.trim() === "") {
1172
+ this.log("[GitService] .gitignore file not found", "debug");
1173
+ return null;
1174
+ }
1175
+ this.log(
1176
+ "[GitService] .gitignore contents retrieved successfully",
1177
+ "debug"
1178
+ );
1179
+ return combinedContent.trimEnd();
1180
+ } catch (error) {
1181
+ const errorMessage = `Failed to get .gitignore contents: ${error.message}`;
1182
+ this.log(`[GitService] ${errorMessage}`, "error", { error });
1183
+ return null;
1184
+ }
1185
+ }
1186
+ async getGitignoreMatcher() {
1187
+ const content = await this.getGitignoreContent();
1188
+ if (!content) return null;
1189
+ return ignore().add(content);
1190
+ }
1191
+ /**
1192
+ * Gets the git repository root directory path
1193
+ * @returns Absolute path to the git repository root
1194
+ */
1195
+ async getGitRoot() {
1196
+ this.log("[GitService] Getting git repository root", "debug");
1197
+ try {
1198
+ const gitRoot = await this.git.revparse(["--show-toplevel"]);
1199
+ this.log("[GitService] Git root retrieved", "debug", { gitRoot });
1200
+ return gitRoot;
1201
+ } catch (error) {
1202
+ const errorMessage = `Failed to get git repository root: ${error.message}`;
1203
+ this.log(`[GitService] ${errorMessage}`, "error", { error });
1204
+ throw new Error(errorMessage);
1205
+ }
1206
+ }
1207
+ /**
1208
+ * Ensures that a specific entry exists in the .gitignore file
1209
+ * Creates .gitignore if it doesn't exist, adds the entry if not present
1210
+ * @param entry The entry to add to .gitignore (e.g., '.mobb', 'node_modules')
1211
+ * @returns True if entry was added, false if it already existed
1212
+ */
1213
+ async ensureGitignoreEntry(entry) {
1214
+ this.log("[GitService] Ensuring .gitignore entry", "debug", { entry });
1215
+ try {
1216
+ const gitRoot = await this.getGitRoot();
1217
+ const gitignorePath = path5.join(gitRoot, ".gitignore");
1218
+ let gitignoreContent = "";
1219
+ if (fs5.existsSync(gitignorePath)) {
1220
+ gitignoreContent = fs5.readFileSync(gitignorePath, "utf8");
1221
+ this.log("[GitService] .gitignore file exists", "debug");
1222
+ } else {
1223
+ this.log("[GitService] Creating .gitignore file", "info", {
1224
+ gitignorePath
1225
+ });
1226
+ }
1227
+ if (gitignoreContent.includes(entry)) {
1228
+ this.log("[GitService] Entry already exists in .gitignore", "debug", {
1229
+ entry
1230
+ });
1231
+ return false;
1232
+ }
1233
+ this.log("[GitService] Adding entry to .gitignore", "info", { entry });
1234
+ const newLine = gitignoreContent.endsWith("\n") || gitignoreContent === "" ? "" : "\n";
1235
+ const updatedContent = `${gitignoreContent}${newLine}${entry}
1236
+ `;
1237
+ fs5.writeFileSync(gitignorePath, updatedContent, "utf8");
1238
+ this.log("[GitService] .gitignore updated successfully", "debug", {
1239
+ entry
1240
+ });
1241
+ return true;
1242
+ } catch (error) {
1243
+ const errorMessage = `Failed to ensure .gitignore entry: ${error.message}`;
1244
+ this.log(`[GitService] ${errorMessage}`, "error", { error, entry });
1245
+ throw new Error(errorMessage);
1246
+ }
1247
+ }
1248
+ /**
1249
+ * Checks if the .gitignore file exists in the repository root
1250
+ * @returns True if .gitignore exists, false otherwise
1251
+ */
1252
+ async gitignoreExists() {
1253
+ this.log("[GitService] Checking if .gitignore exists", "debug");
1254
+ try {
1255
+ const gitRoot = await this.getGitRoot();
1256
+ const gitignorePath = path5.join(gitRoot, ".gitignore");
1257
+ const exists = fs5.existsSync(gitignorePath);
1258
+ this.log("[GitService] .gitignore existence check complete", "debug", {
1259
+ exists
1260
+ });
1261
+ return exists;
1262
+ } catch (error) {
1263
+ const errorMessage = `Failed to check .gitignore existence: ${error.message}`;
1264
+ this.log(`[GitService] ${errorMessage}`, "error", { error });
1265
+ throw new Error(errorMessage);
1266
+ }
1267
+ }
1268
+ /**
1269
+ * Gets timestamps for parent commits in a single git call.
1270
+ * @param parentShas Array of parent commit SHAs
1271
+ * @returns Array of parent commits with timestamps, or undefined if unavailable
1272
+ */
1273
+ async getParentCommitTimestamps(parentShas) {
1274
+ if (parentShas.length === 0) {
1275
+ return void 0;
1276
+ }
1277
+ try {
1278
+ const output = await this.git.raw([
1279
+ "log",
1280
+ "--format=%H %cI",
1281
+ "--no-walk",
1282
+ ...parentShas
1283
+ ]);
1284
+ const parentCommits = output.trim().split("\n").filter(Boolean).map((line) => {
1285
+ const [sha, ts] = line.split(" ");
1286
+ return { sha: sha ?? "", timestamp: new Date(ts ?? "") };
1287
+ }).filter((p) => p.sha !== "");
1288
+ return parentCommits.length > 0 ? parentCommits : void 0;
1289
+ } catch {
1290
+ this.log("[GitService] Could not get parent commit timestamps", "debug", {
1291
+ parentShas
1292
+ });
1293
+ return void 0;
1294
+ }
1295
+ }
1296
+ /**
1297
+ * Gets local commit data including diff, timestamp, and parent commits.
1298
+ * Used by Tracy extension to send commit data directly without requiring SCM token.
1299
+ * @param commitSha The commit SHA to get data for
1300
+ * @param maxDiffSizeBytes Maximum diff size in bytes (default 3MB). Returns null if exceeded.
1301
+ * @returns Commit data or null if unavailable/too large
1302
+ */
1303
+ async getLocalCommitData(commitSha, maxDiffSizeBytes = MAX_COMMIT_DIFF_SIZE_BYTES) {
1304
+ this.log("[GitService] Getting local commit data", "debug", { commitSha });
1305
+ try {
1306
+ const DIFF_DELIMITER = "---MOBB_DIFF_START---";
1307
+ const output = await this.git.show([
1308
+ commitSha,
1309
+ `--format=%cI%n%P%n${DIFF_DELIMITER}`,
1310
+ "--patch"
1311
+ ]);
1312
+ const delimiterIndex = output.indexOf(DIFF_DELIMITER);
1313
+ if (delimiterIndex === -1) {
1314
+ this.log("[GitService] Could not parse git show output", "warning", {
1315
+ commitSha
1316
+ });
1317
+ return null;
1318
+ }
1319
+ const metadataOutput = output.substring(0, delimiterIndex);
1320
+ const diff = output.substring(delimiterIndex + DIFF_DELIMITER.length + 1);
1321
+ const diffSizeBytes = Buffer.byteLength(diff, "utf8");
1322
+ if (diffSizeBytes > maxDiffSizeBytes) {
1323
+ this.log("[GitService] Commit diff exceeds size limit", "warning", {
1324
+ commitSha,
1325
+ diffSizeBytes,
1326
+ maxDiffSizeBytes
1327
+ });
1328
+ return null;
1329
+ }
1330
+ const metadataLines = metadataOutput.trim().split("\n");
1331
+ if (metadataLines.length < 1 || !metadataLines[0]) {
1332
+ this.log("[GitService] Unexpected metadata format", "warning", {
1333
+ commitSha,
1334
+ metadataLines
1335
+ });
1336
+ return null;
1337
+ }
1338
+ const timestampStr = metadataLines[0];
1339
+ const timestamp = new Date(timestampStr);
1340
+ const parentShas = (metadataLines[1] ?? "").trim().split(/\s+/).filter(Boolean);
1341
+ const parentCommits = await this.getParentCommitTimestamps(parentShas);
1342
+ this.log("[GitService] Local commit data retrieved", "debug", {
1343
+ commitSha,
1344
+ diffSizeBytes,
1345
+ timestamp: timestamp.toISOString(),
1346
+ parentCommitCount: parentCommits?.length ?? 0
1347
+ });
1348
+ return {
1349
+ diff,
1350
+ timestamp,
1351
+ parentCommits
1352
+ };
1353
+ } catch (error) {
1354
+ const errorMessage = `Failed to get local commit data: ${error.message}`;
1355
+ this.log(`[GitService] ${errorMessage}`, "debug", { error, commitSha });
1356
+ return null;
1357
+ }
1358
+ }
1359
+ };
90
1360
  }
91
1361
  });
92
1362
 
@@ -895,9 +2165,10 @@ var GetPromptSummaryDocument = `
895
2165
  goal
896
2166
  developersPlan
897
2167
  aiImplementationDetails
898
- importantInstructionsAndPushbacks
899
- frictionScore {
900
- score
2168
+ developersPushbacks
2169
+ importantInstructionsAndDecisions
2170
+ backAndForthLevel {
2171
+ level
901
2172
  justification
902
2173
  }
903
2174
  }
@@ -4644,6 +5915,7 @@ var REPORT_DEFAULT_FILE_NAME = "report.json";
4644
5915
  init_env();
4645
5916
 
4646
5917
  // src/features/analysis/scm/github/GithubSCMLib.ts
5918
+ import pLimit from "p-limit";
4647
5919
  import { z as z22 } from "zod";
4648
5920
 
4649
5921
  // src/features/analysis/scm/github/github.ts
@@ -4656,6 +5928,9 @@ import sodium from "libsodium-wrappers";
4656
5928
  import { Octokit } from "octokit";
4657
5929
  import { fetch as fetch2, ProxyAgent } from "undici";
4658
5930
 
5931
+ // src/features/analysis/scm/github/GithubSCMLib.ts
5932
+ var GITHUB_COMMIT_FETCH_CONCURRENCY = parseInt(process.env["GITHUB_COMMIT_CONCURRENCY"] || "10", 10) || 10;
5933
+
4659
5934
  // src/features/analysis/scm/gitlab/gitlab.ts
4660
5935
  import querystring3 from "querystring";
4661
5936
  import {
@@ -5355,6 +6630,9 @@ async function handleMobbLogin({
5355
6630
  return newGqlClient;
5356
6631
  }
5357
6632
 
6633
+ // src/args/commands/upload_ai_blame.ts
6634
+ init_GitService();
6635
+
5358
6636
  // src/features/analysis/upload-file.ts
5359
6637
  import Debug7 from "debug";
5360
6638
  import fetch3, { File, fileFrom, FormData } from "node-fetch";
@@ -5675,6 +6953,19 @@ var PromptItemZ = z26.object({
5675
6953
  }).optional()
5676
6954
  });
5677
6955
  var PromptItemArrayZ = z26.array(PromptItemZ);
6956
+ async function getRepositoryUrl() {
6957
+ try {
6958
+ const gitService = new GitService(process.cwd());
6959
+ const isRepo = await gitService.isGitRepository();
6960
+ if (!isRepo) {
6961
+ return null;
6962
+ }
6963
+ const remoteUrl = await gitService.getRemoteUrl();
6964
+ return isGitHubUrl(remoteUrl) ? remoteUrl : null;
6965
+ } catch {
6966
+ return null;
6967
+ }
6968
+ }
5678
6969
  function getSystemInfo() {
5679
6970
  let userName;
5680
6971
  try {
@@ -5771,7 +7062,8 @@ async function uploadAiBlameHandlerFromExtension(args) {
5771
7062
  args: uploadArgs,
5772
7063
  exitOnError: false,
5773
7064
  apiUrl: args.apiUrl,
5774
- webAppUrl: args.webAppUrl
7065
+ webAppUrl: args.webAppUrl,
7066
+ repositoryUrl: args.repositoryUrl
5775
7067
  });
5776
7068
  });
5777
7069
  });
@@ -5807,6 +7099,7 @@ async function uploadAiBlameHandler(options) {
5807
7099
  }
5808
7100
  const nowIso = (/* @__PURE__ */ new Date()).toISOString();
5809
7101
  const { computerName, userName } = getSystemInfo();
7102
+ const repositoryUrl = options.repositoryUrl !== void 0 ? options.repositoryUrl : await getRepositoryUrl();
5810
7103
  const sessions = [];
5811
7104
  for (let i = 0; i < prompts.length; i++) {
5812
7105
  const promptPath = String(prompts[i]);
@@ -5833,7 +7126,8 @@ async function uploadAiBlameHandler(options) {
5833
7126
  blameType: blameTypes[i] || "CHAT" /* Chat */,
5834
7127
  computerName,
5835
7128
  userName,
5836
- sessionId: sessionIds[i]
7129
+ sessionId: sessionIds[i],
7130
+ repositoryUrl
5837
7131
  });
5838
7132
  }
5839
7133
  const authenticatedClient = await getAuthenticatedGQLClient({
@@ -5889,7 +7183,8 @@ async function uploadAiBlameHandler(options) {
5889
7183
  blameType: s.blameType,
5890
7184
  computerName: s.computerName,
5891
7185
  userName: s.userName,
5892
- sessionId: s.sessionId
7186
+ sessionId: s.sessionId,
7187
+ repositoryUrl: s.repositoryUrl
5893
7188
  };
5894
7189
  });
5895
7190
  try {