lacuna-cli 0.2.2 → 0.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +215 -392
- package/dist/agent/fix-loop.d.ts.map +1 -1
- package/dist/agent/fix-loop.js +29 -7
- package/dist/agent/fix-loop.js.map +1 -1
- package/dist/agent/generator.d.ts.map +1 -1
- package/dist/agent/generator.js +17 -9
- package/dist/agent/generator.js.map +1 -1
- package/dist/agent/loop.d.ts.map +1 -1
- package/dist/agent/loop.js +6 -2
- package/dist/agent/loop.js.map +1 -1
- package/dist/lib/typecheck.d.ts.map +1 -1
- package/dist/lib/typecheck.js +61 -34
- package/dist/lib/typecheck.js.map +1 -1
- package/dist/lib/validate.d.ts.map +1 -1
- package/dist/lib/validate.js +168 -22
- package/dist/lib/validate.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,118 +1,118 @@
|
|
|
1
1
|
# lacuna
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> Find untested code, write tests for it, and verify they pass, in one command.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
[](https://www.npmjs.com/package/lacuna-cli)
|
|
6
|
+
[](https://www.npmjs.com/package/lacuna-cli)
|
|
7
|
+
[](https://github.com/Octagon-simon/lacuna/actions/workflows/release.yml)
|
|
8
|
+
[](https://nodejs.org)
|
|
9
|
+
[](#license)
|
|
10
|
+
|
|
11
|
+
Lacuna is a command-line tool that reads your code, finds the parts your tests don't cover, and writes tests to fill the gaps. It runs every test it writes and retries the ones that fail, so what lands in your repo actually passes.
|
|
12
|
+
|
|
13
|
+
It works with any OpenAI-compatible model (including local ones via Ollama or LM Studio), so you can run it without sending code to a hosted provider if you'd rather not.
|
|
6
14
|
|
|
7
15
|
```bash
|
|
8
|
-
lacuna generate
|
|
16
|
+
$ lacuna generate
|
|
9
17
|
```
|
|
10
18
|
|
|
11
19
|
---
|
|
12
20
|
|
|
13
|
-
##
|
|
21
|
+
## Getting started
|
|
14
22
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
├── Extracts used symbol definitions ├── Reads imported type definitions
|
|
27
|
-
│ (hook return shapes, service ├── Reads tsconfig paths, deps, setup file
|
|
28
|
-
│ method sigs, transitive types) ├── Detects network mocking issues
|
|
29
|
-
├── Reads tsconfig paths, deps, ├── AI reasons in <thinking>, writes fix
|
|
30
|
-
│ and test setup file ├── Writes the fixed file
|
|
31
|
-
├── Sends full context to AI model ├── ✅ Pass → next file
|
|
32
|
-
├── AI reasons then writes tests └── ❌ Fail → records what failed,
|
|
33
|
-
├── Runs the tests detects oscillation (stops early),
|
|
34
|
-
├── ✅ Pass → next file retries with negative constraints
|
|
35
|
-
└── ❌ Fail → records what failed, restores original on final failure
|
|
36
|
-
detects oscillation (stops early),
|
|
37
|
-
retries with negative constraints
|
|
38
|
-
restores original on final failure
|
|
23
|
+
### 1. Install
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
$ npm install -g lacuna-cli
|
|
39
27
|
```
|
|
40
28
|
|
|
41
|
-
|
|
29
|
+
Requires Node 20 or newer.
|
|
30
|
+
|
|
31
|
+
### 2. Set an API key
|
|
42
32
|
|
|
43
|
-
|
|
33
|
+
Lacuna defaults to DeepSeek. Create a key at [platform.deepseek.com](https://platform.deepseek.com) and export it:
|
|
44
34
|
|
|
45
35
|
```bash
|
|
46
|
-
|
|
36
|
+
$ export DEEPSEEK_API_KEY=sk-...
|
|
47
37
|
```
|
|
48
38
|
|
|
49
|
-
|
|
39
|
+
Prefer a different model? See [Models](#models); every option, including free local ones, is listed there. You can pick one during `lacuna init`.
|
|
40
|
+
|
|
41
|
+
### 3. Configure your project
|
|
50
42
|
|
|
51
|
-
|
|
43
|
+
From your project root:
|
|
52
44
|
|
|
53
45
|
```bash
|
|
54
|
-
lacuna
|
|
55
|
-
|
|
46
|
+
$ lacuna init
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
This is an interactive wizard. It detects your test runner, asks which model to use, and writes a `.lacuna.json`. For React, React Native, and Next.js projects it also installs the testing libraries and creates a working test config and setup file.
|
|
50
|
+
|
|
51
|
+
### 4. See what's untested
|
|
56
52
|
|
|
57
|
-
|
|
58
|
-
lacuna
|
|
59
|
-
lacuna analyze # see what's uncovered (read-only)
|
|
60
|
-
lacuna generate # AI fills the gaps
|
|
53
|
+
```bash
|
|
54
|
+
$ lacuna analyze
|
|
61
55
|
```
|
|
62
56
|
|
|
63
|
-
|
|
57
|
+
Read-only. It runs your suite, collects coverage, and lists the files and functions below your threshold. Nothing is written.
|
|
64
58
|
|
|
65
|
-
|
|
59
|
+
### 5. Generate the tests
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
$ lacuna generate
|
|
63
|
+
```
|
|
66
64
|
|
|
67
|
-
Lacuna
|
|
65
|
+
Lacuna writes tests for the gaps, runs them, and retries failures. When it finishes, the new tests are already passing.
|
|
68
66
|
|
|
69
|
-
|
|
67
|
+
To target a single file and skip the full coverage run:
|
|
70
68
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
| **React** | Vitest, Jest | RTL query hierarchy, `act()` async rules, loading state (unmounted vs disabled button), unhandled rejection handling, mock lifecycle (`mockResolvedValueOnce`), `findBy` over `waitFor` preference |
|
|
75
|
-
| **React Native** / Expo | Jest (`jest-expo`) | RNTL v14 async contract (`render()` + `fireEvent` must be awaited), infra mocks (Reanimated, AsyncStorage, safe area, vector icons), compound component factories, mock shape accuracy (hook destructure matching, service method names verbatim from source), factory functions for mock contexts (prevents `clearAllMocks()` wipes), Fragment-wrapped button `testID` pattern, Modal `visible={false}` query isolation, icon library completeness (grep every import — one missing icon crashes render), unique mock list data, dynamic text template resolution, toast vs screen text, icon-only button handling, `useFocusEffect` mocking, test cascade detection |
|
|
76
|
-
| **Next.js** | Vitest | Server/client boundary mocks, `next/navigation`, `next/headers`, `next/cache`, server actions, `'use client'`/`'use server'` directive detection |
|
|
69
|
+
```bash
|
|
70
|
+
$ lacuna generate --file src/utils/math.ts
|
|
71
|
+
```
|
|
77
72
|
|
|
78
|
-
|
|
73
|
+
That's the whole loop. The rest of this README is reference.
|
|
79
74
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
| Python | pytest | Runner + coverage detection works. AI prompt guidance is generic — framework-specific tuning (pytest-django, FastAPI, etc.) will be added once field-tested. |
|
|
84
|
-
| PHP | PHPUnit, Pest | Runner + coverage detection works. Framework-specific prompt tuning coming once field-tested. |
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## How it works
|
|
85
78
|
|
|
86
|
-
|
|
79
|
+
```
|
|
80
|
+
lacuna generate lacuna fix
|
|
81
|
+
│ │
|
|
82
|
+
├─ 1. Collect coverage ├─ 1. Find failing files
|
|
83
|
+
│ ├─ report < 10 min old → reuse it │ ├─ --file → that file only
|
|
84
|
+
│ └─ otherwise → run the suite │ ├─ cache < 30 min old → reuse it
|
|
85
|
+
├─ 2. Find files below threshold │ └─ otherwise → run the suite
|
|
86
|
+
│ │
|
|
87
|
+
└─ For each gap: └─ For each failing file:
|
|
88
|
+
├─ Read source + existing tests ├─ Run it alone, capture the error
|
|
89
|
+
├─ Extract used symbol definitions ├─ Read the test + source + types
|
|
90
|
+
│ (return shapes, method signatures) ├─ Read tsconfig paths, deps, setup
|
|
91
|
+
├─ Read tsconfig paths, deps, setup ├─ Model writes a surgical fix
|
|
92
|
+
├─ Send full context to the model ├─ Pass → next file
|
|
93
|
+
├─ Run the generated tests └─ Fail → record it, detect loops,
|
|
94
|
+
├─ Pass → next file retry, restore on giving up
|
|
95
|
+
└─ Fail → retry with the error,
|
|
96
|
+
keep the best attempt
|
|
97
|
+
```
|
|
87
98
|
|
|
88
|
-
|
|
99
|
+
Two rules hold throughout: lacuna never leaves a half-written file behind, and it never removes passing tests. If it can't fully fix a file, it keeps the attempt with the most passing tests — and if nothing beat the starting point, it puts the original back.
|
|
89
100
|
|
|
90
101
|
---
|
|
91
102
|
|
|
92
103
|
## Commands
|
|
93
104
|
|
|
94
105
|
### `lacuna init`
|
|
95
|
-
Interactive setup wizard. Configures your model, test runner, source directory, coverage threshold, and mock file. Creates `.lacuna.json` in your project root.
|
|
96
|
-
|
|
97
|
-
Works from any subdirectory — lacuna finds the project root automatically.
|
|
98
106
|
|
|
99
|
-
|
|
100
|
-
- Installs `@testing-library/react`, `@testing-library/jest-dom`, `@testing-library/user-event`, and `jsdom`
|
|
101
|
-
- Creates `vitest.config.ts` with `environment: 'jsdom'` and `restoreMocks: true` + `clearMocks: true`
|
|
102
|
-
- Creates a setup file with `@testing-library/jest-dom` and `beforeEach`/`afterEach` mock cleanup hooks
|
|
107
|
+
Sets up lacuna in your project. Detects the test runner, picks a model, and writes `.lacuna.json`. Run it from anywhere in the project; it finds the root on its own.
|
|
103
108
|
|
|
104
|
-
For
|
|
105
|
-
- Installs the same testing-library packages but does **not** add `environment: 'jsdom'` to vitest config (Next.js manages its own environment)
|
|
106
|
-
- Adds `@/` alias (from your `tsconfig.json`) and a `server-only` stub alias to vitest config
|
|
107
|
-
- Creates a setup file with global `vi.mock()` calls for `next/navigation`, `next/headers`, `next/cache`, `next/image`, and `next/font` — these are Next.js-specific mocks that would crash a plain React project
|
|
108
|
-
- If test dependencies are found in `node_modules` but not declared in `package.json` (e.g. from a different branch), lacuna will prompt you to add them — undeclared deps break CI on a fresh checkout
|
|
109
|
+
For React, it installs `@testing-library/react`, `jest-dom`, `user-event`, and `jsdom`, then writes a `vitest.config.ts` and setup file with mock cleanup hooks.
|
|
109
110
|
|
|
110
|
-
|
|
111
|
-
lacuna init
|
|
112
|
-
```
|
|
111
|
+
For Next.js it does the same but skips the `jsdom` environment (Next manages its own), adds your `@/` alias, and pre-mocks `next/navigation`, `next/headers`, `next/cache`, `next/image`, and `next/font`.
|
|
113
112
|
|
|
114
113
|
### `lacuna analyze`
|
|
115
|
-
|
|
114
|
+
|
|
115
|
+
Runs the suite, collects coverage, and reports what's below threshold. Writes nothing.
|
|
116
116
|
|
|
117
117
|
```bash
|
|
118
118
|
lacuna analyze
|
|
@@ -122,55 +122,48 @@ lacuna analyze --format markdown
|
|
|
122
122
|
```
|
|
123
123
|
|
|
124
124
|
### `lacuna generate`
|
|
125
|
-
The main command. Runs the full agent loop — analyzes gaps, writes tests, runs them, retries failures.
|
|
126
|
-
|
|
127
|
-
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.
|
|
128
|
-
|
|
129
|
-
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.
|
|
130
|
-
|
|
131
|
-
If all retries fail, lacuna keeps the best attempt **when it adds passing tests over what was there before** — and tells you to run `lacuna fix --file …` to finish the remaining failures — otherwise it restores the original. Either way your workspace is never left worse than it started or with a half-written file. If the model oscillates (produces the same code twice), the retry loop stops early rather than burning remaining iterations.
|
|
132
125
|
|
|
133
|
-
|
|
126
|
+
The main command: find gaps, write tests, run them, retry failures.
|
|
134
127
|
|
|
135
128
|
```bash
|
|
136
129
|
lacuna generate
|
|
137
|
-
lacuna generate --file src/utils/math.ts #
|
|
138
|
-
lacuna generate --dry-run
|
|
139
|
-
lacuna generate --verbose
|
|
140
|
-
lacuna generate --workers 4
|
|
141
|
-
lacuna generate --fresh
|
|
130
|
+
lacuna generate --file src/utils/math.ts # one file, skips the coverage run
|
|
131
|
+
lacuna generate --dry-run # preview, write nothing
|
|
132
|
+
lacuna generate --verbose # live panel as the model writes
|
|
133
|
+
lacuna generate --workers 4 # process 4 files in parallel
|
|
134
|
+
lacuna generate --fresh # ignore the cached coverage report
|
|
142
135
|
lacuna generate --format json --output report.json
|
|
143
136
|
```
|
|
144
137
|
|
|
138
|
+
If you ran `analyze` in the last 10 minutes, `generate` reuses that report instead of running the suite again (`--fresh` forces a new run). When retries are exhausted, lacuna keeps the best attempt **only if it adds passing tests** and points you to `lacuna fix` for the rest; otherwise it restores the original. If the model produces the same output twice, the loop stops early instead of wasting iterations.
|
|
139
|
+
|
|
145
140
|
### `lacuna fix`
|
|
146
|
-
|
|
141
|
+
|
|
142
|
+
Finds failing tests and repairs them. Each failing file goes to the model with its error output and source; the model patches what's broken and lacuna reruns until it passes. A fix that makes the tests pass is kept even if minor type warnings remain. `fix` never reverts a working change — and when it can't reach all-green, it keeps the attempt with the most passing tests rather than discarding a partial improvement.
|
|
147
143
|
|
|
148
144
|
```bash
|
|
149
145
|
lacuna fix
|
|
150
|
-
lacuna fix --
|
|
151
|
-
lacuna fix --
|
|
152
|
-
lacuna fix --types
|
|
153
|
-
lacuna fix --dry-run
|
|
154
|
-
lacuna fix --verbose
|
|
155
|
-
lacuna fix --fresh
|
|
156
|
-
lacuna fix --no-regenerate-on-failure
|
|
157
|
-
lacuna fix --fix-polluters
|
|
146
|
+
lacuna fix --file src/utils/math.test.ts # one file, skips the full suite
|
|
147
|
+
lacuna fix --workers 4 # 4 files in parallel
|
|
148
|
+
lacuna fix --types # repair files that pass but fail type-checking
|
|
149
|
+
lacuna fix --dry-run
|
|
150
|
+
lacuna fix --verbose
|
|
151
|
+
lacuna fix --fresh
|
|
152
|
+
lacuna fix --no-regenerate-on-failure # don't fall back to regenerating
|
|
153
|
+
lacuna fix --fix-polluters # handle tests that pass alone but fail in the suite
|
|
158
154
|
```
|
|
159
155
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
**Regeneration fallback (default on):** When fix retries are exhausted on a *genuinely broken* file (zero passing tests to lose), lacuna deletes it and regenerates from scratch using the generate path — a clean conversation and full source context beat another round of patchwork. Two guardrails keep this safe: a file that already has passing tests is **never** regenerated (it's left repaired-or-restored, never nuked to chase a few failures), and a regeneration that would *reduce* the passing-test count is discarded and the original restored. Use `--no-regenerate-on-failure` to disable the fallback entirely.
|
|
163
|
-
|
|
164
|
-
**Type errors (`--types`):** `lacuna fix --types` selects files by TypeScript type errors instead of test failures — one project-wide `tsc` pass finds every test file that fails type-checking (even if its tests pass), then repairs them (honors `--workers`). Type-checking respects each file's **governing tsconfig**: if the nearest `tsconfig.json` disables `noImplicitAny` (common in monorepo packages that loosen a strict root), implicit-any is not treated as an error to chase. A plain `lacuna fix --file …` also repairs a file that passes but has type errors — this is what `lacuna generate` points you to when it leaves a green-but-type-dirty file.
|
|
165
|
-
|
|
166
|
-
**Passing in isolation, failing in suite (`--fix-polluters`):** Some tests pass when run alone but fail in the full suite. `--fix-polluters` handles these in two phases: (1) bisect the test suite to find if another file is leaking state (e.g. an uncleaned mock or global), and fix the polluter; (2) if no polluter can be isolated (the test has an internal spy lifecycle bug), delete and regenerate the victim file directly.
|
|
156
|
+
A few behaviors worth knowing:
|
|
167
157
|
|
|
168
|
-
If
|
|
158
|
+
- **Regeneration fallback (on by default).** If repair is exhausted on a *genuinely broken* file (one with no passing tests to lose), lacuna deletes it and regenerates from source, since a clean start beats more patching. A file that already has passing tests is never deleted, and a regeneration that would lower the passing count is discarded. Turn it off with `--no-regenerate-on-failure`.
|
|
159
|
+
- **Type errors (`--types`).** Selects files by TypeScript errors instead of test failures, finding every test file that fails type-checking even if its tests pass. Type-checking runs against each file's **governing `tsconfig`** (the nearest one walking up), not the repo root — so in a monorepo a package's `@/` path aliases, `jsx`, and `moduleResolution` resolve correctly and a clean file isn't flagged with false `Cannot find module`/`Cannot use JSX` errors. It also respects that config's rules: if the nearest one disables `noImplicitAny` (common in monorepo packages), implicit-`any` isn't treated as an error. Files are grouped by config and checked one scoped `tsc` run per package.
|
|
160
|
+
- **Polluters (`--fix-polluters`).** For tests that pass alone but fail in the full suite, lacuna bisects the suite to find the file leaking state and fixes it; if none can be isolated, it regenerates the affected test.
|
|
169
161
|
|
|
170
|
-
|
|
162
|
+
Without `--file`, the failing-files list is cached for 30 minutes and trimmed to whatever's still failing after each run, so re-running picks up where you left off.
|
|
171
163
|
|
|
172
164
|
### `lacuna run`
|
|
173
|
-
|
|
165
|
+
|
|
166
|
+
Runs your suite and reports coverage. No model involved.
|
|
174
167
|
|
|
175
168
|
```bash
|
|
176
169
|
lacuna run
|
|
@@ -178,11 +171,11 @@ lacuna run
|
|
|
178
171
|
|
|
179
172
|
---
|
|
180
173
|
|
|
181
|
-
## Configuration
|
|
174
|
+
## Configuration
|
|
182
175
|
|
|
183
|
-
|
|
176
|
+
`lacuna init` writes `.lacuna.json`. Every field is optional and has a sensible default.
|
|
184
177
|
|
|
185
|
-
|
|
178
|
+
The file includes a `$schema` line, so editors like VS Code give you key completion and inline docs as you type. To add it to an existing config, put this first:
|
|
186
179
|
|
|
187
180
|
```json
|
|
188
181
|
{
|
|
@@ -190,6 +183,8 @@ Created by `lacuna init`. All fields are optional with sensible defaults.
|
|
|
190
183
|
}
|
|
191
184
|
```
|
|
192
185
|
|
|
186
|
+
A typical config:
|
|
187
|
+
|
|
193
188
|
```json
|
|
194
189
|
{
|
|
195
190
|
"$schema": "https://raw.githubusercontent.com/Octagon-simon/lacuna/main/lacuna.schema.json",
|
|
@@ -198,11 +193,8 @@ Created by `lacuna init`. All fields are optional with sensible defaults.
|
|
|
198
193
|
"baseURL": "https://api.deepseek.com/v1",
|
|
199
194
|
"apiKeyEnv": "DEEPSEEK_API_KEY",
|
|
200
195
|
"testRunner": "jest",
|
|
201
|
-
"coverageFormat": "lcov",
|
|
202
|
-
"coverageDir": "coverage",
|
|
203
196
|
"sourceDir": "src",
|
|
204
197
|
"threshold": 80,
|
|
205
|
-
"maxIterations": 3,
|
|
206
198
|
"mocksFile": "src/test/mocks.ts",
|
|
207
199
|
"setupFile": "src/test/setup.ts",
|
|
208
200
|
"ignore": ["src/graphql/", "src/theme/"]
|
|
@@ -214,194 +206,101 @@ Created by `lacuna init`. All fields are optional with sensible defaults.
|
|
|
214
206
|
| `provider` | `openai-compatible` | `anthropic` or `openai-compatible` |
|
|
215
207
|
| `model` | `deepseek-chat` | Model name |
|
|
216
208
|
| `apiKeyEnv` | `DEEPSEEK_API_KEY` | Env var holding your API key |
|
|
217
|
-
| `baseURL` | `https://api.deepseek.com/v1` | API base URL
|
|
218
|
-
| `testRunner` | auto
|
|
219
|
-
| `coverageFormat` | `lcov` | `lcov
|
|
220
|
-
| `coverageDir` | `coverage` | Where your
|
|
221
|
-
| `sourceDir` | `
|
|
209
|
+
| `baseURL` | `https://api.deepseek.com/v1` | API base URL (required for `openai-compatible`) |
|
|
210
|
+
| `testRunner` | auto | `jest`, `vitest`, `pytest`, `mocha`, `go-test`, and more |
|
|
211
|
+
| `coverageFormat` | `lcov` | `lcov`, `json-summary`, or `cobertura` |
|
|
212
|
+
| `coverageDir` | `coverage` | Where your runner writes coverage |
|
|
213
|
+
| `sourceDir` | `src` | Directory to scan. A string, or an array like `["src", "lib"]` |
|
|
222
214
|
| `threshold` | `80` | Minimum line coverage % to pass |
|
|
223
|
-
| `maxIterations` | `3` |
|
|
224
|
-
| `coverageTimeout` | `300` | Seconds before the
|
|
225
|
-
| `mocksFile` |
|
|
226
|
-
| `setupFile` |
|
|
227
|
-
| `ignore` | `[]` |
|
|
228
|
-
| `maxTokens` | `16000` |
|
|
229
|
-
| `debug` | `false` |
|
|
215
|
+
| `maxIterations` | `3` | Retries per failing test before giving up |
|
|
216
|
+
| `coverageTimeout` | `300` | Seconds before the suite is killed (guards against hung handles) |
|
|
217
|
+
| `mocksFile` | (none) | Shared mock file every generated test imports from (see [Shared mocks](#shared-mocks)) |
|
|
218
|
+
| `setupFile` | (none) | Your test setup file; its contents are shown to the model so it knows what's already available |
|
|
219
|
+
| `ignore` | `[]` | Path substrings to skip, e.g. `"src/graphql/"` |
|
|
220
|
+
| `maxTokens` | `16000` | Max output tokens per call. Lower for strict providers (Groq free tier ~8000); raise if large files are cut off |
|
|
221
|
+
| `debug` | `false` | Log every prompt and response (see [Debugging](#debugging)) |
|
|
230
222
|
|
|
231
223
|
---
|
|
232
224
|
|
|
233
|
-
##
|
|
234
|
-
|
|
235
|
-
When a test generation loop behaves unexpectedly — wrong patch anchors, bad mock shapes, repeated failures you can't reproduce locally — you need to see exactly what the model received and returned.
|
|
225
|
+
## Models
|
|
236
226
|
|
|
237
|
-
|
|
227
|
+
Lacuna works with any model behind an OpenAI-compatible API, plus Anthropic directly. Switch any time by re-running `lacuna init` or editing `.lacuna.json`.
|
|
238
228
|
|
|
239
|
-
|
|
240
|
-
LACUNA_DEBUG=1 lacuna generate --file src/payments/processor.ts
|
|
241
|
-
```
|
|
242
|
-
|
|
243
|
-
Or add it to `.lacuna.json` so it persists across runs:
|
|
244
|
-
|
|
245
|
-
```json
|
|
246
|
-
{
|
|
247
|
-
"debug": true
|
|
248
|
-
}
|
|
249
|
-
```
|
|
250
|
-
|
|
251
|
-
(You don't pick a filename — lacuna names each log after the target file automatically.)
|
|
252
|
-
|
|
253
|
-
lacuna writes a **separate log per target file** — e.g. `lacuna-debug.MessagingService.txt` (a file's `generate` and `fix` logs share the same name). Each per-file log is **cleared when that file's `generate`/`fix` begins** and appended through its retries, so it's a self-contained record. Per-file logs mean `--workers` runs and multi-file suites never share or clobber one stream — a single shared file would be truncated and interleaved by concurrent workers.
|
|
254
|
-
|
|
255
|
-
Each section is separated by a header like:
|
|
256
|
-
|
|
257
|
-
```
|
|
258
|
-
════════════════════════════════════════════════════════════════════════
|
|
259
|
-
PROMPT (generate) — 2026-06-17T10:32:01.234Z
|
|
260
|
-
════════════════════════════════════════════════════════════════════════
|
|
261
|
-
```
|
|
262
|
-
|
|
263
|
-
This is the most effective way to diagnose issues like:
|
|
264
|
-
- A model omitting required quotes from patch anchor headers
|
|
265
|
-
- A model producing correct test logic but hitting a format the parser doesn't expect
|
|
266
|
-
- Unexpected truncation mid-file
|
|
267
|
-
|
|
268
|
-
The env var takes precedence over the config value, so you can temporarily override it per-run without editing `.lacuna.json`.
|
|
269
|
-
|
|
270
|
-
**Reporting a bug?** If a file keeps failing, run with `LACUNA_DEBUG` and attach the debug file to your GitHub issue — it gives us the exact prompt and raw model response and cuts diagnosis time dramatically.
|
|
271
|
-
|
|
272
|
-
---
|
|
273
|
-
|
|
274
|
-
## Supported models
|
|
275
|
-
|
|
276
|
-
Lacuna works with any AI model — local or cloud.
|
|
277
|
-
|
|
278
|
-
| Preset | Model | API key env | Notes |
|
|
229
|
+
| Preset | Model | API key | Notes |
|
|
279
230
|
|---|---|---|---|
|
|
280
|
-
| **DeepSeek (default)
|
|
231
|
+
| **DeepSeek** (default) | `deepseek-chat` | `DEEPSEEK_API_KEY` | Fast and cheap; a good default |
|
|
281
232
|
| DeepSeek R1 | `deepseek-reasoner` | `DEEPSEEK_API_KEY` | Reasoning model |
|
|
282
233
|
| Claude Sonnet | `claude-sonnet-4-6` | `ANTHROPIC_API_KEY` | High quality |
|
|
283
234
|
| Claude Opus | `claude-opus-4-7` | `ANTHROPIC_API_KEY` | Most capable |
|
|
284
|
-
| DeepSeek R1 | `deepseek-reasoner` | `DEEPSEEK_API_KEY` | Reasoning model |
|
|
285
235
|
| GPT-4o | `gpt-4o` | `OPENAI_API_KEY` | |
|
|
286
236
|
| Groq | `llama-3.3-70b-versatile` | `GROQ_API_KEY` | Fast, free tier |
|
|
287
|
-
| Gemini 2.5 Pro | `gemini-2.5-pro` | `GEMINI_API_KEY` |
|
|
288
|
-
| Gemini 2.5 Flash | `gemini-2.5-flash` | `GEMINI_API_KEY` |
|
|
289
|
-
| OpenRouter | any
|
|
290
|
-
| Ollama | any local
|
|
291
|
-
| LM Studio | any local
|
|
292
|
-
| Custom |
|
|
293
|
-
|
|
294
|
-
Switch models any time by re-running `lacuna init` or editing `.lacuna.json` directly.
|
|
237
|
+
| Gemini 2.5 Pro | `gemini-2.5-pro` | `GEMINI_API_KEY` | |
|
|
238
|
+
| Gemini 2.5 Flash | `gemini-2.5-flash` | `GEMINI_API_KEY` | Faster, cheaper |
|
|
239
|
+
| OpenRouter | any | `OPENROUTER_API_KEY` | One key, many models |
|
|
240
|
+
| Ollama | any local | none | Runs fully on your machine |
|
|
241
|
+
| LM Studio | any local | none | Runs fully on your machine |
|
|
242
|
+
| Custom | any | configurable | Any OpenAI-compatible endpoint |
|
|
295
243
|
|
|
296
244
|
---
|
|
297
245
|
|
|
298
|
-
##
|
|
246
|
+
## Supported stacks
|
|
299
247
|
|
|
300
|
-
|
|
248
|
+
Lacuna can run the suite and collect coverage for a wide range of languages. The quality of the *generated* tests depends on how much prompt tuning a stack has had.
|
|
301
249
|
|
|
302
|
-
|
|
250
|
+
**Tuned and tested:**
|
|
303
251
|
|
|
304
|
-
|
|
252
|
+
| Stack | Runner | Focus |
|
|
253
|
+
|---|---|---|
|
|
254
|
+
| TypeScript / JavaScript | Vitest, Jest | Hook return shapes, service method signatures, type-safe mocks, `vi.mocked()`/`jest.mocked()`, factory hoisting |
|
|
255
|
+
| React | Vitest, Jest | RTL queries, `act()` async rules, loading states, mock lifecycle, `findBy` over `waitFor` |
|
|
256
|
+
| React Native / Expo | Jest (`jest-expo`) | RNTL v14 async contract, infra mocks (Reanimated, AsyncStorage, vector icons), mock-shape accuracy, query isolation |
|
|
257
|
+
| Next.js | Vitest | Server/client boundaries, `next/navigation`, `next/headers`, `next/cache`, server actions, directive detection |
|
|
258
|
+
|
|
259
|
+
**Runner support, lighter tuning:** Vue (Vitest), Python (pytest), PHP (PHPUnit, Pest). These run and collect coverage, but framework-specific prompt tuning is still in progress.
|
|
260
|
+
|
|
261
|
+
**Runner only:** Go, Ruby (RSpec), Rust (cargo), C# (dotnet), Java (Gradle/Maven), Swift. Suites run and coverage is collected, but test generation isn't tuned for them yet.
|
|
262
|
+
|
|
263
|
+
---
|
|
264
|
+
|
|
265
|
+
## Shared mocks
|
|
266
|
+
|
|
267
|
+
In a large codebase, redefining the same mocks in every test file gets painful fast. Point lacuna at a single mock file and every generated test imports from it.
|
|
268
|
+
|
|
269
|
+
Create the file:
|
|
305
270
|
|
|
306
271
|
```ts
|
|
272
|
+
// src/test/mocks.ts
|
|
307
273
|
import { vi } from 'vitest'
|
|
308
274
|
|
|
309
|
-
// API clients
|
|
310
|
-
export const mockAxios = {
|
|
311
|
-
get: vi.fn(),
|
|
312
|
-
post: vi.fn(),
|
|
313
|
-
put: vi.fn(),
|
|
314
|
-
delete: vi.fn(),
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
// Router
|
|
318
275
|
export const mockNavigate = vi.fn()
|
|
319
|
-
export const mockUseNavigate = () => mockNavigate
|
|
320
276
|
vi.mock('react-router-dom', () => ({
|
|
321
|
-
useNavigate:
|
|
277
|
+
useNavigate: () => mockNavigate,
|
|
322
278
|
useParams: vi.fn(() => ({})),
|
|
323
279
|
}))
|
|
324
280
|
|
|
325
|
-
|
|
326
|
-
export const mockUser = {
|
|
327
|
-
id: 'user-1',
|
|
328
|
-
email: 'test@example.com',
|
|
329
|
-
role: 'admin',
|
|
330
|
-
}
|
|
281
|
+
export const mockUser = { id: 'user-1', email: 'test@example.com', role: 'admin' }
|
|
331
282
|
export const mockUseAuth = vi.fn(() => ({ user: mockUser, isLoading: false }))
|
|
332
283
|
|
|
333
|
-
|
|
334
|
-
beforeEach(() => {
|
|
335
|
-
vi.clearAllMocks()
|
|
336
|
-
})
|
|
284
|
+
beforeEach(() => vi.clearAllMocks())
|
|
337
285
|
```
|
|
338
286
|
|
|
339
|
-
|
|
287
|
+
Reference it in `.lacuna.json`:
|
|
340
288
|
|
|
341
289
|
```json
|
|
342
|
-
{
|
|
343
|
-
"mocksFile": "src/test/mocks.ts"
|
|
344
|
-
}
|
|
345
|
-
```
|
|
346
|
-
|
|
347
|
-
3. Run lacuna normally:
|
|
348
|
-
|
|
349
|
-
```bash
|
|
350
|
-
lacuna generate
|
|
290
|
+
{ "mocksFile": "src/test/mocks.ts" }
|
|
351
291
|
```
|
|
352
292
|
|
|
353
|
-
|
|
293
|
+
Now generated tests import from that file instead of inventing their own mocks. If a test needs a mock that doesn't exist yet, lacuna adds it to the shared file and imports it.
|
|
354
294
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
Lacuna parses your mock file before every prompt and builds a **mock inventory** — a structured table of every `vi.mock()` call, its line number, and all the keys it exports:
|
|
358
|
-
|
|
359
|
-
```
|
|
360
|
-
'react-router-dom' → useNavigate, useParams
|
|
361
|
-
'axios' → get, post, put, delete
|
|
362
|
-
```
|
|
363
|
-
|
|
364
|
-
The inventory is injected into every prompt so the AI knows which modules are already mocked. The **full file** is also included for generate prompts — the AI needs to read exact text to write accurate patch anchors.
|
|
365
|
-
|
|
366
|
-
When the AI needs to change the mock file, it uses **surgical patch operations** rather than rewriting the whole file:
|
|
367
|
-
|
|
368
|
-
```
|
|
369
|
-
// ---MOCKS_PATCH---
|
|
370
|
-
// @@@ REPLACE:
|
|
371
|
-
export class MockWalletService {
|
|
372
|
-
getBalance = jest.fn()
|
|
373
|
-
}
|
|
374
|
-
// @@@ WITH:
|
|
375
|
-
export class MockWalletService {
|
|
376
|
-
getBalance = jest.fn()
|
|
377
|
-
transfer = jest.fn() ← method added
|
|
378
|
-
}
|
|
379
|
-
// @@@ END
|
|
380
|
-
|
|
381
|
-
// @@@ APPEND_EXPORT:
|
|
382
|
-
export const mockNewHelper = jest.fn()
|
|
383
|
-
// @@@ END
|
|
384
|
-
|
|
385
|
-
// @@@ ADD_TO_BEFOREEACH:
|
|
386
|
-
mockNewHelper.mockReset()
|
|
387
|
-
// @@@ END
|
|
388
|
-
```
|
|
389
|
-
|
|
390
|
-
**Rules the AI follows:**
|
|
391
|
-
- Module already in inventory → patch existing block, never write a second `vi.mock()` for the same path
|
|
392
|
-
- Method missing from a class mock (e.g. `MockWalletService`) → add it via `REPLACE`, never create a separate inline mock
|
|
393
|
-
- Service/hook with no mock yet → add it to the shared file via `APPEND_EXPORT`, never keep it inline in the test file
|
|
394
|
-
- New mock → reset it in `beforeEach` via `ADD_TO_BEFOREEACH`
|
|
395
|
-
|
|
396
|
-
For **fix prompts**, the mock file is filtered to only the exports the broken test actually imports (reducing a 700-line file to ~40 lines for a targeted fix), then compressed (multi-line stub bodies collapsed to `vi.fn()`). For **generate prompts**, the full file is sent so the AI can write exact REPLACE anchors.
|
|
295
|
+
Under the hood, lacuna parses the mock file before each run and builds an inventory of every `vi.mock()` call and its exports, so the model knows what's already mocked and edits it surgically instead of duplicating it. When a mock needs changing, the model patches the existing block rather than rewriting the file.
|
|
397
296
|
|
|
398
297
|
---
|
|
399
298
|
|
|
400
299
|
## CI / GitHub Actions
|
|
401
300
|
|
|
402
|
-
|
|
301
|
+
Run lacuna on pull requests to generate missing tests and block merges below threshold.
|
|
403
302
|
|
|
404
|
-
|
|
303
|
+
`.github/workflows/lacuna.yml`:
|
|
405
304
|
|
|
406
305
|
```yaml
|
|
407
306
|
name: lacuna coverage
|
|
@@ -416,7 +315,6 @@ jobs:
|
|
|
416
315
|
permissions:
|
|
417
316
|
contents: write
|
|
418
317
|
pull-requests: write
|
|
419
|
-
|
|
420
318
|
steps:
|
|
421
319
|
- uses: actions/checkout@v4
|
|
422
320
|
with:
|
|
@@ -432,141 +330,91 @@ jobs:
|
|
|
432
330
|
- name: Run lacuna
|
|
433
331
|
id: lacuna
|
|
434
332
|
uses: Octagon-simon/lacuna@v1
|
|
435
|
-
continue-on-error: true
|
|
333
|
+
continue-on-error: true # let the commit step run even if coverage is low
|
|
436
334
|
with:
|
|
437
335
|
threshold: 80
|
|
438
|
-
workers: 2
|
|
439
|
-
model: deepseek
|
|
336
|
+
workers: 2
|
|
337
|
+
model: deepseek
|
|
440
338
|
deepseek-api-key: ${{ secrets.DEEPSEEK_API_KEY }}
|
|
441
339
|
|
|
442
|
-
# Runs even when lacuna exits with code 1 (below threshold) so generated
|
|
443
|
-
# tests are never lost. Skips the commit if nothing was written.
|
|
444
340
|
- name: Commit generated tests
|
|
445
341
|
if: steps.lacuna.outcome != 'cancelled'
|
|
446
342
|
run: |
|
|
447
343
|
git config user.name "lacuna[bot]"
|
|
448
344
|
git config user.email "lacuna[bot]@users.noreply.github.com"
|
|
449
345
|
git add -A
|
|
450
|
-
git diff --staged --quiet || git commit -m "chore:
|
|
346
|
+
git diff --staged --quiet || git commit -m "chore: add lacuna-generated tests"
|
|
451
347
|
git push
|
|
452
348
|
```
|
|
453
349
|
|
|
454
|
-
On
|
|
455
|
-
- Generate missing tests
|
|
456
|
-
- Post a coverage report as a PR comment (updated on each push, no spam)
|
|
457
|
-
- Block the merge if coverage stays below your threshold
|
|
458
|
-
|
|
459
|
-
### Switching models
|
|
350
|
+
On each PR, lacuna generates the missing tests, posts a coverage report as a comment (updated in place, not re-posted), and fails the check if coverage stays below threshold.
|
|
460
351
|
|
|
461
|
-
|
|
352
|
+
To use a different model, pass its preset and key:
|
|
462
353
|
|
|
463
354
|
```yaml
|
|
464
|
-
# GPT-4o
|
|
465
355
|
with:
|
|
466
356
|
model: gpt-4o
|
|
467
357
|
openai-api-key: ${{ secrets.OPENAI_API_KEY }}
|
|
468
|
-
|
|
469
|
-
# Gemini 2.5 Pro
|
|
470
|
-
with:
|
|
471
|
-
model: gemini
|
|
472
|
-
gemini-api-key: ${{ secrets.GEMINI_API_KEY }}
|
|
473
|
-
|
|
474
|
-
# DeepSeek (cost-effective)
|
|
475
|
-
with:
|
|
476
|
-
model: deepseek
|
|
477
|
-
deepseek-api-key: ${{ secrets.DEEPSEEK_API_KEY }}
|
|
478
|
-
|
|
479
|
-
# Groq (free tier available)
|
|
480
|
-
with:
|
|
481
|
-
model: groq
|
|
482
|
-
groq-api-key: ${{ secrets.GROQ_API_KEY }}
|
|
483
358
|
```
|
|
484
359
|
|
|
485
360
|
---
|
|
486
361
|
|
|
487
|
-
##
|
|
488
|
-
|
|
489
|
-
All commands support `--format` and `--output`:
|
|
362
|
+
## Debugging
|
|
490
363
|
|
|
491
|
-
|
|
492
|
-
# Terminal (default)
|
|
493
|
-
lacuna analyze
|
|
364
|
+
When a run behaves oddly (bad mock shapes, patches that won't apply, failures you can't reproduce), turn on debug logging to see exactly what the model received and returned.
|
|
494
365
|
|
|
495
|
-
|
|
496
|
-
lacuna analyze --format json
|
|
497
|
-
lacuna generate --format json --output lacuna-report.json
|
|
366
|
+
Per run:
|
|
498
367
|
|
|
499
|
-
|
|
500
|
-
lacuna
|
|
368
|
+
```bash
|
|
369
|
+
LACUNA_DEBUG=1 lacuna generate --file src/payments/processor.ts
|
|
501
370
|
```
|
|
502
371
|
|
|
503
|
-
|
|
372
|
+
Or persist it in `.lacuna.json`:
|
|
504
373
|
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
| `1` | Fail — coverage below threshold or some files could not be tested |
|
|
509
|
-
| `2` | Error — test runner failed, config issue, or zero tests generated |
|
|
510
|
-
|
|
511
|
-
---
|
|
512
|
-
|
|
513
|
-
## Contextual tips
|
|
514
|
-
|
|
515
|
-
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.
|
|
516
|
-
|
|
517
|
-
**Tips shown during `lacuna generate`:**
|
|
518
|
-
- Use `-w 4` (`--workers`) to process multiple files in parallel
|
|
519
|
-
- Use `-f src/utils/math.ts` (`--file`) to target a single file
|
|
520
|
-
- Use `--dry-run` to preview without writing files
|
|
521
|
-
- Use `-v` (`--verbose`) to watch a live code panel as the AI writes each test file
|
|
522
|
-
- Use `-m claude-opus-4-7` (`--model`) to switch to a more capable model
|
|
523
|
-
- Use `--fresh` to force a new coverage run instead of reusing a cached report
|
|
524
|
-
- Use `-t 90` (`--threshold`) to raise the coverage bar
|
|
525
|
-
- Use `--format json --output report.json` to export results
|
|
526
|
-
- Set `mocksFile` in `.lacuna.json` to share mocks across all generated tests
|
|
527
|
-
- Add paths to `ignore[]` in `.lacuna.json` to skip directories
|
|
528
|
-
- Run `lacuna fix` to repair failing tests
|
|
529
|
-
- Run `lacuna analyze` to inspect gaps without writing files
|
|
530
|
-
- Increase `coverageTimeout` in `.lacuna.json` if your suite is being killed
|
|
531
|
-
- Set `maxTokens` in `.lacuna.json` if tests are cut off mid-generation (lower for Groq/Ollama, raise for large files)
|
|
374
|
+
```json
|
|
375
|
+
{ "debug": true }
|
|
376
|
+
```
|
|
532
377
|
|
|
533
|
-
|
|
378
|
+
Lacuna writes one log per target file, named after its path: `src/queue/processor.ts` becomes `lacuna-debug.src_queue_processor.txt` (a file's `generate` and `fix` share the log). The full path is used, not just the file name, so identically-named files like `send-email/route.ts` and `login/route.ts` get separate logs instead of overwriting each other. Each log is cleared when that file's run starts and appended through its retries, so parallel runs never clobber each other. The env var wins over the config value, so you can override per run without editing anything.
|
|
534
379
|
|
|
535
|
-
|
|
380
|
+
Filing a bug? Attach the debug file; it has the exact prompt and raw response, which is what makes an issue reproducible.
|
|
536
381
|
|
|
537
382
|
---
|
|
538
383
|
|
|
539
|
-
##
|
|
384
|
+
## Reference
|
|
540
385
|
|
|
541
|
-
|
|
386
|
+
### Output formats
|
|
542
387
|
|
|
543
|
-
|
|
544
|
-
`types/`, `constants/`, `assets/`, `images/`, `icons/`, `fonts/`, `styles/`, `generated/`, `__generated__/`, `mocks/`, `fixtures/`, `migrations/`, `i18n/`, `locales/`, `translations/`
|
|
388
|
+
Every command takes `--format` and `--output`:
|
|
545
389
|
|
|
546
|
-
|
|
547
|
-
|
|
390
|
+
```bash
|
|
391
|
+
lacuna analyze # terminal (default)
|
|
392
|
+
lacuna analyze --format json # for scripts and CI
|
|
393
|
+
lacuna analyze --format markdown # for PR comments
|
|
394
|
+
lacuna generate --format json --output report.json
|
|
395
|
+
```
|
|
548
396
|
|
|
549
|
-
|
|
397
|
+
### Exit codes
|
|
550
398
|
|
|
551
|
-
|
|
399
|
+
| Code | Meaning |
|
|
400
|
+
|---|---|
|
|
401
|
+
| `0` | Coverage meets threshold |
|
|
402
|
+
| `1` | Coverage below threshold, or some files couldn't be tested |
|
|
403
|
+
| `2` | Error: runner failed, bad config, or no tests generated |
|
|
552
404
|
|
|
553
|
-
|
|
554
|
-
{
|
|
555
|
-
"ignore": ["src/graphql/", "src/theme/", "src/generated/"]
|
|
556
|
-
}
|
|
557
|
-
```
|
|
405
|
+
### Test placement
|
|
558
406
|
|
|
559
|
-
`
|
|
407
|
+
Lacuna follows your existing layout. If tests sit next to source files, new tests go there too. If they live in a separate tree (`test/`, `tests/`, `test/unit/`, …) that actually contains tests, it mirrors that. Otherwise it uses a `__tests__/` folder beside the source, creating it if needed.
|
|
560
408
|
|
|
561
|
-
|
|
409
|
+
### What gets skipped
|
|
562
410
|
|
|
563
|
-
|
|
411
|
+
Files with no testable logic are skipped automatically:
|
|
564
412
|
|
|
565
|
-
|
|
413
|
+
- **By directory:** `types/`, `constants/`, `assets/`, `images/`, `icons/`, `fonts/`, `styles/`, `generated/`, `__generated__/`, `mocks/`, `fixtures/`, `migrations/`, `i18n/`, `locales/`, `translations/`
|
|
414
|
+
- **By filename:** `*.d.ts`, `*.test.*`, `*.spec.*`, `*.stories.*`, `*.config.*`, `*.mock.*`, `*.types.ts`, `*.constants.ts`, `*.enum.*`, `index.*`
|
|
415
|
+
- **By content:** any file that exports only types, interfaces, enums, or constants
|
|
566
416
|
|
|
567
|
-
|
|
568
|
-
- Otherwise, tests go in `__tests__/` inside the same directory as the source file
|
|
569
|
-
- `__tests__/` is created automatically if it doesn't exist
|
|
417
|
+
Add your own with `ignore` in `.lacuna.json`. Entries match as path substrings.
|
|
570
418
|
|
|
571
419
|
---
|
|
572
420
|
|
|
@@ -575,67 +423,42 @@ Lacuna follows your project's existing conventions:
|
|
|
575
423
|
```
|
|
576
424
|
lacuna/
|
|
577
425
|
├── src/
|
|
578
|
-
│ ├── commands/ # CLI commands
|
|
579
|
-
│ ├── agent/
|
|
580
|
-
│ │ ├── loop.ts #
|
|
581
|
-
│ │ ├── fix-loop.ts # fix → run → retry loop
|
|
582
|
-
│ │ ├── context.ts # builds context
|
|
583
|
-
│ │ ├── generator.ts # calls the
|
|
584
|
-
│ │ └── prompts/ # prompt builders split by framework
|
|
585
|
-
│ │ ├── index.ts # system prompt + generate/fix/retry prompt assembly
|
|
586
|
-
│ │ ├── react-native.ts # RNTL v14 rules, infra mock guidance, error detectors
|
|
587
|
-
│ │ ├── nextjs.ts # Next.js boundary analysis, server action mocks
|
|
588
|
-
│ │ ├── react.ts # React web testing rules (act, RTL, timers)
|
|
589
|
-
│ │ ├── vue.ts # Vue testing guidance
|
|
590
|
-
│ │ └── runners/ # runner-specific prompt rules
|
|
591
|
-
│ │ ├── js-common.ts # rules shared across Jest + Vitest + Mocha
|
|
592
|
-
│ │ ├── vitest.ts # Vitest-specific: vi.mock hoisting, vi.hoisted(), resetAllMocks
|
|
593
|
-
│ │ └── typescript.ts # TypeScript type-safety rules (no as any, T[] not Array<T>)
|
|
426
|
+
│ ├── commands/ # CLI commands: analyze, generate, fix, run, init
|
|
427
|
+
│ ├── agent/
|
|
428
|
+
│ │ ├── loop.ts # generate → run → retry loop
|
|
429
|
+
│ │ ├── fix-loop.ts # fix → run → retry loop
|
|
430
|
+
│ │ ├── context.ts # builds model context (source, tests, mocks, types)
|
|
431
|
+
│ │ ├── generator.ts # calls the model, manages conversation history
|
|
432
|
+
│ │ └── prompts/ # prompt builders, split by framework and runner
|
|
594
433
|
│ ├── lib/
|
|
595
|
-
│ │ ├── config.ts #
|
|
596
|
-
│ │ ├── detector.ts #
|
|
434
|
+
│ │ ├── config.ts # config loader + zod schema
|
|
435
|
+
│ │ ├── detector.ts # detects test runner and language
|
|
597
436
|
│ │ ├── runner.ts # spawns test commands, captures output
|
|
598
|
-
│ │ ├── reporter.ts # terminal / JSON / markdown
|
|
599
|
-
│ │ ├──
|
|
600
|
-
│ │ ├──
|
|
601
|
-
│ │ ├──
|
|
602
|
-
│ │
|
|
603
|
-
│
|
|
604
|
-
│ │ ├── providers/ # AI provider abstraction
|
|
605
|
-
│ │ │ ├── anthropic.ts
|
|
606
|
-
│ │ │ ├── openai-compatible.ts
|
|
607
|
-
│ │ │ └── types.ts # ModelProvider interface + presets
|
|
608
|
-
│ │ └── coverage/
|
|
609
|
-
│ │ ├── lcov.ts # LCOV parser
|
|
610
|
-
│ │ ├── json.ts # JSON summary parser
|
|
611
|
-
│ │ ├── gaps.ts # gap extractor
|
|
612
|
-
│ │ └── types.ts # shared coverage types
|
|
613
|
-
│ └── ci/
|
|
614
|
-
│ ├── comment.ts # posts coverage report as GitHub PR comment
|
|
615
|
-
│ └── parse-outputs.ts # sets GitHub Actions step outputs
|
|
616
|
-
├── app/ # SaaS dashboard (Next.js + Postgres + Payaza)
|
|
437
|
+
│ │ ├── reporter.ts # terminal / JSON / markdown output
|
|
438
|
+
│ │ ├── validate.ts # patch application, regression + broken-import detection
|
|
439
|
+
│ │ ├── typecheck.ts # tsc pass and type-error scoping
|
|
440
|
+
│ │ ├── providers/ # model provider abstraction (anthropic, openai-compatible)
|
|
441
|
+
│ │ └── coverage/ # lcov / json parsers, gap extraction
|
|
442
|
+
│ └── ci/ # PR comment + GitHub Actions outputs
|
|
617
443
|
├── action.yml # GitHub Action definition
|
|
618
|
-
└── .github/workflows/
|
|
619
|
-
└── example.yml # example CI workflow to copy into your repo
|
|
444
|
+
└── .github/workflows/ # example workflow + release pipeline
|
|
620
445
|
```
|
|
621
446
|
|
|
622
447
|
---
|
|
623
448
|
|
|
624
449
|
## Contributing
|
|
625
450
|
|
|
626
|
-
Issues and PRs welcome. The codebase is TypeScript throughout.
|
|
451
|
+
Issues and PRs are welcome. The codebase is TypeScript throughout.
|
|
627
452
|
|
|
628
453
|
```bash
|
|
629
454
|
git clone https://github.com/Octagon-simon/lacuna
|
|
630
455
|
cd lacuna
|
|
631
456
|
npm install
|
|
632
457
|
npm run build
|
|
633
|
-
npm link
|
|
458
|
+
npm link # makes `lacuna` point at your local build
|
|
634
459
|
```
|
|
635
460
|
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
**Suggesting a feature?** Use the feature request template and describe the workflow problem it solves.
|
|
461
|
+
When reporting a bug, the bug-report template asks for your test runner, model, lacuna version, and terminal output, the things needed to reproduce it.
|
|
639
462
|
|
|
640
463
|
---
|
|
641
464
|
|