lacuna-cli 0.1.1

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 (144) hide show
  1. package/README.md +451 -0
  2. package/bin/run.js +5 -0
  3. package/dist/agent/context.d.ts +25 -0
  4. package/dist/agent/context.d.ts.map +1 -0
  5. package/dist/agent/context.js +366 -0
  6. package/dist/agent/context.js.map +1 -0
  7. package/dist/agent/fix-loop.d.ts +20 -0
  8. package/dist/agent/fix-loop.d.ts.map +1 -0
  9. package/dist/agent/fix-loop.js +466 -0
  10. package/dist/agent/fix-loop.js.map +1 -0
  11. package/dist/agent/generator.d.ts +35 -0
  12. package/dist/agent/generator.d.ts.map +1 -0
  13. package/dist/agent/generator.js +220 -0
  14. package/dist/agent/generator.js.map +1 -0
  15. package/dist/agent/loop.d.ts +23 -0
  16. package/dist/agent/loop.d.ts.map +1 -0
  17. package/dist/agent/loop.js +394 -0
  18. package/dist/agent/loop.js.map +1 -0
  19. package/dist/agent/project-memory.d.ts +10 -0
  20. package/dist/agent/project-memory.d.ts.map +1 -0
  21. package/dist/agent/project-memory.js +57 -0
  22. package/dist/agent/project-memory.js.map +1 -0
  23. package/dist/agent/prompts.d.ts +44 -0
  24. package/dist/agent/prompts.d.ts.map +1 -0
  25. package/dist/agent/prompts.js +377 -0
  26. package/dist/agent/prompts.js.map +1 -0
  27. package/dist/ci/comment.d.ts +2 -0
  28. package/dist/ci/comment.d.ts.map +1 -0
  29. package/dist/ci/comment.js +97 -0
  30. package/dist/ci/comment.js.map +1 -0
  31. package/dist/ci/parse-outputs.d.ts +2 -0
  32. package/dist/ci/parse-outputs.d.ts.map +1 -0
  33. package/dist/ci/parse-outputs.js +30 -0
  34. package/dist/ci/parse-outputs.js.map +1 -0
  35. package/dist/commands/analyze.d.ts +13 -0
  36. package/dist/commands/analyze.d.ts.map +1 -0
  37. package/dist/commands/analyze.js +151 -0
  38. package/dist/commands/analyze.js.map +1 -0
  39. package/dist/commands/fix.d.ts +15 -0
  40. package/dist/commands/fix.d.ts.map +1 -0
  41. package/dist/commands/fix.js +106 -0
  42. package/dist/commands/fix.js.map +1 -0
  43. package/dist/commands/generate.d.ts +18 -0
  44. package/dist/commands/generate.d.ts.map +1 -0
  45. package/dist/commands/generate.js +129 -0
  46. package/dist/commands/generate.js.map +1 -0
  47. package/dist/commands/init.d.ts +7 -0
  48. package/dist/commands/init.d.ts.map +1 -0
  49. package/dist/commands/init.js +131 -0
  50. package/dist/commands/init.js.map +1 -0
  51. package/dist/commands/run.d.ts +10 -0
  52. package/dist/commands/run.d.ts.map +1 -0
  53. package/dist/commands/run.js +45 -0
  54. package/dist/commands/run.js.map +1 -0
  55. package/dist/lib/config.d.ts +58 -0
  56. package/dist/lib/config.d.ts.map +1 -0
  57. package/dist/lib/config.js +68 -0
  58. package/dist/lib/config.js.map +1 -0
  59. package/dist/lib/coverage/gaps.d.ts +12 -0
  60. package/dist/lib/coverage/gaps.d.ts.map +1 -0
  61. package/dist/lib/coverage/gaps.js +186 -0
  62. package/dist/lib/coverage/gaps.js.map +1 -0
  63. package/dist/lib/coverage/index.d.ts +7 -0
  64. package/dist/lib/coverage/index.d.ts.map +1 -0
  65. package/dist/lib/coverage/index.js +24 -0
  66. package/dist/lib/coverage/index.js.map +1 -0
  67. package/dist/lib/coverage/json.d.ts +3 -0
  68. package/dist/lib/coverage/json.d.ts.map +1 -0
  69. package/dist/lib/coverage/json.js +24 -0
  70. package/dist/lib/coverage/json.js.map +1 -0
  71. package/dist/lib/coverage/lcov.d.ts +3 -0
  72. package/dist/lib/coverage/lcov.d.ts.map +1 -0
  73. package/dist/lib/coverage/lcov.js +58 -0
  74. package/dist/lib/coverage/lcov.js.map +1 -0
  75. package/dist/lib/coverage/types.d.ts +27 -0
  76. package/dist/lib/coverage/types.d.ts.map +1 -0
  77. package/dist/lib/coverage/types.js +2 -0
  78. package/dist/lib/coverage/types.js.map +1 -0
  79. package/dist/lib/coverage-spinner.d.ts +6 -0
  80. package/dist/lib/coverage-spinner.d.ts.map +1 -0
  81. package/dist/lib/coverage-spinner.js +101 -0
  82. package/dist/lib/coverage-spinner.js.map +1 -0
  83. package/dist/lib/detector.d.ts +13 -0
  84. package/dist/lib/detector.d.ts.map +1 -0
  85. package/dist/lib/detector.js +106 -0
  86. package/dist/lib/detector.js.map +1 -0
  87. package/dist/lib/extract-error.d.ts +2 -0
  88. package/dist/lib/extract-error.d.ts.map +1 -0
  89. package/dist/lib/extract-error.js +116 -0
  90. package/dist/lib/extract-error.js.map +1 -0
  91. package/dist/lib/providers/anthropic.d.ts +8 -0
  92. package/dist/lib/providers/anthropic.d.ts.map +1 -0
  93. package/dist/lib/providers/anthropic.js +38 -0
  94. package/dist/lib/providers/anthropic.js.map +1 -0
  95. package/dist/lib/providers/index.d.ts +6 -0
  96. package/dist/lib/providers/index.d.ts.map +1 -0
  97. package/dist/lib/providers/index.js +27 -0
  98. package/dist/lib/providers/index.js.map +1 -0
  99. package/dist/lib/providers/openai-compatible.d.ts +11 -0
  100. package/dist/lib/providers/openai-compatible.d.ts.map +1 -0
  101. package/dist/lib/providers/openai-compatible.js +93 -0
  102. package/dist/lib/providers/openai-compatible.js.map +1 -0
  103. package/dist/lib/providers/types.d.ts +17 -0
  104. package/dist/lib/providers/types.d.ts.map +1 -0
  105. package/dist/lib/providers/types.js +97 -0
  106. package/dist/lib/providers/types.js.map +1 -0
  107. package/dist/lib/report-upload.d.ts +3 -0
  108. package/dist/lib/report-upload.d.ts.map +1 -0
  109. package/dist/lib/report-upload.js +15 -0
  110. package/dist/lib/report-upload.js.map +1 -0
  111. package/dist/lib/reporter.d.ts +51 -0
  112. package/dist/lib/reporter.d.ts.map +1 -0
  113. package/dist/lib/reporter.js +172 -0
  114. package/dist/lib/reporter.js.map +1 -0
  115. package/dist/lib/runner.d.ts +9 -0
  116. package/dist/lib/runner.d.ts.map +1 -0
  117. package/dist/lib/runner.js +50 -0
  118. package/dist/lib/runner.js.map +1 -0
  119. package/dist/lib/skeleton.d.ts +8 -0
  120. package/dist/lib/skeleton.d.ts.map +1 -0
  121. package/dist/lib/skeleton.js +122 -0
  122. package/dist/lib/skeleton.js.map +1 -0
  123. package/dist/lib/streaming-viewer.d.ts +14 -0
  124. package/dist/lib/streaming-viewer.d.ts.map +1 -0
  125. package/dist/lib/streaming-viewer.js +80 -0
  126. package/dist/lib/streaming-viewer.js.map +1 -0
  127. package/dist/lib/tips.d.ts +16 -0
  128. package/dist/lib/tips.d.ts.map +1 -0
  129. package/dist/lib/tips.js +76 -0
  130. package/dist/lib/tips.js.map +1 -0
  131. package/dist/lib/typecheck.d.ts +3 -0
  132. package/dist/lib/typecheck.d.ts.map +1 -0
  133. package/dist/lib/typecheck.js +28 -0
  134. package/dist/lib/typecheck.js.map +1 -0
  135. package/dist/lib/validate.d.ts +7 -0
  136. package/dist/lib/validate.d.ts.map +1 -0
  137. package/dist/lib/validate.js +82 -0
  138. package/dist/lib/validate.js.map +1 -0
  139. package/dist/lib/worker-display.d.ts +45 -0
  140. package/dist/lib/worker-display.d.ts.map +1 -0
  141. package/dist/lib/worker-display.js +168 -0
  142. package/dist/lib/worker-display.js.map +1 -0
  143. package/oclif.manifest.json +295 -0
  144. package/package.json +62 -0
