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
package/README.md ADDED
@@ -0,0 +1,451 @@
1
+ # lacuna
2
+
3
+ **Agentic test coverage — finds gaps, writes tests, verifies they pass.**
4
+
5
+ Lacuna is a CLI tool that uses AI to analyze your codebase, identify untested code, generate meaningful tests, run them, and retry if they fail — all in one command.
6
+
7
+ ```bash
8
+ lacuna generate
9
+ ```
10
+
11
+ ---
12
+
13
+ ## How it works
14
+
15
+ ```
16
+ lacuna analyze / lacuna generate lacuna fix
17
+ │ │
18
+ ├── 1. Collect coverage ├── 1. Find failing files
19
+ │ ├── If report is < 10 min old: │ ├── --file: run that file only (fast)
20
+ │ │ reuse cached report │ ├── No --file + cache < 5 min old: use cache
21
+ │ └── Otherwise: run full suite │ └── Otherwise: run full suite
22
+ ├── 2. Find files below threshold │
23
+ │ └── For each failing test file:
24
+ └── For each gap: (generate only) ├── Runs file alone → captures error output
25
+ ├── Reads source + existing tests ├── Reads the test file + its source file
26
+ ├── Reads imported type definitions ├── Reads imported type definitions
27
+ ├── Reads tsconfig paths, deps, ├── Reads tsconfig paths, deps, setup file
28
+ │ and test setup file ├── Detects network mocking issues
29
+ ├── Sends full context to AI model ├── AI reasons in <thinking>, writes fix
30
+ ├── AI reasons then writes tests ├── Writes the fixed file
31
+ ├── Runs the tests ├── ✅ Pass → next file
32
+ ├── ✅ Pass → next file └── ❌ Fail → records what failed,
33
+ └── ❌ Fail → records what failed, detects oscillation (stops early),
34
+ detects oscillation (stops early), retries with negative constraints
35
+ retries with negative constraints restores original on final failure
36
+ restores original on final failure
37
+ ```
38
+
39
+ ---
40
+
41
+ ## Install
42
+
43
+ ```bash
44
+ npm install -g lacuna-cli
45
+ ```
46
+
47
+ ---
48
+
49
+ ## Quick start
50
+
51
+ ```bash
52
+ cd your-project
53
+ lacuna init # interactive setup wizard
54
+ lacuna analyze # see what's uncovered (read-only)
55
+ lacuna generate # AI fills the gaps
56
+ ```
57
+
58
+ ---
59
+
60
+ ## Commands
61
+
62
+ ### `lacuna init`
63
+ Interactive setup wizard. Configures your model, test runner, coverage threshold, and mock file.
64
+ Creates `.lacuna.json` in your project root.
65
+
66
+ ```bash
67
+ lacuna init
68
+ ```
69
+
70
+ ### `lacuna analyze`
71
+ Runs your test suite, collects coverage, and prints which files and functions are below threshold. **Does not write any files.**
72
+
73
+ ```bash
74
+ lacuna analyze
75
+ lacuna analyze --threshold 90
76
+ lacuna analyze --format json --output report.json
77
+ lacuna analyze --format markdown
78
+ ```
79
+
80
+ ### `lacuna generate`
81
+ The main command. Runs the full agent loop — analyzes gaps, writes tests, runs them, retries failures.
82
+
83
+ When `--file` is given, lacuna skips the coverage suite entirely and goes straight to the AI — no waiting for a full suite run. The generated tests are verified by running just that file. Use this to increase coverage on a specific file without touching the rest of the project.
84
+
85
+ If you ran `lacuna analyze` recently (within 10 minutes), `generate` will reuse the existing coverage report instead of running the suite again. Use `--fresh` to force a new run.
86
+
87
+ If all retries fail, the original test file is restored — your workspace is never left with a half-written file. If the model oscillates (produces the same code twice), the retry loop stops early rather than burning remaining iterations.
88
+
89
+ If a fix attempt breaks an import and causes the test runner to collect 0 tests, lacuna detects this and sends the model the original error alongside an explicit warning — so it knows it over-reached and what it was actually supposed to fix. The same applies if a fix reduces the number of passing tests: the model is told it caused a regression and shown what the baseline was.
90
+
91
+ ```bash
92
+ lacuna generate
93
+ lacuna generate --file src/utils/math.ts # target one file
94
+ lacuna generate --dry-run # preview without writing
95
+ lacuna generate --verbose # live code panel as model writes each file
96
+ lacuna generate --workers 4 # run 4 files in parallel
97
+ lacuna generate --fresh # force a new coverage run
98
+ lacuna generate --format json --output report.json
99
+ ```
100
+
101
+ ### `lacuna fix`
102
+ Finds all failing tests and repairs them using AI — without rewriting them from scratch. Sends each failing file along with its error output and source code to the model, which surgically fixes what's broken and retries until it passes.
103
+
104
+ ```bash
105
+ lacuna fix
106
+ lacuna fix --workers 4 # fix 4 files in parallel
107
+ lacuna fix --file src/utils/math.test.ts # fix a single test file (skips full suite run)
108
+ lacuna fix --dry-run # preview fixes without writing
109
+ lacuna fix --verbose # live code panel as model writes each fix
110
+ lacuna fix --fresh # re-run the suite even if cache is recent
111
+ ```
112
+
113
+ Unlike `lacuna generate`, which creates new tests, `lacuna fix` operates on existing failing tests. It preserves all test logic and only changes what is necessary to make the suite pass.
114
+
115
+ If all retries fail or the model oscillates (identical output detected), the original file is restored automatically. Your test suite is always left in a coherent state.
116
+
117
+ If a fix attempt breaks an import (causing 0 tests to be collected) or reduces the number of passing tests, lacuna detects the regression and tells the model exactly what the original failure was — so it doesn't waste further iterations trying to recover from the wrong problem.
118
+
119
+ When `--file` is given, lacuna skips the full suite and runs only the target file — much faster for iterating on a single broken test. Without `--file`, the failing-files list is cached for 30 minutes. After a fix run, the cache is updated to contain only the files that are still failing — so re-running `lacuna fix` immediately picks up exactly where the last run left off. Once all files are fixed, the cache is cleared so the next run does a clean suite scan.
120
+
121
+ ### `lacuna run`
122
+ Runs your test suite and reports coverage. No AI involved.
123
+
124
+ ```bash
125
+ lacuna run
126
+ ```
127
+
128
+ ---
129
+
130
+ ## Configuration — `.lacuna.json`
131
+
132
+ Created by `lacuna init`. All fields are optional with sensible defaults.
133
+
134
+ ```json
135
+ {
136
+ "provider": "anthropic",
137
+ "model": "claude-sonnet-4-6",
138
+ "apiKeyEnv": "ANTHROPIC_API_KEY",
139
+ "testRunner": "jest",
140
+ "coverageFormat": "lcov",
141
+ "coverageDir": "coverage",
142
+ "sourceDir": "src",
143
+ "threshold": 80,
144
+ "maxIterations": 3,
145
+ "mocksFile": "src/test/mocks.ts",
146
+ "setupFile": "src/test/setup.ts",
147
+ "ignore": ["src/graphql/", "src/theme/"]
148
+ }
149
+ ```
150
+
151
+ | Field | Default | Description |
152
+ |---|---|---|
153
+ | `provider` | `anthropic` | `anthropic` or `openai-compatible` |
154
+ | `model` | `claude-sonnet-4-6` | Model name |
155
+ | `apiKeyEnv` | `ANTHROPIC_API_KEY` | Env var holding your API key |
156
+ | `baseURL` | — | Required for `openai-compatible` provider |
157
+ | `testRunner` | auto-detect | `jest` \| `vitest` \| `pytest` \| `mocha` \| `go-test` |
158
+ | `coverageFormat` | `lcov` | `lcov` \| `json-summary` |
159
+ | `coverageDir` | `coverage` | Where your test runner writes coverage |
160
+ | `sourceDir` | `src` | Root directory of source files |
161
+ | `threshold` | `80` | Minimum line coverage % to pass |
162
+ | `maxIterations` | `3` | How many times to retry a failing generated test |
163
+ | `coverageTimeout` | `300` | Seconds before the test suite is killed (prevents hanging on open handles) |
164
+ | `mocksFile` | — | Path to shared mock file (see Enterprise Mocks below) |
165
+ | `setupFile` | — | Path to your test setup file — lacuna passes its contents to the AI so it knows which globals and matchers are already available |
166
+ | `ignore` | `[]` | Extra path substrings to exclude from gap detection (e.g. `"src/graphql/"`) |
167
+ | `maxTokens` | `16000` | Maximum output tokens per model call. Lower this for providers with strict limits (Groq free tier: ~8000, Ollama: depends on model). Raise it if large test files are being cut off mid-generation. |
168
+
169
+ ---
170
+
171
+ ## Supported models
172
+
173
+ Lacuna works with any AI model — local or cloud.
174
+
175
+ | Preset | Model | API key env | Notes |
176
+ |---|---|---|---|
177
+ | Claude (default) | `claude-sonnet-4-6` | `ANTHROPIC_API_KEY` | Best for code |
178
+ | Claude Opus | `claude-opus-4-7` | `ANTHROPIC_API_KEY` | Most capable |
179
+ | DeepSeek | `deepseek-chat` | `DEEPSEEK_API_KEY` | Very cost-effective |
180
+ | DeepSeek R1 | `deepseek-reasoner` | `DEEPSEEK_API_KEY` | Reasoning model |
181
+ | GPT-4o | `gpt-4o` | `OPENAI_API_KEY` | |
182
+ | Groq | `llama-3.3-70b-versatile` | `GROQ_API_KEY` | Fast, free tier |
183
+ | Gemini 2.5 Pro | `gemini-2.5-pro` | `GEMINI_API_KEY` | Google's most capable |
184
+ | Gemini 2.5 Flash | `gemini-2.5-flash` | `GEMINI_API_KEY` | Fast & cheap |
185
+ | OpenRouter | any model | `OPENROUTER_API_KEY` | 100+ models, one key |
186
+ | Ollama | any local model | none | Fully local, free |
187
+ | LM Studio | any local model | none | Fully local, free |
188
+ | Custom | configurable | configurable | Any OpenAI-compatible API |
189
+
190
+ Switch models any time by re-running `lacuna init` or editing `.lacuna.json` directly.
191
+
192
+ ---
193
+
194
+ ## Enterprise mocks
195
+
196
+ For large codebases, ad-hoc mocks in every test file create maintenance nightmares. Lacuna supports a **shared mock file** — a single source of truth for all mocks that every generated test imports from.
197
+
198
+ ### Setup
199
+
200
+ 1. Create `src/test/mocks.ts`:
201
+
202
+ ```ts
203
+ import { vi } from 'vitest'
204
+
205
+ // API clients
206
+ export const mockAxios = {
207
+ get: vi.fn(),
208
+ post: vi.fn(),
209
+ put: vi.fn(),
210
+ delete: vi.fn(),
211
+ }
212
+
213
+ // Router
214
+ export const mockNavigate = vi.fn()
215
+ export const mockUseNavigate = () => mockNavigate
216
+ vi.mock('react-router-dom', () => ({
217
+ useNavigate: mockUseNavigate,
218
+ useParams: vi.fn(() => ({})),
219
+ }))
220
+
221
+ // Auth
222
+ export const mockUser = {
223
+ id: 'user-1',
224
+ email: 'test@example.com',
225
+ role: 'admin',
226
+ }
227
+ export const mockUseAuth = vi.fn(() => ({ user: mockUser, isLoading: false }))
228
+
229
+ // Reset all mocks between tests
230
+ beforeEach(() => {
231
+ vi.clearAllMocks()
232
+ })
233
+ ```
234
+
235
+ 2. Add `mocksFile` to `.lacuna.json`:
236
+
237
+ ```json
238
+ {
239
+ "mocksFile": "src/test/mocks.ts"
240
+ }
241
+ ```
242
+
243
+ 3. Run lacuna normally:
244
+
245
+ ```bash
246
+ lacuna generate
247
+ ```
248
+
249
+ Every generated test will import from `src/test/mocks.ts` instead of creating its own `vi.fn()` calls. If a test needs a mock that doesn't exist yet, Claude will add it to the mocks file and import it — keeping everything centralized.
250
+
251
+ ---
252
+
253
+ ## CI / GitHub Actions
254
+
255
+ Add lacuna to your PR workflow to automatically generate tests and block merges below threshold.
256
+
257
+ Create `.github/workflows/lacuna.yml`:
258
+
259
+ ```yaml
260
+ name: lacuna coverage
261
+
262
+ on:
263
+ pull_request:
264
+ branches: [main]
265
+
266
+ jobs:
267
+ coverage:
268
+ runs-on: ubuntu-latest
269
+ permissions:
270
+ contents: write
271
+ pull-requests: write
272
+
273
+ steps:
274
+ - uses: actions/checkout@v4
275
+ with:
276
+ ref: ${{ github.head_ref }}
277
+ token: ${{ secrets.GITHUB_TOKEN }}
278
+
279
+ - uses: actions/setup-node@v4
280
+ with:
281
+ node-version: '20'
282
+ cache: npm
283
+ - run: npm ci
284
+
285
+ - name: Run lacuna
286
+ uses: lacuna-dev/lacuna@v1
287
+ with:
288
+ threshold: 80
289
+ anthropic-api-key: ${{ secrets.ANTHROPIC_API_KEY }}
290
+
291
+ - name: Commit generated tests
292
+ run: |
293
+ git config user.name "lacuna[bot]"
294
+ git config user.email "lacuna[bot]@users.noreply.github.com"
295
+ git add -A
296
+ git diff --staged --quiet || git commit -m "chore: lacuna — add generated tests"
297
+ git push
298
+ ```
299
+
300
+ On every PR lacuna will:
301
+ - Generate missing tests
302
+ - Post a coverage report as a PR comment (updated on each push, no spam)
303
+ - Block the merge if coverage stays below your threshold
304
+
305
+ ---
306
+
307
+ ## Output formats
308
+
309
+ All commands support `--format` and `--output`:
310
+
311
+ ```bash
312
+ # Terminal (default)
313
+ lacuna analyze
314
+
315
+ # JSON — for scripts and CI pipelines
316
+ lacuna analyze --format json
317
+ lacuna generate --format json --output lacuna-report.json
318
+
319
+ # Markdown — for PR comments and docs
320
+ lacuna analyze --format markdown
321
+ ```
322
+
323
+ ### Exit codes
324
+
325
+ | Code | Meaning |
326
+ |---|---|
327
+ | `0` | Pass — coverage meets threshold |
328
+ | `1` | Fail — coverage below threshold |
329
+ | `2` | Error — test runner failed or config issue |
330
+
331
+ ---
332
+
333
+ ## Contextual tips
334
+
335
+ While tests are generating, lacuna shows rotating tips in the terminal — hints about flags and config options you might not be using yet. Tips are context-aware: if you're already using a flag, its tip won't appear.
336
+
337
+ **Tips shown during `lacuna generate`:**
338
+ - Use `-w 4` (`--workers`) to process multiple files in parallel
339
+ - Use `-f src/utils/math.ts` (`--file`) to target a single file
340
+ - Use `--dry-run` to preview without writing files
341
+ - Use `-v` (`--verbose`) to watch a live code panel as the AI writes each test file
342
+ - Use `-m claude-opus-4-7` (`--model`) to switch to a more capable model
343
+ - Use `--fresh` to force a new coverage run instead of reusing a cached report
344
+ - Use `-t 90` (`--threshold`) to raise the coverage bar
345
+ - Use `--format json --output report.json` to export results
346
+ - Set `mocksFile` in `.lacuna.json` to share mocks across all generated tests
347
+ - Add paths to `ignore[]` in `.lacuna.json` to skip directories
348
+ - Run `lacuna fix` to repair failing tests
349
+ - Run `lacuna analyze` to inspect gaps without writing files
350
+ - Increase `coverageTimeout` in `.lacuna.json` if your suite is being killed
351
+ - Set `maxTokens` in `.lacuna.json` if tests are cut off mid-generation (lower for Groq/Ollama, raise for large files)
352
+
353
+ **Tips shown during `lacuna fix`** are the same, minus flags that `fix` doesn't support (`--threshold`, `--format`).
354
+
355
+ In parallel mode (`--workers`), tips rotate every ~5 seconds in the live worker display. In single-worker mode, a different tip appears before each file is processed.
356
+
357
+ ---
358
+
359
+ ## What gets skipped
360
+
361
+ Lacuna automatically skips files that have no testable runtime logic — no point generating tests for them.
362
+
363
+ **Skipped by directory name** (anywhere in the path):
364
+ `types/`, `constants/`, `assets/`, `images/`, `icons/`, `fonts/`, `styles/`, `generated/`, `__generated__/`, `mocks/`, `fixtures/`, `migrations/`, `i18n/`, `locales/`, `translations/`
365
+
366
+ **Skipped by file name pattern:**
367
+ `*.d.ts`, `*.test.*`, `*.spec.*`, `*.stories.*`, `*.config.*`, `*.mock.*`, `*.types.ts`, `*.constants.ts`, `*.enum.*`, `index.*`
368
+
369
+ **Skipped by content:** Even if a file doesn't match the patterns above, lacuna reads it and skips it if it contains no functions, arrow functions, or classes — i.e. only type/interface/enum/constant exports.
370
+
371
+ **Add your own exclusions** via `.lacuna.json`:
372
+
373
+ ```json
374
+ {
375
+ "ignore": ["src/graphql/", "src/theme/", "src/generated/"]
376
+ }
377
+ ```
378
+
379
+ `ignore` entries are matched as path substrings — any file whose path contains the string is excluded.
380
+
381
+ ---
382
+
383
+ ## Test placement
384
+
385
+ Lacuna follows your project's existing conventions:
386
+
387
+ - If test files exist **next to source files** (co-located), new tests go there too
388
+ - Otherwise, tests go in `__tests__/` inside the same directory as the source file
389
+ - `__tests__/` is created automatically if it doesn't exist
390
+
391
+ ---
392
+
393
+ ## Project structure
394
+
395
+ ```
396
+ lacuna/
397
+ ├── src/
398
+ │ ├── commands/ # CLI commands (analyze, generate, fix, run, init)
399
+ │ ├── agent/ # AI agent loop
400
+ │ │ ├── loop.ts # main generate → run → retry loop
401
+ │ │ ├── fix-loop.ts # fix → run → retry loop for failing tests
402
+ │ │ ├── context.ts # builds context for the AI (source + tests + mocks + type definitions)
403
+ │ │ ├── generator.ts # calls the AI model, manages conversation history
404
+ │ │ └── prompts.ts # system prompt + user prompt templates
405
+ │ ├── lib/
406
+ │ │ ├── config.ts # cosmiconfig loader + zod schema
407
+ │ │ ├── detector.ts # auto-detects test runner and language
408
+ │ │ ├── runner.ts # spawns test commands, captures output
409
+ │ │ ├── reporter.ts # terminal / JSON / markdown reporters
410
+ │ │ ├── skeleton.ts # collapses already-covered function bodies to reduce prompt size
411
+ │ │ ├── extract-error.ts # strips passing-test noise from runner output before retry
412
+ │ │ ├── validate.ts # checks generated code has real test calls; detects regressions and broken imports in retry output
413
+ │ │ ├── streaming-viewer.ts # live bordered code panel for --verbose mode (typewriter effect)
414
+ │ │ ├── typecheck.ts # post-vitest tsc pass; retries if type errors found
415
+ │ │ ├── providers/ # AI provider abstraction
416
+ │ │ │ ├── anthropic.ts
417
+ │ │ │ ├── openai-compatible.ts
418
+ │ │ │ └── types.ts # ModelProvider interface + presets
419
+ │ │ └── coverage/
420
+ │ │ ├── lcov.ts # LCOV parser
421
+ │ │ ├── json.ts # JSON summary parser
422
+ │ │ ├── gaps.ts # gap extractor
423
+ │ │ └── types.ts # shared coverage types
424
+ │ └── ci/
425
+ │ ├── comment.ts # posts coverage report as GitHub PR comment
426
+ │ └── parse-outputs.ts # sets GitHub Actions step outputs
427
+ ├── app/ # SaaS dashboard (Next.js + Postgres + Payaza)
428
+ ├── action.yml # GitHub Action definition
429
+ └── .github/workflows/
430
+ └── example.yml # example CI workflow to copy into your repo
431
+ ```
432
+
433
+ ---
434
+
435
+ ## Contributing
436
+
437
+ Issues and PRs welcome. The codebase is TypeScript throughout.
438
+
439
+ ```bash
440
+ git clone https://github.com/lacuna-dev/lacuna
441
+ cd lacuna
442
+ npm install
443
+ npm run build
444
+ npm link # makes `lacuna` available globally from your local build
445
+ ```
446
+
447
+ ---
448
+
449
+ ## License
450
+
451
+ MIT
package/bin/run.js ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { execute } from '@oclif/core'
4
+
5
+ await execute({ dir: import.meta.url })
@@ -0,0 +1,25 @@
1
+ import type { DetectedEnvironment } from '../lib/detector.js';
2
+ import type { LacunaConfig } from '../lib/config.js';
3
+ export interface FileContext {
4
+ sourceFile: string;
5
+ sourceCode: string;
6
+ existingTestFile: string | null;
7
+ existingTestCode: string | null;
8
+ suggestedTestFile: string;
9
+ sourceImportPath: string | null;
10
+ mocksCode: string | null;
11
+ mocksImportPath: string | null;
12
+ setupFileCode: string | null;
13
+ packageDeps: string | null;
14
+ tsconfigPaths: string | null;
15
+ typeDefinitions: string | null;
16
+ localImportPaths: string[] | null;
17
+ reactMajorVersion: number | null;
18
+ }
19
+ export declare function computeRelativeImport(fromFile: string, toFile: string): string;
20
+ export declare function collectTypeDefinitions(sourceCode: string, absoluteSourcePath: string, cwd: string): Promise<string | null>;
21
+ export declare function collectLocalImportPaths(sourceCode: string, absoluteSourcePath: string, absoluteTestFilePath: string, cwd: string): Promise<string[] | null>;
22
+ export declare function detectReactMajorVersion(cwd: string): Promise<number | null>;
23
+ export declare function buildFixFileContext(absTestPath: string, cwd: string, config?: LacunaConfig): Promise<Pick<FileContext, 'mocksCode' | 'mocksImportPath' | 'setupFileCode' | 'packageDeps' | 'tsconfigPaths'>>;
24
+ export declare function buildFileContext(sourceFilePath: string, cwd: string, env: DetectedEnvironment, config?: LacunaConfig): Promise<FileContext>;
25
+ //# sourceMappingURL=context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/agent/context.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAA;AAC7D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAEpD,MAAM,WAAW,WAAW;IAC1B,UAAU,EAAE,MAAM,CAAA;IAClB,UAAU,EAAE,MAAM,CAAA;IAClB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAA;IAC/B,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAA;IAC/B,iBAAiB,EAAE,MAAM,CAAA;IACzB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAA;IAC/B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,aAAa,EAAE,MAAM,GAAG,IAAI,CAAA;IAC5B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,aAAa,EAAE,MAAM,GAAG,IAAI,CAAA;IAC5B,eAAe,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,gBAAgB,EAAE,MAAM,EAAE,GAAG,IAAI,CAAA;IACjC,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAA;CACjC;AAGD,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAI9E;AA8JD,wBAAsB,sBAAsB,CAC1C,UAAU,EAAE,MAAM,EAClB,kBAAkB,EAAE,MAAM,EAC1B,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CA6CxB;AAMD,wBAAsB,uBAAuB,CAC3C,UAAU,EAAE,MAAM,EAClB,kBAAkB,EAAE,MAAM,EAC1B,oBAAoB,EAAE,MAAM,EAC5B,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,CAmB1B;AAGD,wBAAsB,uBAAuB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAWjF;AAkFD,wBAAsB,mBAAmB,CACvC,WAAW,EAAE,MAAM,EACnB,GAAG,EAAE,MAAM,EACX,MAAM,CAAC,EAAE,YAAY,GACpB,OAAO,CAAC,IAAI,CAAC,WAAW,EAAE,WAAW,GAAG,iBAAiB,GAAG,eAAe,GAAG,aAAa,GAAG,eAAe,CAAC,CAAC,CAwBjH;AAED,wBAAsB,gBAAgB,CACpC,cAAc,EAAE,MAAM,EACtB,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,mBAAmB,EACxB,MAAM,CAAC,EAAE,YAAY,GACpB,OAAO,CAAC,WAAW,CAAC,CAuDtB"}