lacuna-cli 0.2.2 → 0.2.3

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 CHANGED
@@ -1,118 +1,118 @@
1
1
  # lacuna
2
2
 
3
- **Agentic test coverage finds gaps, writes tests, verifies they pass.**
3
+ > Find untested code, write tests for it, and verify they pass, in one command.
4
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.
5
+ [![npm version](https://img.shields.io/npm/v/lacuna-cli.svg)](https://www.npmjs.com/package/lacuna-cli)
6
+ [![npm downloads](https://img.shields.io/npm/dm/lacuna-cli.svg)](https://www.npmjs.com/package/lacuna-cli)
7
+ [![Release](https://github.com/Octagon-simon/lacuna/actions/workflows/release.yml/badge.svg)](https://github.com/Octagon-simon/lacuna/actions/workflows/release.yml)
8
+ [![Node](https://img.shields.io/node/v/lacuna-cli.svg)](https://nodejs.org)
9
+ [![License: MIT](https://img.shields.io/npm/l/lacuna-cli.svg)](#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
- ## How it works
21
+ ## Getting started
14
22
 
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
- ├── 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
- ## Install
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
- npm install -g lacuna-cli
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
- ## Quick start
43
+ From your project root:
52
44
 
53
45
  ```bash
54
- lacuna --version # or -V
55
- lacuna --help # or -h
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
- cd your-project
58
- lacuna init # interactive setup wizard — installs test runner if missing
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
- ## Supported stacks
59
+ ### 5. Generate the tests
60
+
61
+ ```bash
62
+ $ lacuna generate
63
+ ```
66
64
 
67
- Lacuna's AI prompts are tuned for frameworks that have been field-tested. Runner support (coverage collection + test generation) exists for other languages, but prompt guidance quality will vary.
65
+ Lacuna writes tests for the gaps, runs them, and retries failures. When it finishes, the new tests are already passing.
68
66
 
69
- ### Tested works well
67
+ To target a single file and skip the full coverage run:
70
68
 
71
- | Stack | Test runner | What's tuned |
72
- |---|---|---|
73
- | **TypeScript / JavaScript** (utilities, services, hooks) | Vitest, Jest | Hook return shape extraction, service method verification, TypeScript type-safety rules (`T[]` not `Array<T>`, no `as any` on mock shapes), mock structure (object vs factory), `jest.mocked()` / `vi.mocked()` pattern, factory hoisting rules |
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
- ### Supported not yet field-tested
73
+ That's the whole loop. The rest of this README is reference.
79
74
 
80
- | Stack | Test runner | Status |
81
- |---|---|---|
82
- | Vue | Vitest | Runner works. `@testing-library/vue` guidance included. Field-testing pending. |
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
- ### Runner support only
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
+ restore original if it can't
97
+ ```
87
98
 
88
- Go, Ruby (RSpec), Rust (cargo test), C# (dotnet test), Java (Gradle / Maven), Swift lacuna can run their suites and collect coverage, but AI prompt guidance is not yet tuned for these.
99
+ Two rules hold throughout: lacuna never leaves a half-written file behind, and it never removes passing tests. If it can't improve a file, 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
- For **React** projects, `lacuna init` also:
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 **Next.js** projects, the setup is different Next.js and plain React are not compatible:
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
- ```bash
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
- Runs your test suite, collects coverage, and prints which files and functions are below threshold. **Does not write any files.**
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
- 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.
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 # target one file
138
- lacuna generate --dry-run # preview without writing
139
- lacuna generate --verbose # live code panel as model writes each file
140
- lacuna generate --workers 4 # run 4 files in parallel
141
- lacuna generate --fresh # force a new coverage run
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
- Finds all failing tests and repairs them using AI. 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. A fix that makes the failing tests pass is **kept even if minor type warnings remain** — fix never reverts a behavioral improvement. If repair is exhausted on a *genuinely broken* file (no passing tests to lose), lacuna falls back to regenerating it from source; a file that already has passing tests is never deleted or regenerated.
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.
147
143
 
148
144
  ```bash
149
145
  lacuna fix
150
- lacuna fix --workers 4 # fix 4 files in parallel
151
- lacuna fix --file src/utils/math.test.ts # fix a single test file (skips full suite run)
152
- lacuna fix --types # repair files that pass but fail type-checking (project-wide)
153
- lacuna fix --dry-run # preview fixes without writing
154
- lacuna fix --verbose # live code panel as model writes each fix
155
- lacuna fix --fresh # re-run the suite even if cache is recent
156
- lacuna fix --no-regenerate-on-failure # disable the regen fallback (fix only, no delete)
157
- lacuna fix --fix-polluters # also handle tests that pass alone but fail in suite
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
- Unlike `lacuna generate`, which creates new tests, `lacuna fix` operates on existing failing tests and preserves all test logic where possible.
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 all retries fail or the model oscillates (identical output detected), the original file is restored automatically. If a fix attempt breaks an import or reduces passing tests, lacuna detects the regression and anchors the next retry to the original error.
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: one project-wide `tsc` finds every test file that fails type-checking, even if its tests pass. It respects each file's governing `tsconfig`: if the nearest one disables `noImplicitAny` (common in monorepo packages), implicit-`any` isn't treated as an error.
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
- When `--file` is given, lacuna skips the full suite and runs only the target file. Without `--file`, the failing-files list is cached for 30 minutes. After a fix run, the cache is updated to contain only the still-failing files so re-running immediately picks up where the last run left off.
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
- Runs your test suite and reports coverage. No AI involved.
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 — `.lacuna.json`
174
+ ## Configuration
182
175
 
183
- Created by `lacuna init`. All fields are optional with sensible defaults.
176
+ `lacuna init` writes `.lacuna.json`. Every field is optional and has a sensible default.
184
177
 
185
- `lacuna init` adds a `"$schema"` line so editors (VS Code, etc.) give you **key completion and hover docs** while editing `.lacuna.json` — start typing a key and you'll see what each one means. To enable it on an existing config, add this as the first line:
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 required for `openai-compatible` provider |
218
- | `testRunner` | auto-detect | `jest` \| `vitest` \| `pytest` \| `mocha` \| `go-test` |
219
- | `coverageFormat` | `lcov` | `lcov` \| `json-summary` |
220
- | `coverageDir` | `coverage` | Where your test runner writes coverage |
221
- | `sourceDir` | `"src"` | Source directory to scan. Accepts a string or an array `["src", "lib", "utils"]` scans multiple directories |
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` | How many times to retry a failing generated test |
224
- | `coverageTimeout` | `300` | Seconds before the test suite is killed (prevents hanging on open handles) |
225
- | `mocksFile` | | Path to shared mock file (see Enterprise Mocks below) |
226
- | `setupFile` | | Path to your test setup file lacuna passes its contents to the AI so it knows which globals and matchers are already available |
227
- | `ignore` | `[]` | Extra path substrings to exclude from gap detection (e.g. `"src/graphql/"`) |
228
- | `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. |
229
- | `debug` | `false` | Set `true` to log every raw model prompt and response. Each target file gets its own log (`lacuna-debug.<file>.txt`), so parallel/multi-file runs never share one stream. Equivalent to the `LACUNA_DEBUG` env var (env var takes precedence). Use this to diagnose unexpected model output without guessing. |
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
- ## Debugging
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
- Set `LACUNA_DEBUG=1` before running any lacuna command:
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
- ```bash
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)** | `deepseek-chat` | `DEEPSEEK_API_KEY` | Best value — fast, cheap, no rate-limit issues |
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` | Google's most capable |
288
- | Gemini 2.5 Flash | `gemini-2.5-flash` | `GEMINI_API_KEY` | Fast & cheap |
289
- | OpenRouter | any model | `OPENROUTER_API_KEY` | 100+ models, one key |
290
- | Ollama | any local model | none | Fully local, free |
291
- | LM Studio | any local model | none | Fully local, free |
292
- | Custom | configurable | configurable | Any OpenAI-compatible API |
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
- ## Enterprise mocks
246
+ ## Supported stacks
299
247
 
300
- 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.
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
- ### Setup
250
+ **Tuned and tested:**
303
251
 
304
- 1. Create `src/test/mocks.ts`:
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: mockUseNavigate,
277
+ useNavigate: () => mockNavigate,
322
278
  useParams: vi.fn(() => ({})),
323
279
  }))
324
280
 
325
- // Auth
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
- // Reset all mocks between tests
334
- beforeEach(() => {
335
- vi.clearAllMocks()
336
- })
284
+ beforeEach(() => vi.clearAllMocks())
337
285
  ```
338
286
 
339
- 2. Add `mocksFile` to `.lacuna.json`:
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
- 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, lacuna will add it to the mocks file and import it — keeping everything centralized.
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
- ### How lacuna reads and updates the shared mock file
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
- Add lacuna to your PR workflow to automatically generate tests and block merges below threshold.
301
+ Run lacuna on pull requests to generate missing tests and block merges below threshold.
403
302
 
404
- Create `.github/workflows/lacuna.yml`:
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 # allow commit step to run even if coverage is below threshold
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 # parallel workers — increase for larger repos
439
- model: deepseek # default — cost-effective, no rate-limit issues
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: lacuna — add generated tests"
346
+ git diff --staged --quiet || git commit -m "chore: add lacuna-generated tests"
451
347
  git push
452
348
  ```
453
349
 
454
- On every PR lacuna will:
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
- Pass any lacuna model preset or full model name via the `model` input, along with the matching API key:
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
- ## Output formats
488
-
489
- All commands support `--format` and `--output`:
362
+ ## Debugging
490
363
 
491
- ```bash
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
- # JSON — for scripts and CI pipelines
496
- lacuna analyze --format json
497
- lacuna generate --format json --output lacuna-report.json
366
+ Per run:
498
367
 
499
- # Markdown — for PR comments and docs
500
- lacuna analyze --format markdown
368
+ ```bash
369
+ LACUNA_DEBUG=1 lacuna generate --file src/payments/processor.ts
501
370
  ```
502
371
 
503
- ### Exit codes
372
+ Or persist it in `.lacuna.json`:
504
373
 
505
- | Code | Meaning |
506
- |---|---|
507
- | `0` | Pass — coverage meets threshold |
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
- **Tips shown during `lacuna fix`** are the same, minus flags that `fix` doesn't support (`--threshold`, `--format`).
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
- 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.
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
- ## What gets skipped
384
+ ## Reference
540
385
 
541
- Lacuna automatically skips files that have no testable runtime logic — no point generating tests for them.
386
+ ### Output formats
542
387
 
543
- **Skipped by directory name** (anywhere in the path):
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
- **Skipped by file name pattern:**
547
- `*.d.ts`, `*.test.*`, `*.spec.*`, `*.stories.*`, `*.config.*`, `*.mock.*`, `*.types.ts`, `*.constants.ts`, `*.enum.*`, `index.*`
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
- **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.
397
+ ### Exit codes
550
398
 
551
- **Add your own exclusions** via `.lacuna.json`:
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
- ```json
554
- {
555
- "ignore": ["src/graphql/", "src/theme/", "src/generated/"]
556
- }
557
- ```
405
+ ### Test placement
558
406
 
559
- `ignore` entries are matched as path substrings any file whose path contains the string is excluded.
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
- ## Test placement
411
+ Files with no testable logic are skipped automatically:
564
412
 
565
- Lacuna follows your project's existing conventions:
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
- - If test files exist **next to source files** (co-located), new tests go there too
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 (analyze, generate, fix, run, init)
579
- │ ├── agent/ # AI agent loop
580
- │ │ ├── loop.ts # main generate → run → retry loop
581
- │ │ ├── fix-loop.ts # fix → run → retry loop for failing tests
582
- │ │ ├── context.ts # builds context for the AI (source + tests + mocks + type definitions)
583
- │ │ ├── generator.ts # calls the AI model, manages conversation history
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 # cosmiconfig loader + zod schema
596
- │ │ ├── detector.ts # auto-detects test runner and language
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 reporters
599
- │ │ ├── skeleton.ts # collapses already-covered function bodies to reduce prompt size
600
- │ │ ├── extract-error.ts # strips passing-test noise from runner output before retry
601
- │ │ ├── validate.ts # checks generated code has real test calls; detects regressions and broken imports in retry output
602
- │ │ ├── streaming-viewer.ts # live bordered code panel for --verbose mode (typewriter effect)
603
- │ ├── typecheck.ts # post-vitest tsc pass; retries if type errors found
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 # makes `lacuna` available globally from your local build
458
+ npm link # makes `lacuna` point at your local build
634
459
  ```
635
460
 
636
- **Reporting a bug?** Use the bug report template — it asks for your test runner, model, lacuna version, and terminal output, which are the four things we need to reproduce any issue fast.
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