@@ -0,0 +1,131 @@
1
+ import { Command } from '@oclif/core';
2
+ import { writeFile, access } from 'fs/promises';
3
+ import { join } from 'path';
4
+ import { select, input, confirm } from '@inquirer/prompts';
5
+ import chalk from 'chalk';
6
+ import { detectEnvironment } from '../lib/detector.js';
7
+ import { PRESETS } from '../lib/providers/index.js';
8
+ export default class Init extends Command {
9
+ static description = 'Interactive setup wizard — configure lacuna for your project';
10
+ static examples = ['$ lacuna init'];
11
+ async run() {
12
+ const configPath = join(process.cwd(), '.lacuna.json');
13
+ try {
14
+ await access(configPath);
15
+ const overwrite = await confirm({
16
+ message: '.lacuna.json already exists. Overwrite it?',
17
+ default: false,
18
+ });
19
+ if (!overwrite) {
20
+ this.log('Keeping existing config.');
21
+ return;
22
+ }
23
+ }
24
+ catch { /* file doesn't exist — proceed */ }
25
+ this.log(chalk.bold('\nlacuna init\n'));
26
+ const env = await detectEnvironment();
27
+ // ── Model / provider ──────────────────────────────────────────────────
28
+ const presetKey = await select({
29
+ message: 'Which model do you want to use?',
30
+ choices: [
31
+ ...Object.entries(PRESETS).map(([key, p]) => ({ value: key, name: p.label })),
32
+ ],
33
+ });
34
+ let preset = PRESETS[presetKey];
35
+ if (presetKey === 'custom') {
36
+ preset = {
37
+ ...preset,
38
+ baseURL: await input({ message: 'Base URL (e.g. https://api.example.com/v1):' }),
39
+ model: await input({ message: 'Model name:' }),
40
+ apiKeyEnv: await input({ message: 'API key env var name:', default: 'LLM_API_KEY' }),
41
+ apiKeyHint: '',
42
+ };
43
+ }
44
+ else if (presetKey === 'openrouter') {
45
+ const orModel = await input({
46
+ message: 'OpenRouter model (leave blank for default):',
47
+ default: preset.model,
48
+ });
49
+ preset = { ...preset, model: orModel };
50
+ }
51
+ else if (presetKey === 'ollama') {
52
+ const ollamaModel = await input({
53
+ message: 'Ollama model name:',
54
+ default: 'llama3.2',
55
+ });
56
+ preset = { ...preset, model: ollamaModel };
57
+ }
58
+ // ── Test runner ───────────────────────────────────────────────────────
59
+ const detectedRunner = env.testRunner !== 'unknown' ? env.testRunner : undefined;
60
+ const testRunner = await select({
61
+ message: 'Test runner:',
62
+ choices: [
63
+ { value: 'jest', name: `jest${detectedRunner === 'jest' ? ' (detected)' : ''}` },
64
+ { value: 'vitest', name: `vitest${detectedRunner === 'vitest' ? ' (detected)' : ''}` },
65
+ { value: 'mocha', name: `mocha${detectedRunner === 'mocha' ? ' (detected)' : ''}` },
66
+ { value: 'pytest', name: `pytest${detectedRunner === 'pytest' ? ' (detected)' : ''}` },
67
+ { value: 'go-test', name: `go test${detectedRunner === 'go-test' ? ' (detected)' : ''}` },
68
+ ],
69
+ default: detectedRunner ?? 'jest',
70
+ });
71
+ // ── Mocks file ────────────────────────────────────────────────────────
72
+ const hasMocks = await confirm({
73
+ message: 'Do you have (or want) a shared mock file for all tests?',
74
+ default: true,
75
+ });
76
+ let mocksFile;
77
+ if (hasMocks) {
78
+ mocksFile = await input({
79
+ message: 'Path to shared mock file:',
80
+ default: 'src/test/mocks.ts',
81
+ });
82
+ }
83
+ // ── Coverage threshold ────────────────────────────────────────────────
84
+ const thresholdStr = await input({
85
+ message: 'Coverage threshold (%):',
86
+ default: '80',
87
+ });
88
+ const threshold = parseInt(thresholdStr, 10);
89
+ // ── Build config ──────────────────────────────────────────────────────
90
+ const config = {
91
+ provider: preset.provider,
92
+ model: preset.model,
93
+ apiKeyEnv: preset.apiKeyEnv || undefined,
94
+ testRunner: testRunner,
95
+ coverageFormat: 'lcov',
96
+ coverageDir: 'coverage',
97
+ sourceDir: 'src',
98
+ threshold,
99
+ maxIterations: 3,
100
+ };
101
+ if (preset.baseURL)
102
+ config.baseURL = preset.baseURL;
103
+ if (mocksFile)
104
+ config.mocksFile = mocksFile;
105
+ // remove undefined keys
106
+ const clean = Object.fromEntries(Object.entries(config).filter(([, v]) => v !== undefined));
107
+ await writeFile(configPath, JSON.stringify(clean, null, 2) + '\n');
108
+ // ── Summary ───────────────────────────────────────────────────────────
109
+ this.log(chalk.green('\n✓ Created .lacuna.json\n'));
110
+ this.log(chalk.bold('Setup summary:'));
111
+ this.log(` Model: ${chalk.cyan(preset.model)} via ${preset.provider}`);
112
+ this.log(` Runner: ${chalk.cyan(testRunner)}`);
113
+ this.log(` Threshold: ${threshold}%`);
114
+ if (preset.apiKeyEnv) {
115
+ const keySet = process.env[preset.apiKeyEnv];
116
+ const keyStatus = keySet ? chalk.green('set ✓') : chalk.red('NOT set ✗');
117
+ this.log(` API key: ${chalk.dim(preset.apiKeyEnv)} — ${keyStatus}`);
118
+ if (!keySet) {
119
+ this.log(chalk.yellow(`\n Get your key: ${preset.apiKeyHint}`));
120
+ this.log(chalk.dim(` Then run: export ${preset.apiKeyEnv}=your-key-here`));
121
+ }
122
+ }
123
+ else {
124
+ this.log(` API key: ${chalk.dim('none (local model)')}`);
125
+ }
126
+ this.log(`\nNext steps:`);
127
+ this.log(` ${chalk.cyan('lacuna analyze')} — see coverage gaps`);
128
+ this.log(` ${chalk.cyan('lacuna generate')} — fill them with AI-generated tests\n`);
129
+ }
130
+ }
131
+ //# sourceMappingURL=init.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class Run extends Command {
3
+ static description: string;
4
+ static examples: string[];
5
+ static flags: {
6
+ verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
7
+ };
8
+ run(): Promise<void>;
9
+ }
10
+ //# sourceMappingURL=run.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/commands/run.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAS,MAAM,aAAa,CAAA;AAM5C,MAAM,CAAC,OAAO,OAAO,GAAI,SAAQ,OAAO;IACtC,MAAM,CAAC,WAAW,SAA2C;IAE7D,MAAM,CAAC,QAAQ,WAEd;IAED,MAAM,CAAC,KAAK;;MAMX;IAEK,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;CAgC3B"}
@@ -0,0 +1,45 @@
1
+ import { Command, Flags } from '@oclif/core';
2
+ import chalk from 'chalk';
3
+ import { loadConfig } from '../lib/config.js';
4
+ import { detectEnvironment } from '../lib/detector.js';
5
+ import { runCommand } from '../lib/runner.js';
6
+ export default class Run extends Command {
7
+ static description = 'Run the test suite and report coverage';
8
+ static examples = [
9
+ '$ lacuna run',
10
+ ];
11
+ static flags = {
12
+ verbose: Flags.boolean({
13
+ char: 'v',
14
+ description: 'Show full test output',
15
+ default: false,
16
+ }),
17
+ };
18
+ async run() {
19
+ const { flags } = await this.parse(Run);
20
+ const config = await loadConfig();
21
+ const env = await detectEnvironment(process.cwd(), config.testRunner);
22
+ this.log(chalk.bold('\nlacuna run\n'));
23
+ if (env.testRunner === 'unknown') {
24
+ this.warn('Could not detect test runner.');
25
+ this.exit(1);
26
+ }
27
+ this.log(`${chalk.dim('Runner:')} ${chalk.cyan(env.testRunner)}\n`);
28
+ this.log(chalk.dim(`$ ${env.testCommand}\n`));
29
+ const result = await runCommand(env.testCommand);
30
+ if (flags.verbose) {
31
+ this.log(result.stdout);
32
+ }
33
+ if (result.success) {
34
+ this.log(chalk.green('\nAll tests passed.'));
35
+ }
36
+ else {
37
+ this.log(chalk.red('\nTests failed:'));
38
+ this.log(result.stdout);
39
+ this.log(result.stderr);
40
+ this.exit(1);
41
+ }
42
+ void config;
43
+ }
44
+ }
45
+ //# sourceMappingURL=run.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run.js","sourceRoot":"","sources":["../../src/commands/run.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAA;AAC5C,OAAO,KAAK,MAAM,OAAO,CAAA;AACzB,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAC7C,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAA;AACtD,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAE7C,MAAM,CAAC,OAAO,OAAO,GAAI,SAAQ,OAAO;IACtC,MAAM,CAAC,WAAW,GAAG,wCAAwC,CAAA;IAE7D,MAAM,CAAC,QAAQ,GAAG;QAChB,cAAc;KACf,CAAA;IAED,MAAM,CAAC,KAAK,GAAG;QACb,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC;YACrB,IAAI,EAAE,GAAG;YACT,WAAW,EAAE,uBAAuB;YACpC,OAAO,EAAE,KAAK;SACf,CAAC;KACH,CAAA;IAED,KAAK,CAAC,GAAG;QACP,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;QACvC,MAAM,MAAM,GAAG,MAAM,UAAU,EAAE,CAAA;QACjC,MAAM,GAAG,GAAG,MAAM,iBAAiB,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,MAAM,CAAC,UAAU,CAAC,CAAA;QAErE,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC,CAAA;QAEtC,IAAI,GAAG,CAAC,UAAU,KAAK,SAAS,EAAE,CAAC;YACjC,IAAI,CAAC,IAAI,CAAC,+BAA+B,CAAC,CAAA;YAC1C,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;QACd,CAAC;QAED,IAAI,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,CAAA;QACnE,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,GAAG,CAAC,WAAW,IAAI,CAAC,CAAC,CAAA;QAE7C,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,GAAG,CAAC,WAAW,CAAC,CAAA;QAEhD,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;YAClB,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;QACzB,CAAC;QAED,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;YACnB,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,qBAAqB,CAAC,CAAC,CAAA;QAC9C,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC,CAAA;YACtC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;YACvB,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;YACvB,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;QACd,CAAC;QAED,KAAK,MAAM,CAAA;IACb,CAAC"}
@@ -0,0 +1,58 @@
1
+ import { z } from 'zod';
2
+ declare const ConfigSchema: z.ZodObject<{
3
+ testRunner: z.ZodOptional<z.ZodEnum<["jest", "vitest", "pytest", "mocha", "go-test"]>>;
4
+ coverageFormat: z.ZodDefault<z.ZodEnum<["lcov", "json-summary", "cobertura"]>>;
5
+ coverageDir: z.ZodDefault<z.ZodString>;
6
+ sourceDir: z.ZodDefault<z.ZodString>;
7
+ threshold: z.ZodDefault<z.ZodNumber>;
8
+ maxIterations: z.ZodDefault<z.ZodNumber>;
9
+ coverageTimeout: z.ZodDefault<z.ZodNumber>;
10
+ testDir: z.ZodOptional<z.ZodString>;
11
+ ignore: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
12
+ mocksFile: z.ZodOptional<z.ZodString>;
13
+ setupFile: z.ZodOptional<z.ZodString>;
14
+ provider: z.ZodDefault<z.ZodEnum<["anthropic", "openai-compatible"]>>;
15
+ model: z.ZodDefault<z.ZodString>;
16
+ baseURL: z.ZodOptional<z.ZodString>;
17
+ apiKeyEnv: z.ZodDefault<z.ZodString>;
18
+ maxTokens: z.ZodDefault<z.ZodNumber>;
19
+ }, "strip", z.ZodTypeAny, {
20
+ coverageFormat: "lcov" | "json-summary" | "cobertura";
21
+ coverageDir: string;
22
+ sourceDir: string;
23
+ threshold: number;
24
+ maxIterations: number;
25
+ coverageTimeout: number;
26
+ ignore: string[];
27
+ provider: "anthropic" | "openai-compatible";
28
+ model: string;
29
+ apiKeyEnv: string;
30
+ maxTokens: number;
31
+ testRunner?: "jest" | "vitest" | "pytest" | "mocha" | "go-test" | undefined;
32
+ testDir?: string | undefined;
33
+ mocksFile?: string | undefined;
34
+ setupFile?: string | undefined;
35
+ baseURL?: string | undefined;
36
+ }, {
37
+ testRunner?: "jest" | "vitest" | "pytest" | "mocha" | "go-test" | undefined;
38
+ coverageFormat?: "lcov" | "json-summary" | "cobertura" | undefined;
39
+ coverageDir?: string | undefined;
40
+ sourceDir?: string | undefined;
41
+ threshold?: number | undefined;
42
+ maxIterations?: number | undefined;
43
+ coverageTimeout?: number | undefined;
44
+ testDir?: string | undefined;
45
+ ignore?: string[] | undefined;
46
+ mocksFile?: string | undefined;
47
+ setupFile?: string | undefined;
48
+ provider?: "anthropic" | "openai-compatible" | undefined;
49
+ model?: string | undefined;
50
+ baseURL?: string | undefined;
51
+ apiKeyEnv?: string | undefined;
52
+ maxTokens?: number | undefined;
53
+ }>;
54
+ export type LacunaConfig = z.infer<typeof ConfigSchema>;
55
+ export declare function applyModelOverride(config: LacunaConfig, model: string): void;
56
+ export declare function loadConfig(cwd?: string): Promise<LacunaConfig>;
57
+ export {};
58
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAGvB,QAAA,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAmBhB,CAAA;AAEF,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAA;AAmBvD,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAkB5E;AAED,wBAAsB,UAAU,CAAC,GAAG,GAAE,MAAsB,GAAG,OAAO,CAAC,YAAY,CAAC,CAInF"}
@@ -0,0 +1,68 @@
1
+ import { cosmiconfig } from 'cosmiconfig';
2
+ import { z } from 'zod';
3
+ import { PRESETS } from './providers/types.js';
4
+ const ConfigSchema = z.object({
5
+ testRunner: z.enum(['jest', 'vitest', 'pytest', 'mocha', 'go-test']).optional(),
6
+ coverageFormat: z.enum(['lcov', 'json-summary', 'cobertura']).default('lcov'),
7
+ coverageDir: z.string().default('coverage'),
8
+ sourceDir: z.string().default('src'),
9
+ threshold: z.number().min(0).max(100).default(80),
10
+ maxIterations: z.number().min(1).max(10).default(3),
11
+ coverageTimeout: z.number().min(30).default(300), // seconds; kills hung test suite
12
+ testDir: z.string().optional(),
13
+ ignore: z.array(z.string()).default([]),
14
+ // mock configuration
15
+ mocksFile: z.string().optional(), // path to shared mock file (e.g. src/test/mocks.ts)
16
+ setupFile: z.string().optional(), // path to test setup file (e.g. src/test/setup.ts)
17
+ // provider config
18
+ provider: z.enum(['anthropic', 'openai-compatible']).default('anthropic'),
19
+ model: z.string().default('claude-sonnet-4-6'),
20
+ baseURL: z.string().optional(),
21
+ apiKeyEnv: z.string().default('ANTHROPIC_API_KEY'),
22
+ maxTokens: z.number().min(1024).max(128000).default(16000),
23
+ });
24
+ const explorer = cosmiconfig('lacuna', {
25
+ searchPlaces: [
26
+ 'package.json',
27
+ '.lacuna.json',
28
+ '.lacunarc',
29
+ '.lacunarc.json',
30
+ '.lacunarc.yaml',
31
+ '.lacunarc.yml',
32
+ 'lacuna.config.js',
33
+ 'lacuna.config.cjs',
34
+ ],
35
+ });
36
+ // Applies a -m / --model flag to the config. If the value matches a preset key
37
+ // (e.g. "gemini") or a preset model name (e.g. "gemini-2.5-pro"), the full preset
38
+ // is applied so provider/baseURL/apiKeyEnv are also updated. Otherwise, only
39
+ // config.model is updated (caller already has the right provider config).
40
+ export function applyModelOverride(config, model) {
41
+ const byKey = PRESETS[model];
42
+ if (byKey) {
43
+ config.provider = byKey.provider;
44
+ config.model = byKey.model;
45
+ if (byKey.baseURL)
46
+ config.baseURL = byKey.baseURL;
47
+ if (byKey.apiKeyEnv)
48
+ config.apiKeyEnv = byKey.apiKeyEnv;
49
+ return;
50
+ }
51
+ const byModel = Object.values(PRESETS).find((p) => p.model === model);
52
+ if (byModel) {
53
+ config.provider = byModel.provider;
54
+ config.model = byModel.model;
55
+ if (byModel.baseURL)
56
+ config.baseURL = byModel.baseURL;
57
+ if (byModel.apiKeyEnv)
58
+ config.apiKeyEnv = byModel.apiKeyEnv;
59
+ return;
60
+ }
61
+ config.model = model;
62
+ }
63
+ export async function loadConfig(cwd = process.cwd()) {
64
+ const result = await explorer.search(cwd);
65
+ const raw = result?.config ?? {};
66
+ return ConfigSchema.parse(raw);
67
+ }
68
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ import type { CoverageReport, CoverageGap } from './types.js';
2
+ export declare function extractGaps(report: CoverageReport, threshold: number): CoverageGap[];
3
+ export declare function filterTestableGaps(gaps: CoverageGap[], userIgnore?: string[]): Promise<CoverageGap[]>;
4
+ export declare function findUncoveredFiles(report: CoverageReport, sourceDir: string, cwd: string, userIgnore?: string[]): Promise<CoverageGap[]>;
5
+ export declare function formatCoverageSummary(report: CoverageReport): string;
6
+ export declare function findTestFiles(cwd: string, _env: {
7
+ sourceDir?: string;
8
+ }, config: {
9
+ sourceDir: string;
10
+ ignore: string[];
11
+ }): Promise<string[]>;
12
+ //# sourceMappingURL=gaps.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gaps.d.ts","sourceRoot":"","sources":["../../../src/lib/coverage/gaps.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAE7D,wBAAgB,WAAW,CAAC,MAAM,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,GAAG,WAAW,EAAE,CASpF;AAID,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,WAAW,EAAE,EAAE,UAAU,GAAE,MAAM,EAAO,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,CAU/G;AA4HD,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,cAAc,EACtB,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,EACX,UAAU,GAAE,MAAM,EAAO,GACxB,OAAO,CAAC,WAAW,EAAE,CAAC,CAyBxB;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,CAIpE;AAID,wBAAsB,aAAa,CACjC,GAAG,EAAE,MAAM,EACX,IAAI,EAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,EAC5B,MAAM,EAAE;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,GAC9C,OAAO,CAAC,MAAM,EAAE,CAAC,CAOnB"}
@@ -0,0 +1,186 @@
1
+ import { readdir, readFile, access } from 'fs/promises';
2
+ import { join, extname, sep, dirname, basename } from 'path';
3
+ export function extractGaps(report, threshold) {
4
+ return report.files
5
+ .filter((file) => file.lineRate * 100 < threshold)
6
+ .map((file) => ({
7
+ filePath: file.path,
8
+ uncoveredLines: file.lines.filter((l) => l.hit === 0).map((l) => l.line),
9
+ uncoveredFunctions: file.functions.filter((f) => f.hit === 0).map((f) => f.name),
10
+ }))
11
+ .filter((gap) => gap.uncoveredLines.length > 0 || gap.uncoveredFunctions.length > 0);
12
+ }
13
+ // Filters out gaps where the source file contains only types, interfaces, enums, or constants,
14
+ // or where a test file already exists for the source file.
15
+ export async function filterTestableGaps(gaps, userIgnore = []) {
16
+ const results = [];
17
+ for (const gap of gaps) {
18
+ if (userIgnore.some((p) => gap.filePath.includes(p)))
19
+ continue;
20
+ if (shouldIgnore(gap.filePath, []))
21
+ continue;
22
+ if (await testFileExists(gap.filePath))
23
+ continue;
24
+ const source = await readFile(gap.filePath, 'utf-8').catch(() => '');
25
+ if (hasTestableCode(source))
26
+ results.push(gap);
27
+ }
28
+ return results;
29
+ }
30
+ const SOURCE_EXTENSIONS = new Set(['.ts', '.tsx', '.js', '.jsx', '.mts', '.cts']);
31
+ // Returns true if the file content has at least one testable unit:
32
+ // a function declaration, an arrow function with a block body, or a class.
33
+ // Files that export only types, interfaces, enums, and plain constants are skipped.
34
+ function hasTestableCode(source) {
35
+ // strip line comments and string literals to avoid false positives in type signatures
36
+ const stripped = source
37
+ .replace(/\/\/[^\n]*/g, '')
38
+ .replace(/\/\*[\s\S]*?\*\//g, '')
39
+ .replace(/'[^'\\]*(?:\\.[^'\\]*)*'/g, '""')
40
+ .replace(/"[^"\\]*(?:\\.[^"\\]*)*"/g, '""')
41
+ .replace(/`[^`\\]*(?:\\.[^`\\]*)*`/g, '""');
42
+ // function keyword (declaration or expression: function foo / function(
43
+ if (/\bfunction\s*[\w(]/.test(stripped))
44
+ return true;
45
+ // arrow function with a block body: `) => {` or `=> {`
46
+ if (/=>\s*\{/.test(stripped))
47
+ return true;
48
+ // class declaration or expression
49
+ if (/\bclass\s+\w/.test(stripped))
50
+ return true;
51
+ return false;
52
+ }
53
+ // Directories that never contain testable runtime logic
54
+ const IGNORE_DIRS = new Set([
55
+ 'node_modules',
56
+ '__tests__',
57
+ 'types',
58
+ 'type',
59
+ 'constants',
60
+ 'constant',
61
+ 'assets',
62
+ 'images',
63
+ 'icons',
64
+ 'fonts',
65
+ 'styles',
66
+ 'style',
67
+ 'css',
68
+ 'generated',
69
+ '__generated__',
70
+ 'mocks',
71
+ 'mock',
72
+ 'fixtures',
73
+ 'migrations',
74
+ 'seeds',
75
+ 'i18n',
76
+ 'locales',
77
+ 'locale',
78
+ 'translations',
79
+ ]);
80
+ // File name patterns that are not worth testing
81
+ const IGNORE_FILE_PATTERNS = [
82
+ /\.d\.ts$/, // TypeScript declaration files
83
+ /\.test\.[^.]+$/, // existing test files
84
+ /\.spec\.[^.]+$/,
85
+ /\.stories\.[^.]+$/, // Storybook stories
86
+ /\.config\.[^.]+$/, // config files (vite.config.ts etc)
87
+ /\.mock\.[^.]+$/, // mock files
88
+ /\.fixture\.[^.]+$/, // fixture files
89
+ /\.enum\.[^.]+$/, // pure enum files
90
+ /\.types?\.[^.]+$/, // *.type.ts / *.types.ts
91
+ /\.constants?\.[^.]+$/, // *.constant.ts / *.constants.ts
92
+ /\.interface\.[^.]+$/, // *.interface.ts
93
+ /\/index\.[^.]+$/, // barrel re-export files
94
+ ];
95
+ function shouldIgnore(absPath, userIgnore) {
96
+ const parts = absPath.split(sep);
97
+ // check every path segment against ignored dirs
98
+ for (const part of parts) {
99
+ if (IGNORE_DIRS.has(part.toLowerCase()))
100
+ return true;
101
+ }
102
+ // check file name patterns
103
+ if (IGNORE_FILE_PATTERNS.some((p) => p.test(absPath)))
104
+ return true;
105
+ // check user-defined ignore strings (substring match against the full path)
106
+ if (userIgnore.some((pattern) => absPath.includes(pattern)))
107
+ return true;
108
+ return false;
109
+ }
110
+ async function walkDir(dir) {
111
+ const entries = await readdir(dir, { withFileTypes: true }).catch(() => []);
112
+ const files = [];
113
+ for (const entry of entries) {
114
+ if (entry.name.startsWith('.'))
115
+ continue;
116
+ const full = join(dir, entry.name);
117
+ if (entry.isDirectory()) {
118
+ if (!IGNORE_DIRS.has(entry.name.toLowerCase())) {
119
+ files.push(...(await walkDir(full)));
120
+ }
121
+ }
122
+ else if (SOURCE_EXTENSIONS.has(extname(entry.name))) {
123
+ files.push(full);
124
+ }
125
+ }
126
+ return files;
127
+ }
128
+ async function testFileExists(absSourcePath) {
129
+ const dir = dirname(absSourcePath);
130
+ const ext = extname(absSourcePath);
131
+ const base = basename(absSourcePath, ext);
132
+ const candidates = [
133
+ join(dir, '__tests__', `${base}.test${ext}`),
134
+ join(dir, '__tests__', `${base}.spec${ext}`),
135
+ join(dir, `${base}.test${ext}`),
136
+ join(dir, `${base}.spec${ext}`),
137
+ join(dir, `test_${base}${ext}`),
138
+ join(dir, `${base}_test${ext}`),
139
+ ];
140
+ for (const c of candidates) {
141
+ try {
142
+ await access(c);
143
+ return true;
144
+ }
145
+ catch { /* not found */ }
146
+ }
147
+ return false;
148
+ }
149
+ export async function findUncoveredFiles(report, sourceDir, cwd, userIgnore = []) {
150
+ // Normalize LCOV paths: they can be absolute or relative depending on the runner.
151
+ // walkDir always returns absolute paths, so normalise here to avoid false misses.
152
+ const coveredPaths = new Set(report.files.map((f) => (f.path.startsWith('/') ? f.path : join(cwd, f.path))));
153
+ const absoluteSourceDir = join(cwd, sourceDir);
154
+ const allSourceFiles = await walkDir(absoluteSourceDir).catch(() => []);
155
+ const uncovered = [];
156
+ for (const absPath of allSourceFiles) {
157
+ if (shouldIgnore(absPath, userIgnore))
158
+ continue;
159
+ if (coveredPaths.has(absPath))
160
+ continue;
161
+ // skip if a test file already exists for this source file
162
+ if (await testFileExists(absPath))
163
+ continue;
164
+ // skip files that contain only types, interfaces, enums, or plain constants
165
+ const source = await readFile(absPath, 'utf-8').catch(() => '');
166
+ if (!hasTestableCode(source))
167
+ continue;
168
+ uncovered.push({ filePath: absPath, uncoveredLines: [], uncoveredFunctions: [] });
169
+ }
170
+ return uncovered;
171
+ }
172
+ export function formatCoverageSummary(report) {
173
+ const lineRate = (report.totalLineRate * 100).toFixed(1);
174
+ const fnRate = (report.totalFunctionRate * 100).toFixed(1);
175
+ return `Lines: ${lineRate}% Functions: ${fnRate}%`;
176
+ }
177
+ const TEST_FILE_RE = /\.(test|spec)\.[^.]+$|^test_[^/]+$|_test\.[^.]+$/;
178
+ export async function findTestFiles(cwd, _env, config) {
179
+ const root = join(cwd, config.sourceDir);
180
+ const all = await walkDir(root).catch(() => []);
181
+ return all.filter((f) => {
182
+ const rel = f.replace(cwd + sep, '').replace(cwd + '/', '');
183
+ return TEST_FILE_RE.test(rel) && !shouldIgnore(f, config.ignore);
184
+ });
185
+ }
186
+ //# sourceMappingURL=gaps.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gaps.js","sourceRoot":"","sources":["../../../src/lib/coverage/gaps.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACvD,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,MAAM,CAAA;AAG5D,MAAM,UAAU,WAAW,CAAC,MAAsB,EAAE,SAAiB;IACnE,OAAO,MAAM,CAAC,KAAK;SAChB,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,GAAG,GAAG,GAAG,SAAS,CAAC;SACjD,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QACd,QAAQ,EAAE,IAAI,CAAC,IAAI;QACnB,cAAc,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;QACxE,kBAAkB,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;KACjF,CAAC,CAAC;SACF,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,cAAc,CAAC,MAAM,GAAG,CAAC,IAAI,GAAG,CAAC,kBAAkB,CAAC,MAAM,GAAG,CAAC,CAAC,CAAA;AACxF,CAAC;AAED,+FAA+F;AAC/F,2DAA2D;AAC3D,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,IAAmB,EAAE,aAAuB,EAAE;IACrF,MAAM,OAAO,GAAkB,EAAE,CAAA;IACjC,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,IAAI,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;YAAE,SAAQ;QAC9D,IAAI,YAAY,CAAC,GAAG,CAAC,QAAQ,EAAE,EAAE,CAAC;YAAE,SAAQ;QAC5C,IAAI,MAAM,cAAc,CAAC,GAAG,CAAC,QAAQ,CAAC;YAAE,SAAQ;QAChD,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,GAAG,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAA;QACpE,IAAI,eAAe,CAAC,MAAM,CAAC;YAAE,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IAChD,CAAC;IACD,OAAO,OAAO,CAAA;AAChB,CAAC;AAED,MAAM,iBAAiB,GAAG,IAAI,GAAG,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;AAEjF,mEAAmE;AACnE,2EAA2E;AAC3E,oFAAoF;AACpF,SAAS,eAAe,CAAC,MAAc;IACrC,sFAAsF;IACtF,MAAM,QAAQ,GAAG,MAAM;SACpB,OAAO,CAAC,aAAa,EAAE,EAAE,CAAC;SAC1B,OAAO,CAAC,mBAAmB,EAAE,EAAE,CAAC;SAChC,OAAO,CAAC,2BAA2B,EAAE,IAAI,CAAC;SAC1C,OAAO,CAAC,2BAA2B,EAAE,IAAI,CAAC;SAC1C,OAAO,CAAC,2BAA2B,EAAE,IAAI,CAAC,CAAA;IAE7C,wEAAwE;IACxE,IAAI,oBAAoB,CAAC,IAAI,CAAC,QAAQ,CAAC;QAAE,OAAO,IAAI,CAAA;IACpD,uDAAuD;IACvD,IAAI,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC;QAAE,OAAO,IAAI,CAAA;IACzC,kCAAkC;IAClC,IAAI,cAAc,CAAC,IAAI,CAAC,QAAQ,CAAC;QAAE,OAAO,IAAI,CAAA;IAE9C,OAAO,KAAK,CAAA;AACd,CAAC;AAED,wDAAwD;AACxD,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC;IAC1B,cAAc;IACd,WAAW;IACX,OAAO;IACP,MAAM;IACN,WAAW;IACX,UAAU;IACV,QAAQ;IACR,QAAQ;IACR,OAAO;IACP,OAAO;IACP,QAAQ;IACR,OAAO;IACP,KAAK;IACL,WAAW;IACX,eAAe;IACf,OAAO;IACP,MAAM;IACN,UAAU;IACV,YAAY;IACZ,OAAO;IACP,MAAM;IACN,SAAS;IACT,QAAQ;IACR,cAAc;CACf,CAAC,CAAA;AAEF,gDAAgD;AAChD,MAAM,oBAAoB,GAAG;IAC3B,UAAU,EAAqB,+BAA+B;IAC9D,gBAAgB,EAAe,sBAAsB;IACrD,gBAAgB;IAChB,mBAAmB,EAAY,oBAAoB;IACnD,kBAAkB,EAAa,oCAAoC;IACnE,gBAAgB,EAAe,aAAa;IAC5C,mBAAmB,EAAY,gBAAgB;IAC/C,gBAAgB,EAAe,kBAAkB;IACjD,kBAAkB,EAAa,yBAAyB;IACxD,sBAAsB,EAAS,iCAAiC;IAChE,qBAAqB,EAAU,iBAAiB;IAChD,iBAAiB,EAAc,yBAAyB;CACzD,CAAA;AAED,SAAS,YAAY,CAAC,OAAe,EAAE,UAAoB;IACzD,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;IAEhC,gDAAgD;IAChD,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;YAAE,OAAO,IAAI,CAAA;IACtD,CAAC;IAED,2BAA2B;IAC3B,IAAI,oBAAoB,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAAE,OAAO,IAAI,CAAA;IAElE,4EAA4E;IAC5E,IAAI,UAAU,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QAAE,OAAO,IAAI,CAAA;IAExE,OAAO,KAAK,CAAA;AACd,CAAC;AAED,KAAK,UAAU,OAAO,CAAC,GAAW;IAChC,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAA;IAC3E,MAAM,KAAK,GAAa,EAAE,CAAA;IAC1B,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,IAAI,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,SAAQ;QACxC,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC,CAAA;QAClC,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;YACxB,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,EAAE,CAAC;gBAC/C,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;YACtC,CAAC;QACH,CAAC;aAAM,IAAI,iBAAiB,CAAC,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC;YACtD,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAClB,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAA;AACd,CAAC;AAED,KAAK,UAAU,cAAc,CAAC,aAAqB;IACjD,MAAM,GAAG,GAAG,OAAO,CAAC,aAAa,CAAC,CAAA;IAClC,MAAM,GAAG,GAAG,OAAO,CAAC,aAAa,CAAC,CAAA;IAClC,MAAM,IAAI,GAAG,QAAQ,CAAC,aAAa,EAAE,GAAG,CAAC,CAAA;IAEzC,MAAM,UAAU,GAAG;QACjB,IAAI,CAAC,GAAG,EAAE,WAAW,EAAE,GAAG,IAAI,QAAQ,GAAG,EAAE,CAAC;QAC5C,IAAI,CAAC,GAAG,EAAE,WAAW,EAAE,GAAG,IAAI,QAAQ,GAAG,EAAE,CAAC;QAC5C,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,QAAQ,GAAG,EAAE,CAAC;QAC/B,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,QAAQ,GAAG,EAAE,CAAC;QAC/B,IAAI,CAAC,GAAG,EAAE,QAAQ,IAAI,GAAG,GAAG,EAAE,CAAC;QAC/B,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,QAAQ,GAAG,EAAE,CAAC;KAChC,CAAA;IAED,KAAK,MAAM,CAAC,IAAI,UAAU,EAAE,CAAC;QAC3B,IAAI,CAAC;YAAC,MAAM,MAAM,CAAC,CAAC,CAAC,CAAC;YAAC,OAAO,IAAI,CAAA;QAAC,CAAC;QAAC,MAAM,CAAC,CAAC,eAAe,CAAC,CAAC;IAChE,CAAC;IACD,OAAO,KAAK,CAAA;AACd,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,MAAsB,EACtB,SAAiB,EACjB,GAAW,EACX,aAAuB,EAAE;IAEzB,kFAAkF;IAClF,kFAAkF;IAClF,MAAM,YAAY,GAAG,IAAI,GAAG,CAC1B,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAC/E,CAAA;IACD,MAAM,iBAAiB,GAAG,IAAI,CAAC,GAAG,EAAE,SAAS,CAAC,CAAA;IAE9C,MAAM,cAAc,GAAG,MAAM,OAAO,CAAC,iBAAiB,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAc,CAAC,CAAA;IAEnF,MAAM,SAAS,GAAkB,EAAE,CAAA;IACnC,KAAK,MAAM,OAAO,IAAI,cAAc,EAAE,CAAC;QACrC,IAAI,YAAY,CAAC,OAAO,EAAE,UAAU,CAAC;YAAE,SAAQ;QAC/C,IAAI,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC;YAAE,SAAQ;QAEvC,0DAA0D;QAC1D,IAAI,MAAM,cAAc,CAAC,OAAO,CAAC;YAAE,SAAQ;QAE3C,4EAA4E;QAC5E,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAA;QAC/D,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC;YAAE,SAAQ;QAEtC,SAAS,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,cAAc,EAAE,EAAE,EAAE,kBAAkB,EAAE,EAAE,EAAE,CAAC,CAAA;IACnF,CAAC;IACD,OAAO,SAAS,CAAA;AAClB,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,MAAsB;IAC1D,MAAM,QAAQ,GAAG,CAAC,MAAM,CAAC,aAAa,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAA;IACxD,MAAM,MAAM,GAAG,CAAC,MAAM,CAAC,iBAAiB,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAA;IAC1D,OAAO,UAAU,QAAQ,iBAAiB,MAAM,GAAG,CAAA;AACrD,CAAC;AAED,MAAM,YAAY,GAAG,kDAAkD,CAAA;AAEvE,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,GAAW,EACX,IAA4B,EAC5B,MAA+C;IAE/C,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,SAAS,CAAC,CAAA;IACxC,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAA;IAC/C,OAAO,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE;QACtB,MAAM,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,GAAG,GAAG,GAAG,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,GAAG,GAAG,EAAE,EAAE,CAAC,CAAA;QAC3D,OAAO,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,EAAE,MAAM,CAAC,MAAM,CAAC,CAAA;IAClE,CAAC,CAAC,CAAA;AACJ,CAAC"}
@@ -0,0 +1,7 @@
1
+ import type { LacunaConfig } from '../config.js';
2
+ import type { CoverageReport } from './types.js';
3
+ export declare function loadCoverage(config: LacunaConfig, cwd?: string): Promise<CoverageReport>;
4
+ export declare function coverageAgeSeconds(config: LacunaConfig, cwd?: string): Promise<number | null>;
5
+ export { extractGaps, filterTestableGaps, findUncoveredFiles, formatCoverageSummary } from './gaps.js';
6
+ export type { CoverageReport, CoverageGap, FileCoverage } from './types.js';
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lib/coverage/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAGhD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAEhD,wBAAsB,YAAY,CAAC,MAAM,EAAE,YAAY,EAAE,GAAG,GAAE,MAAsB,GAAG,OAAO,CAAC,cAAc,CAAC,CAK7G;AAED,wBAAsB,kBAAkB,CAAC,MAAM,EAAE,YAAY,EAAE,GAAG,GAAE,MAAsB,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAUlH;AAED,OAAO,EAAE,WAAW,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,MAAM,WAAW,CAAA;AACtG,YAAY,EAAE,cAAc,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA"}
@@ -0,0 +1,24 @@
1
+ import { stat } from 'fs/promises';
2
+ import { join } from 'path';
3
+ import { parseLcov } from './lcov.js';
4
+ import { parseJsonSummary } from './json.js';
5
+ export async function loadCoverage(config, cwd = process.cwd()) {
6
+ if (config.coverageFormat === 'json-summary') {
7
+ return parseJsonSummary(config.coverageDir, cwd);
8
+ }
9
+ return parseLcov(config.coverageDir, cwd);
10
+ }
11
+ export async function coverageAgeSeconds(config, cwd = process.cwd()) {
12
+ const file = config.coverageFormat === 'json-summary'
13
+ ? join(cwd, config.coverageDir, 'coverage-summary.json')
14
+ : join(cwd, config.coverageDir, 'lcov.info');
15
+ try {
16
+ const { mtimeMs } = await stat(file);
17
+ return (Date.now() - mtimeMs) / 1000;
18
+ }
19
+ catch {
20
+ return null;
21
+ }
22
+ }
23
+ export { extractGaps, filterTestableGaps, findUncoveredFiles, formatCoverageSummary } from './gaps.js';
24
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/lib/coverage/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,aAAa,CAAA;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAE3B,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAA;AACrC,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAA;AAG5C,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,MAAoB,EAAE,MAAc,OAAO,CAAC,GAAG,EAAE;IAClF,IAAI,MAAM,CAAC,cAAc,KAAK,cAAc,EAAE,CAAC;QAC7C,OAAO,gBAAgB,CAAC,MAAM,CAAC,WAAW,EAAE,GAAG,CAAC,CAAA;IAClD,CAAC;IACD,OAAO,SAAS,CAAC,MAAM,CAAC,WAAW,EAAE,GAAG,CAAC,CAAA;AAC3C,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,MAAoB,EAAE,MAAc,OAAO,CAAC,GAAG,EAAE;IACxF,MAAM,IAAI,GAAG,MAAM,CAAC,cAAc,KAAK,cAAc;QACnD,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,WAAW,EAAE,uBAAuB,CAAC;QACxD,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,WAAW,EAAE,WAAW,CAAC,CAAA;IAC9C,IAAI,CAAC;QACH,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,CAAA;QACpC,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC,GAAG,IAAI,CAAA;IACtC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAA;IACb,CAAC;AACH,CAAC;AAED,OAAO,EAAE,WAAW,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,MAAM,WAAW,CAAA"}
@@ -0,0 +1,3 @@
1
+ import type { CoverageReport } from './types.js';
2
+ export declare function parseJsonSummary(coverageDir: string, cwd?: string): Promise<CoverageReport>;
3
+ //# sourceMappingURL=json.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json.d.ts","sourceRoot":"","sources":["../../../src/lib/coverage/json.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAgB,MAAM,YAAY,CAAA;AAqB9D,wBAAsB,gBAAgB,CAAC,WAAW,EAAE,MAAM,EAAE,GAAG,GAAE,MAAsB,GAAG,OAAO,CAAC,cAAc,CAAC,CAchH"}
@@ -0,0 +1,24 @@
1
+ import { readFile } from 'fs/promises';
2
+ import { join } from 'path';
3
+ function entryToFileCoverage(path, entry) {
4
+ return {
5
+ path,
6
+ lines: [],
7
+ functions: [],
8
+ lineRate: entry.lines.total ? entry.lines.covered / entry.lines.total : 1,
9
+ functionRate: entry.functions.total ? entry.functions.covered / entry.functions.total : 1,
10
+ };
11
+ }
12
+ export async function parseJsonSummary(coverageDir, cwd = process.cwd()) {
13
+ const summaryPath = join(cwd, coverageDir, 'coverage-summary.json');
14
+ const raw = await readFile(summaryPath, 'utf-8');
15
+ const summary = JSON.parse(raw);
16
+ const files = Object.entries(summary)
17
+ .filter(([path]) => path !== 'total')
18
+ .map(([path, entry]) => entryToFileCoverage(path, entry));
19
+ const total = summary['total'];
20
+ const totalLineRate = total ? total.lines.pct / 100 : files.reduce((s, f) => s + f.lineRate, 0) / (files.length || 1);
21
+ const totalFunctionRate = total ? total.functions.pct / 100 : files.reduce((s, f) => s + f.functionRate, 0) / (files.length || 1);
22
+ return { files, totalLineRate, totalFunctionRate };
23
+ }
24
+ //# sourceMappingURL=json.js.map