@kakarot-ci/core 0.2.0 → 0.4.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.
Files changed (102) hide show
  1. package/dist/cli/index.js +2289 -0
  2. package/dist/cli/index.js.map +7 -0
  3. package/dist/index.cjs +863 -101
  4. package/dist/index.cjs.map +4 -4
  5. package/dist/index.js +852 -100
  6. package/dist/index.js.map +4 -4
  7. package/dist/src/cli/index.d.ts +7 -0
  8. package/dist/src/cli/index.d.ts.map +1 -0
  9. package/dist/src/core/orchestrator.d.ts +32 -0
  10. package/dist/src/core/orchestrator.d.ts.map +1 -0
  11. package/dist/src/github/client.d.ts.map +1 -0
  12. package/dist/{index.d.ts → src/index.d.ts} +14 -0
  13. package/dist/src/index.d.ts.map +1 -0
  14. package/dist/src/llm/factory.d.ts.map +1 -0
  15. package/dist/src/llm/parser.d.ts.map +1 -0
  16. package/dist/src/llm/prompts/coverage-summary.d.ts +8 -0
  17. package/dist/src/llm/prompts/coverage-summary.d.ts.map +1 -0
  18. package/dist/src/llm/prompts/test-fix.d.ts.map +1 -0
  19. package/dist/src/llm/prompts/test-generation.d.ts.map +1 -0
  20. package/dist/src/llm/providers/anthropic.d.ts.map +1 -0
  21. package/dist/src/llm/providers/base.d.ts.map +1 -0
  22. package/dist/src/llm/providers/google.d.ts.map +1 -0
  23. package/dist/src/llm/providers/openai.d.ts.map +1 -0
  24. package/dist/{llm → src/llm}/test-generator.d.ts +7 -0
  25. package/dist/src/llm/test-generator.d.ts.map +1 -0
  26. package/dist/{types → src/types}/config.d.ts +9 -0
  27. package/dist/src/types/config.d.ts.map +1 -0
  28. package/dist/src/types/config.test.d.ts +2 -0
  29. package/dist/src/types/config.test.d.ts.map +1 -0
  30. package/dist/src/types/coverage.d.ts +40 -0
  31. package/dist/src/types/coverage.d.ts.map +1 -0
  32. package/dist/src/types/diff.d.ts.map +1 -0
  33. package/dist/src/types/github.d.ts.map +1 -0
  34. package/dist/src/types/llm.d.ts.map +1 -0
  35. package/dist/src/types/test-runner.d.ts +30 -0
  36. package/dist/src/types/test-runner.d.ts.map +1 -0
  37. package/dist/src/utils/ast-analyzer.d.ts.map +1 -0
  38. package/dist/src/utils/config-loader.d.ts +10 -0
  39. package/dist/src/utils/config-loader.d.ts.map +1 -0
  40. package/dist/src/utils/coverage-reader.d.ts +6 -0
  41. package/dist/src/utils/coverage-reader.d.ts.map +1 -0
  42. package/dist/src/utils/diff-parser.d.ts.map +1 -0
  43. package/dist/src/utils/diff-parser.test.d.ts +2 -0
  44. package/dist/src/utils/diff-parser.test.d.ts.map +1 -0
  45. package/dist/src/utils/logger.d.ts.map +1 -0
  46. package/dist/src/utils/package-manager-detector.d.ts +6 -0
  47. package/dist/src/utils/package-manager-detector.d.ts.map +1 -0
  48. package/dist/src/utils/package-manager-detector.test.d.ts +2 -0
  49. package/dist/src/utils/package-manager-detector.test.d.ts.map +1 -0
  50. package/dist/src/utils/test-file-path.d.ts +7 -0
  51. package/dist/src/utils/test-file-path.d.ts.map +1 -0
  52. package/dist/src/utils/test-file-path.test.d.ts +2 -0
  53. package/dist/src/utils/test-file-path.test.d.ts.map +1 -0
  54. package/dist/src/utils/test-file-writer.d.ts +8 -0
  55. package/dist/src/utils/test-file-writer.d.ts.map +1 -0
  56. package/dist/src/utils/test-runner/factory.d.ts +6 -0
  57. package/dist/src/utils/test-runner/factory.d.ts.map +1 -0
  58. package/dist/src/utils/test-runner/jest-runner.d.ts +5 -0
  59. package/dist/src/utils/test-runner/jest-runner.d.ts.map +1 -0
  60. package/dist/src/utils/test-runner/vitest-runner.d.ts +5 -0
  61. package/dist/src/utils/test-runner/vitest-runner.d.ts.map +1 -0
  62. package/dist/src/utils/test-target-extractor.d.ts.map +1 -0
  63. package/dist/vitest.config.d.ts +3 -0
  64. package/dist/vitest.config.d.ts.map +1 -0
  65. package/package.json +16 -4
  66. package/dist/github/client.d.ts.map +0 -1
  67. package/dist/index.d.ts.map +0 -1
  68. package/dist/llm/factory.d.ts.map +0 -1
  69. package/dist/llm/parser.d.ts.map +0 -1
  70. package/dist/llm/prompts/test-fix.d.ts.map +0 -1
  71. package/dist/llm/prompts/test-generation.d.ts.map +0 -1
  72. package/dist/llm/providers/anthropic.d.ts.map +0 -1
  73. package/dist/llm/providers/base.d.ts.map +0 -1
  74. package/dist/llm/providers/google.d.ts.map +0 -1
  75. package/dist/llm/providers/openai.d.ts.map +0 -1
  76. package/dist/llm/test-generator.d.ts.map +0 -1
  77. package/dist/types/config.d.ts.map +0 -1
  78. package/dist/types/diff.d.ts.map +0 -1
  79. package/dist/types/github.d.ts.map +0 -1
  80. package/dist/types/llm.d.ts.map +0 -1
  81. package/dist/utils/ast-analyzer.d.ts.map +0 -1
  82. package/dist/utils/config-loader.d.ts +0 -6
  83. package/dist/utils/config-loader.d.ts.map +0 -1
  84. package/dist/utils/diff-parser.d.ts.map +0 -1
  85. package/dist/utils/logger.d.ts.map +0 -1
  86. package/dist/utils/test-target-extractor.d.ts.map +0 -1
  87. /package/dist/{github → src/github}/client.d.ts +0 -0
  88. /package/dist/{llm → src/llm}/factory.d.ts +0 -0
  89. /package/dist/{llm → src/llm}/parser.d.ts +0 -0
  90. /package/dist/{llm → src/llm}/prompts/test-fix.d.ts +0 -0
  91. /package/dist/{llm → src/llm}/prompts/test-generation.d.ts +0 -0
  92. /package/dist/{llm → src/llm}/providers/anthropic.d.ts +0 -0
  93. /package/dist/{llm → src/llm}/providers/base.d.ts +0 -0
  94. /package/dist/{llm → src/llm}/providers/google.d.ts +0 -0
  95. /package/dist/{llm → src/llm}/providers/openai.d.ts +0 -0
  96. /package/dist/{types → src/types}/diff.d.ts +0 -0
  97. /package/dist/{types → src/types}/github.d.ts +0 -0
  98. /package/dist/{types → src/types}/llm.d.ts +0 -0
  99. /package/dist/{utils → src/utils}/ast-analyzer.d.ts +0 -0
  100. /package/dist/{utils → src/utils}/diff-parser.d.ts +0 -0
  101. /package/dist/{utils → src/utils}/logger.d.ts +0 -0
  102. /package/dist/{utils → src/utils}/test-target-extractor.d.ts +0 -0
