testpulse 0.1.0__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.
Files changed (41) hide show
  1. testpulse-0.1.0/.gitignore +49 -0
  2. testpulse-0.1.0/LICENSE +21 -0
  3. testpulse-0.1.0/PKG-INFO +432 -0
  4. testpulse-0.1.0/README.md +407 -0
  5. testpulse-0.1.0/deployment.env +1 -0
  6. testpulse-0.1.0/hatch_build.py +49 -0
  7. testpulse-0.1.0/pyproject.toml +95 -0
  8. testpulse-0.1.0/scripts/bootstrap_deployment_env.sh +65 -0
  9. testpulse-0.1.0/scripts/deployment_env.py +18 -0
  10. testpulse-0.1.0/scripts/deployment_environment_verification.py +16 -0
  11. testpulse-0.1.0/scripts/sync_project_name.py +51 -0
  12. testpulse-0.1.0/scripts/verify_deployment_environment.py +21 -0
  13. testpulse-0.1.0/scripts/write_deployment_env.py +32 -0
  14. testpulse-0.1.0/src/testpulse/__init__.py +110 -0
  15. testpulse-0.1.0/src/testpulse/_distribution.py +3 -0
  16. testpulse-0.1.0/src/testpulse/_entry.py +13 -0
  17. testpulse-0.1.0/src/testpulse/api.py +121 -0
  18. testpulse-0.1.0/src/testpulse/auth_store.py +148 -0
  19. testpulse-0.1.0/src/testpulse/bundled_deployment.env +1 -0
  20. testpulse-0.1.0/src/testpulse/ci_stats.py +166 -0
  21. testpulse-0.1.0/src/testpulse/citations.py +115 -0
  22. testpulse-0.1.0/src/testpulse/cli.py +350 -0
  23. testpulse-0.1.0/src/testpulse/config.py +93 -0
  24. testpulse-0.1.0/src/testpulse/deployment_verify.py +133 -0
  25. testpulse-0.1.0/src/testpulse/explanations.py +164 -0
  26. testpulse-0.1.0/src/testpulse/flakiness.py +129 -0
  27. testpulse-0.1.0/src/testpulse/git_diff.py +51 -0
  28. testpulse-0.1.0/src/testpulse/health.py +35 -0
  29. testpulse-0.1.0/src/testpulse/heuristics.py +64 -0
  30. testpulse-0.1.0/src/testpulse/llm_client.py +89 -0
  31. testpulse-0.1.0/src/testpulse/paths.py +14 -0
  32. testpulse-0.1.0/src/testpulse/py.typed +0 -0
  33. testpulse-0.1.0/src/testpulse/pytest_plugin.py +17 -0
  34. testpulse-0.1.0/src/testpulse/recommend.py +105 -0
  35. testpulse-0.1.0/src/testpulse/recording.py +143 -0
  36. testpulse-0.1.0/src/testpulse/regret_hits.py +83 -0
  37. testpulse-0.1.0/src/testpulse/report.py +282 -0
  38. testpulse-0.1.0/src/testpulse/risk.py +110 -0
  39. testpulse-0.1.0/src/testpulse/store.py +400 -0
  40. testpulse-0.1.0/src/testpulse/terminal_ui.py +44 -0
  41. testpulse-0.1.0/src/testpulse/web_app.py +173 -0
