smartselect 0.1.3__tar.gz

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.
@@ -0,0 +1,48 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+ strategy:
13
+ matrix:
14
+ python-version: ["3.10", "3.11", "3.12", "3.13"]
15
+
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+
19
+ - name: Set up Python ${{ matrix.python-version }}
20
+ uses: actions/setup-python@v5
21
+ with:
22
+ python-version: ${{ matrix.python-version }}
23
+
24
+ - name: Install dependencies
25
+ run: pip install -e ".[dev]"
26
+
27
+ - name: Lint
28
+ run: ruff check src/ tests/
29
+
30
+ - name: Test
31
+ run: pytest --cov=testwise --cov-report=term-missing
32
+
33
+ lint:
34
+ runs-on: ubuntu-latest
35
+ steps:
36
+ - uses: actions/checkout@v4
37
+
38
+ - uses: actions/setup-python@v5
39
+ with:
40
+ python-version: "3.12"
41
+
42
+ - run: pip install -e ".[dev]"
43
+
44
+ - name: Ruff format check
45
+ run: ruff format --check src/ tests/
46
+
47
+ - name: Type check
48
+ run: mypy src/testwise/
@@ -0,0 +1,28 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ permissions:
8
+ id-token: write
9
+
10
+ jobs:
11
+ publish:
12
+ runs-on: ubuntu-latest
13
+ environment: pypi
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+
17
+ - uses: actions/setup-python@v5
18
+ with:
19
+ python-version: "3.12"
20
+
21
+ - name: Install build tools
22
+ run: pip install build
23
+
24
+ - name: Build package
25
+ run: python -m build
26
+
27
+ - name: Publish to PyPI
28
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,35 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ dist/
6
+ build/
7
+ *.egg
8
+
9
+ # Virtual environments
10
+ .venv/
11
+ venv/
12
+
13
+ # IDE
14
+ .idea/
15
+ .vscode/
16
+ *.swp
17
+ *.swo
18
+
19
+ # OS
20
+ .DS_Store
21
+ Thumbs.db
22
+
23
+ # Testing
24
+ .coverage
25
+ htmlcov/
26
+ .pytest_cache/
27
+
28
+ # mypy
29
+ .mypy_cache/
30
+
31
+ # ruff
32
+ .ruff_cache/
33
+
34
+ # Environment
35
+ .env
@@ -0,0 +1,54 @@
1
+ # Testwise Configuration
2
+ # Copy this file to .testwise.yml in your repo root
3
+
4
+ runners:
5
+ # Python/pytest example
6
+ - name: pytest
7
+ command: pytest
8
+ args: ["-v", "--tb=short"]
9
+ test_patterns: ["tests/**/*.py", "test_*.py", "*_test.py"]
10
+ parser: pytest # Use the built-in pytest parser for test-level selection
11
+ select_mode: test # "test" for individual test functions, "file" for file-level
12
+ timeout_seconds: 300
13
+
14
+ # JavaScript/Jest example (uncomment to use)
15
+ # - name: jest
16
+ # command: npx jest
17
+ # args: ["--verbose"]
18
+ # test_patterns: ["**/*.test.ts", "**/*.test.js", "**/*.spec.ts", "**/*.spec.js"]
19
+ # parser: generic # No built-in JS parser yet (file-level selection)
20
+ # select_mode: file
21
+ # file_arg_style: flag
22
+ # file_arg_flag: "--testPathPattern"
23
+ # timeout_seconds: 300
24
+
25
+ # Go example (uncomment to use)
26
+ # - name: gotest
27
+ # command: go
28
+ # args: ["test", "-v"]
29
+ # test_patterns: ["**/*_test.go"]
30
+ # parser: generic
31
+ # select_mode: file
32
+ # file_arg_style: none
33
+ # timeout_seconds: 300
34
+
35
+ # LLM configuration
36
+ llm:
37
+ model: anthropic/claude-sonnet-4-20250514 # Any model supported by litellm
38
+ api_key_env: ANTHROPIC_API_KEY # Environment variable containing the API key
39
+ max_context_tokens: 100000 # Token budget for context
40
+ temperature: 0.0 # Keep deterministic
41
+ timeout_seconds: 60
42
+
43
+ # Context assembly
44
+ context:
45
+ include_test_contents: true # Send test file contents to LLM (more accurate, more tokens)
46
+ max_diff_lines: 5000 # Truncate diffs larger than this
47
+
48
+ # Safety
49
+ fallback_on_error: true # Run all tests if LLM fails
50
+ run_should_run: true # Also run "should_run" tests (not just "must_run")
51
+
52
+ # Optional: filter which files are analyzed
53
+ # include_patterns: ["src/**", "lib/**"]
54
+ # exclude_patterns: ["**/*.md", "docs/**"]
@@ -0,0 +1,75 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## What is Testwise
6
+
7
+ Testwise is an LLM-powered test selection tool for CI/CD pipelines. It analyzes git diffs, uses an LLM (via litellm) to classify tests as `must_run`, `should_run`, or `skip`, then executes only the relevant tests. It ships as both a CLI (`testwise`) and a GitHub Actions composite action.
8
+
9
+ ## Development Commands
10
+
11
+ ```bash
12
+ # Install in dev mode
13
+ pip install -e ".[dev]"
14
+
15
+ # Run all tests
16
+ pytest
17
+
18
+ # Run tests with coverage
19
+ pytest --cov=testwise --cov-report=term-missing
20
+
21
+ # Run a single test file
22
+ pytest tests/test_parsers/test_pytest_parser.py
23
+
24
+ # Run a single test
25
+ pytest tests/test_parsers/test_pytest_parser.py::test_function_name
26
+
27
+ # Linting
28
+ ruff check src/ tests/
29
+ ruff format src/ tests/
30
+
31
+ # Type checking
32
+ mypy src/testwise/
33
+ ```
34
+
35
+ ## Architecture
36
+
37
+ The pipeline flows through these stages in order, orchestrated by `cli.py`:
38
+
39
+ 1. **Config** (`config.py`) — Loads `.testwise.yml`, resolves defaults via `TestwiseConfig` Pydantic model
40
+ 2. **Diff** (`diff_analyzer.py`) — Extracts git diff between base/head refs, filters and truncates
41
+ 3. **Discovery** (`test_discovery.py`) — Finds test files matching runner glob patterns
42
+ 4. **Parsing** (`test_discovery.py` → `parsers/`) — Delegates to parser plugins to extract individual test functions with metadata
43
+ 5. **Context** (`context_builder.py`) — Assembles diff + parsed tests into LLM messages, respecting token budget
44
+ 6. **LLM Selection** (`llm_selector.py`) — Calls LLM via litellm with structured output (falls back to text mode JSON extraction). Returns `LLMSelectionResponse` with per-test classifications
45
+ 7. **Execution** (`test_runner.py`) — Runs selected tests using parser's `build_run_command()`
46
+ 8. **Reporting** (`reporter.py`) — Outputs results as text, JSON, or GitHub Actions annotations
47
+
48
+ ### Parser Plugin System
49
+
50
+ Parsers are registered via Python entry points (`testwise.parsers` group) and loaded at runtime by `parsers/__init__.py`. Each parser extends `BaseParser` ABC and implements:
51
+ - `parse_test_file()` — extracts `ParsedTest` objects with names, tags, `@covers` annotations, imports
52
+ - `build_run_command()` — builds the CLI command to run specific tests
53
+
54
+ Built-in parsers: `pytest` (test-level granularity) and `generic` (file-level fallback).
55
+
56
+ ### Key Models (`models.py`)
57
+
58
+ All data flows through Pydantic models: `DiffResult` → `ParsedTestFile`/`ParsedTest` → `LLMSelectionResponse`/`TestSelection` → `TestResult` → `RunReport`. The `TestClassification` enum (`must_run`, `should_run`, `skip`) is central to the selection logic.
59
+
60
+ ### LLM Fallback Strategy
61
+
62
+ `llm_selector.py` has a three-tier approach: (1) structured JSON output via `response_format`, (2) text mode with JSON schema in prompt, (3) raise `LLMError`. The caller in `cli.py` catches `LLMError` and falls back to running all tests when `fallback_on_error: true`.
63
+
64
+ ## Configuration
65
+
66
+ - Config file: `.testwise.yml` (see `.testwise.example.yml` for all options)
67
+ - Uses litellm model format (e.g., `anthropic/claude-sonnet-4-20250514`, `openai/gpt-4o`)
68
+ - API key is read from the env var specified by `llm.api_key_env`
69
+
70
+ ## Project Setup
71
+
72
+ - Python >=3.10, build system: hatchling
73
+ - Ruff config: line-length 100, target py310, rules: E, F, I, N, W, UP
74
+ - mypy: strict mode
75
+ - Tests directory: `tests/` with `fixtures/` and `test_parsers/` subdirectories
@@ -0,0 +1,130 @@
1
+ # Contributing to Testwise
2
+
3
+ Thanks for your interest in contributing! Here's how to get started.
4
+
5
+ ## Development Setup
6
+
7
+ ```bash
8
+ git clone https://github.com/mattfrautnick/testwise.git
9
+ cd testwise
10
+ pip install -e ".[dev]"
11
+ ```
12
+
13
+ ## Running Tests
14
+
15
+ ```bash
16
+ pytest
17
+ pytest --cov=testwise --cov-report=term-missing
18
+ ```
19
+
20
+ ## Linting
21
+
22
+ ```bash
23
+ ruff check src/ tests/
24
+ ruff format src/ tests/
25
+ mypy src/testwise/
26
+ ```
27
+
28
+ ## Project Structure
29
+
30
+ ```
31
+ src/testwise/
32
+ cli.py # CLI entry point + orchestration
33
+ models.py # All Pydantic data models
34
+ config.py # Configuration loading
35
+ diff_analyzer.py # Git diff extraction
36
+ test_discovery.py # Test file discovery
37
+ context_builder.py # LLM context assembly
38
+ llm_selector.py # LLM interaction
39
+ test_runner.py # Test execution
40
+ reporter.py # Results formatting
41
+ parsers/ # Parser plugin system
42
+ __init__.py # BaseParser ABC + registry
43
+ pytest_parser.py # Python/pytest parser
44
+ generic_parser.py # File-level fallback
45
+ ```
46
+
47
+ ## Writing a Parser Plugin
48
+
49
+ Parser plugins enable test-level selection for new languages/frameworks.
50
+
51
+ ### 1. Create the Parser
52
+
53
+ Implement `BaseParser`:
54
+
55
+ ```python
56
+ from testwise.parsers import BaseParser
57
+ from testwise.models import ParsedTest, ParsedTestFile, RunnerConfig
58
+ from pathlib import Path
59
+
60
+ class MyParser(BaseParser):
61
+ name = "myframework"
62
+ languages = ["javascript"]
63
+ file_patterns = ["*.test.js"]
64
+
65
+ def parse_test_file(self, file_path: Path, content: str) -> ParsedTestFile:
66
+ """Parse a test file and extract individual tests."""
67
+ tests = []
68
+ # Your parsing logic here - extract test names, tags, etc.
69
+ return ParsedTestFile(
70
+ file_path=str(file_path),
71
+ language="javascript",
72
+ tests=tests,
73
+ imports=[], # Module imports for dependency mapping
74
+ fixtures_used=[], # Shared setup/fixtures
75
+ )
76
+
77
+ def build_run_command(
78
+ self,
79
+ tests: list[ParsedTest],
80
+ runner_config: RunnerConfig,
81
+ repo_root: Path,
82
+ ) -> list[str]:
83
+ """Build CLI command to run specific tests."""
84
+ cmd = [runner_config.command, *runner_config.args]
85
+ # Add test selection flags specific to your framework
86
+ return cmd
87
+ ```
88
+
89
+ ### 2. Register via Entry Points
90
+
91
+ In your package's `pyproject.toml`:
92
+
93
+ ```toml
94
+ [project.entry-points."testwise.parsers"]
95
+ myframework = "my_package.parser:MyParser"
96
+ ```
97
+
98
+ ### 3. Test Your Parser
99
+
100
+ Write tests that verify:
101
+ - Test functions are correctly extracted from sample files
102
+ - Tags/annotations are properly parsed
103
+ - The `build_run_command` produces valid CLI commands
104
+ - Edge cases: empty files, syntax errors, nested classes
105
+
106
+ ### What Makes a Good Parser
107
+
108
+ - **Extract test names** with qualified paths (file::class::test)
109
+ - **Parse annotations/decorators** into tags and covers lists
110
+ - **Detect parametrized tests** (the LLM uses this info)
111
+ - **Extract imports** (helps the LLM map code dependencies)
112
+ - **Handle errors gracefully** (syntax errors should fall back to file-level)
113
+
114
+ ## Pull Request Guidelines
115
+
116
+ 1. Fork and create a feature branch
117
+ 2. Write tests for new functionality
118
+ 3. Ensure all tests pass: `pytest`
119
+ 4. Ensure linting passes: `ruff check src/ tests/`
120
+ 5. Keep commits focused and well-described
121
+ 6. Open a PR with a clear description of what and why
122
+
123
+ ## Reporting Issues
124
+
125
+ Open an issue with:
126
+ - What you expected to happen
127
+ - What actually happened
128
+ - Steps to reproduce
129
+ - Your testwise version (`testwise --version`)
130
+ - Your config file (redact API keys)
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Matthew Frautnick
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,269 @@
1
+ Metadata-Version: 2.4
2
+ Name: smartselect
3
+ Version: 0.1.3
4
+ Summary: LLM-powered test selection for CI/CD pipelines
5
+ Project-URL: Homepage, https://github.com/mattfrautnick/testwise
6
+ Project-URL: Repository, https://github.com/mattfrautnick/testwise
7
+ Project-URL: Issues, https://github.com/mattfrautnick/testwise/issues
8
+ Author: Matthew Frautnick
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: ai,cd,ci,llm,test-selection,testing
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: Software Development :: Testing
21
+ Requires-Python: >=3.10
22
+ Requires-Dist: click>=8.0
23
+ Requires-Dist: litellm>=1.40.0
24
+ Requires-Dist: pydantic>=2.0
25
+ Requires-Dist: pyyaml>=6.0
26
+ Requires-Dist: tiktoken>=0.7.0
27
+ Provides-Extra: dev
28
+ Requires-Dist: mypy; extra == 'dev'
29
+ Requires-Dist: pytest-cov; extra == 'dev'
30
+ Requires-Dist: pytest-mock; extra == 'dev'
31
+ Requires-Dist: pytest>=8.0; extra == 'dev'
32
+ Requires-Dist: ruff; extra == 'dev'
33
+ Requires-Dist: types-pyyaml; extra == 'dev'
34
+ Description-Content-Type: text/markdown
35
+
36
+ <p align="center">
37
+ <h1 align="center">Testwise</h1>
38
+ <p align="center">
39
+ LLM-powered test selection for CI/CD pipelines
40
+ <br />
41
+ <em>Run only the tests that matter. Save CI time without sacrificing coverage.</em>
42
+ </p>
43
+ <p align="center">
44
+ <a href="https://github.com/mattfrautnick/testwise/actions/workflows/ci.yml"><img src="https://github.com/mattfrautnick/testwise/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
45
+ <a href="https://pypi.org/project/testwise/"><img src="https://img.shields.io/pypi/v/testwise.svg" alt="PyPI"></a>
46
+ <a href="https://pypi.org/project/testwise/"><img src="https://img.shields.io/pypi/pyversions/testwise.svg" alt="Python"></a>
47
+ <a href="https://github.com/mattfrautnick/testwise/blob/main/LICENSE"><img src="https://img.shields.io/github/license/mattfrautnick/testwise" alt="License"></a>
48
+ </p>
49
+ </p>
50
+
51
+ ---
52
+
53
+ Testwise analyzes your git diff and uses an LLM to classify every test as `must_run`, `should_run`, or `skip` — then executes only what's needed. It supports **test-level granularity** for languages with parser plugins and falls back to file-level selection for everything else.
54
+
55
+ ## Why Testwise?
56
+
57
+ Large test suites slow down CI. Most changes only affect a fraction of your tests, but running the full suite every time wastes minutes (or hours). Existing static-analysis approaches miss indirect dependencies and cross-cutting concerns. Testwise uses an LLM that actually understands your code changes and test structure to make smarter decisions — with a safe fallback to run everything if it's ever uncertain.
58
+
59
+ ## How It Works
60
+
61
+ ```
62
+ git diff ─> Discover Tests ─> Parse with Plugins ─> LLM Classifies ─> Run Selected ─> Report
63
+ ```
64
+
65
+ 1. **Diff Analysis** — Extracts the git diff between base and head refs
66
+ 2. **Test Discovery** — Finds all test files and parses individual test functions via parser plugins
67
+ 3. **LLM Classification** — Sends diff + test inventory to an LLM with structured output
68
+ 4. **Selective Execution** — Runs only selected tests and reports results with GitHub annotations
69
+
70
+ ## Features
71
+
72
+ - **Hybrid Granularity** — Test-level selection for languages with parser plugins (pytest built-in), file-level fallback for others
73
+ - **Plugin Architecture** — Extensible parser system via Python entry points. [Write a parser](#writing-a-parser-plugin) for any test framework.
74
+ - **Any LLM Provider** — Uses [litellm](https://github.com/BerriAI/litellm) to support Claude, GPT, Gemini, and 100+ other models
75
+ - **GitHub Actions** — Ships as a composite action with step summary, annotations, and outputs
76
+ - **Safe Fallback** — If the LLM fails or is uncertain, falls back to running all tests
77
+ - **Test Annotations** — Supports `@pytest.mark.covers()` to explicitly map tests to code areas
78
+
79
+ ## Quick Start
80
+
81
+ ### Install
82
+
83
+ ```bash
84
+ pip install smartselect
85
+ ```
86
+
87
+ ### Configure
88
+
89
+ Create `.testwise.yml` in your repo root:
90
+
91
+ ```yaml
92
+ runners:
93
+ - name: pytest
94
+ command: pytest
95
+ args: ["-v", "--tb=short"]
96
+ test_patterns: ["tests/**/*.py", "test_*.py"]
97
+ parser: pytest
98
+ select_mode: test
99
+
100
+ llm:
101
+ model: anthropic/claude-sonnet-4-20250514
102
+ api_key_env: ANTHROPIC_API_KEY
103
+ ```
104
+
105
+ ### Run
106
+
107
+ ```bash
108
+ # Dry run — see what the LLM would select
109
+ testwise --dry-run
110
+
111
+ # Run selected tests
112
+ testwise
113
+
114
+ # Force all tests (bypass LLM)
115
+ testwise --fallback
116
+ ```
117
+
118
+ ### GitHub Actions
119
+
120
+ ```yaml
121
+ jobs:
122
+ test:
123
+ runs-on: ubuntu-latest
124
+ steps:
125
+ - uses: actions/checkout@v4
126
+ with:
127
+ fetch-depth: 0 # Full history needed for diff
128
+
129
+ - uses: mattfrautnick/testwise@v1
130
+ with:
131
+ api-key: ${{ secrets.ANTHROPIC_API_KEY }}
132
+ run-level: should_run
133
+ ```
134
+
135
+ The action writes a Markdown summary to `$GITHUB_STEP_SUMMARY` and emits `::error::` annotations for failing tests inline in your PR diff.
136
+
137
+ ## Test Annotations
138
+
139
+ Testwise's pytest parser understands standard markers and a custom `@covers` annotation that explicitly maps tests to code areas:
140
+
141
+ ```python
142
+ import pytest
143
+
144
+ @pytest.mark.covers("auth_module", "user.login")
145
+ def test_login_success(client, db):
146
+ """Verify successful login flow."""
147
+ ...
148
+
149
+ @pytest.mark.integration
150
+ @pytest.mark.covers("payment_service")
151
+ def test_checkout_flow(client):
152
+ ...
153
+
154
+ @pytest.mark.parametrize("role", ["admin", "user", "guest"])
155
+ def test_permissions(role):
156
+ ...
157
+ ```
158
+
159
+ The parser also extracts imports and fixture references automatically — no annotation required for basic dependency mapping.
160
+
161
+ ## Parser Plugins
162
+
163
+ Testwise uses a plugin architecture for language-specific test parsing. Plugins are registered via Python entry points.
164
+
165
+ ### Built-in Parsers
166
+
167
+ | Parser | Language | Granularity | Features |
168
+ |--------|----------|-------------|----------|
169
+ | `pytest` | Python | Test-level | Markers, covers, parametrize, fixtures, imports |
170
+ | `generic` | Any | File-level | Fallback for unsupported languages |
171
+
172
+ ### Writing a Parser Plugin
173
+
174
+ Implement `BaseParser` and register it as an entry point:
175
+
176
+ ```python
177
+ from testwise.parsers import BaseParser
178
+ from testwise.models import ParsedTest, ParsedTestFile, RunnerConfig
179
+ from pathlib import Path
180
+
181
+ class JestParser(BaseParser):
182
+ name = "jest"
183
+ languages = ["javascript", "typescript"]
184
+ file_patterns = ["*.test.ts", "*.test.js", "*.spec.ts", "*.spec.js"]
185
+
186
+ def parse_test_file(self, file_path: Path, content: str) -> ParsedTestFile:
187
+ # Parse describe/it blocks, extract test names
188
+ ...
189
+
190
+ def build_run_command(self, tests, runner_config, repo_root):
191
+ # Build jest --testNamePattern command
192
+ ...
193
+ ```
194
+
195
+ ```toml
196
+ # pyproject.toml
197
+ [project.entry-points."testwise.parsers"]
198
+ jest = "my_package.jest_parser:JestParser"
199
+ ```
200
+
201
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for a full guide on writing and testing parser plugins.
202
+
203
+ ## CLI Reference
204
+
205
+ ```
206
+ testwise [OPTIONS]
207
+
208
+ Options:
209
+ -c, --config PATH Path to .testwise.yml
210
+ -b, --base-ref TEXT Base git ref to diff against
211
+ --head-ref TEXT Head git ref (default: HEAD)
212
+ -o, --output [text|json|github] Output format (default: text)
213
+ --output-file PATH Write JSON report to file
214
+ --dry-run Show selections without running tests
215
+ --fallback Skip LLM, run all tests
216
+ --run-level [must_run|should_run|all] Minimum classification to run
217
+ -v, --verbose Verbose logging
218
+ --version Show version
219
+ --help Show this message
220
+ ```
221
+
222
+ ## Configuration Reference
223
+
224
+ See [`.testwise.example.yml`](.testwise.example.yml) for a fully commented example.
225
+
226
+ | Key | Type | Default | Description |
227
+ |-----|------|---------|-------------|
228
+ | `runners[].name` | string | required | Runner identifier |
229
+ | `runners[].command` | string | required | Test runner command |
230
+ | `runners[].args` | list | `[]` | Additional arguments |
231
+ | `runners[].test_patterns` | list | `[]` | Glob patterns for test files |
232
+ | `runners[].parser` | string | `"generic"` | Parser plugin name |
233
+ | `runners[].select_mode` | string | `"file"` | `"test"` or `"file"` |
234
+ | `runners[].timeout_seconds` | int | `300` | Per-runner timeout |
235
+ | `llm.model` | string | `"anthropic/claude-sonnet-4-20250514"` | LLM model ([litellm format](https://docs.litellm.ai/docs/providers)) |
236
+ | `llm.api_key_env` | string | `"ANTHROPIC_API_KEY"` | Env var containing API key |
237
+ | `llm.max_context_tokens` | int | `100000` | Token budget for context |
238
+ | `llm.temperature` | float | `0.0` | LLM temperature |
239
+ | `fallback_on_error` | bool | `true` | Run all tests if LLM fails |
240
+ | `run_should_run` | bool | `true` | Also run "should_run" tests |
241
+
242
+ ## Roadmap
243
+
244
+ Testwise is in early development. Here's what's planned:
245
+
246
+ - [ ] Jest/Vitest parser plugin
247
+ - [ ] Go test parser plugin
248
+ - [ ] Caching layer — skip LLM call for identical diffs
249
+ - [ ] Cost tracking — log token usage and estimated cost per run
250
+ - [ ] Confidence threshold — auto-fallback below a configurable confidence
251
+ - [ ] Test impact analysis — learn from historical runs which tests fail for which changes
252
+ - [ ] GitLab CI integration
253
+
254
+ Have an idea? [Open an issue](https://github.com/mattfrautnick/testwise/issues) or [start a discussion](https://github.com/mattfrautnick/testwise/discussions).
255
+
256
+ ## Contributing
257
+
258
+ Contributions are welcome! Whether it's a bug fix, a new parser plugin, or documentation improvements — all contributions help.
259
+
260
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup, architecture overview, and the full guide to writing parser plugins.
261
+
262
+ ## Community
263
+
264
+ - [GitHub Issues](https://github.com/mattfrautnick/testwise/issues) — Bug reports and feature requests
265
+ - [GitHub Discussions](https://github.com/mattfrautnick/testwise/discussions) — Questions, ideas, and show & tell
266
+
267
+ ## License
268
+
269
+ [MIT](LICENSE)