@corbat-tech/coco 2.14.1 → 2.15.0

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/index.js CHANGED
@@ -3972,6 +3972,18 @@ function trackSubprocess(proc) {
3972
3972
 
3973
3973
  // src/quality/analyzers/coverage.ts
3974
3974
  async function detectTestFramework(projectPath) {
3975
+ try {
3976
+ await access(join(projectPath, "pom.xml"), constants.R_OK);
3977
+ return "maven";
3978
+ } catch {
3979
+ }
3980
+ for (const f of ["build.gradle", "build.gradle.kts"]) {
3981
+ try {
3982
+ await access(join(projectPath, f), constants.R_OK);
3983
+ return "gradle";
3984
+ } catch {
3985
+ }
3986
+ }
3975
3987
  try {
3976
3988
  const pkgPath = join(projectPath, "package.json");
3977
3989
  const pkgContent = await readFile(pkgPath, "utf-8");
@@ -4033,6 +4045,55 @@ function parseCoverageSummary(report) {
4033
4045
  }
4034
4046
  };
4035
4047
  }
4048
+ function parseJacocoCsv(csv) {
4049
+ const lines = csv.trim().split("\n").slice(1);
4050
+ let lineMissed = 0, lineCovered = 0;
4051
+ let branchMissed = 0, branchCovered = 0;
4052
+ let methodMissed = 0, methodCovered = 0;
4053
+ let instrMissed = 0, instrCovered = 0;
4054
+ for (const line of lines) {
4055
+ const cols = line.split(",");
4056
+ if (cols.length < 13) continue;
4057
+ instrMissed += parseInt(cols[3] ?? "0", 10);
4058
+ instrCovered += parseInt(cols[4] ?? "0", 10);
4059
+ branchMissed += parseInt(cols[5] ?? "0", 10);
4060
+ branchCovered += parseInt(cols[6] ?? "0", 10);
4061
+ lineMissed += parseInt(cols[7] ?? "0", 10);
4062
+ lineCovered += parseInt(cols[8] ?? "0", 10);
4063
+ methodMissed += parseInt(cols[11] ?? "0", 10);
4064
+ methodCovered += parseInt(cols[12] ?? "0", 10);
4065
+ }
4066
+ const pct = (covered, missed) => {
4067
+ const total = covered + missed;
4068
+ return total > 0 ? Math.round(covered / total * 1e3) / 10 : 0;
4069
+ };
4070
+ return {
4071
+ lines: {
4072
+ total: lineCovered + lineMissed,
4073
+ covered: lineCovered,
4074
+ skipped: 0,
4075
+ percentage: pct(lineCovered, lineMissed)
4076
+ },
4077
+ branches: {
4078
+ total: branchCovered + branchMissed,
4079
+ covered: branchCovered,
4080
+ skipped: 0,
4081
+ percentage: pct(branchCovered, branchMissed)
4082
+ },
4083
+ functions: {
4084
+ total: methodCovered + methodMissed,
4085
+ covered: methodCovered,
4086
+ skipped: 0,
4087
+ percentage: pct(methodCovered, methodMissed)
4088
+ },
4089
+ statements: {
4090
+ total: instrCovered + instrMissed,
4091
+ covered: instrCovered,
4092
+ skipped: 0,
4093
+ percentage: pct(instrCovered, instrMissed)
4094
+ }
4095
+ };
4096
+ }
4036
4097
  var CoverageAnalyzer = class {
4037
4098
  constructor(projectPath) {
4038
4099
  this.projectPath = projectPath;
@@ -4042,29 +4103,53 @@ var CoverageAnalyzer = class {
4042
4103
  */
4043
4104
  async analyze() {
4044
4105
  const framework = await detectTestFramework(this.projectPath);
4045
- const coverageTool = await detectCoverageTool(this.projectPath);
4046
4106
  if (!framework) {
4047
- throw new Error("No test framework detected (vitest, jest, or mocha)");
4107
+ return this.zeroCoverage();
4048
4108
  }
4049
- const existingCoverage = await this.readExistingCoverage();
4109
+ const existingCoverage = await this.readExistingCoverage(framework);
4050
4110
  if (existingCoverage) {
4051
4111
  return existingCoverage;
4052
4112
  }
4113
+ if (framework === "maven" || framework === "gradle") {
4114
+ return this.zeroCoverage();
4115
+ }
4116
+ const coverageTool = await detectCoverageTool(this.projectPath);
4053
4117
  return await this.runWithCoverage(framework, coverageTool);
4054
4118
  }
4119
+ /** Return empty coverage metrics (graceful fallback) */
4120
+ zeroCoverage() {
4121
+ const zero = { total: 0, covered: 0, skipped: 0, percentage: 0 };
4122
+ return { lines: zero, branches: zero, functions: zero, statements: zero };
4123
+ }
4055
4124
  /**
4056
- * Read existing coverage report if available
4125
+ * Read existing coverage report if available.
4126
+ * Supports Node.js (c8/nyc JSON) and JVM (JaCoCo CSV) formats.
4057
4127
  */
4058
- async readExistingCoverage() {
4128
+ async readExistingCoverage(framework) {
4129
+ if (framework === "maven" || framework === "gradle") {
4130
+ const jacocoPaths = framework === "maven" ? [
4131
+ join(this.projectPath, "target", "site", "jacoco", "jacoco.csv"),
4132
+ join(this.projectPath, "target", "site", "jacoco-ut", "jacoco.csv")
4133
+ ] : [join(this.projectPath, "build", "reports", "jacoco", "test", "jacocoTestReport.csv")];
4134
+ for (const csvPath of jacocoPaths) {
4135
+ try {
4136
+ await access(csvPath, constants.R_OK);
4137
+ const csv = await readFile(csvPath, "utf-8");
4138
+ return parseJacocoCsv(csv);
4139
+ } catch {
4140
+ }
4141
+ }
4142
+ return null;
4143
+ }
4059
4144
  const possiblePaths = [
4060
4145
  join(this.projectPath, "coverage", "coverage-summary.json"),
4061
4146
  join(this.projectPath, ".coverage", "coverage-summary.json"),
4062
4147
  join(this.projectPath, "coverage", "lcov-report", "coverage-summary.json")
4063
4148
  ];
4064
- for (const path40 of possiblePaths) {
4149
+ for (const p5 of possiblePaths) {
4065
4150
  try {
4066
- await access(path40, constants.R_OK);
4067
- const content = await readFile(path40, "utf-8");
4151
+ await access(p5, constants.R_OK);
4152
+ const content = await readFile(p5, "utf-8");
4068
4153
  const report = JSON.parse(content);
4069
4154
  return parseCoverageSummary(report);
4070
4155
  } catch {
@@ -4724,7 +4809,7 @@ var BuildVerifier = class {
4724
4809
  stderr: ""
4725
4810
  };
4726
4811
  }
4727
- const SAFE_BUILD_PATTERN = /^(npm|pnpm|yarn|bun)\s+(run\s+)?[\w:.-]+$|^npx\s+tsc(\s+--[\w-]+)*$/;
4812
+ const SAFE_BUILD_PATTERN = /^(npm|pnpm|yarn|bun)\s+(run\s+)?[\w:.-]+$|^npx\s+tsc(\s+--[\w-]+)*$|^\.(\/|\\)(mvnw|gradlew)(\s+[\w:.-]+)*(\s+-[\w-]+)*$/;
4728
4813
  if (!SAFE_BUILD_PATTERN.test(buildCommand.trim())) {
4729
4814
  return {
4730
4815
  success: false,
@@ -4829,9 +4914,20 @@ var BuildVerifier = class {
4829
4914
  }
4830
4915
  }
4831
4916
  /**
4832
- * Detect build command from package.json
4917
+ * Detect build command from project build files.
4918
+ * Checks Maven, Gradle, and Node.js in that order.
4833
4919
  */
4834
4920
  async detectBuildCommand() {
4921
+ if (await this.fileExists(path17.join(this.projectPath, "pom.xml"))) {
4922
+ const wrapper = path17.join(this.projectPath, "mvnw");
4923
+ return await this.fileExists(wrapper) ? "./mvnw compile -B -q" : "mvn compile -B -q";
4924
+ }
4925
+ for (const f of ["build.gradle", "build.gradle.kts"]) {
4926
+ if (await this.fileExists(path17.join(this.projectPath, f))) {
4927
+ const wrapper = path17.join(this.projectPath, "gradlew");
4928
+ return await this.fileExists(wrapper) ? "./gradlew classes -q" : "gradle classes -q";
4929
+ }
4930
+ }
4835
4931
  try {
4836
4932
  const packageJsonPath = path17.join(this.projectPath, "package.json");
4837
4933
  const content = await fs16.readFile(packageJsonPath, "utf-8");
@@ -4842,10 +4938,9 @@ var BuildVerifier = class {
4842
4938
  if (packageJson.devDependencies?.typescript || packageJson.dependencies?.typescript) {
4843
4939
  return "npx tsc --noEmit";
4844
4940
  }
4845
- return null;
4846
4941
  } catch {
4847
- return null;
4848
4942
  }
4943
+ return null;
4849
4944
  }
4850
4945
  /**
4851
4946
  * Parse errors from build output
@@ -4919,6 +5014,16 @@ var BuildVerifier = class {
4919
5014
  };
4920
5015
 
4921
5016
  // src/quality/analyzers/correctness.ts
5017
+ async function resolveJvmExecutable(projectPath, tool) {
5018
+ const wrapper = tool === "maven" ? "mvnw" : "gradlew";
5019
+ const fallback = tool === "maven" ? "mvn" : "gradle";
5020
+ try {
5021
+ await access(join(projectPath, wrapper));
5022
+ return join(projectPath, wrapper);
5023
+ } catch {
5024
+ return fallback;
5025
+ }
5026
+ }
4922
5027
  function parseVitestOutput(stdout) {
4923
5028
  const testsMatch = stdout.match(
4924
5029
  /Tests\s+(?:(\d+)\s+passed)?(?:\s*\|\s*(\d+)\s+failed)?(?:\s*\|\s*(\d+)\s+skipped)?/
@@ -4971,10 +5076,29 @@ function buildTestCommand(framework) {
4971
5076
  return { command: "npx", args: ["jest", "--json"] };
4972
5077
  case "mocha":
4973
5078
  return { command: "npx", args: ["mocha", "--reporter=json"] };
5079
+ case "maven":
5080
+ return { command: "__maven__", args: ["test", "--no-transfer-progress", "-B"] };
5081
+ case "gradle":
5082
+ return { command: "__gradle__", args: ["test"] };
4974
5083
  default:
4975
5084
  return null;
4976
5085
  }
4977
5086
  }
5087
+ function parseMavenOutput(output) {
5088
+ let passed = 0, failed = 0, skipped = 0;
5089
+ const pattern = /Tests run:\s*(\d+),\s*Failures:\s*(\d+),\s*Errors:\s*(\d+),\s*Skipped:\s*(\d+)/gi;
5090
+ for (const match of output.matchAll(pattern)) {
5091
+ const total = parseInt(match[1] ?? "0", 10);
5092
+ const failures = parseInt(match[2] ?? "0", 10);
5093
+ const errors = parseInt(match[3] ?? "0", 10);
5094
+ const skip = parseInt(match[4] ?? "0", 10);
5095
+ const f = failures + errors;
5096
+ passed += total - f - skip;
5097
+ failed += f;
5098
+ skipped += skip;
5099
+ }
5100
+ return { passed, failed, skipped };
5101
+ }
4978
5102
  var CorrectnessAnalyzer = class {
4979
5103
  constructor(projectPath) {
4980
5104
  this.projectPath = projectPath;
@@ -5026,6 +5150,11 @@ var CorrectnessAnalyzer = class {
5026
5150
  if (!cmd) {
5027
5151
  return { passed: 0, failed: 0, skipped: 0 };
5028
5152
  }
5153
+ if (cmd.command === "__maven__") {
5154
+ cmd.command = await resolveJvmExecutable(this.projectPath, "maven");
5155
+ } else if (cmd.command === "__gradle__") {
5156
+ cmd.command = await resolveJvmExecutable(this.projectPath, "gradle");
5157
+ }
5029
5158
  try {
5030
5159
  const proc = execa(cmd.command, cmd.args, {
5031
5160
  cwd: this.projectPath,
@@ -5037,15 +5166,15 @@ var CorrectnessAnalyzer = class {
5037
5166
  });
5038
5167
  trackSubprocess(proc);
5039
5168
  const result = await proc;
5040
- const output = result.stdout + "\n" + result.stderr;
5169
+ const output = (result.stdout ?? "") + "\n" + (result.stderr ?? "");
5041
5170
  switch (framework) {
5042
5171
  case "vitest":
5043
5172
  return parseVitestOutput(output);
5044
5173
  case "jest":
5045
- return parseJestOutput(result.stdout);
5174
+ return parseJestOutput(result.stdout ?? "");
5046
5175
  case "mocha": {
5047
5176
  try {
5048
- const json2 = JSON.parse(result.stdout);
5177
+ const json2 = JSON.parse(result.stdout ?? "");
5049
5178
  return {
5050
5179
  passed: json2.stats?.passes ?? 0,
5051
5180
  failed: json2.stats?.failures ?? 0,
@@ -5055,6 +5184,9 @@ var CorrectnessAnalyzer = class {
5055
5184
  return { passed: 0, failed: 0, skipped: 0 };
5056
5185
  }
5057
5186
  }
5187
+ case "maven":
5188
+ case "gradle":
5189
+ return parseMavenOutput(output);
5058
5190
  default:
5059
5191
  return { passed: 0, failed: 0, skipped: 0 };
5060
5192
  }
@@ -7854,9 +7986,31 @@ var QualityEvaluator = class {
7854
7986
  return suggestions;
7855
7987
  }
7856
7988
  /**
7857
- * Find source files in project
7989
+ * Find source files in project, adapting to the detected language stack.
7858
7990
  */
7859
7991
  async findSourceFiles() {
7992
+ const { access: access10 } = await import('fs/promises');
7993
+ const { join: join18 } = await import('path');
7994
+ let isJava = false;
7995
+ try {
7996
+ await access10(join18(this.projectPath, "pom.xml"));
7997
+ isJava = true;
7998
+ } catch {
7999
+ for (const f of ["build.gradle", "build.gradle.kts"]) {
8000
+ try {
8001
+ await access10(join18(this.projectPath, f));
8002
+ isJava = true;
8003
+ break;
8004
+ } catch {
8005
+ }
8006
+ }
8007
+ }
8008
+ if (isJava) {
8009
+ return glob("src/main/java/**/*.java", {
8010
+ cwd: this.projectPath,
8011
+ absolute: true
8012
+ });
8013
+ }
7860
8014
  return glob("**/*.{ts,js,tsx,jsx}", {
7861
8015
  cwd: this.projectPath,
7862
8016
  absolute: true,
@@ -17482,6 +17636,18 @@ var checkAgentCapabilityTool = defineTool({
17482
17636
  });
17483
17637
  var simpleAgentTools = [spawnSimpleAgentTool, checkAgentCapabilityTool];
17484
17638
  async function detectTestFramework2(cwd) {
17639
+ try {
17640
+ await fs16__default.access(path17__default.join(cwd, "pom.xml"));
17641
+ return "maven";
17642
+ } catch {
17643
+ }
17644
+ for (const gradleFile of ["build.gradle", "build.gradle.kts"]) {
17645
+ try {
17646
+ await fs16__default.access(path17__default.join(cwd, gradleFile));
17647
+ return "gradle";
17648
+ } catch {
17649
+ }
17650
+ }
17485
17651
  try {
17486
17652
  const pkgPath = path17__default.join(cwd, "package.json");
17487
17653
  const pkgContent = await fs16__default.readFile(pkgPath, "utf-8");
@@ -17499,36 +17665,79 @@ async function detectTestFramework2(cwd) {
17499
17665
  return null;
17500
17666
  }
17501
17667
  }
17668
+ function toMavenTestFilter(pattern) {
17669
+ const base = path17__default.basename(pattern).replace(/\.java$/, "");
17670
+ return base;
17671
+ }
17672
+ function toGradleTestFilter(pattern) {
17673
+ const base = path17__default.basename(pattern).replace(/\.java$/, "");
17674
+ return `*${base}`;
17675
+ }
17676
+ async function mavenExecutable(cwd) {
17677
+ try {
17678
+ await fs16__default.access(path17__default.join(cwd, "mvnw"));
17679
+ return "./mvnw";
17680
+ } catch {
17681
+ return "mvn";
17682
+ }
17683
+ }
17684
+ async function gradleExecutable(cwd) {
17685
+ try {
17686
+ await fs16__default.access(path17__default.join(cwd, "gradlew"));
17687
+ return "./gradlew";
17688
+ } catch {
17689
+ return "gradle";
17690
+ }
17691
+ }
17502
17692
  var runTestsTool = defineTool({
17503
17693
  name: "run_tests",
17504
- description: `Run tests in the project (auto-detects vitest, jest, or mocha).
17694
+ description: `Run tests in the project (auto-detects Maven/Gradle/JUnit, vitest, jest, or mocha).
17505
17695
 
17506
17696
  Examples:
17507
17697
  - Run all tests: {}
17508
17698
  - With coverage: { "coverage": true }
17509
- - Specific pattern: { "pattern": "src/**/*.test.ts" }
17510
- - Specific framework: { "framework": "vitest" }`,
17699
+ - Specific pattern (JS): { "pattern": "src/**/*.test.ts" }
17700
+ - Specific test class (Java): { "pattern": "**/ItemRestControllerIT.java" }
17701
+ - Specific framework: { "framework": "maven" }
17702
+ - Maven module: { "framework": "maven", "args": ["-pl", "stock-core"] }`,
17511
17703
  category: "test",
17512
17704
  parameters: z.object({
17513
17705
  cwd: z.string().optional().describe("Project directory"),
17514
- pattern: z.string().optional().describe("Test file pattern"),
17706
+ pattern: z.string().optional().describe("Test file pattern or class glob"),
17515
17707
  coverage: z.boolean().optional().default(false).describe("Collect coverage"),
17516
- framework: z.string().optional().describe("Test framework (vitest, jest, mocha)"),
17517
- watch: z.boolean().optional().default(false).describe("Watch mode")
17708
+ framework: z.string().optional().describe("Test framework (maven, gradle, vitest, jest, mocha)"),
17709
+ watch: z.boolean().optional().default(false).describe("Watch mode"),
17710
+ args: z.array(z.string()).optional().describe("Extra arguments (e.g. Maven -pl module)")
17518
17711
  }),
17519
- async execute({ cwd, pattern, coverage, framework, watch: watch2 }) {
17712
+ async execute({ cwd, pattern, coverage, framework, watch: watch2, args: extraArgs }) {
17520
17713
  const projectDir = cwd ?? process.cwd();
17521
17714
  const detectedFramework = framework ?? await detectTestFramework2(projectDir);
17522
17715
  if (!detectedFramework) {
17523
- throw new ToolError("No test framework detected. Install vitest, jest, or mocha.", {
17524
- tool: "run_tests"
17525
- });
17716
+ throw new ToolError(
17717
+ "No test framework detected. For Java projects ensure pom.xml or build.gradle exists. For Node.js projects install vitest, jest, or mocha.",
17718
+ { tool: "run_tests" }
17719
+ );
17526
17720
  }
17527
17721
  const startTime = performance.now();
17528
17722
  try {
17529
17723
  const args = [];
17530
17724
  let command = "npx";
17531
17725
  switch (detectedFramework) {
17726
+ case "maven": {
17727
+ command = await mavenExecutable(projectDir);
17728
+ args.push(coverage ? "verify" : "test");
17729
+ if (extraArgs && extraArgs.length > 0) args.push(...extraArgs);
17730
+ if (pattern) args.push(`-Dtest=${toMavenTestFilter(pattern)}`);
17731
+ break;
17732
+ }
17733
+ case "gradle": {
17734
+ command = await gradleExecutable(projectDir);
17735
+ args.push("test");
17736
+ if (extraArgs && extraArgs.length > 0) args.push(...extraArgs);
17737
+ if (pattern) args.push("--tests", toGradleTestFilter(pattern));
17738
+ if (coverage) args.push("jacocoTestReport");
17739
+ break;
17740
+ }
17532
17741
  case "vitest":
17533
17742
  args.push("vitest", "run");
17534
17743
  if (coverage) args.push("--coverage");
@@ -17566,8 +17775,8 @@ Examples:
17566
17775
  const duration = performance.now() - startTime;
17567
17776
  return parseTestResults(
17568
17777
  detectedFramework,
17569
- result.stdout,
17570
- result.stderr,
17778
+ result.stdout ?? "",
17779
+ result.stderr ?? "",
17571
17780
  result.exitCode ?? 0,
17572
17781
  duration
17573
17782
  );
@@ -17581,18 +17790,37 @@ Examples:
17581
17790
  }
17582
17791
  });
17583
17792
  function parseTestResults(framework, stdout, stderr, exitCode, duration) {
17584
- try {
17585
- const jsonMatch = stdout.match(/\{[\s\S]*\}/);
17586
- if (jsonMatch) {
17587
- const json2 = JSON.parse(jsonMatch[0]);
17588
- if (framework === "vitest" || framework === "jest") {
17793
+ if (framework === "vitest" || framework === "jest") {
17794
+ try {
17795
+ const jsonMatch = stdout.match(/\{[\s\S]*\}/);
17796
+ if (jsonMatch) {
17797
+ const json2 = JSON.parse(jsonMatch[0]);
17589
17798
  return parseJestLikeResults(json2, duration);
17590
17799
  }
17800
+ } catch {
17591
17801
  }
17592
- } catch {
17593
17802
  }
17594
- const passMatch = stdout.match(/(\d+)\s*(?:passed|passing)/i);
17595
- const failMatch = stdout.match(/(\d+)\s*(?:failed|failing)/i);
17803
+ const mavenMatch = stdout.match(
17804
+ /Tests run:\s*(\d+),\s*Failures:\s*(\d+),\s*Errors:\s*(\d+),\s*Skipped:\s*(\d+)/i
17805
+ );
17806
+ if (mavenMatch) {
17807
+ const total = parseInt(mavenMatch[1] ?? "0", 10);
17808
+ const failures = parseInt(mavenMatch[2] ?? "0", 10);
17809
+ const errors = parseInt(mavenMatch[3] ?? "0", 10);
17810
+ const skipped2 = parseInt(mavenMatch[4] ?? "0", 10);
17811
+ const failed2 = failures + errors;
17812
+ return {
17813
+ passed: total - failed2 - skipped2,
17814
+ failed: failed2,
17815
+ skipped: skipped2,
17816
+ total,
17817
+ duration,
17818
+ success: exitCode === 0,
17819
+ failures: failed2 > 0 ? parseFailuresFromOutput(stderr || stdout) : []
17820
+ };
17821
+ }
17822
+ const passMatch = stdout.match(/(\d+)\s*(?:passed|passing|tests\s+run)/i);
17823
+ const failMatch = stdout.match(/(\d+)\s*(?:failed|failing|failures)/i);
17596
17824
  const skipMatch = stdout.match(/(\d+)\s*(?:skipped|pending)/i);
17597
17825
  const passed = passMatch ? parseInt(passMatch[1] ?? "0", 10) : 0;
17598
17826
  const failed = failMatch ? parseInt(failMatch[1] ?? "0", 10) : 0;
@@ -17649,6 +17877,37 @@ function parseFailuresFromOutput(output) {
17649
17877
  }
17650
17878
  return failures;
17651
17879
  }
17880
+ function parseJacocoCsvCoverage(csv) {
17881
+ const lines = csv.trim().split("\n").slice(1);
17882
+ if (lines.length === 0) return null;
17883
+ let lineMissed = 0, lineCovered = 0;
17884
+ let branchMissed = 0, branchCovered = 0;
17885
+ let methodMissed = 0, methodCovered = 0;
17886
+ let instrMissed = 0, instrCovered = 0;
17887
+ for (const line of lines) {
17888
+ const cols = line.split(",");
17889
+ if (cols.length < 13) continue;
17890
+ instrMissed += parseInt(cols[3] ?? "0", 10);
17891
+ instrCovered += parseInt(cols[4] ?? "0", 10);
17892
+ branchMissed += parseInt(cols[5] ?? "0", 10);
17893
+ branchCovered += parseInt(cols[6] ?? "0", 10);
17894
+ lineMissed += parseInt(cols[7] ?? "0", 10);
17895
+ lineCovered += parseInt(cols[8] ?? "0", 10);
17896
+ methodMissed += parseInt(cols[11] ?? "0", 10);
17897
+ methodCovered += parseInt(cols[12] ?? "0", 10);
17898
+ }
17899
+ if (lineCovered + lineMissed === 0) return null;
17900
+ const pct = (covered, missed) => {
17901
+ const total = covered + missed;
17902
+ return total > 0 ? Math.round(covered / total * 1e3) / 10 : 0;
17903
+ };
17904
+ return {
17905
+ lines: pct(lineCovered, lineMissed),
17906
+ branches: pct(branchCovered, branchMissed),
17907
+ functions: pct(methodCovered, methodMissed),
17908
+ statements: pct(instrCovered, instrMissed)
17909
+ };
17910
+ }
17652
17911
  var getCoverageTool = defineTool({
17653
17912
  name: "get_coverage",
17654
17913
  description: `Get test coverage report (requires running tests with --coverage first).
@@ -17667,11 +17926,23 @@ Examples:
17667
17926
  const coverageLocations = [
17668
17927
  path17__default.join(projectDir, "coverage", "coverage-summary.json"),
17669
17928
  path17__default.join(projectDir, "coverage", "coverage-final.json"),
17670
- path17__default.join(projectDir, ".nyc_output", "coverage-summary.json")
17929
+ path17__default.join(projectDir, ".nyc_output", "coverage-summary.json"),
17930
+ // Maven JaCoCo
17931
+ path17__default.join(projectDir, "target", "site", "jacoco", "jacoco.csv"),
17932
+ path17__default.join(projectDir, "target", "site", "jacoco-ut", "jacoco.csv"),
17933
+ // Gradle JaCoCo
17934
+ path17__default.join(projectDir, "build", "reports", "jacoco", "test", "jacocoTestReport.csv")
17671
17935
  ];
17672
17936
  for (const location of coverageLocations) {
17673
17937
  try {
17674
17938
  const content = await fs16__default.readFile(location, "utf-8");
17939
+ if (location.endsWith(".csv")) {
17940
+ const result = parseJacocoCsvCoverage(content);
17941
+ if (result) {
17942
+ return { ...result, report: format === "detailed" ? content : void 0 };
17943
+ }
17944
+ continue;
17945
+ }
17675
17946
  const coverage = JSON.parse(content);
17676
17947
  if (coverage.total) {
17677
17948
  return {
@@ -17685,9 +17956,10 @@ Examples:
17685
17956
  } catch {
17686
17957
  }
17687
17958
  }
17688
- throw new ToolError("Coverage data not found. Run tests with --coverage first.", {
17689
- tool: "get_coverage"
17690
- });
17959
+ throw new ToolError(
17960
+ "Coverage data not found. For Maven projects run 'mvn verify' with JaCoCo plugin. For Node.js run tests with --coverage.",
17961
+ { tool: "get_coverage" }
17962
+ );
17691
17963
  } catch (error) {
17692
17964
  if (error instanceof ToolError) throw error;
17693
17965
  const msg = error instanceof Error ? error.message : String(error);
@@ -17704,25 +17976,41 @@ var runTestFileTool = defineTool({
17704
17976
 
17705
17977
  Examples:
17706
17978
  - Single file: { "file": "src/utils.test.ts" }
17707
- - With framework: { "file": "test/app.spec.js", "framework": "jest" }`,
17979
+ - Java test: { "file": "**/ItemRestControllerIT.java" }
17980
+ - With framework: { "file": "test/app.spec.js", "framework": "jest" }
17981
+ - Maven module: { "file": "**/MyTest.java", "args": ["-pl", "my-module"] }`,
17708
17982
  category: "test",
17709
17983
  parameters: z.object({
17710
17984
  cwd: z.string().optional().describe("Project directory"),
17711
- file: z.string().describe("Test file path"),
17712
- framework: z.string().optional().describe("Test framework")
17985
+ file: z.string().describe("Test file path or class glob"),
17986
+ framework: z.string().optional().describe("Test framework (maven, gradle, vitest, jest, mocha)"),
17987
+ args: z.array(z.string()).optional().describe("Extra arguments (e.g. Maven -pl module)")
17713
17988
  }),
17714
- async execute({ cwd, file, framework }) {
17989
+ async execute({ cwd, file, framework, args }) {
17715
17990
  return runTestsTool.execute({
17716
17991
  cwd,
17717
17992
  pattern: file,
17718
17993
  coverage: false,
17719
17994
  framework,
17720
- watch: false
17995
+ watch: false,
17996
+ args
17721
17997
  });
17722
17998
  }
17723
17999
  });
17724
18000
  var testTools = [runTestsTool, getCoverageTool, runTestFileTool];
17725
18001
  async function detectLinter2(cwd) {
18002
+ try {
18003
+ await fs16__default.access(path17__default.join(cwd, "pom.xml"));
18004
+ return "maven-checkstyle";
18005
+ } catch {
18006
+ }
18007
+ for (const f of ["build.gradle", "build.gradle.kts"]) {
18008
+ try {
18009
+ await fs16__default.access(path17__default.join(cwd, f));
18010
+ return "gradle-checkstyle";
18011
+ } catch {
18012
+ }
18013
+ }
17726
18014
  try {
17727
18015
  const pkgPath = path17__default.join(cwd, "package.json");
17728
18016
  const pkgContent = await fs16__default.readFile(pkgPath, "utf-8");
@@ -17739,21 +18027,60 @@ async function detectLinter2(cwd) {
17739
18027
  return null;
17740
18028
  }
17741
18029
  }
18030
+ async function mavenExec(cwd) {
18031
+ try {
18032
+ await fs16__default.access(path17__default.join(cwd, "mvnw"));
18033
+ return "./mvnw";
18034
+ } catch {
18035
+ return "mvn";
18036
+ }
18037
+ }
18038
+ async function gradleExec(cwd) {
18039
+ try {
18040
+ await fs16__default.access(path17__default.join(cwd, "gradlew"));
18041
+ return "./gradlew";
18042
+ } catch {
18043
+ return "gradle";
18044
+ }
18045
+ }
18046
+ function parseCheckstyleOutput(stdout, stderr) {
18047
+ const output = stdout + "\n" + stderr;
18048
+ const issues = [];
18049
+ let errors = 0;
18050
+ let warnings = 0;
18051
+ const lineRe = /\[(ERROR|WARN(?:ING)?)\]\s+(.+?):(?:\[(\d+)(?:,(\d+))?\])?\s*(?:\([^)]*\))?\s*(.+)/gi;
18052
+ for (const m of output.matchAll(lineRe)) {
18053
+ const sev = (m[1] ?? "").toUpperCase().startsWith("ERROR") ? "error" : "warning";
18054
+ if (sev === "error") errors++;
18055
+ else warnings++;
18056
+ issues.push({
18057
+ file: m[2]?.trim() ?? "",
18058
+ line: parseInt(m[3] ?? "0", 10),
18059
+ column: parseInt(m[4] ?? "0", 10),
18060
+ severity: sev,
18061
+ message: m[5]?.trim() ?? "",
18062
+ rule: ""
18063
+ });
18064
+ }
18065
+ const score = Math.max(0, 100 - errors * 5 - warnings * 2);
18066
+ return { errors, warnings, fixable: 0, issues, score };
18067
+ }
17742
18068
  var runLinterTool = defineTool({
17743
18069
  name: "run_linter",
17744
- description: `Run linter on the codebase (auto-detects eslint, oxlint, or biome).
18070
+ description: `Run linter on the codebase (auto-detects eslint, oxlint, biome for Node.js; checkstyle for Maven/Gradle).
17745
18071
 
17746
18072
  Examples:
17747
18073
  - Lint all: {} \u2192 { "errors": 0, "warnings": 5, "score": 90 }
17748
- - Auto-fix: { "fix": true }
18074
+ - Auto-fix (Node.js): { "fix": true }
17749
18075
  - Specific files: { "files": ["src/app.ts", "src/utils.ts"] }
17750
- - Force linter: { "linter": "eslint" }`,
18076
+ - Force linter: { "linter": "eslint" }
18077
+ - Java project (Maven): automatically runs checkstyle:check if plugin is configured`,
17751
18078
  category: "quality",
17752
18079
  parameters: z.object({
17753
18080
  cwd: z.string().optional().describe("Project directory"),
17754
18081
  files: z.array(z.string()).optional().describe("Specific files to lint"),
17755
- fix: z.boolean().optional().default(false).describe("Auto-fix issues"),
17756
- linter: z.string().optional().describe("Linter to use (eslint, oxlint, biome)")
18082
+ fix: z.boolean().optional().default(false).describe("Auto-fix issues (Node.js only)"),
18083
+ linter: z.string().optional().describe("Linter to use (eslint, oxlint, biome, maven-checkstyle, gradle-checkstyle)")
17757
18084
  }),
17758
18085
  async execute({ cwd, files, fix, linter }) {
17759
18086
  const projectDir = cwd ?? process.cwd();
@@ -17766,13 +18093,23 @@ Examples:
17766
18093
  issues: [],
17767
18094
  score: null,
17768
18095
  linter: "none",
17769
- message: "No linter detected (looked for: eslint, oxlint, biome). Install one or use bash_exec to run a custom linter."
18096
+ message: "No linter detected (looked for: eslint, oxlint, biome for Node.js; checkstyle plugin for Maven/Gradle). Install one or use bash_exec to run a custom linter."
17770
18097
  };
17771
18098
  }
17772
18099
  try {
17773
18100
  const args = [];
17774
18101
  let command = "npx";
17775
18102
  switch (detectedLinter) {
18103
+ case "maven-checkstyle": {
18104
+ command = await mavenExec(projectDir);
18105
+ args.push("checkstyle:check", "--no-transfer-progress", "-q");
18106
+ break;
18107
+ }
18108
+ case "gradle-checkstyle": {
18109
+ command = await gradleExec(projectDir);
18110
+ args.push("checkstyleMain", "--quiet");
18111
+ break;
18112
+ }
17776
18113
  case "oxlint":
17777
18114
  args.push("oxlint");
17778
18115
  if (files && files.length > 0) {
@@ -17813,7 +18150,25 @@ Examples:
17813
18150
  reject: false,
17814
18151
  timeout: 12e4
17815
18152
  });
17816
- return parseLintResults(detectedLinter, result.stdout, result.stderr);
18153
+ const combinedOutput = (result.stdout ?? "") + (result.stderr ?? "");
18154
+ if ((detectedLinter === "maven-checkstyle" || detectedLinter === "gradle-checkstyle") && /No plugin found|Task.*not found|checkstyle.*not configured/i.test(combinedOutput)) {
18155
+ return {
18156
+ errors: 0,
18157
+ warnings: 0,
18158
+ fixable: 0,
18159
+ issues: [],
18160
+ score: null,
18161
+ linter: "none",
18162
+ message: "Checkstyle plugin not configured in build file. Add maven-checkstyle-plugin (Maven) or checkstyle plugin (Gradle) to enable Java linting."
18163
+ };
18164
+ }
18165
+ if (detectedLinter === "maven-checkstyle" || detectedLinter === "gradle-checkstyle") {
18166
+ return {
18167
+ ...parseCheckstyleOutput(result.stdout ?? "", result.stderr ?? ""),
18168
+ linter: detectedLinter
18169
+ };
18170
+ }
18171
+ return parseLintResults(detectedLinter, result.stdout ?? "", result.stderr ?? "");
17817
18172
  } catch (error) {
17818
18173
  throw new ToolError(
17819
18174
  `Linting failed: ${error instanceof Error ? error.message : String(error)}`,
@@ -17916,6 +18271,28 @@ Examples:
17916
18271
  });
17917
18272
  async function findSourceFiles(cwd) {
17918
18273
  const { glob: glob17 } = await import('glob');
18274
+ let isJava = false;
18275
+ try {
18276
+ await fs16__default.access(path17__default.join(cwd, "pom.xml"));
18277
+ isJava = true;
18278
+ } catch {
18279
+ }
18280
+ if (!isJava) {
18281
+ for (const f of ["build.gradle", "build.gradle.kts"]) {
18282
+ try {
18283
+ await fs16__default.access(path17__default.join(cwd, f));
18284
+ isJava = true;
18285
+ break;
18286
+ } catch {
18287
+ }
18288
+ }
18289
+ }
18290
+ if (isJava) {
18291
+ return glob17("src/main/java/**/*.java", {
18292
+ cwd,
18293
+ absolute: true
18294
+ });
18295
+ }
17919
18296
  return glob17("src/**/*.{ts,js,tsx,jsx}", {
17920
18297
  cwd,
17921
18298
  absolute: true,
@@ -17933,6 +18310,8 @@ function analyzeFileComplexity(content, file) {
17933
18310
  const line = lines[i] ?? "";
17934
18311
  const funcMatch = line.match(
17935
18312
  /(?:function|async function)\s+(\w+)|(\w+)\s*(?:=|:)\s*(?:async\s*)?\(?.*\)?\s*=>/
18313
+ ) ?? line.match(
18314
+ /(?:public|private|protected|static|final|native|synchronized|abstract)\s+\S+\s+(\w+)\s*\(/
17936
18315
  );
17937
18316
  if (funcMatch && braceDepth === 0) {
17938
18317
  if (currentFunction) {
@@ -18061,7 +18440,7 @@ Examples:
18061
18440
  if (stats.isFile()) {
18062
18441
  filesToSearch = [targetPath];
18063
18442
  } else {
18064
- const globPattern = include ?? "**/*.{ts,tsx,js,jsx,json,md,txt}";
18443
+ const globPattern = include ?? "**/*.{ts,tsx,js,jsx,java,py,go,rs,json,md,txt}";
18065
18444
  const defaultExclude = ["**/node_modules/**", "**/.git/**", "**/dist/**", "**/coverage/**"];
18066
18445
  const excludePatterns = exclude ?? defaultExclude;
18067
18446
  filesToSearch = await glob(globPattern, {
@@ -18791,7 +19170,205 @@ ${message}
18791
19170
  }
18792
19171
  }
18793
19172
  });
18794
- var buildTools = [runScriptTool, installDepsTool, makeTool, tscTool];
19173
+ async function resolveMaven(cwd) {
19174
+ try {
19175
+ await fs16__default.access(path17__default.join(cwd, "mvnw"));
19176
+ return "./mvnw";
19177
+ } catch {
19178
+ return "mvn";
19179
+ }
19180
+ }
19181
+ async function resolveGradle(cwd) {
19182
+ try {
19183
+ await fs16__default.access(path17__default.join(cwd, "gradlew"));
19184
+ return "./gradlew";
19185
+ } catch {
19186
+ return "gradle";
19187
+ }
19188
+ }
19189
+ var runMavenTool = defineTool({
19190
+ name: "run_maven",
19191
+ description: `Run a Maven goal (auto-detects ./mvnw wrapper).
19192
+
19193
+ Examples:
19194
+ - Compile: { "goal": "compile" }
19195
+ - Run tests: { "goal": "test" }
19196
+ - Package: { "goal": "package" }
19197
+ - Skip tests: { "goal": "package", "args": ["-DskipTests"] }
19198
+ - Specific module: { "goal": "test", "args": ["-pl", "stock-core"] }
19199
+ - Quiet mode: { "goal": "verify", "args": ["-q", "--no-transfer-progress"] }`,
19200
+ category: "build",
19201
+ parameters: z.object({
19202
+ goal: z.string().describe("Maven goal (compile, test, package, verify, clean, install, ...)"),
19203
+ cwd: z.string().optional().describe("Project directory"),
19204
+ args: z.array(z.string()).optional().describe("Additional Maven arguments"),
19205
+ env: z.record(z.string(), z.string()).optional().describe("Environment variables"),
19206
+ timeout: z.number().optional().describe("Timeout in milliseconds")
19207
+ }),
19208
+ async execute({ goal, cwd, args, env: env2, timeout }) {
19209
+ const projectDir = cwd ?? process.cwd();
19210
+ const startTime = performance.now();
19211
+ const timeoutMs = timeout ?? DEFAULT_TIMEOUT_MS4;
19212
+ const { CommandHeartbeat: CommandHeartbeat2 } = await Promise.resolve().then(() => (init_heartbeat(), heartbeat_exports));
19213
+ const heartbeat = new CommandHeartbeat2({
19214
+ onUpdate: (stats) => {
19215
+ if (stats.elapsedSeconds > 10)
19216
+ process.stderr.write(`\r\u23F1\uFE0F ${stats.elapsedSeconds}s elapsed`);
19217
+ },
19218
+ onWarn: (message) => process.stderr.write(`
19219
+ ${message}
19220
+ `)
19221
+ });
19222
+ try {
19223
+ heartbeat.start();
19224
+ const command = await resolveMaven(projectDir);
19225
+ const cmdArgs = [goal, "--no-transfer-progress", "-B", ...args ?? []];
19226
+ const subprocess = execa(command, cmdArgs, {
19227
+ cwd: projectDir,
19228
+ timeout: timeoutMs,
19229
+ env: { ...process.env, ...env2 },
19230
+ reject: false,
19231
+ buffer: false,
19232
+ maxBuffer: MAX_OUTPUT_SIZE2
19233
+ });
19234
+ let stdoutBuffer = "";
19235
+ let stderrBuffer = "";
19236
+ subprocess.stdout?.on("data", (chunk) => {
19237
+ const text = chunk.toString();
19238
+ stdoutBuffer += text;
19239
+ process.stdout.write(text);
19240
+ heartbeat.activity();
19241
+ });
19242
+ subprocess.stderr?.on("data", (chunk) => {
19243
+ const text = chunk.toString();
19244
+ stderrBuffer += text;
19245
+ process.stderr.write(text);
19246
+ heartbeat.activity();
19247
+ });
19248
+ const result = await subprocess;
19249
+ const buildResult = {
19250
+ success: result.exitCode === 0,
19251
+ stdout: truncateOutput2(stdoutBuffer),
19252
+ stderr: truncateOutput2(stderrBuffer),
19253
+ exitCode: result.exitCode ?? 0,
19254
+ duration: performance.now() - startTime
19255
+ };
19256
+ if (!buildResult.success) {
19257
+ buildResult.hint = getBuildHint(stderrBuffer || stdoutBuffer, "run_maven");
19258
+ }
19259
+ return buildResult;
19260
+ } catch (error) {
19261
+ if (error.timedOut) {
19262
+ throw new TimeoutError(`Maven goal '${goal}' timed out after ${timeoutMs}ms`, {
19263
+ timeoutMs,
19264
+ operation: `mvn ${goal}`
19265
+ });
19266
+ }
19267
+ throw new ToolError(
19268
+ `Maven failed: ${error instanceof Error ? error.message : String(error)}`,
19269
+ { tool: "run_maven", cause: error instanceof Error ? error : void 0 }
19270
+ );
19271
+ } finally {
19272
+ heartbeat.stop();
19273
+ process.stderr.write("\r \r");
19274
+ }
19275
+ }
19276
+ });
19277
+ var runGradleTool = defineTool({
19278
+ name: "run_gradle",
19279
+ description: `Run a Gradle task (auto-detects ./gradlew wrapper).
19280
+
19281
+ Examples:
19282
+ - Build: { "task": "build" }
19283
+ - Run tests: { "task": "test" }
19284
+ - Assemble: { "task": "assemble" }
19285
+ - Skip tests: { "task": "build", "args": ["-x", "test"] }
19286
+ - Specific subproject: { "task": ":stock-core:test" }`,
19287
+ category: "build",
19288
+ parameters: z.object({
19289
+ task: z.string().describe("Gradle task (build, test, assemble, clean, check, ...)"),
19290
+ cwd: z.string().optional().describe("Project directory"),
19291
+ args: z.array(z.string()).optional().describe("Additional Gradle arguments"),
19292
+ env: z.record(z.string(), z.string()).optional().describe("Environment variables"),
19293
+ timeout: z.number().optional().describe("Timeout in milliseconds")
19294
+ }),
19295
+ async execute({ task, cwd, args, env: env2, timeout }) {
19296
+ const projectDir = cwd ?? process.cwd();
19297
+ const startTime = performance.now();
19298
+ const timeoutMs = timeout ?? DEFAULT_TIMEOUT_MS4;
19299
+ const { CommandHeartbeat: CommandHeartbeat2 } = await Promise.resolve().then(() => (init_heartbeat(), heartbeat_exports));
19300
+ const heartbeat = new CommandHeartbeat2({
19301
+ onUpdate: (stats) => {
19302
+ if (stats.elapsedSeconds > 10)
19303
+ process.stderr.write(`\r\u23F1\uFE0F ${stats.elapsedSeconds}s elapsed`);
19304
+ },
19305
+ onWarn: (message) => process.stderr.write(`
19306
+ ${message}
19307
+ `)
19308
+ });
19309
+ try {
19310
+ heartbeat.start();
19311
+ const command = await resolveGradle(projectDir);
19312
+ const cmdArgs = [task, "--console=plain", ...args ?? []];
19313
+ const subprocess = execa(command, cmdArgs, {
19314
+ cwd: projectDir,
19315
+ timeout: timeoutMs,
19316
+ env: { ...process.env, ...env2 },
19317
+ reject: false,
19318
+ buffer: false,
19319
+ maxBuffer: MAX_OUTPUT_SIZE2
19320
+ });
19321
+ let stdoutBuffer = "";
19322
+ let stderrBuffer = "";
19323
+ subprocess.stdout?.on("data", (chunk) => {
19324
+ const text = chunk.toString();
19325
+ stdoutBuffer += text;
19326
+ process.stdout.write(text);
19327
+ heartbeat.activity();
19328
+ });
19329
+ subprocess.stderr?.on("data", (chunk) => {
19330
+ const text = chunk.toString();
19331
+ stderrBuffer += text;
19332
+ process.stderr.write(text);
19333
+ heartbeat.activity();
19334
+ });
19335
+ const result = await subprocess;
19336
+ const buildResult = {
19337
+ success: result.exitCode === 0,
19338
+ stdout: truncateOutput2(stdoutBuffer),
19339
+ stderr: truncateOutput2(stderrBuffer),
19340
+ exitCode: result.exitCode ?? 0,
19341
+ duration: performance.now() - startTime
19342
+ };
19343
+ if (!buildResult.success) {
19344
+ buildResult.hint = getBuildHint(stderrBuffer || stdoutBuffer, "run_gradle");
19345
+ }
19346
+ return buildResult;
19347
+ } catch (error) {
19348
+ if (error.timedOut) {
19349
+ throw new TimeoutError(`Gradle task '${task}' timed out after ${timeoutMs}ms`, {
19350
+ timeoutMs,
19351
+ operation: `gradle ${task}`
19352
+ });
19353
+ }
19354
+ throw new ToolError(
19355
+ `Gradle failed: ${error instanceof Error ? error.message : String(error)}`,
19356
+ { tool: "run_gradle", cause: error instanceof Error ? error : void 0 }
19357
+ );
19358
+ } finally {
19359
+ heartbeat.stop();
19360
+ process.stderr.write("\r \r");
19361
+ }
19362
+ }
19363
+ });
19364
+ var buildTools = [
19365
+ runScriptTool,
19366
+ installDepsTool,
19367
+ makeTool,
19368
+ tscTool,
19369
+ runMavenTool,
19370
+ runGradleTool
19371
+ ];
18795
19372
 
18796
19373
  // src/cli/repl/recommended-permissions.ts
18797
19374
  init_paths();