@@ -0,0 +1,49 @@
1
+ # Environment
2
+ .env
3
+ .env.*
4
+ !.env.example
5
+
6
+ # Python
7
+ __pycache__/
8
+ *.py[cod]
9
+ *$py.class
10
+ *.so
11
+ .Python
12
+ build/
13
+ develop-eggs/
14
+ dist/
15
+ downloads/
16
+ eggs/
17
+ .eggs/
18
+ lib/
19
+ lib64/
20
+ parts/
21
+ sdist/
22
+ var/
23
+ wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+
28
+ # Virtual environments
29
+ .venv/
30
+
31
+ # Test / tooling
32
+ .pytest_cache/
33
+ .mypy_cache/
34
+ .ruff_cache/
35
+ .coverage
36
+ htmlcov/
37
+ .hypothesis/
38
+
39
+ # TestPulse local data
40
+ .testpulse/
41
+
42
+ # IDE
43
+ .idea/
44
+ .vscode/
45
+ *.swp
46
+ *~
47
+
48
+ # OS
49
+ .DS_Store
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 TestPulse contributors
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,432 @@
1
+ Metadata-Version: 2.5
2
+ Name: testpulse
3
+ Version: 0.1.0
4
+ Summary: Time your tests, optimize your outcomes.
5
+ Project-URL: Homepage, https://github.com/MaruthiCharan2403/testpulse
6
+ Project-URL: Documentation, https://github.com/MaruthiCharan2403/testpulse#readme
7
+ Project-URL: Repository, https://github.com/MaruthiCharan2403/testpulse
8
+ Project-URL: Issues, https://github.com/MaruthiCharan2403/testpulse/issues
9
+ Author: TestPulse contributors
10
+ License: MIT
11
+ License-File: LICENSE
12
+ Keywords: cli,developer-productivity,pytest,testing
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Environment :: Console
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Software Development :: Testing
22
+ Classifier: Typing :: Typed
23
+ Requires-Python: >=3.11
24
+ Description-Content-Type: text/markdown
25
+
26
+ # TestPulse
27
+
28
+ **Time your tests, optimize your outcomes.**
29
+
30
+ Turn your current git diff into a single, evidence-based pytest command by learning from locally recorded test timings, failures, and which tests historically correlate with your changes; quantify flakiness and produce explicit risk guidance for skipping tests.
31
+
32
+ TestPulse is a **Python library and CLI** (`testtime`). It stores history in a **local SQLite database** (stdlib only—no runtime dependencies). There is **no web application** and **no HTTP API server**; you use pytest, the terminal, and optional library imports.
33
+
34
+ ## Features
35
+
36
+ ### Local test recording (pytest plugin)
37
+
38
+ **What it does:** Records each test’s nodeid, duration, pass/fail/skip/xfail outcome, and hashes of captured stdout/stderr into SQLite. Associates the run with the current git diff paths and updates a **change↔test co-occurrence** graph for later recommendations.
39
+
40
+ **How to use:**
41
+
42
+ ```bash
43
+ export PYPI_PACKAGE_NAME=testpulse
44
+ pytest --testpulse-record
45
+ pytest --testpulse-record --testpulse-ci # quiet CI mode
46
+ pytest --testpulse-record --testpulse-store /path/to/store.sqlite3
47
+ ```
48
+
49
+ Default store: `.testpulse/store.sqlite3` (override with `TESTPULSE_STORE`).
50
+
51
+ ---
52
+
53
+ ### Recommended pytest command (`testtime run`)
54
+
55
+ **What it does:** Reads your git diff (or a commit range), maps changed files to pytest targets via **static heuristics** and **learned co-occurrence**, weights candidates by **expected reliability** from flakiness history, and prints one exact `pytest ...` line.
56
+
57
+ **How to use:**
58
+
59
+ ```bash
60
+ testtime run
61
+ testtime run --commit-range main..HEAD
62
+ testtime run --min-reliability 0.8 --store .testpulse/store.sqlite3
63
+ ```
64
+
65
+ Copy the printed command and run it in your shell.
66
+
67
+ ---
68
+
69
+ ### Flakiness estimates (Bayesian smoothing)
70
+
71
+ **What it does:** Per-test **flaky probability** (inconsistent pass/fail history), **confidence band** (low / medium / high by sample size), and **expected reliability** for ranking and skip-risk logic.
72
+
73
+ **How to use (library):**
74
+
75
+ ```python
76
+ from testpulse import Store, flakiness_estimates, flakiness_for_nodeid
77
+ from testpulse.paths import default_store_path
78
+
79
+ store = Store.open(default_store_path())
80
+ try:
81
+ for est in flakiness_estimates(store):
82
+ print(est.nodeid, est.flaky_probability, est.expected_reliability)
83
+ finally:
84
+ store.close()
85
+ ```
86
+
87
+ Also surfaced in `testtime report` and in recommendation / risk scoring.
88
+
89
+ ---
90
+
91
+ ### Skip and regret risk
92
+
93
+ **What it does:** For tests **not** included in the recommendation, computes a **regret risk** score (likelihood of missing a real failure) and an aggregate **risk level**: `low`, `medium`, `high`, or `critical`.
94
+
95
+ **How to use (library):**
96
+
97
+ ```python
98
+ from testpulse import compute_skip_risk_report
99
+
100
+ report = compute_skip_risk_report(["src/mod.py"], store)
101
+ print(report.aggregate_level, report.max_regret_risk)
102
+ for item in report.excluded:
103
+ print(item.target, item.regret_risk)
104
+ ```
105
+
106
+ Shown in `testtime report` (menu **6**) and `--no-interactive` skip-risk section.
107
+
108
+ ---
109
+
110
+ ### Regret hits
111
+
112
+ **What it does:** Across **consecutive recorded runs**, finds tests that **passed** in an earlier run but **failed** in a later run when diffs link through shared changed paths or learned co-occurrence—tests that “should have caught” a later breakage.
113
+
114
+ **How to use (library):**
115
+
116
+ ```python
117
+ from testpulse import regret_hits
118
+
119
+ for hit in regret_hits(store):
120
+ print(hit.nodeid, hit.prior_run_id, hit.later_run_id, hit.linking_path)
121
+ ```
122
+
123
+ Listed in `testtime report` (menu **5**).
124
+
125
+ ---
126
+
127
+ ### Interactive report (`testtime report`)
128
+
129
+ **What it does:** Terminal menu to explore:
130
+
131
+ 1. Slowest tests (average duration)
132
+ 2. Most flaky tests
133
+ 3. Most change-relevant tests (for current diff)
134
+ 4. Top drivers of today’s recommendation
135
+ 5. Regret hits
136
+ 6. Skip / regret risk summary
137
+
138
+ **Filter:** press **`f`** to filter by pytest **marker** or **class** name (parsed from stored markers and nodeids).
139
+
140
+ **How to use:**
141
+
142
+ ```bash
143
+ testtime report
144
+ testtime report --no-interactive # drivers + skip risk only, then exit
145
+ testtime report --commit-range HEAD~5..HEAD
146
+ ```
147
+
148
+ ---
149
+
150
+ ### Explanations with local citations (`testtime explain`)
151
+
152
+ **What it does:** For each included/excluded candidate, explains **why** and which **changed files** relate, with **citations** pointing at rows in your SQLite history (heuristic maps, co-occurrence links, outcome counts)—no external datasets.
153
+
154
+ **Optional LLM:** With `--llm` and your own API key, adds a short narrative constrained to those citations only.
155
+
156
+ **How to use:**
157
+
158
+ ```bash
159
+ testtime explain
160
+ testtime explain --llm
161
+ ```
162
+
163
+ Exit code **3** if `--llm` is requested but no API key is configured.
164
+
165
+ ---
166
+
167
+ ### CI stats export (de-identified)
168
+
169
+ **What it does:** Exports **aggregated** timing and outcome stats (run counts, outcome totals, duration percentiles, flaky buckets). **No** test logs, **no** raw nodeids in the JSON (only counts and hashed identifiers internally). Optional HTTPS upload when URL and token env vars are set.
170
+
171
+ **How to use:**
172
+
173
+ ```bash
174
+ testtime ci-export --output testpulse-ci-stats.json
175
+ testtime ci-export --upload
176
+ ```
177
+
178
+ GitHub Actions workflow `.github/workflows/testpulse-stats.yml` records (optional), exports JSON, and uploads an artifact.
179
+
180
+ ---
181
+
182
+ ### Brand assets
183
+
184
+ Static logo and favicon for documentation and packaging: `frontend/public/logo.svg`, `frontend/public/favicon.svg` (primary `#003366`, accent `#FFCC00`). Not a runnable UI.
185
+
186
+ ## Pages
187
+
188
+ TestPulse does **not** ship a browser frontend or routes. User-facing surfaces are:
189
+
190
+ | Surface | What you see |
191
+ |---------|----------------|
192
+ | **Terminal — `testtime report`** | Menu of report sections; optional marker/class filter prompts |
193
+ | **Terminal — other commands** | Single-shot stdout (pytest command, explanations, CI JSON path) |
194
+ | **Pytest session** | Optional line `testpulse: recorded N test(s)` when recording (hidden in `--testpulse-ci`) |
195
+
196
+ There are no `/login`, settings, or dashboard URLs—only the CLI and library APIs below.
197
+
198
+ ## API
199
+
200
+ There is **no REST backend**. The stable **Python public API** is exported from `testpulse` (defined in `testpulse.api`). Open a `Store` unless noted.
201
+
202
+ ### Package metadata
203
+
204
+ | Symbol | Purpose |
205
+ |--------|---------|
206
+ | `VERSION`, `tagline` | Version string and product tagline |
207
+ | `distribution_name()` | Read `PYPI_PACKAGE_NAME` |
208
+ | `import_package_name()` | Distribution name with `-` → `_` for imports |
209
+
210
+ ### Recording and storage
211
+
212
+ | Symbol | Purpose |
213
+ |--------|---------|
214
+ | `Store.open(path)` / `Store.connect_memory()` | SQLite store (`testpulse.store`) |
215
+ | Pytest plugin | Entry point `testpulse` — flags `--testpulse-record`, `--testpulse-ci`, `--testpulse-store` |
216
+
217
+ ### Recommendations
218
+
219
+ | Symbol | Purpose |
220
+ |--------|---------|
221
+ | `recommend_pytest_command(changed_paths, store, min_reliability=0.0)` | One `pytest ...` shell line |
222
+
223
+ ### Flakiness
224
+
225
+ | Symbol | Purpose |
226
+ |--------|---------|
227
+ | `flakiness_estimates(store)` | All tests with `FlakinessEstimate` |
228
+ | `flakiness_for_nodeid(store, nodeid)` | Single test or `None` |
229
+ | `flaky_probability(passes, fails)` | Smoothed flaky score |
230
+ | `beta_smoothed_pass_rate(passes, fails)` | Posterior mean pass rate |
231
+ | `confidence_band(passes, fails)` | `low` / `medium` / `high` |
232
+ | `estimate_from_counts(OutcomeCounts)` | Build `FlakinessEstimate` |
233
+
234
+ ### Skip / regret risk
235
+
236
+ | Symbol | Purpose |
237
+ |--------|---------|
238
+ | `compute_skip_risk_report(changed_paths, store, …)` | `SkipRiskReport` with excluded tests |
239
+ | `regret_risk(relevance, fail_probability, max_relevance=…)` | Single regret score |
240
+ | `aggregate_risk_level(excluded)` | Overall `RiskLevel` |
241
+
242
+ ### Regret hits
243
+
244
+ | Symbol | Purpose |
245
+ |--------|---------|
246
+ | `regret_hits(store)` | List of `RegretHit` across consecutive runs |
247
+
248
+ ### Reports
249
+
250
+ | Symbol | Purpose |
251
+ |--------|---------|
252
+ | `build_report_snapshot(changed_paths, store, …)` | `ReportSnapshot` for all sections |
253
+ | `run_interactive_report(changed_paths, store, …)` | Interactive menu |
254
+ | `render_report_view(snapshot, view, filter, …)` | Print one section (`slowest`, `flaky`, `relevant`, `drivers`, `regret`, `skip_risk`) |
255
+ | `ReportFilter`, `matches_filter`, `nodeid_class` | Marker/class filtering helpers |
256
+
257
+ ### Explanations and citations
258
+
259
+ | Symbol | Purpose |
260
+ |--------|---------|
261
+ | `explain_recommendation(changed_paths, store, …)` | Tuple of `TestDecisionExplanation` |
262
+ | `explain_with_optional_llm(…, client)` | Explanations plus optional LLM text |
263
+ | `build_test_explanation(store, paths, target, decision)` | One explanation |
264
+ | `gather_citations(store, paths, target)` | `HistoryCitation` rows from SQLite |
265
+ | `format_explanations_text(explanations)` | Plain-text rendering |
266
+ | `enhance_explanations_with_llm(…, client)` | LLM narrative from citations only |
267
+
268
+ ### LLM (optional, caller credentials)
269
+
270
+ | Symbol | Purpose |
271
+ |--------|---------|
272
+ | `llm_config_from_env()` | `LLMConfig` or `None` |
273
+ | `client_from_env()` | `OpenAICompatibleClient` or `None` |
274
+ | `OpenAICompatibleClient`, `LLMConfig` | OpenAI-compatible HTTP client (stdlib) |
275
+ | `LLMCompletionError` | Request / response errors |
276
+
277
+ ### CI stats
278
+
279
+ | Symbol | Purpose |
280
+ |--------|---------|
281
+ | `build_aggregated_ci_stats(store)` | `AggregatedCiStats` |
282
+ | `export_ci_stats_json(store)` | JSON string |
283
+ | `write_ci_stats_file(path, store)` | Write JSON file |
284
+ | `upload_ci_stats(store, endpoint_url=…, token=…)` | HTTPS POST |
285
+ | `upload_ci_stats_from_env(store)` | Upload if env configured; else `False` |
286
+ | `hash_nodeid(nodeid)` | De-identified hash (not for display of raw ids in export) |
287
+ | `CiUploadError` | Upload failures |
288
+
289
+ ### CLI entry point
290
+
291
+ | Command | Purpose |
292
+ |---------|---------|
293
+ | `testtime run` | Print recommended pytest command |
294
+ | `testtime report` | Interactive or `--no-interactive` report |
295
+ | `testtime explain` | Citations; optional `--llm` |
296
+ | `testtime ci-export` | De-identified JSON; optional `--upload` |
297
+ | `testtime health` | Deployment health JSON (`status`, `version`, `distribution`) |
298
+
299
+ Exit codes: **0** success, **1** failure, **2** usage, **3** missing optional config.
300
+
301
+ ## Configuration
302
+
303
+ Set variables in the environment (names only in `.env.example`; do not commit values).
304
+
305
+ | Variable | Required | Purpose |
306
+ |----------|----------|---------|
307
+ | `PYPI_PACKAGE_NAME` | No (recommended in CI) | Overrides distribution name; defaults to installed package metadata |
308
+ | `TESTPULSE_STORE` | No | SQLite path (default `.testpulse/store.sqlite3`) |
309
+
310
+ ### Third-party keys (optional; you provide your own)
311
+
312
+ | Variable | Feature | Where to get it |
313
+ |----------|---------|-----------------|
314
+ | `TESTPULSE_LLM_API_KEY` or `OPENAI_API_KEY` | LLM explanations (`testtime explain --llm`) | Your OpenAI or compatible provider account |
315
+ | `TESTPULSE_LLM_BASE_URL` | LLM API base URL | Provider docs (default OpenAI v1 URL) |
316
+ | `TESTPULSE_LLM_MODEL` | Chat model id | Provider model list (e.g. `gpt-4o-mini`) |
317
+ | `TESTPULSE_STATS_UPLOAD_URL` | CI stats HTTPS upload | Your webhook or ingest endpoint |
318
+ | `TESTPULSE_STATS_UPLOAD_TOKEN` | Bearer token for upload | Same service that issued the URL |
319
+ | `TESTPULSE_STATS_SALT` | Salt for hashed ids in CI export | Any secret string you choose (default internal salt if unset) |
320
+
321
+ GitHub Actions can store upload URL/token as repository secrets for `.github/workflows/testpulse-stats.yml`.
322
+
323
+ ## Deployment contract
324
+
325
+ After build, run `bash scripts/bootstrap_deployment_env.sh` (or use the manifest `install` command) so
326
+ `PYPI_PACKAGE_NAME` is set when infrastructure did not inject it. Smoke checks:
327
+ `python scripts/verify_deployment_environment.py` and `testtime health`.
328
+
329
+
330
+ TestPulse ships as a **PyPI package** with a CLI-first workflow and an optional stdlib HTTP API for deployment checks:
331
+
332
+ - `testtime health` — CLI health JSON
333
+ - `testtime serve` — `GET /api/health`, `POST /api/auth/sign-up`, `POST /api/auth/sign-in`, protected `GET /api/me` (set `JWT_SECRET`)
334
+ - `python scripts/deployment_environment_verification.py` — Factory env verification (loads committed `deployment.env`)
335
+
336
+ | Contract item | TestPulse behavior |
337
+ |---------------|-------------------|
338
+ | Install / build | `uv sync`, `uv build`; CI runs the same checks in `.github/workflows/ci.yml` |
339
+ | Runtime config | `PYPI_PACKAGE_NAME` optional at runtime; falls back to installed wheel / `pyproject.toml` name |
340
+ | Health / smoke | `testtime health` (JSON), `testtime --help`, and `uv run pytest` in CI |
341
+ | Optional upload | De-identified CI stats only when you set upload URL + token |
342
+
343
+ ## Run it locally
344
+
345
+ Requires **Python 3.11+**, **git**, and **uv**.
346
+
347
+ ```bash
348
+ export PYPI_PACKAGE_NAME=testpulse
349
+ python3 scripts/sync_project_name.py
350
+ uv sync
351
+ ```
352
+
353
+ Record history and get a recommendation:
354
+
355
+ ```bash
356
+ uv run pytest --testpulse-record
357
+ uv run testtime run
358
+ uv run testtime report --no-interactive
359
+ ```
360
+
361
+ Development checks:
362
+
363
+ ```bash
364
+ uv run ruff check .
365
+ uv run mypy src
366
+ uv run pytest
367
+ uv build
368
+ ```
369
+
370
+ Install the console script: `uv sync` exposes **`testtime`** in the project virtualenv.
371
+
372
+ ## Architecture
373
+
374
+ | Layer | Technology |
375
+ |-------|------------|
376
+ | Frontend | None (static SVG brand assets only) |
377
+ | Backend / HTTP API | None |
378
+ | Database | Local SQLite via stdlib (`testpulse.store`) |
379
+ | Vector database | None |
380
+ | Package manager | uv |
381
+ | CLI | `testtime` → `testpulse.cli` |
382
+ | Pytest integration | Entry point `testpulse` → `testpulse.recording` |
383
+
384
+ ### Repository layout
385
+
386
+ ```text
387
+ src/testpulse/
388
+ api.py # Public re-exports
389
+ cli.py # testtime subcommands
390
+ store.py # SQLite schema and queries
391
+ recording.py # Pytest hooks
392
+ git_diff.py # git diff → changed paths
393
+ heuristics.py # Static path → test mapping
394
+ recommend.py # Merge heuristics + co-occurrence → pytest command
395
+ flakiness.py # Bayesian flaky scores
396
+ risk.py # Skip / regret risk
397
+ regret_hits.py # Temporal pass-then-fail analysis
398
+ report.py # Report snapshot and TUI
399
+ citations.py # SQLite citation records
400
+ explanations.py # Template + optional LLM explanations
401
+ llm_client.py # Optional OpenAI-compatible client
402
+ ci_stats.py # De-identified CI export/upload
403
+ paths.py, config.py # Store path and PYPI_PACKAGE_NAME
404
+ tests/ # Unit and CLI tests
405
+ scripts/ # sync_project_name.py for PYPI_PACKAGE_NAME → pyproject name
406
+ .github/workflows/ # CI and optional TestPulse stats artifact
407
+ frontend/public/ # logo.svg, favicon.svg
408
+ ```
409
+
410
+ ### How components connect
411
+
412
+ ```text
413
+ pytest --testpulse-record
414
+ │
415
+ ▼
416
+ recording.py ──► Store (SQLite) ◄── git_diff.py (changed paths per run)
417
+ │ │
418
+ │ ├── learn change_test_links (co-occurrence)
419
+ │ └── test_results (timing, outcome, marker names)
420
+ │
421
+ testtime run / report / explain / ci-export
422
+ │
423
+ ├── recommend.py ← heuristics + co-occurrence + flakiness
424
+ ├── risk.py, regret_hits.py, report.py, explanations.py
425
+ └── ci_stats.py → JSON artifact (optional HTTPS upload)
426
+ ```
427
+
428
+ All learning and recommendations use **your machine’s SQLite file** only; nothing is sent unless you opt into LLM or CI upload with **your** credentials.
429
+
430
+ ## License
431
+
432
+ MIT