package/dist/index.js CHANGED
@@ -3,12 +3,15 @@ import { z } from "zod";
3
3
  var KakarotConfigSchema = z.object({
4
4
  apiKey: z.string(),
5
5
  githubToken: z.string().optional(),
6
+ githubOwner: z.string().optional(),
7
+ githubRepo: z.string().optional(),
6
8
  provider: z.enum(["openai", "anthropic", "google"]).optional(),
7
9
  model: z.string().optional(),
8
10
  maxTokens: z.number().int().min(1).max(1e5).optional(),
9
11
  temperature: z.number().min(0).max(2).optional(),
10
12
  fixTemperature: z.number().min(0).max(2).optional(),
11
13
  maxFixAttempts: z.number().int().min(0).max(5).default(3),
14
+ framework: z.enum(["jest", "vitest"]),
12
15
  testLocation: z.enum(["separate", "co-located"]).default("separate"),
13
16
  testDirectory: z.string().default("__tests__"),
14
17
  testFilePattern: z.string().default("*.test.ts"),
@@ -22,8 +25,8 @@ var KakarotConfigSchema = z.object({
22
25
  });
23
26
 
24
27
  // src/utils/config-loader.ts
25
- import { existsSync, readFileSync } from "fs";
26
- import { join, dirname } from "path";
28
+ import { cosmiconfig } from "cosmiconfig";
29
+ import { findUp } from "find-up";
27
30
 
28
31
  // src/utils/logger.ts
29
32
  var debugMode = false;
@@ -78,108 +81,70 @@ function progress(step, total, message, ...args) {
78
81
  }
79
82
 
80
83
  // src/utils/config-loader.ts
81
- function findProjectRoot(startPath) {
82
- const start = startPath ?? process.cwd();
83
- let current = start;
84
- let previous = null;
85
- while (current !== previous) {
86
- if (existsSync(join(current, "package.json"))) {
87
- return current;
88
- }
89
- previous = current;
90
- current = dirname(current);
91
- }
92
- return start;
93
- }
94
- async function loadTypeScriptConfig(root) {
95
- const configPath = join(root, "kakarot.config.ts");
96
- if (!existsSync(configPath)) {
97
- return null;
98
- }
99
- try {
100
- const configModule = await import(configPath);
101
- return configModule.default || configModule.config || null;
102
- } catch (err) {
103
- error(`Failed to load kakarot.config.ts: ${err instanceof Error ? err.message : String(err)}`);
104
- return null;
105
- }
106
- }
107
- async function loadJavaScriptConfig(root) {
108
- const configPath = join(root, ".kakarot-ci.config.js");
109
- if (!existsSync(configPath)) {
110
- return null;
111
- }
112
- try {
113
- const configModule = await import(configPath);
114
- return configModule.default || configModule.config || null;
115
- } catch (err) {
116
- error(`Failed to load .kakarot-ci.config.js: ${err instanceof Error ? err.message : String(err)}`);
117
- return null;
118
- }
119
- }
120
- function loadJsonConfig(root) {
121
- const configPath = join(root, ".kakarot-ci.config.json");
122
- if (!existsSync(configPath)) {
123
- return null;
124
- }
125
- try {
126
- const content = readFileSync(configPath, "utf-8");
127
- return JSON.parse(content);
128
- } catch (err) {
129
- error(`Failed to load .kakarot-ci.config.json: ${err instanceof Error ? err.message : String(err)}`);
130
- return null;
131
- }
132
- }
133
- function loadPackageJsonConfig(root) {
134
- const packagePath = join(root, "package.json");
135
- if (!existsSync(packagePath)) {
136
- return null;
137
- }
138
- try {
139
- const content = readFileSync(packagePath, "utf-8");
140
- const pkg = JSON.parse(content);
141
- return pkg.kakarotCi || null;
142
- } catch (err) {
143
- error(`Failed to load package.json: ${err instanceof Error ? err.message : String(err)}`);
144
- return null;
145
- }
146
- }
147
- function mergeEnvConfig(config) {
148
- const merged = { ...config };
149
- if (!merged.apiKey && process.env.KAKAROT_API_KEY) {
150
- merged.apiKey = process.env.KAKAROT_API_KEY;
151
- }
152
- if (!merged.githubToken && process.env.GITHUB_TOKEN) {
153
- merged.githubToken = process.env.GITHUB_TOKEN;
84
+ async function findProjectRoot(startPath) {
85
+ const packageJsonPath = await findUp("package.json", {
86
+ cwd: startPath ?? process.cwd()
87
+ });
88
+ if (packageJsonPath) {
89
+ const { dirname: dirname2 } = await import("path");
90
+ return dirname2(packageJsonPath);
154
91
  }
155
- return merged;
92
+ return startPath ?? process.cwd();
156
93
  }
157
94
  async function loadConfig() {
158
- const projectRoot = findProjectRoot();
159
- let config = null;
160
- config = await loadTypeScriptConfig(projectRoot);
161
- if (config) {
162
- return KakarotConfigSchema.parse(mergeEnvConfig(config));
163
- }
164
- config = await loadJavaScriptConfig(projectRoot);
165
- if (config) {
166
- return KakarotConfigSchema.parse(mergeEnvConfig(config));
167
- }
168
- config = loadJsonConfig(projectRoot);
169
- if (config) {
170
- return KakarotConfigSchema.parse(mergeEnvConfig(config));
171
- }
172
- config = loadPackageJsonConfig(projectRoot);
173
- if (config) {
174
- return KakarotConfigSchema.parse(mergeEnvConfig(config));
175
- }
176
- const envConfig = mergeEnvConfig({});
95
+ const explorer = cosmiconfig("kakarot", {
96
+ searchPlaces: [
97
+ "kakarot.config.ts",
98
+ "kakarot.config.js",
99
+ ".kakarot-ci.config.ts",
100
+ ".kakarot-ci.config.js",
101
+ ".kakarot-ci.config.json",
102
+ "package.json"
103
+ ],
104
+ loaders: {
105
+ ".ts": async (filepath) => {
106
+ try {
107
+ const configModule = await import(filepath);
108
+ return configModule.default || configModule.config || null;
109
+ } catch (err) {
110
+ error(`Failed to load TypeScript config: ${err instanceof Error ? err.message : String(err)}`);
111
+ return null;
112
+ }
113
+ }
114
+ }
115
+ });
177
116
  try {
178
- return KakarotConfigSchema.parse(envConfig);
117
+ const result = await explorer.search();
118
+ let config = {};
119
+ if (result?.config) {
120
+ config = result.config;
121
+ }
122
+ if (!result || result.filepath?.endsWith("package.json")) {
123
+ const packageJsonPath = await findUp("package.json");
124
+ if (packageJsonPath) {
125
+ const { readFileSync: readFileSync2 } = await import("fs");
126
+ try {
127
+ const pkg = JSON.parse(readFileSync2(packageJsonPath, "utf-8"));
128
+ if (pkg.kakarotCi) {
129
+ config = { ...config, ...pkg.kakarotCi };
130
+ }
131
+ } catch {
132
+ }
133
+ }
134
+ }
135
+ if (!config.apiKey && process.env.KAKAROT_API_KEY) {
136
+ config.apiKey = process.env.KAKAROT_API_KEY;
137
+ }
138
+ if (!config.githubToken && process.env.GITHUB_TOKEN) {
139
+ config.githubToken = process.env.GITHUB_TOKEN;
140
+ }
141
+ return KakarotConfigSchema.parse(config);
179
142
  } catch (err) {
180
- error(
181
- "Missing required apiKey. Provide it via:\n - Config file (kakarot.config.ts, .kakarot-ci.config.js/json, or package.json)\n - Environment variable: KAKAROT_API_KEY"
182
- );
143
+ if (err instanceof Error && err.message.includes("apiKey")) {
144
+ error(
145
+ "Missing required apiKey. Provide it via:\n - Config file (kakarot.config.ts, .kakarot-ci.config.js/json, or package.json)\n - Environment variable: KAKAROT_API_KEY"
146
+ );
147
+ }
183
148
  throw err;
184
149
  }
185
150
  }
@@ -785,6 +750,30 @@ async function extractTestTargets(files, githubClient, prHeadRef, config) {
785
750
  return targets;
786
751
  }
787
752
 
753
+ // src/utils/test-file-path.ts
754
+ function getTestFilePath(target, config) {
755
+ const sourcePath = target.filePath;
756
+ const lastSlashIndex = sourcePath.lastIndexOf("/");
757
+ const dir = lastSlashIndex >= 0 ? sourcePath.substring(0, lastSlashIndex) : "";
758
+ const baseName = sourcePath.substring(lastSlashIndex + 1).replace(/\.(ts|tsx|js|jsx)$/, "");
759
+ let ext;
760
+ if (sourcePath.endsWith(".tsx"))
761
+ ext = "tsx";
762
+ else if (sourcePath.endsWith(".jsx"))
763
+ ext = "jsx";
764
+ else if (sourcePath.endsWith(".ts"))
765
+ ext = "ts";
766
+ else
767
+ ext = "js";
768
+ const testExt = ext === "tsx" || ext === "ts" ? "ts" : "js";
769
+ if (config.testLocation === "co-located") {
770
+ return dir ? `${dir}/${baseName}.test.${testExt}` : `${baseName}.test.${testExt}`;
771
+ } else {
772
+ const testFileName = config.testFilePattern.replace("*", baseName);
773
+ return `${config.testDirectory}/${testFileName}`;
774
+ }
775
+ }
776
+
788
777
  // src/llm/providers/base.ts
789
778
  var BaseLLMProvider = class {
790
779
  constructor(apiKey, model, defaultOptions) {
@@ -1356,27 +1345,790 @@ var TestGenerator = class {
1356
1345
  throw err;
1357
1346
  }
1358
1347
  }
1348
+ /**
1349
+ * Generate a human-readable coverage summary
1350
+ */
1351
+ async generateCoverageSummary(messages) {
1352
+ try {
1353
+ const response = await this.provider.generate(messages, {
1354
+ temperature: 0.3,
1355
+ maxTokens: 500
1356
+ });
1357
+ return response.content;
1358
+ } catch (err) {
1359
+ error(`Failed to generate coverage summary: ${err instanceof Error ? err.message : String(err)}`);
1360
+ throw err;
1361
+ }
1362
+ }
1363
+ };
1364
+
1365
+ // src/utils/package-manager-detector.ts
1366
+ import { existsSync } from "fs";
1367
+ import { join } from "path";
1368
+ function detectPackageManager(projectRoot) {
1369
+ if (existsSync(join(projectRoot, "pnpm-lock.yaml"))) {
1370
+ return "pnpm";
1371
+ }
1372
+ if (existsSync(join(projectRoot, "yarn.lock"))) {
1373
+ return "yarn";
1374
+ }
1375
+ if (existsSync(join(projectRoot, "package-lock.json"))) {
1376
+ return "npm";
1377
+ }
1378
+ return "npm";
1379
+ }
1380
+
1381
+ // src/utils/test-runner/jest-runner.ts
1382
+ import { exec } from "child_process";
1383
+ import { promisify } from "util";
1384
+ var execAsync = promisify(exec);
1385
+ var JestRunner = class {
1386
+ async runTests(options) {
1387
+ const { testFiles, packageManager, projectRoot, coverage } = options;
1388
+ debug(`Running Jest tests for ${testFiles.length} file(s)`);
1389
+ const testFilesArg = testFiles.map((f) => `"${f}"`).join(" ");
1390
+ const coverageFlag = coverage ? "--coverage --coverageReporters=json" : "--no-coverage";
1391
+ const cmd = `${packageManager} test -- --json ${coverageFlag} ${testFilesArg}`;
1392
+ try {
1393
+ const { stdout, stderr } = await execAsync(cmd, {
1394
+ cwd: projectRoot,
1395
+ maxBuffer: 10 * 1024 * 1024
1396
+ // 10MB
1397
+ });
1398
+ if (stderr && !stderr.includes("PASS") && !stderr.includes("FAIL")) {
1399
+ debug(`Jest stderr: ${stderr}`);
1400
+ }
1401
+ const result = JSON.parse(stdout);
1402
+ return testFiles.map((testFile, index) => {
1403
+ const testResult = result.testResults[index] || result.testResults[0];
1404
+ const failures = [];
1405
+ if (testResult) {
1406
+ for (const assertion of testResult.assertionResults) {
1407
+ if (assertion.status === "failed" && assertion.failureMessages.length > 0) {
1408
+ const failureMessage = assertion.failureMessages[0];
1409
+ failures.push({
1410
+ testName: assertion.title,
1411
+ message: failureMessage,
1412
+ stack: failureMessage
1413
+ });
1414
+ }
1415
+ }
1416
+ }
1417
+ return {
1418
+ success: result.numFailedTests === 0,
1419
+ testFile,
1420
+ passed: result.numPassedTests,
1421
+ failed: result.numFailedTests,
1422
+ total: result.numTotalTests,
1423
+ duration: 0,
1424
+ // Jest JSON doesn't include duration per file
1425
+ failures
1426
+ };
1427
+ });
1428
+ } catch (err) {
1429
+ if (err && typeof err === "object" && "stdout" in err) {
1430
+ try {
1431
+ const result = JSON.parse(err.stdout);
1432
+ return testFiles.map((testFile) => {
1433
+ const failures = [];
1434
+ for (const testResult of result.testResults) {
1435
+ for (const assertion of testResult.assertionResults) {
1436
+ if (assertion.status === "failed" && assertion.failureMessages.length > 0) {
1437
+ failures.push({
1438
+ testName: assertion.title,
1439
+ message: assertion.failureMessages[0],
1440
+ stack: assertion.failureMessages[0]
1441
+ });
1442
+ }
1443
+ }
1444
+ }
1445
+ return {
1446
+ success: result.numFailedTests === 0,
1447
+ testFile,
1448
+ passed: result.numPassedTests,
1449
+ failed: result.numFailedTests,
1450
+ total: result.numTotalTests,
1451
+ duration: 0,
1452
+ failures
1453
+ };
1454
+ });
1455
+ } catch (parseErr) {
1456
+ error(`Failed to parse Jest output: ${parseErr instanceof Error ? parseErr.message : String(parseErr)}`);
1457
+ throw err;
1458
+ }
1459
+ }
1460
+ error(`Jest test execution failed: ${err instanceof Error ? err.message : String(err)}`);
1461
+ throw err;
1462
+ }
1463
+ }
1359
1464
  };
1465
+
1466
+ // src/utils/test-runner/vitest-runner.ts
1467
+ import { exec as exec2 } from "child_process";
1468
+ import { promisify as promisify2 } from "util";
1469
+ var execAsync2 = promisify2(exec2);
1470
+ var VitestRunner = class {
1471
+ async runTests(options) {
1472
+ const { testFiles, packageManager, projectRoot, coverage } = options;
1473
+ debug(`Running Vitest tests for ${testFiles.length} file(s)`);
1474
+ const testFilesArg = testFiles.map((f) => `"${f}"`).join(" ");
1475
+ const coverageFlag = coverage ? "--coverage" : "";
1476
+ const cmd = `${packageManager} test -- --reporter=json ${coverageFlag} ${testFilesArg}`;
1477
+ try {
1478
+ const { stdout, stderr } = await execAsync2(cmd, {
1479
+ cwd: projectRoot,
1480
+ maxBuffer: 10 * 1024 * 1024
1481
+ // 10MB
1482
+ });
1483
+ if (stderr && !stderr.includes("PASS") && !stderr.includes("FAIL")) {
1484
+ debug(`Vitest stderr: ${stderr}`);
1485
+ }
1486
+ const lines = stdout.trim().split("\n");
1487
+ const jsonLine = lines[lines.length - 1];
1488
+ if (!jsonLine || !jsonLine.startsWith("{")) {
1489
+ throw new Error("No valid JSON output from Vitest");
1490
+ }
1491
+ const result = JSON.parse(jsonLine);
1492
+ return testFiles.map((testFile, index) => {
1493
+ const testResult = result.testResults[index] || result.testResults[0];
1494
+ const failures = [];
1495
+ if (testResult) {
1496
+ for (const assertion of testResult.assertionResults) {
1497
+ if (assertion.status === "failed" && assertion.failureMessages.length > 0) {
1498
+ const failureMessage = assertion.failureMessages[0];
1499
+ failures.push({
1500
+ testName: assertion.title,
1501
+ message: failureMessage,
1502
+ stack: failureMessage
1503
+ });
1504
+ }
1505
+ }
1506
+ }
1507
+ return {
1508
+ success: result.numFailedTests === 0,
1509
+ testFile,
1510
+ passed: result.numPassedTests,
1511
+ failed: result.numFailedTests,
1512
+ total: result.numTotalTests,
1513
+ duration: 0,
1514
+ // Vitest JSON doesn't include duration per file
1515
+ failures
1516
+ };
1517
+ });
1518
+ } catch (err) {
1519
+ if (err && typeof err === "object" && "stdout" in err) {
1520
+ try {
1521
+ const lines = err.stdout.trim().split("\n");
1522
+ const jsonLine = lines[lines.length - 1];
1523
+ if (jsonLine && jsonLine.startsWith("{")) {
1524
+ const result = JSON.parse(jsonLine);
1525
+ return testFiles.map((testFile) => {
1526
+ const failures = [];
1527
+ for (const testResult of result.testResults) {
1528
+ for (const assertion of testResult.assertionResults) {
1529
+ if (assertion.status === "failed" && assertion.failureMessages.length > 0) {
1530
+ failures.push({
1531
+ testName: assertion.title,
1532
+ message: assertion.failureMessages[0],
1533
+ stack: assertion.failureMessages[0]
1534
+ });
1535
+ }
1536
+ }
1537
+ }
1538
+ return {
1539
+ success: result.numFailedTests === 0,
1540
+ testFile,
1541
+ passed: result.numPassedTests,
1542
+ failed: result.numFailedTests,
1543
+ total: result.numTotalTests,
1544
+ duration: 0,
1545
+ failures
1546
+ };
1547
+ });
1548
+ }
1549
+ } catch (parseErr) {
1550
+ error(`Failed to parse Vitest output: ${parseErr instanceof Error ? parseErr.message : String(parseErr)}`);
1551
+ throw err;
1552
+ }
1553
+ }
1554
+ error(`Vitest test execution failed: ${err instanceof Error ? err.message : String(err)}`);
1555
+ throw err;
1556
+ }
1557
+ }
1558
+ };
1559
+
1560
+ // src/utils/test-runner/factory.ts
1561
+ function createTestRunner(framework) {
1562
+ switch (framework) {
1563
+ case "jest":
1564
+ return new JestRunner();
1565
+ case "vitest":
1566
+ return new VitestRunner();
1567
+ default:
1568
+ throw new Error(`Unsupported test framework: ${framework}`);
1569
+ }
1570
+ }
1571
+
1572
+ // src/utils/test-file-writer.ts
1573
+ import { writeFileSync, mkdirSync, existsSync as existsSync2 } from "fs";
1574
+ import { dirname, join as join2 } from "path";
1575
+ function writeTestFiles(testFiles, projectRoot) {
1576
+ const writtenPaths = [];
1577
+ for (const [relativePath, fileData] of testFiles.entries()) {
1578
+ const fullPath = join2(projectRoot, relativePath);
1579
+ const dir = dirname(fullPath);
1580
+ if (!existsSync2(dir)) {
1581
+ mkdirSync(dir, { recursive: true });
1582
+ debug(`Created directory: ${dir}`);
1583
+ }
1584
+ writeFileSync(fullPath, fileData.content, "utf-8");
1585
+ writtenPaths.push(relativePath);
1586
+ debug(`Wrote test file: ${relativePath}`);
1587
+ }
1588
+ return writtenPaths;
1589
+ }
1590
+
1591
+ // src/utils/coverage-reader.ts
1592
+ import { readFileSync, existsSync as existsSync3 } from "fs";
1593
+ import { join as join3 } from "path";
1594
+ function parseJestCoverage(data) {
1595
+ const files = [];
1596
+ let totalStatements = 0;
1597
+ let coveredStatements = 0;
1598
+ let totalBranches = 0;
1599
+ let coveredBranches = 0;
1600
+ let totalFunctions = 0;
1601
+ let coveredFunctions = 0;
1602
+ let totalLines = 0;
1603
+ let coveredLines = 0;
1604
+ for (const [filePath, coverage] of Object.entries(data)) {
1605
+ const statementCounts = Object.values(coverage.statements);
1606
+ const branchCounts = Object.values(coverage.branches);
1607
+ const functionCounts = Object.values(coverage.functions);
1608
+ const lineCounts = Object.values(coverage.lines);
1609
+ const fileStatements = {
1610
+ total: statementCounts.length,
1611
+ covered: statementCounts.filter((c) => c > 0).length,
1612
+ percentage: statementCounts.length > 0 ? statementCounts.filter((c) => c > 0).length / statementCounts.length * 100 : 100
1613
+ };
1614
+ const fileBranches = {
1615
+ total: branchCounts.length,
1616
+ covered: branchCounts.filter((c) => c > 0).length,
1617
+ percentage: branchCounts.length > 0 ? branchCounts.filter((c) => c > 0).length / branchCounts.length * 100 : 100
1618
+ };
1619
+ const fileFunctions = {
1620
+ total: functionCounts.length,
1621
+ covered: functionCounts.filter((c) => c > 0).length,
1622
+ percentage: functionCounts.length > 0 ? functionCounts.filter((c) => c > 0).length / functionCounts.length * 100 : 100
1623
+ };
1624
+ const fileLines = {
1625
+ total: lineCounts.length,
1626
+ covered: lineCounts.filter((c) => c > 0).length,
1627
+ percentage: lineCounts.length > 0 ? lineCounts.filter((c) => c > 0).length / lineCounts.length * 100 : 100
1628
+ };
1629
+ files.push({
1630
+ path: filePath,
1631
+ metrics: {
1632
+ statements: fileStatements,
1633
+ branches: fileBranches,
1634
+ functions: fileFunctions,
1635
+ lines: fileLines
1636
+ }
1637
+ });
1638
+ totalStatements += fileStatements.total;
1639
+ coveredStatements += fileStatements.covered;
1640
+ totalBranches += fileBranches.total;
1641
+ coveredBranches += fileBranches.covered;
1642
+ totalFunctions += fileFunctions.total;
1643
+ coveredFunctions += fileFunctions.covered;
1644
+ totalLines += fileLines.total;
1645
+ coveredLines += fileLines.covered;
1646
+ }
1647
+ return {
1648
+ total: {
1649
+ statements: {
1650
+ total: totalStatements,
1651
+ covered: coveredStatements,
1652
+ percentage: totalStatements > 0 ? coveredStatements / totalStatements * 100 : 100
1653
+ },
1654
+ branches: {
1655
+ total: totalBranches,
1656
+ covered: coveredBranches,
1657
+ percentage: totalBranches > 0 ? coveredBranches / totalBranches * 100 : 100
1658
+ },
1659
+ functions: {
1660
+ total: totalFunctions,
1661
+ covered: coveredFunctions,
1662
+ percentage: totalFunctions > 0 ? coveredFunctions / totalFunctions * 100 : 100
1663
+ },
1664
+ lines: {
1665
+ total: totalLines,
1666
+ covered: coveredLines,
1667
+ percentage: totalLines > 0 ? coveredLines / totalLines * 100 : 100
1668
+ }
1669
+ },
1670
+ files
1671
+ };
1672
+ }
1673
+ function parseVitestCoverage(data) {
1674
+ return parseJestCoverage(data);
1675
+ }
1676
+ function readCoverageReport(projectRoot, framework) {
1677
+ const coveragePath = join3(projectRoot, "coverage", "coverage-final.json");
1678
+ if (!existsSync3(coveragePath)) {
1679
+ debug(`Coverage file not found at ${coveragePath}`);
1680
+ return null;
1681
+ }
1682
+ try {
1683
+ const content = readFileSync(coveragePath, "utf-8");
1684
+ const data = JSON.parse(content);
1685
+ if (framework === "jest") {
1686
+ return parseJestCoverage(data);
1687
+ } else {
1688
+ return parseVitestCoverage(data);
1689
+ }
1690
+ } catch (err) {
1691
+ warn(`Failed to read coverage report: ${err instanceof Error ? err.message : String(err)}`);
1692
+ return null;
1693
+ }
1694
+ }
1695
+
1696
+ // src/llm/prompts/coverage-summary.ts
1697
+ function buildCoverageSummaryPrompt(coverageReport, testResults, functionsTested, coverageDelta) {
1698
+ const systemPrompt = `You are a technical writer specializing in test coverage reports. Your task is to generate a clear, concise, and actionable summary of test coverage metrics.
1699
+
1700
+ Requirements:
1701
+ 1. Use clear, professional language
1702
+ 2. Highlight key metrics (lines, branches, functions, statements)
1703
+ 3. Mention which functions were tested
1704
+ 4. If coverage delta is provided, explain the change
1705
+ 5. Provide actionable insights or recommendations
1706
+ 6. Format as markdown suitable for GitHub PR comments
1707
+ 7. Keep it concise (2-3 paragraphs max)`;
1708
+ const totalTests = testResults.reduce((sum, r) => sum + r.total, 0);
1709
+ const passedTests = testResults.reduce((sum, r) => sum + r.passed, 0);
1710
+ const failedTests = testResults.reduce((sum, r) => sum + r.failed, 0);
1711
+ const userPrompt = `Generate a human-readable test coverage summary with the following information:
1712
+
1713
+ **Coverage Metrics:**
1714
+ - Lines: ${coverageReport.total.lines.percentage.toFixed(1)}% (${coverageReport.total.lines.covered}/${coverageReport.total.lines.total})
1715
+ - Branches: ${coverageReport.total.branches.percentage.toFixed(1)}% (${coverageReport.total.branches.covered}/${coverageReport.total.branches.total})
1716
+ - Functions: ${coverageReport.total.functions.percentage.toFixed(1)}% (${coverageReport.total.functions.covered}/${coverageReport.total.functions.total})
1717
+ - Statements: ${coverageReport.total.statements.percentage.toFixed(1)}% (${coverageReport.total.statements.covered}/${coverageReport.total.statements.total})
1718
+
1719
+ **Test Results:**
1720
+ - Total tests: ${totalTests}
1721
+ - Passed: ${passedTests}
1722
+ - Failed: ${failedTests}
1723
+
1724
+ **Functions Tested:**
1725
+ ${functionsTested.length > 0 ? functionsTested.map((f) => `- ${f}`).join("\n") : "None"}
1726
+
1727
+ ${coverageDelta ? `**Coverage Changes:**
1728
+ - Lines: ${coverageDelta.lines > 0 ? "+" : ""}${coverageDelta.lines.toFixed(1)}%
1729
+ - Branches: ${coverageDelta.branches > 0 ? "+" : ""}${coverageDelta.branches.toFixed(1)}%
1730
+ - Functions: ${coverageDelta.functions > 0 ? "+" : ""}${coverageDelta.functions.toFixed(1)}%
1731
+ - Statements: ${coverageDelta.statements > 0 ? "+" : ""}${coverageDelta.statements.toFixed(1)}%
1732
+ ` : ""}
1733
+
1734
+ Generate a concise, professional summary that explains what was tested and the coverage achieved.`;
1735
+ return [
1736
+ { role: "system", content: systemPrompt },
1737
+ { role: "user", content: userPrompt }
1738
+ ];
1739
+ }
1740
+
1741
+ // src/core/orchestrator.ts
1742
+ async function runPullRequest(context) {
1743
+ const config = await loadConfig();
1744
+ initLogger(config);
1745
+ info(`Processing PR #${context.prNumber} for ${context.owner}/${context.repo}`);
1746
+ const githubToken = context.githubToken || config.githubToken;
1747
+ if (!githubToken) {
1748
+ throw new Error("GitHub token is required. Provide it via config.githubToken or context.githubToken");
1749
+ }
1750
+ const githubClient = new GitHubClient({
1751
+ token: githubToken,
1752
+ owner: context.owner,
1753
+ repo: context.repo
1754
+ });
1755
+ const pr = await githubClient.getPullRequest(context.prNumber);
1756
+ if (pr.state !== "open") {
1757
+ warn(`PR #${context.prNumber} is ${pr.state}, skipping test generation`);
1758
+ return {
1759
+ targetsProcessed: 0,
1760
+ testsGenerated: 0,
1761
+ testsFailed: 0,
1762
+ testFiles: [],
1763
+ errors: []
1764
+ };
1765
+ }
1766
+ info(`PR: ${pr.title} (${pr.head.ref} -> ${pr.base.ref})`);
1767
+ const prFiles = await githubClient.listPullRequestFiles(context.prNumber);
1768
+ if (prFiles.length === 0) {
1769
+ info("No files changed in this PR");
1770
+ return {
1771
+ targetsProcessed: 0,
1772
+ testsGenerated: 0,
1773
+ testsFailed: 0,
1774
+ testFiles: [],
1775
+ errors: []
1776
+ };
1777
+ }
1778
+ info(`Found ${prFiles.length} file(s) changed in PR`);
1779
+ const prHeadRef = pr.head.sha;
1780
+ const targets = await extractTestTargets(
1781
+ prFiles,
1782
+ githubClient,
1783
+ prHeadRef,
1784
+ config
1785
+ );
1786
+ if (targets.length === 0) {
1787
+ info("No test targets found in changed files");
1788
+ return {
1789
+ targetsProcessed: 0,
1790
+ testsGenerated: 0,
1791
+ testsFailed: 0,
1792
+ testFiles: [],
1793
+ errors: []
1794
+ };
1795
+ }
1796
+ const limitedTargets = targets.slice(0, config.maxTestsPerPR);
1797
+ if (targets.length > limitedTargets.length) {
1798
+ warn(`Limiting to ${config.maxTestsPerPR} test targets (found ${targets.length})`);
1799
+ }
1800
+ info(`Found ${limitedTargets.length} test target(s)`);
1801
+ const framework = config.framework;
1802
+ info(`Using test framework: ${framework}`);
1803
+ const testGenerator = new TestGenerator({
1804
+ apiKey: config.apiKey,
1805
+ provider: config.provider,
1806
+ model: config.model,
1807
+ maxTokens: config.maxTokens,
1808
+ maxFixAttempts: config.maxFixAttempts,
1809
+ temperature: config.temperature,
1810
+ fixTemperature: config.fixTemperature
1811
+ });
1812
+ let testFiles = /* @__PURE__ */ new Map();
1813
+ const errors = [];
1814
+ let testsGenerated = 0;
1815
+ let testsFailed = 0;
1816
+ for (let i = 0; i < limitedTargets.length; i++) {
1817
+ const target = limitedTargets[i];
1818
+ progress(i + 1, limitedTargets.length, `Generating test for ${target.functionName}`);
1819
+ try {
1820
+ const testFilePath = getTestFilePath(target, config);
1821
+ let existingTestFile;
1822
+ const testFileExists = await githubClient.fileExists(prHeadRef, testFilePath);
1823
+ if (testFileExists) {
1824
+ try {
1825
+ const fileContents = await githubClient.getFileContents(prHeadRef, testFilePath);
1826
+ existingTestFile = fileContents.content;
1827
+ debug(`Found existing test file at ${testFilePath}`);
1828
+ } catch (err) {
1829
+ debug(`Could not fetch existing test file ${testFilePath}: ${err instanceof Error ? err.message : String(err)}`);
1830
+ }
1831
+ } else {
1832
+ debug(`No existing test file at ${testFilePath}, will create new file`);
1833
+ }
1834
+ const result = await testGenerator.generateTest({
1835
+ target: {
1836
+ filePath: target.filePath,
1837
+ functionName: target.functionName,
1838
+ functionType: target.functionType,
1839
+ code: target.code,
1840
+ context: target.context
1841
+ },
1842
+ framework,
1843
+ existingTestFile
1844
+ });
1845
+ if (!testFiles.has(testFilePath)) {
1846
+ const baseContent = existingTestFile || "";
1847
+ testFiles.set(testFilePath, { content: baseContent, targets: [] });
1848
+ }
1849
+ const fileData = testFiles.get(testFilePath);
1850
+ if (fileData.content) {
1851
+ fileData.content += "\n\n" + result.testCode;
1852
+ } else {
1853
+ fileData.content = result.testCode;
1854
+ }
1855
+ fileData.targets.push(target.functionName);
1856
+ testsGenerated++;
1857
+ info(`\u2713 Generated test for ${target.functionName}`);
1858
+ } catch (err) {
1859
+ const errorMessage = err instanceof Error ? err.message : String(err);
1860
+ error(`\u2717 Failed to generate test for ${target.functionName}: ${errorMessage}`);
1861
+ errors.push({
1862
+ target: `${target.filePath}:${target.functionName}`,
1863
+ error: errorMessage
1864
+ });
1865
+ testsFailed++;
1866
+ }
1867
+ }
1868
+ const projectRoot = await findProjectRoot();
1869
+ const packageManager = detectPackageManager(projectRoot);
1870
+ info(`Detected package manager: ${packageManager}`);
1871
+ if (testFiles.size > 0) {
1872
+ const writtenPaths = writeTestFiles(testFiles, projectRoot);
1873
+ info(`Wrote ${writtenPaths.length} test file(s) to disk`);
1874
+ const testRunner = createTestRunner(framework);
1875
+ const finalTestFiles = await runTestsAndFix(
1876
+ testRunner,
1877
+ testFiles,
1878
+ writtenPaths,
1879
+ framework,
1880
+ packageManager,
1881
+ projectRoot,
1882
+ testGenerator,
1883
+ config.maxFixAttempts
1884
+ );
1885
+ testFiles = finalTestFiles;
1886
+ }
1887
+ const summary = {
1888
+ targetsProcessed: limitedTargets.length,
1889
+ testsGenerated,
1890
+ testsFailed,
1891
+ testFiles: Array.from(testFiles.entries()).map(([path, data]) => ({
1892
+ path,
1893
+ targets: data.targets
1894
+ })),
1895
+ errors
1896
+ };
1897
+ if (testFiles.size > 0) {
1898
+ const testRunner = createTestRunner(framework);
1899
+ const writtenPaths = Array.from(testFiles.keys());
1900
+ info("Running tests with coverage...");
1901
+ const finalTestResults = await testRunner.runTests({
1902
+ testFiles: writtenPaths,
1903
+ framework,
1904
+ packageManager,
1905
+ projectRoot,
1906
+ coverage: true
1907
+ });
1908
+ const coverageReport = readCoverageReport(projectRoot, framework);
1909
+ if (coverageReport) {
1910
+ info(`Coverage collected: ${coverageReport.total.lines.percentage.toFixed(1)}% lines`);
1911
+ summary.coverageReport = coverageReport;
1912
+ summary.testResults = finalTestResults;
1913
+ } else {
1914
+ warn("Could not read coverage report");
1915
+ }
1916
+ }
1917
+ if (config.enableAutoCommit && testFiles.size > 0) {
1918
+ await commitTests(
1919
+ githubClient,
1920
+ pr,
1921
+ Array.from(testFiles.entries()).map(([path, data]) => ({
1922
+ path,
1923
+ content: data.content
1924
+ })),
1925
+ config,
1926
+ summary
1927
+ );
1928
+ }
1929
+ if (config.enablePRComments) {
1930
+ await postPRComment(githubClient, context.prNumber, summary, framework, testGenerator);
1931
+ }
1932
+ success(`Completed: ${testsGenerated} test(s) generated, ${testsFailed} failed`);
1933
+ return summary;
1934
+ }
1935
+ async function runTestsAndFix(testRunner, testFiles, testFilePaths, framework, packageManager, projectRoot, testGenerator, maxFixAttempts) {
1936
+ const currentTestFiles = new Map(testFiles);
1937
+ let attempt = 0;
1938
+ while (attempt < maxFixAttempts) {
1939
+ info(`Running tests (attempt ${attempt + 1}/${maxFixAttempts})`);
1940
+ const results = await testRunner.runTests({
1941
+ testFiles: testFilePaths,
1942
+ framework,
1943
+ packageManager,
1944
+ projectRoot,
1945
+ coverage: false
1946
+ });
1947
+ const allPassed = results.every((r) => r.success);
1948
+ if (allPassed) {
1949
+ success(`All tests passed on attempt ${attempt + 1}`);
1950
+ return currentTestFiles;
1951
+ }
1952
+ const failures = [];
1953
+ for (const result of results) {
1954
+ if (!result.success && result.failures.length > 0) {
1955
+ failures.push({ testFile: result.testFile, result });
1956
+ }
1957
+ }
1958
+ info(`Found ${failures.length} failing test file(s), attempting fixes...`);
1959
+ let fixedAny = false;
1960
+ for (const { testFile, result } of failures) {
1961
+ const testFileContent = currentTestFiles.get(testFile)?.content;
1962
+ if (!testFileContent) {
1963
+ warn(`Could not find content for test file: ${testFile}`);
1964
+ continue;
1965
+ }
1966
+ const firstFailure = result.failures[0];
1967
+ if (!firstFailure)
1968
+ continue;
1969
+ try {
1970
+ const fixedResult = await testGenerator.fixTest({
1971
+ testCode: testFileContent,
1972
+ errorMessage: firstFailure.message,
1973
+ testOutput: firstFailure.stack,
1974
+ originalCode: "",
1975
+ // We'd need to pass this from the target
1976
+ framework,
1977
+ attempt: attempt + 1,
1978
+ maxAttempts: maxFixAttempts
1979
+ });
1980
+ currentTestFiles.set(testFile, {
1981
+ content: fixedResult.testCode,
1982
+ targets: currentTestFiles.get(testFile)?.targets || []
1983
+ });
1984
+ const { writeFileSync: writeFileSync2 } = await import("fs");
1985
+ const { join: join4 } = await import("path");
1986
+ writeFileSync2(join4(projectRoot, testFile), fixedResult.testCode, "utf-8");
1987
+ fixedAny = true;
1988
+ info(`\u2713 Fixed test file: ${testFile}`);
1989
+ } catch (err) {
1990
+ error(`Failed to fix test file ${testFile}: ${err instanceof Error ? err.message : String(err)}`);
1991
+ }
1992
+ }
1993
+ if (!fixedAny) {
1994
+ warn(`Could not fix any failing tests on attempt ${attempt + 1}`);
1995
+ break;
1996
+ }
1997
+ attempt++;
1998
+ }
1999
+ if (attempt >= maxFixAttempts) {
2000
+ warn(`Reached maximum fix attempts (${maxFixAttempts}), some tests may still be failing`);
2001
+ }
2002
+ return currentTestFiles;
2003
+ }
2004
+ async function commitTests(githubClient, pr, testFiles, config, summary) {
2005
+ info(`Committing ${testFiles.length} test file(s)`);
2006
+ try {
2007
+ if (config.commitStrategy === "branch-pr") {
2008
+ const branchName = `kakarot-ci/tests-pr-${pr.number}`;
2009
+ const baseSha = await githubClient.createBranch(branchName, pr.head.ref);
2010
+ await githubClient.commitFiles({
2011
+ files: testFiles.map((file) => ({
2012
+ path: file.path,
2013
+ content: file.content
2014
+ })),
2015
+ message: `test: add unit tests for PR #${pr.number}
2016
+
2017
+ Generated ${summary.testsGenerated} test(s) for ${summary.targetsProcessed} function(s)`,
2018
+ branch: branchName,
2019
+ baseSha
2020
+ });
2021
+ const testPR = await githubClient.createPullRequest(
2022
+ `test: Add unit tests for PR #${pr.number}`,
2023
+ `This PR contains automatically generated unit tests for PR #${pr.number}.
2024
+
2025
+ - ${summary.testsGenerated} test(s) generated
2026
+ - ${summary.targetsProcessed} function(s) tested
2027
+ - ${testFiles.length} test file(s) created/updated`,
2028
+ branchName,
2029
+ pr.head.ref
2030
+ );
2031
+ success(`Created PR #${testPR.number} with generated tests`);
2032
+ } else {
2033
+ await githubClient.commitFiles({
2034
+ files: testFiles.map((file) => ({
2035
+ path: file.path,
2036
+ content: file.content
2037
+ })),
2038
+ message: `test: add unit tests
2039
+
2040
+ Generated ${summary.testsGenerated} test(s) for ${summary.targetsProcessed} function(s)`,
2041
+ branch: pr.head.ref,
2042
+ baseSha: pr.head.sha
2043
+ });
2044
+ success(`Committed ${testFiles.length} test file(s) to ${pr.head.ref}`);
2045
+ }
2046
+ } catch (err) {
2047
+ error(`Failed to commit tests: ${err instanceof Error ? err.message : String(err)}`);
2048
+ throw err;
2049
+ }
2050
+ }
2051
+ async function postPRComment(githubClient, prNumber, summary, framework, testGenerator) {
2052
+ let comment = `## \u{1F9EA} Kakarot CI Test Generation Summary
2053
+
2054
+ **Framework:** ${framework}
2055
+ **Targets Processed:** ${summary.targetsProcessed}
2056
+ **Tests Generated:** ${summary.testsGenerated}
2057
+ **Failures:** ${summary.testsFailed}
2058
+
2059
+ ### Test Files
2060
+ ${summary.testFiles.length > 0 ? summary.testFiles.map((f) => `- \`${f.path}\` (${f.targets.length} test(s))`).join("\n") : "No test files generated"}
2061
+
2062
+ ${summary.errors.length > 0 ? `### Errors
2063
+ ${summary.errors.map((e) => `- \`${e.target}\`: ${e.error}`).join("\n")}` : ""}`;
2064
+ if (summary.coverageReport && summary.testResults) {
2065
+ try {
2066
+ const functionsTested = summary.testFiles.flatMap((f) => f.targets);
2067
+ const messages = buildCoverageSummaryPrompt(
2068
+ summary.coverageReport,
2069
+ summary.testResults,
2070
+ functionsTested
2071
+ );
2072
+ const coverageSummary = await testGenerator.generateCoverageSummary(messages);
2073
+ comment += `
2074
+
2075
+ ## \u{1F4CA} Coverage Summary
2076
+
2077
+ ${coverageSummary}`;
2078
+ } catch (err) {
2079
+ warn(`Failed to generate coverage summary: ${err instanceof Error ? err.message : String(err)}`);
2080
+ const cov = summary.coverageReport.total;
2081
+ comment += `
2082
+
2083
+ ## \u{1F4CA} Coverage Summary
2084
+
2085
+ - **Lines:** ${cov.lines.percentage.toFixed(1)}% (${cov.lines.covered}/${cov.lines.total})
2086
+ - **Branches:** ${cov.branches.percentage.toFixed(1)}% (${cov.branches.covered}/${cov.branches.total})
2087
+ - **Functions:** ${cov.functions.percentage.toFixed(1)}% (${cov.functions.covered}/${cov.functions.total})
2088
+ - **Statements:** ${cov.statements.percentage.toFixed(1)}% (${cov.statements.covered}/${cov.statements.total})`;
2089
+ }
2090
+ }
2091
+ comment += `
2092
+
2093
+ ---
2094
+ *Generated by [Kakarot CI](https://github.com/kakarot-ci)*`;
2095
+ try {
2096
+ await githubClient.commentPR(prNumber, comment);
2097
+ info("Posted PR comment with test generation summary");
2098
+ } catch (err) {
2099
+ warn(`Failed to post PR comment: ${err instanceof Error ? err.message : String(err)}`);
2100
+ }
2101
+ }
1360
2102
  export {
1361
2103
  GitHubClient,
2104
+ JestRunner,
1362
2105
  KakarotConfigSchema,
1363
2106
  TestGenerator,
2107
+ VitestRunner,
1364
2108
  analyzeFile,
2109
+ buildCoverageSummaryPrompt,
1365
2110
  buildTestFixPrompt,
1366
2111
  buildTestGenerationPrompt,
1367
2112
  createLLMProvider,
2113
+ createTestRunner,
1368
2114
  debug,
2115
+ detectPackageManager,
1369
2116
  error,
1370
2117
  extractTestTargets,
2118
+ findProjectRoot,
1371
2119
  getChangedRanges,
2120
+ getTestFilePath,
1372
2121
  info,
1373
2122
  initLogger,
1374
2123
  loadConfig,
1375
2124
  parsePullRequestFiles,
1376
2125
  parseTestCode,
1377
2126
  progress,
2127
+ readCoverageReport,
2128
+ runPullRequest,
1378
2129
  success,
1379
2130
  validateTestCodeStructure,
1380
- warn
2131
+ warn,
2132
+ writeTestFiles
1381
2133
  };
1382
2134
  //# sourceMappingURL=index.js.map