jitcatch 0.2.0__py3-none-any.whl
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.
- jitcatch/__init__.py +2 -0
- jitcatch/__main__.py +5 -0
- jitcatch/adapters/__init__.py +36 -0
- jitcatch/adapters/base.py +137 -0
- jitcatch/adapters/golang.py +88 -0
- jitcatch/adapters/java.py +136 -0
- jitcatch/adapters/javascript.py +165 -0
- jitcatch/adapters/python.py +187 -0
- jitcatch/assessors/__init__.py +94 -0
- jitcatch/assessors/failure_parse.py +126 -0
- jitcatch/assessors/llm_judge.py +75 -0
- jitcatch/assessors/rules.py +343 -0
- jitcatch/cli.py +338 -0
- jitcatch/config.py +259 -0
- jitcatch/engine.py +521 -0
- jitcatch/github.py +90 -0
- jitcatch/gitutil.py +231 -0
- jitcatch/hook.py +98 -0
- jitcatch/jsonutil.py +87 -0
- jitcatch/llm/__init__.py +21 -0
- jitcatch/llm/base.py +100 -0
- jitcatch/llm/providers.py +127 -0
- jitcatch/models.py +150 -0
- jitcatch/prompts.py +191 -0
- jitcatch/report.py +163 -0
- jitcatch/targeting.py +44 -0
- jitcatch/templates.py +76 -0
- jitcatch/userconfig.py +188 -0
- jitcatch-0.2.0.dist-info/METADATA +164 -0
- jitcatch-0.2.0.dist-info/RECORD +34 -0
- jitcatch-0.2.0.dist-info/WHEEL +5 -0
- jitcatch-0.2.0.dist-info/entry_points.txt +2 -0
- jitcatch-0.2.0.dist-info/licenses/LICENSE +21 -0
- jitcatch-0.2.0.dist-info/top_level.txt +1 -0
jitcatch/userconfig.py
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
"""Per-user settings: provider choice, stored API keys, and the interactive first-run setup.
|
|
2
|
+
|
|
3
|
+
Lives in ~/.config/jitcatch/ (or $JITCATCH_CONFIG_HOME / $XDG_CONFIG_HOME/jitcatch):
|
|
4
|
+
config.toml provider + roles, merged under any repo .jitcatch.toml
|
|
5
|
+
credentials.toml API keys (mode 0600); environment variables always take precedence
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import getpass
|
|
10
|
+
import json
|
|
11
|
+
import os
|
|
12
|
+
import sys
|
|
13
|
+
import tomllib
|
|
14
|
+
from dataclasses import dataclass
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import Any, Callable
|
|
17
|
+
|
|
18
|
+
from .config import ConfigError
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def config_home() -> Path:
|
|
22
|
+
if os.environ.get("JITCATCH_CONFIG_HOME"):
|
|
23
|
+
return Path(os.environ["JITCATCH_CONFIG_HOME"])
|
|
24
|
+
base = os.environ.get("XDG_CONFIG_HOME") or str(Path.home() / ".config")
|
|
25
|
+
return Path(base) / "jitcatch"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def cache_home() -> Path:
|
|
29
|
+
if os.environ.get("JITCATCH_CACHE_DIR"):
|
|
30
|
+
return Path(os.environ["JITCATCH_CACHE_DIR"])
|
|
31
|
+
base = os.environ.get("XDG_CACHE_HOME") or str(Path.home() / ".cache")
|
|
32
|
+
return Path(base) / "jitcatch"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def user_config_path() -> Path:
|
|
36
|
+
return config_home() / "config.toml"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def credentials_path() -> Path:
|
|
40
|
+
return config_home() / "credentials.toml"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _read_toml(p: Path) -> dict[str, Any]:
|
|
44
|
+
if not p.is_file():
|
|
45
|
+
return {}
|
|
46
|
+
try:
|
|
47
|
+
with open(p, "rb") as f:
|
|
48
|
+
return tomllib.load(f)
|
|
49
|
+
except (OSError, tomllib.TOMLDecodeError):
|
|
50
|
+
return {}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def load_user_config() -> dict[str, Any]:
|
|
54
|
+
return _read_toml(user_config_path())
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def stored_key(env_name: str) -> str:
|
|
58
|
+
v = _read_toml(credentials_path()).get(env_name, "")
|
|
59
|
+
return v if isinstance(v, str) else ""
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _toml_str(s: str) -> str:
|
|
63
|
+
return json.dumps(s) # a JSON string is a valid TOML basic string
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def save_key(env_name: str, value: str) -> Path:
|
|
67
|
+
p = credentials_path()
|
|
68
|
+
p.parent.mkdir(parents=True, exist_ok=True)
|
|
69
|
+
data = {k: v for k, v in _read_toml(p).items() if isinstance(v, str)}
|
|
70
|
+
data[env_name] = value
|
|
71
|
+
body = "# jitcatch API keys (environment variables take precedence)\n" + "".join(
|
|
72
|
+
f"{k} = {_toml_str(v)}\n" for k, v in sorted(data.items()))
|
|
73
|
+
fd = os.open(p, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600)
|
|
74
|
+
with os.fdopen(fd, "w", encoding="utf-8") as f:
|
|
75
|
+
f.write(body)
|
|
76
|
+
os.chmod(p, 0o600)
|
|
77
|
+
return p
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def save_user_provider(name: str, preset: str, model: str) -> Path:
|
|
81
|
+
p = user_config_path()
|
|
82
|
+
p.parent.mkdir(parents=True, exist_ok=True)
|
|
83
|
+
p.write_text(
|
|
84
|
+
"# jitcatch user settings (written by `jitcatch setup`; a repo .jitcatch.toml overrides these)\n\n"
|
|
85
|
+
f"[providers.{name}]\npreset = {_toml_str(preset)}\nmodel = {_toml_str(model)}\n\n"
|
|
86
|
+
f"[roles]\ngenerator = {_toml_str(name)}\njudges = [{_toml_str(name)}]\n",
|
|
87
|
+
encoding="utf-8")
|
|
88
|
+
return p
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
# ------------------------------------------------------------------------- interactive setup
|
|
92
|
+
|
|
93
|
+
@dataclass(frozen=True)
|
|
94
|
+
class Choice:
|
|
95
|
+
label: str
|
|
96
|
+
name: str # provider name written to config
|
|
97
|
+
preset: str
|
|
98
|
+
model: str # suggested default model
|
|
99
|
+
key_env: str # "" = no key needed
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
CHOICES = [
|
|
103
|
+
Choice("Z.ai GLM Coding Plan", "glm", "zai-coding", "glm-5.3-flash", "ZAI_API_KEY"),
|
|
104
|
+
Choice("Anthropic (Claude)", "claude", "anthropic", "claude-sonnet-5-5", "ANTHROPIC_API_KEY"),
|
|
105
|
+
Choice("OpenAI", "gpt", "openai", "gpt-5", "OPENAI_API_KEY"),
|
|
106
|
+
Choice("Ollama (local, no key)", "local", "ollama", "qwen2.5-coder:32b", ""),
|
|
107
|
+
]
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class SetupNeeded(ConfigError):
|
|
111
|
+
"""Setup is incomplete and we cannot ask (no terminal)."""
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def is_interactive() -> bool:
|
|
115
|
+
return sys.stdin.isatty() and sys.stdout.isatty()
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def missing_keys(cfg) -> list[tuple[str, str]]:
|
|
119
|
+
"""(provider name, env var) for every provider used by the roles that has no API key."""
|
|
120
|
+
roles = cfg.roles
|
|
121
|
+
used = [roles.get("generator"), *(roles.get("judges") or []), roles.get("logprob_judge")]
|
|
122
|
+
if not roles.get("generator") and len(cfg.providers) == 1:
|
|
123
|
+
used.append(next(iter(cfg.providers)))
|
|
124
|
+
out = []
|
|
125
|
+
for n in dict.fromkeys(u for u in used if u):
|
|
126
|
+
p = cfg.providers.get(n)
|
|
127
|
+
if p is not None and p.api_key_env and not p.resolve_api_key():
|
|
128
|
+
out.append((n, p.api_key_env))
|
|
129
|
+
return out
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def ensure_setup(load: Callable[[], Any], ask: Callable[[str], str] = input,
|
|
133
|
+
ask_secret: Callable[[str], str] = getpass.getpass,
|
|
134
|
+
interactive: bool | None = None, out=None) -> Any:
|
|
135
|
+
"""Return a ready config, asking at most 3 questions (provider, model, key) for whatever is missing.
|
|
136
|
+
|
|
137
|
+
Steps that are already satisfied are skipped; when nothing is missing nothing is asked."""
|
|
138
|
+
cfg = load()
|
|
139
|
+
need_provider = not cfg.providers
|
|
140
|
+
keys = [] if need_provider else missing_keys(cfg)
|
|
141
|
+
if not need_provider and not keys:
|
|
142
|
+
return cfg
|
|
143
|
+
out = out or sys.stderr
|
|
144
|
+
if interactive is None:
|
|
145
|
+
interactive = is_interactive()
|
|
146
|
+
if not interactive:
|
|
147
|
+
what = "no LLM provider configured" if need_provider else \
|
|
148
|
+
"missing API key " + ", ".join(env for _, env in keys)
|
|
149
|
+
raise SetupNeeded(f"{what}: run `jitcatch setup` in a terminal (or set the environment variable)")
|
|
150
|
+
|
|
151
|
+
if need_provider:
|
|
152
|
+
print("\njitcatch setup (asked once; saved for every project)", file=out)
|
|
153
|
+
print("\n1. Which LLM provider?", file=out)
|
|
154
|
+
for i, c in enumerate(CHOICES, 1):
|
|
155
|
+
print(f" {i}) {c.label}", file=out)
|
|
156
|
+
choice = CHOICES[0]
|
|
157
|
+
while True:
|
|
158
|
+
ans = ask(" Choose 1-4 [1]: ").strip()
|
|
159
|
+
if not ans:
|
|
160
|
+
break
|
|
161
|
+
if ans.isdigit() and 1 <= int(ans) <= len(CHOICES):
|
|
162
|
+
choice = CHOICES[int(ans) - 1]
|
|
163
|
+
break
|
|
164
|
+
print(" please enter a number from the list", file=out)
|
|
165
|
+
|
|
166
|
+
print("\n2. Which model?", file=out)
|
|
167
|
+
model = ask(f" Model [{choice.model}]: ").strip() or choice.model
|
|
168
|
+
path = save_user_provider(choice.name, choice.preset, model)
|
|
169
|
+
|
|
170
|
+
if choice.key_env and not (os.environ.get(choice.key_env) or stored_key(choice.key_env)):
|
|
171
|
+
print(f"\n3. API key ({choice.key_env})", file=out)
|
|
172
|
+
_ask_key(choice.key_env, ask_secret, out)
|
|
173
|
+
print(f"\nSaved to {path}. You won't be asked again.\n", file=out)
|
|
174
|
+
else:
|
|
175
|
+
for _, env in keys:
|
|
176
|
+
print(f"\njitcatch needs an API key ({env}); asked once.", file=out)
|
|
177
|
+
_ask_key(env, ask_secret, out)
|
|
178
|
+
return load()
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def _ask_key(env: str, ask_secret: Callable[[str], str], out) -> None:
|
|
182
|
+
while True:
|
|
183
|
+
key = ask_secret(" Paste key (input hidden): ").strip()
|
|
184
|
+
if key:
|
|
185
|
+
p = save_key(env, key)
|
|
186
|
+
print(f" saved to {p} (only you can read it)", file=out)
|
|
187
|
+
return
|
|
188
|
+
print(" the key cannot be empty", file=out)
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: jitcatch
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Just-in-Time catching test generation: LLM-generated tests that fail on risky code changes, with rule-based and LLM-judge false-positive filtering.
|
|
5
|
+
Author: Ahmad Waqar
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/khawjaahmad/jitcatch
|
|
8
|
+
Project-URL: Repository, https://github.com/khawjaahmad/jitcatch
|
|
9
|
+
Project-URL: Issues, https://github.com/khawjaahmad/jitcatch/issues
|
|
10
|
+
Project-URL: Paper, https://arxiv.org/abs/2601.22832
|
|
11
|
+
Keywords: testing,regression,llm,mutation-testing,test-generation,code-review
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Operating System :: MacOS
|
|
17
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Topic :: Software Development :: Testing
|
|
23
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
24
|
+
Requires-Python: >=3.11
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
License-File: LICENSE
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: pytest>=7; extra == "dev"
|
|
29
|
+
Dynamic: license-file
|
|
30
|
+
|
|
31
|
+
# JiTCatch
|
|
32
|
+
|
|
33
|
+
Just-in-Time **catching** test generation for any Git repository, modelled on
|
|
34
|
+
Becker et al., *"Just-in-Time Catching Test Generation at Meta"* (FSE Companion '26, arXiv:2601.22832).
|
|
35
|
+
|
|
36
|
+
Hardening tests pass today and guard the future. **Catching tests are generated for one specific change and
|
|
37
|
+
are meant to fail on it**: they pass on the parent revision and fail on the change, exposing a regression
|
|
38
|
+
before it merges. JiTCatch then filters out false positives and asks the author a plain question:
|
|
39
|
+
*"`can_delete()` now returns False for a resource's owner. Is this expected?"*
|
|
40
|
+
|
|
41
|
+
| Paper concept | Where it lives |
|
|
42
|
+
|---|---|
|
|
43
|
+
| Diff risk targeting (DRS) | `targeting.py` (transparent heuristic; `run.min_risk_score`) |
|
|
44
|
+
| Intent-aware workflow: intent → risks → mutants of parent → tests that kill them | `engine.py` `_infer_intent`, `_make_mutant`, `_generate_test` |
|
|
45
|
+
| Mutant must build and pass existing tests | `run.check_mutants_with_existing_tests` |
|
|
46
|
+
| "Dodgy diff" workflow: treat the change itself as a mutant | `engine.py` `_dodgy_diff` |
|
|
47
|
+
| Weak catch = passes on parent, fails on change | `models.GeneratedTest.is_weak_catch` |
|
|
48
|
+
| RubFake rule-based assessor (Tables 2 & 3) | `assessors/rules.py` |
|
|
49
|
+
| LLM-as-judge ensemble, High/Medium/Low → 1/0/−1, **median** | `assessors/llm_judge.py` |
|
|
50
|
+
| Normalised token-probability Yes/No score | `assessors/llm_judge.py` `logprob_score` |
|
|
51
|
+
| Score in [−1, 1], rank, threshold | `assessors/__init__.py`, `run.report_threshold` |
|
|
52
|
+
| Low-friction "is this expected?" report | `report.py` (Markdown, JSON, PR comment) |
|
|
53
|
+
| Coincidental hardening tests as a by-product | `.jitcatch/runs/<id>/hardening/` |
|
|
54
|
+
|
|
55
|
+
## Supported stacks
|
|
56
|
+
|
|
57
|
+
| Language | Runner | Generated test location |
|
|
58
|
+
|---|---|---|
|
|
59
|
+
| Python | pytest | next to the related existing test (else next to the source): `test_jitcatch_<id>.py` |
|
|
60
|
+
| TypeScript / JavaScript | Jest or Vitest (auto-detected per `package.json`) | `<name>.jitcatch-<id>.test.<ext>` |
|
|
61
|
+
| Java | JUnit 4/5 via Maven or Gradle (wrappers preferred) | `src/test/java/<pkg>/<Class>JitcatchTest<ID>.java` |
|
|
62
|
+
| Go | `go test` | `<name>_jitcatch_<id>_test.go`, functions `TestJitcatch<ID>…` |
|
|
63
|
+
|
|
64
|
+
LLM providers: **Anthropic Messages**, **OpenAI Chat Completions**, **OpenAI Responses**, and anything compatible
|
|
65
|
+
with them, with presets for Z.ai GLM Coding Plan (`zai-coding` → `https://api.z.ai/api/coding/paas/v4`,
|
|
66
|
+
`zai-coding-anthropic`, `zai-coding-responses`) and local servers (`ollama`, `lmstudio`, `vllm`).
|
|
67
|
+
No third-party Python dependencies; Python ≥ 3.11.
|
|
68
|
+
|
|
69
|
+
## Install
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
pipx install jitcatch # or: uv tool install jitcatch / pip install jitcatch
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Use
|
|
76
|
+
|
|
77
|
+
Inside any git project, run:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
jitcatch
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
The first time, it asks at most three questions (provider, model, API key) and saves the answers in
|
|
84
|
+
`~/.config/jitcatch/` for every project. Questions whose answer is already known are skipped; for example,
|
|
85
|
+
the key isn't asked for if `ZAI_API_KEY` / `ANTHROPIC_API_KEY` / `OPENAI_API_KEY` is already set.
|
|
86
|
+
Run `jitcatch setup --reset` to change provider or model.
|
|
87
|
+
|
|
88
|
+
What gets tested is picked automatically:
|
|
89
|
+
|
|
90
|
+
1. uncommitted changes (including new files), compared with the last commit;
|
|
91
|
+
2. otherwise, commits on your branch that aren't on the default branch yet;
|
|
92
|
+
3. otherwise, the last commit.
|
|
93
|
+
|
|
94
|
+
Nothing in your checkout is modified. Uncommitted work is snapshotted with a throwaway index, and run
|
|
95
|
+
artifacts in `.jitcatch/` are hidden through `.git/info/exclude`. To compare specific revisions:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
jitcatch run --base origin/main --head HEAD
|
|
99
|
+
jitcatch run --base main --head feature --json report.json --markdown report.md --fail-on-catch
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
The project's own toolchain must work (`node_modules`, Maven/Gradle dependencies, Go modules). For Python,
|
|
103
|
+
tests run in the project's `.venv` so its dependencies are importable; if that environment has no pytest,
|
|
104
|
+
jitcatch installs a private copy into `~/.cache/jitcatch/` and leaves your project untouched.
|
|
105
|
+
`jitcatch doctor --ping` checks configuration, keys, toolchains and provider round-trips.
|
|
106
|
+
|
|
107
|
+
**GitHub Action** – copy `examples/github-workflow.yml`. It needs `fetch-depth: 0`,
|
|
108
|
+
`permissions: pull-requests: write`, your project's normal setup steps, and API keys as secrets.
|
|
109
|
+
It posts one sticky PR comment (updated on each push), writes a job summary, and exposes
|
|
110
|
+
`catches` / `weak-catches` outputs. jitcatch is installed in its own venv so it never shadows your toolchain.
|
|
111
|
+
|
|
112
|
+
**Pre-push hook**
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
jitcatch install-hook # reports only
|
|
116
|
+
jitcatch install-hook --blocking # a reported catch blocks the push
|
|
117
|
+
JITCATCH_SKIP=1 git push # bypass once
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Tool or provider failures never block a push.
|
|
121
|
+
|
|
122
|
+
## How scoring works
|
|
123
|
+
|
|
124
|
+
Every weak catch is scored by up to three assessors, each in [−1 (false positive), 1 (true positive)]:
|
|
125
|
+
|
|
126
|
+
1. **rules**: deterministic false-positive patterns from the paper (broken runner, reflection, type
|
|
127
|
+
mismatch, mocks, private/protected members, NotImplemented, key ordering, undefined names, call-count
|
|
128
|
+
expectations, network down, flakiness) and true-positive patterns (unexpected key/index error, emptied
|
|
129
|
+
container, construction failure, flipped boolean, behaviour change in a refactor, dead-code removal or
|
|
130
|
+
additive change, unexpected null, RBAC). A **high** false-positive pattern discards the catch before any
|
|
131
|
+
judge is called (`assessors.discard_on_high_fp_rule`).
|
|
132
|
+
2. **llm_bucket**: median of the judges' High/Medium/Low.
|
|
133
|
+
3. **llm_logprob** (optional): P(Yes) vs P(No) for the first answer token from an `openai_chat` provider
|
|
134
|
+
with `logprobs = true`.
|
|
135
|
+
|
|
136
|
+
The combined score is a weighted mean of the available scores (`assessors.weights`); catches at or above
|
|
137
|
+
`run.report_threshold` are reported, best first. All judge rationales are kept individually.
|
|
138
|
+
|
|
139
|
+
## Output
|
|
140
|
+
|
|
141
|
+
`.jitcatch/runs/<timestamp>-<sha>/` contains `report.json` (everything: intent, risks, mutants, every test with
|
|
142
|
+
its runs on parent / mutant / change, all assessor details), `report.md`, `weak_catches/` and `hardening/`
|
|
143
|
+
(tests that pass on both revisions; candidates to add to your suite).
|
|
144
|
+
|
|
145
|
+
## Cost and safety notes
|
|
146
|
+
|
|
147
|
+
* LLM calls per changed file are about 1 intent call (shared) + 2 × risks (mutant + test) + dodgy tests +
|
|
148
|
+
repairs, plus judges for each weak catch that is not auto-discarded. Bound it with `max_target_files`,
|
|
149
|
+
`max_risks_per_file`, `dodgy_tests_per_file`, `max_fix_attempts`, `min_risk_score`.
|
|
150
|
+
* **Generated tests are LLM-written code executed on the machine** (CI runner or, with the hook, your laptop).
|
|
151
|
+
Run it where that is acceptable.
|
|
152
|
+
* Targeting is a heuristic stand-in for Meta's learned risk model, which needs historical data.
|
|
153
|
+
* Logprob judging only reads Chat Completions logprobs; whether a given server returns them varies
|
|
154
|
+
(`jitcatch doctor --ping` reports it).
|
|
155
|
+
|
|
156
|
+
## Development
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
pip install -e '.[dev]' && pytest
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
The suite runs the full pipeline end-to-end with a scripted LLM against real pytest, Go, Vitest (TypeScript)
|
|
163
|
+
and Jest (JavaScript) projects, and against HTTP fake servers for the provider and GitHub wire formats.
|
|
164
|
+
Java/JUnit execution is covered by placement/classification unit tests only.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
jitcatch/__init__.py,sha256=V7QhGRoQxOSVqzU4u_94DRbqQl804T-ht0Mjcx4Ttbg,108
|
|
2
|
+
jitcatch/__main__.py,sha256=E6Gls0DNz8GQK2K-kOUIx8cYhgANW_CH54VKrfCfs14,52
|
|
3
|
+
jitcatch/cli.py,sha256=bUoKYZYsSF_AKpvYOgGyxr-H07KJo5n_AdlKwrzzxmg,14485
|
|
4
|
+
jitcatch/config.py,sha256=W0MQPHUOgzwKyb8xJeXdPPMkTRJMEbT3FTdXi4LdXcM,8667
|
|
5
|
+
jitcatch/engine.py,sha256=pQM-RaVPO0Rr_Xt1McHkzGcxvmnOJQImxS54aSrzXvo,25347
|
|
6
|
+
jitcatch/github.py,sha256=hGs24ZJiyvYL0h2yAK6M85hbt9mp2aGr1iU-f5aBKsc,3247
|
|
7
|
+
jitcatch/gitutil.py,sha256=B5x3lwUnXVLT5184rMsoIkv6jFoT77p-J9OOx0wlEAU,10136
|
|
8
|
+
jitcatch/hook.py,sha256=1SaTccKMWXNIVYjWxjf_l4RmMtQCDtJKY6xe3WWM1gA,3410
|
|
9
|
+
jitcatch/jsonutil.py,sha256=qny5utgwztYArpnZaAWHN33JVQfJ7ICT4LXXjO1Y8Ss,2620
|
|
10
|
+
jitcatch/models.py,sha256=j9ue5Z5tiM2361b_dLNMfBw9ERxHQ5BS6q-3A_Gz1ws,4342
|
|
11
|
+
jitcatch/prompts.py,sha256=_BryDEQh1HqOE01lMuQrCac_UjFG7drds_tIk5zB60M,6175
|
|
12
|
+
jitcatch/report.py,sha256=E9PSmj0ZbvEi20pBJ1kym80Sj4uxfE59tYYzAWkbAk8,7365
|
|
13
|
+
jitcatch/targeting.py,sha256=4INqRzqe7J85AUXrI3h-jq8Bz-Kf1Rra6mA7EvGRiKo,1812
|
|
14
|
+
jitcatch/templates.py,sha256=JKwtJZ7eTWUAxRNAXrF7dFUFMs03bOF4zffmeZMODWw,2993
|
|
15
|
+
jitcatch/userconfig.py,sha256=exZxyW3hsNw5a_BaPXsxfmKoJ5cvG4TIPfOQPs9LtVQ,6795
|
|
16
|
+
jitcatch/adapters/__init__.py,sha256=OI7WyKVxfgmiaeBa3jTBctnTz0k0dF0AB73Po7krS7M,943
|
|
17
|
+
jitcatch/adapters/base.py,sha256=XcMDys_k4C9orUPVElGhV_bw4CCKrBdy6VZfj_KA2Ig,5767
|
|
18
|
+
jitcatch/adapters/golang.py,sha256=KoYrPMIQDf6iXg3iRMFadkHbRLZQDFSX9k7YW5hXvOM,4157
|
|
19
|
+
jitcatch/adapters/java.py,sha256=m1eIlhv9rgsQzfhRzxFzLix_i70WnSto2yhLJsUt3RE,6562
|
|
20
|
+
jitcatch/adapters/javascript.py,sha256=4w_DaB2rOL3uZnBUsuvfyTGZpN-L6fbcyvsFA1kxNmk,8107
|
|
21
|
+
jitcatch/adapters/python.py,sha256=kRvUA3DPZXAG9G1NAr4pXMtoV9DZ_SItRV_sbNmclYU,8548
|
|
22
|
+
jitcatch/assessors/__init__.py,sha256=1-Y3mQya3dRQFZLjxVSlZz9ozqWe-JL7SERnGAkudlg,4158
|
|
23
|
+
jitcatch/assessors/failure_parse.py,sha256=TSENddQeTdSg29243AzPf_OU6QparRWhX1NfeZGMdtQ,4976
|
|
24
|
+
jitcatch/assessors/llm_judge.py,sha256=ZlUG6gviLqhszjR-02d53gS2kPC6Gm91g44jwKQevi0,3664
|
|
25
|
+
jitcatch/assessors/rules.py,sha256=XlgVCZkaeAprt-rpHYf0FXjyjPDdV2JPnn6lTBV6d2k,16133
|
|
26
|
+
jitcatch/llm/__init__.py,sha256=SCUlx_RRBc0Isf2ZKX-Wd0PGx3PMJ69glfJ8HotUGAI,627
|
|
27
|
+
jitcatch/llm/base.py,sha256=4IZDEmkyQAX3n6fio1of10W42cXSRy0bprXn3ZezIEo,3984
|
|
28
|
+
jitcatch/llm/providers.py,sha256=HX5Do6zccOx4Kp9pyn5wnooXzQFOZ2Rx_VjcXQ7e98A,5533
|
|
29
|
+
jitcatch-0.2.0.dist-info/licenses/LICENSE,sha256=xLd32VBsKO-bgUFqBy2I8vphK6fwHdx3bQPT00XtoWk,1068
|
|
30
|
+
jitcatch-0.2.0.dist-info/METADATA,sha256=hcUkQyu81Eurq8t8dgimW5gKJt-3wym_TiqYXF6KTKA,8466
|
|
31
|
+
jitcatch-0.2.0.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
|
|
32
|
+
jitcatch-0.2.0.dist-info/entry_points.txt,sha256=JfFzcIGORqr97JwBPSw9FGe3ezdmavAnr0WNBR4Epks,47
|
|
33
|
+
jitcatch-0.2.0.dist-info/top_level.txt,sha256=SKJKVPRcvS1U_PkrlD1eAcYM96O-q0oyIQQNtyJRB1c,9
|
|
34
|
+
jitcatch-0.2.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ahmad Waqar
|
|
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 @@
|
|
|
1
|
+
jitcatch
|