wup 0.2.1__tar.gz → 0.2.6__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.
@@ -1,11 +1,11 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wup
3
- Version: 0.2.1
3
+ Version: 0.2.6
4
4
  Summary: WUP (What's Up) - Intelligent file watcher for regression testing in large projects
5
5
  Author-email: Tom Sapletta <tom@sapletta.com>
6
6
  License-Expression: Apache-2.0
7
- Project-URL: Homepage, https://github.com/yourusername/wup
8
- Project-URL: Repository, https://github.com/yourusername/wup
7
+ Project-URL: Homepage, https://github.com/semcod/wup
8
+ Project-URL: Repository, https://github.com/semcod/wup
9
9
  Keywords: wup,watcher,testing,regression,file-monitoring
10
10
  Classifier: Development Status :: 3 - Alpha
11
11
  Classifier: Intended Audience :: Developers
@@ -28,17 +28,17 @@ Dynamic: license-file
28
28
 
29
29
  ## AI Cost Tracking
30
30
 
31
- ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.2.1-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
32
- ![AI Cost](https://img.shields.io/badge/AI%20Cost-$0.75-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-2.0h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
31
+ ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.2.6-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
32
+ ![AI Cost](https://img.shields.io/badge/AI%20Cost-$1.05-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-2.0h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
33
33
 
34
- - 🤖 **LLM usage:** $0.7500 (5 commits)
34
+ - 🤖 **LLM usage:** $1.0500 (7 commits)
35
35
  - 👤 **Human dev:** ~$200 (2.0h @ $100/h, 30min dedup)
36
36
 
37
37
  Generated on 2026-04-29 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
38
38
 
39
39
  ---
40
40
 
41
- ![PyPI](https://img.shields.io/badge/pypi-wup-blue) ![Version](https://img.shields.io/badge/version-0.2.1-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
41
+ ![PyPI](https://img.shields.io/badge/pypi-wup-blue) ![Version](https://img.shields.io/badge/version-0.2.6-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
42
42
 
43
43
  **WUP (What's Up)** - Intelligent file watcher for regression testing in large projects.
44
44
 
@@ -56,6 +56,9 @@ WUP monitors file changes and runs intelligent regression tests using a 3-layer
56
56
  - 🔍 **Dependency Mapping**: Automatic detection of files → endpoints → services
57
57
  - 🚀 **Framework Support**: FastAPI, Flask, Django, Express.js, and more
58
58
  - 📝 **Blame Reports**: Detailed regression reports with file/line/commit info
59
+ - ⚙️ **Configuration System**: Declarative configuration via `wup.yaml` file
60
+ - 🎛️ **Per-Service Settings**: Custom test strategies per service
61
+ - 🧪 **TestQL Integration**: Native support for TestQL scenarios
59
62
 
60
63
  ## Installation
61
64
 
@@ -70,13 +73,16 @@ pip install -e ".[dev]"
70
73
  ## Quick Start
71
74
 
72
75
  ```bash
73
- # 1. Build dependency map (one-time setup)
76
+ # 1. Initialize configuration (optional)
77
+ wup init
78
+
79
+ # 2. Build dependency map (one-time setup)
74
80
  wup map-deps ./my-project
75
81
 
76
- # 2. Start watching for changes
82
+ # 3. Start watching for changes
77
83
  wup watch ./my-project
78
84
 
79
- # 3. Start with live dashboard
85
+ # 4. Start with live dashboard
80
86
  wup watch ./my-project --dashboard
81
87
  ```
82
88
 
@@ -98,7 +104,7 @@ wup map-deps ./my-project --output my-deps.json
98
104
  ### Watch Project
99
105
 
100
106
  ```bash
101
- # Basic watching
107
+ # Basic watching (uses wup.yaml if present)
102
108
  wup watch ./my-project
103
109
 
104
110
  # With custom settings
@@ -109,6 +115,25 @@ wup watch ./my-project \
109
115
 
110
116
  # With live dashboard
111
117
  wup watch ./my-project --dashboard
118
+
119
+ # Use specific config file
120
+ wup watch ./my-project --config custom-config.yaml
121
+
122
+ # TestQL mode
123
+ wup watch ./my-project --mode testql
124
+
125
+ # Discover endpoints from TestQL scenarios
126
+ wup testql-endpoints /path/to/scenarios --output testql-deps.json
127
+ ```
128
+
129
+ ### Initialize Configuration
130
+
131
+ ```bash
132
+ # Generate default wup.yaml configuration
133
+ wup init
134
+
135
+ # Generate with custom output path
136
+ wup init --output .wup.yaml
112
137
  ```
113
138
 
114
139
  ### Check Status
@@ -127,24 +152,24 @@ wup status --deps my-deps.json
127
152
 
128
153
  ```
129
154
  ┌─────────────────────────────────────────────────────────────┐
130
- │ DETECTION LAYER
155
+ │ DETECTION LAYER
131
156
  │ File watching with watchdog + heuristics │
132
157
  │ Skips: .git, __pycache__, node_modules, .venv │
133
158
  └──────────────────────┬──────────────────────────────────────┘
134
159
  │ File change
135
160
 
136
161
  ┌─────────────────────────────────────────────────────────────┐
137
- │ PRIORITY LAYER
162
+ │ PRIORITY LAYER
138
163
  │ Quick test: 3 endpoints max per service │
139
- │ Duration: ~1-2 seconds
140
- │ Result: Pass → Done, Fail → Escalate
164
+ │ Duration: ~1-2 seconds
165
+ │ Result: Pass → Done, Fail → Escalate
141
166
  └──────────────────────┬──────────────────────────────────────┘
142
167
  │ Failure
143
168
 
144
169
  ┌─────────────────────────────────────────────────────────────┐
145
- │ DETAIL LAYER
170
+ │ DETAIL LAYER
146
171
  │ Full test: All endpoints with blame report │
147
- │ Duration: ~3-5 seconds
172
+ │ Duration: ~3-5 seconds
148
173
  │ Result: Regression report with file/line/commit │
149
174
  └─────────────────────────────────────────────────────────────┘
150
175
  ```
@@ -160,15 +185,94 @@ Full regression: 15s test → 15% CPU spike
160
185
 
161
186
  ## Configuration
162
187
 
188
+ ### wup.yaml Configuration File
189
+
190
+ WUP supports declarative configuration via `wup.yaml` (or `.wup.yaml`) in your project root. This allows you to define watch paths, service-specific settings, and test strategies.
191
+
192
+ Generate a default configuration:
193
+
194
+ ```bash
195
+ wup init
196
+ ```
197
+
198
+ Example `wup.yaml`:
199
+
200
+ ```yaml
201
+ project:
202
+ name: "my-project"
203
+ description: "My awesome project"
204
+
205
+ watch:
206
+ # Folders to watch (supports glob patterns)
207
+ paths:
208
+ - "app/**"
209
+ - "src/**"
210
+ - "routes/**"
211
+ - "!tests/**" # exclusion
212
+ - "!node_modules/**"
213
+
214
+ # Global exclude patterns
215
+ exclude_patterns:
216
+ - "*.md"
217
+ - "*.txt"
218
+ - "migrations/**"
219
+
220
+ # File types to watch (empty = watch all files)
221
+ # Only changes to these file extensions will trigger tests
222
+ file_types:
223
+ - ".py"
224
+ - ".ts"
225
+ - ".jsx"
226
+
227
+ services:
228
+ # Service configurations - simplified with auto-detection
229
+ # If paths are empty, WUP auto-detects files by service name
230
+
231
+ - name: "users-shell"
232
+ type: "shell"
233
+ # Auto-detects files containing "users-shell"
234
+
235
+ - name: "users-web"
236
+ type: "web"
237
+ # Auto-detects files containing "users-web"
238
+ # Will detect coincidence with users-shell
239
+
240
+ # Or use explicit paths (old style still works)
241
+ - name: "payments"
242
+ paths:
243
+ - "app/payments/**"
244
+ type: "auto"
245
+
246
+ test_strategy:
247
+ quick:
248
+ debounce_s: 2
249
+ max_queue: 5
250
+ timeout_s: 10
251
+ detail:
252
+ debounce_s: 10
253
+ max_queue: 1
254
+ timeout_s: 30
255
+
256
+ testql:
257
+ # TestQL-specific configuration
258
+ scenario_dir: "scenarios/tests"
259
+ smoke_scenario: "smoke.testql.toon.yaml"
260
+ output_format: "json"
261
+ extra_args:
262
+ - "--timeout 10s"
263
+ ```
264
+
163
265
  ### CLI Options
164
266
 
165
267
  | Option | Default | Description |
166
268
  |--------|---------|-------------|
269
+ | `--config` | auto | Path to wup.yaml config file |
167
270
  | `--cpu-throttle` | 0.8 | CPU usage threshold (0.0-1.0) |
168
271
  | `--debounce` | 2 | Debounce time in seconds |
169
272
  | `--cooldown` | 300 | Test cooldown in seconds |
170
273
  | `--dashboard` | false | Enable live dashboard |
171
274
  | `--deps` | deps.json | Dependency map file path |
275
+ | `--mode` | default | Watcher mode: default or testql |
172
276
 
173
277
  ### Environment Variables
174
278
 
@@ -213,11 +317,21 @@ async def run_detail_test(self, service: str, endpoints: List[str]) -> Dict:
213
317
  wup/
214
318
  ├── wup/
215
319
  │ ├── __init__.py # Package exports
320
+ │ ├── config.py # Configuration loader
321
+ │ ├── models/
322
+ │ │ ├── __init__.py # Models package
323
+ │ │ └── config.py # Configuration dataclasses
216
324
  │ ├── core.py # WupWatcher implementation
217
325
  │ ├── dependency_mapper.py # Dependency mapping logic
326
+ │ ├── testql_watcher.py # TestQL integration
218
327
  │ └── cli.py # CLI interface
219
328
  ├── tests/
220
329
  │ └── test_wup.py # Unit tests
330
+ ├── docs/
331
+ │ ├── 2.md # Refactoring documentation
332
+ │ ├── 3.md # Configuration plan
333
+ │ └── TESTQL_INTEGRATION.md # TestQL integration docs
334
+ ├── wup.yaml.example # Example configuration
221
335
  ├── pyproject.toml # Package configuration
222
336
  └── README.md # This file
223
337
  ```
@@ -3,17 +3,17 @@
3
3
 
4
4
  ## AI Cost Tracking
5
5
 
6
- ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.2.1-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
7
- ![AI Cost](https://img.shields.io/badge/AI%20Cost-$0.75-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-2.0h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
6
+ ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.2.6-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
7
+ ![AI Cost](https://img.shields.io/badge/AI%20Cost-$1.05-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-2.0h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
8
8
 
9
- - 🤖 **LLM usage:** $0.7500 (5 commits)
9
+ - 🤖 **LLM usage:** $1.0500 (7 commits)
10
10
  - 👤 **Human dev:** ~$200 (2.0h @ $100/h, 30min dedup)
11
11
 
12
12
  Generated on 2026-04-29 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
13
13
 
14
14
  ---
15
15
 
16
- ![PyPI](https://img.shields.io/badge/pypi-wup-blue) ![Version](https://img.shields.io/badge/version-0.2.1-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
16
+ ![PyPI](https://img.shields.io/badge/pypi-wup-blue) ![Version](https://img.shields.io/badge/version-0.2.6-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
17
17
 
18
18
  **WUP (What's Up)** - Intelligent file watcher for regression testing in large projects.
19
19
 
@@ -31,6 +31,9 @@ WUP monitors file changes and runs intelligent regression tests using a 3-layer
31
31
  - 🔍 **Dependency Mapping**: Automatic detection of files → endpoints → services
32
32
  - 🚀 **Framework Support**: FastAPI, Flask, Django, Express.js, and more
33
33
  - 📝 **Blame Reports**: Detailed regression reports with file/line/commit info
34
+ - ⚙️ **Configuration System**: Declarative configuration via `wup.yaml` file
35
+ - 🎛️ **Per-Service Settings**: Custom test strategies per service
36
+ - 🧪 **TestQL Integration**: Native support for TestQL scenarios
34
37
 
35
38
  ## Installation
36
39
 
@@ -45,13 +48,16 @@ pip install -e ".[dev]"
45
48
  ## Quick Start
46
49
 
47
50
  ```bash
48
- # 1. Build dependency map (one-time setup)
51
+ # 1. Initialize configuration (optional)
52
+ wup init
53
+
54
+ # 2. Build dependency map (one-time setup)
49
55
  wup map-deps ./my-project
50
56
 
51
- # 2. Start watching for changes
57
+ # 3. Start watching for changes
52
58
  wup watch ./my-project
53
59
 
54
- # 3. Start with live dashboard
60
+ # 4. Start with live dashboard
55
61
  wup watch ./my-project --dashboard
56
62
  ```
57
63
 
@@ -73,7 +79,7 @@ wup map-deps ./my-project --output my-deps.json
73
79
  ### Watch Project
74
80
 
75
81
  ```bash
76
- # Basic watching
82
+ # Basic watching (uses wup.yaml if present)
77
83
  wup watch ./my-project
78
84
 
79
85
  # With custom settings
@@ -84,6 +90,25 @@ wup watch ./my-project \
84
90
 
85
91
  # With live dashboard
86
92
  wup watch ./my-project --dashboard
93
+
94
+ # Use specific config file
95
+ wup watch ./my-project --config custom-config.yaml
96
+
97
+ # TestQL mode
98
+ wup watch ./my-project --mode testql
99
+
100
+ # Discover endpoints from TestQL scenarios
101
+ wup testql-endpoints /path/to/scenarios --output testql-deps.json
102
+ ```
103
+
104
+ ### Initialize Configuration
105
+
106
+ ```bash
107
+ # Generate default wup.yaml configuration
108
+ wup init
109
+
110
+ # Generate with custom output path
111
+ wup init --output .wup.yaml
87
112
  ```
88
113
 
89
114
  ### Check Status
@@ -102,24 +127,24 @@ wup status --deps my-deps.json
102
127
 
103
128
  ```
104
129
  ┌─────────────────────────────────────────────────────────────┐
105
- │ DETECTION LAYER
130
+ │ DETECTION LAYER
106
131
  │ File watching with watchdog + heuristics │
107
132
  │ Skips: .git, __pycache__, node_modules, .venv │
108
133
  └──────────────────────┬──────────────────────────────────────┘
109
134
  │ File change
110
135
 
111
136
  ┌─────────────────────────────────────────────────────────────┐
112
- │ PRIORITY LAYER
137
+ │ PRIORITY LAYER
113
138
  │ Quick test: 3 endpoints max per service │
114
- │ Duration: ~1-2 seconds
115
- │ Result: Pass → Done, Fail → Escalate
139
+ │ Duration: ~1-2 seconds
140
+ │ Result: Pass → Done, Fail → Escalate
116
141
  └──────────────────────┬──────────────────────────────────────┘
117
142
  │ Failure
118
143
 
119
144
  ┌─────────────────────────────────────────────────────────────┐
120
- │ DETAIL LAYER
145
+ │ DETAIL LAYER
121
146
  │ Full test: All endpoints with blame report │
122
- │ Duration: ~3-5 seconds
147
+ │ Duration: ~3-5 seconds
123
148
  │ Result: Regression report with file/line/commit │
124
149
  └─────────────────────────────────────────────────────────────┘
125
150
  ```
@@ -135,15 +160,94 @@ Full regression: 15s test → 15% CPU spike
135
160
 
136
161
  ## Configuration
137
162
 
163
+ ### wup.yaml Configuration File
164
+
165
+ WUP supports declarative configuration via `wup.yaml` (or `.wup.yaml`) in your project root. This allows you to define watch paths, service-specific settings, and test strategies.
166
+
167
+ Generate a default configuration:
168
+
169
+ ```bash
170
+ wup init
171
+ ```
172
+
173
+ Example `wup.yaml`:
174
+
175
+ ```yaml
176
+ project:
177
+ name: "my-project"
178
+ description: "My awesome project"
179
+
180
+ watch:
181
+ # Folders to watch (supports glob patterns)
182
+ paths:
183
+ - "app/**"
184
+ - "src/**"
185
+ - "routes/**"
186
+ - "!tests/**" # exclusion
187
+ - "!node_modules/**"
188
+
189
+ # Global exclude patterns
190
+ exclude_patterns:
191
+ - "*.md"
192
+ - "*.txt"
193
+ - "migrations/**"
194
+
195
+ # File types to watch (empty = watch all files)
196
+ # Only changes to these file extensions will trigger tests
197
+ file_types:
198
+ - ".py"
199
+ - ".ts"
200
+ - ".jsx"
201
+
202
+ services:
203
+ # Service configurations - simplified with auto-detection
204
+ # If paths are empty, WUP auto-detects files by service name
205
+
206
+ - name: "users-shell"
207
+ type: "shell"
208
+ # Auto-detects files containing "users-shell"
209
+
210
+ - name: "users-web"
211
+ type: "web"
212
+ # Auto-detects files containing "users-web"
213
+ # Will detect coincidence with users-shell
214
+
215
+ # Or use explicit paths (old style still works)
216
+ - name: "payments"
217
+ paths:
218
+ - "app/payments/**"
219
+ type: "auto"
220
+
221
+ test_strategy:
222
+ quick:
223
+ debounce_s: 2
224
+ max_queue: 5
225
+ timeout_s: 10
226
+ detail:
227
+ debounce_s: 10
228
+ max_queue: 1
229
+ timeout_s: 30
230
+
231
+ testql:
232
+ # TestQL-specific configuration
233
+ scenario_dir: "scenarios/tests"
234
+ smoke_scenario: "smoke.testql.toon.yaml"
235
+ output_format: "json"
236
+ extra_args:
237
+ - "--timeout 10s"
238
+ ```
239
+
138
240
  ### CLI Options
139
241
 
140
242
  | Option | Default | Description |
141
243
  |--------|---------|-------------|
244
+ | `--config` | auto | Path to wup.yaml config file |
142
245
  | `--cpu-throttle` | 0.8 | CPU usage threshold (0.0-1.0) |
143
246
  | `--debounce` | 2 | Debounce time in seconds |
144
247
  | `--cooldown` | 300 | Test cooldown in seconds |
145
248
  | `--dashboard` | false | Enable live dashboard |
146
249
  | `--deps` | deps.json | Dependency map file path |
250
+ | `--mode` | default | Watcher mode: default or testql |
147
251
 
148
252
  ### Environment Variables
149
253
 
@@ -188,11 +292,21 @@ async def run_detail_test(self, service: str, endpoints: List[str]) -> Dict:
188
292
  wup/
189
293
  ├── wup/
190
294
  │ ├── __init__.py # Package exports
295
+ │ ├── config.py # Configuration loader
296
+ │ ├── models/
297
+ │ │ ├── __init__.py # Models package
298
+ │ │ └── config.py # Configuration dataclasses
191
299
  │ ├── core.py # WupWatcher implementation
192
300
  │ ├── dependency_mapper.py # Dependency mapping logic
301
+ │ ├── testql_watcher.py # TestQL integration
193
302
  │ └── cli.py # CLI interface
194
303
  ├── tests/
195
304
  │ └── test_wup.py # Unit tests
305
+ ├── docs/
306
+ │ ├── 2.md # Refactoring documentation
307
+ │ ├── 3.md # Configuration plan
308
+ │ └── TESTQL_INTEGRATION.md # TestQL integration docs
309
+ ├── wup.yaml.example # Example configuration
196
310
  ├── pyproject.toml # Package configuration
197
311
  └── README.md # This file
198
312
  ```
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "wup"
7
- version = "0.2.1"
7
+ version = "0.2.6"
8
8
  description = "WUP (What's Up) - Intelligent file watcher for regression testing in large projects"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -33,8 +33,8 @@ classifiers = [
33
33
  wup = "wup.cli:app"
34
34
 
35
35
  [project.urls]
36
- Homepage = "https://github.com/yourusername/wup"
37
- Repository = "https://github.com/yourusername/wup"
36
+ Homepage = "https://github.com/semcod/wup"
37
+ Repository = "https://github.com/semcod/wup"
38
38
 
39
39
  [tool.pfix]
40
40
  # Self-healing Python configuration
@@ -7,7 +7,7 @@ WUP monitors file changes and runs intelligent regression tests using a 3-layer
7
7
  3. Detail Layer: Full tests with blame reports (only on failure)
8
8
  """
9
9
 
10
- __version__ = "0.2.1"
10
+ __version__ = "0.2.6"
11
11
  __author__ = "Tom Sapletta"
12
12
 
13
13
  from .config import load_config, save_config, get_default_config
@@ -263,6 +263,69 @@ def init(
263
263
  console.print(f"[dim]Edit this file to customize your WUP setup[/dim]")
264
264
 
265
265
 
266
+ @app.command()
267
+ def testql_endpoints(
268
+ scenarios_dir: str = typer.Argument(..., help="Path to TestQL scenarios directory"),
269
+ output: str = typer.Option("testql-deps.json", "--output", "-o", help="Output dependency map file path"),
270
+ testql_bin: str = typer.Option("testql", "--testql-bin", help="TestQL executable name or path"),
271
+ ):
272
+ """
273
+ Discover endpoints from TestQL scenario files and build dependency map.
274
+ """
275
+ from .testql_discovery import TestQLEndpointDiscovery
276
+ from rich.table import Table
277
+
278
+ scenarios_path = Path(scenarios_dir)
279
+
280
+ if not scenarios_path.exists():
281
+ console.print(f"[red]Error: Scenarios directory '{scenarios_dir}' does not exist[/red]")
282
+ raise typer.Exit(1)
283
+
284
+ console.print(f"[cyan]🔍 Discovering endpoints from TestQL scenarios...[/cyan]")
285
+ console.print(f"[dim]Scenarios directory: {scenarios_dir}[/dim]")
286
+ console.print()
287
+
288
+ discovery = TestQLEndpointDiscovery(scenarios_dir, testql_bin)
289
+ dependency_map = discovery.to_dependency_map()
290
+
291
+ # Display results
292
+ table = Table(title="Discovered Endpoints")
293
+ table.add_column("Service", style="cyan")
294
+ table.add_column("Endpoints", style="green")
295
+ table.add_column("Scenarios", style="yellow")
296
+
297
+ total_endpoints = 0
298
+ total_scenarios = 0
299
+
300
+ for service, info in sorted(dependency_map.get("services", {}).items()):
301
+ endpoints_count = len(info.get("endpoints", []))
302
+ scenarios_count = len(info.get("scenarios", []))
303
+ total_endpoints += endpoints_count
304
+ total_scenarios += scenarios_count
305
+
306
+ table.add_row(
307
+ service,
308
+ str(endpoints_count),
309
+ str(scenarios_count)
310
+ )
311
+
312
+ console.print(table)
313
+ console.print()
314
+ console.print(f"[bold]Summary:[/bold]")
315
+ console.print(f" Services: {len(dependency_map.get('services', {}))}")
316
+ console.print(f" Total endpoints: {total_endpoints}")
317
+ console.print(f" Total scenarios: {total_scenarios}")
318
+ console.print()
319
+
320
+ # Save to file
321
+ import json
322
+ output_path = Path(output)
323
+ with open(output_path, 'w') as f:
324
+ json.dump(dependency_map, f, indent=2)
325
+
326
+ console.print(f"[green]✓ Dependency map saved to {output_path}[/green]")
327
+
328
+
266
329
  @app.command()
267
330
  def version():
268
331
  """Show WUP version."""
@@ -99,7 +99,8 @@ def validate_config(raw: dict) -> WupConfig:
99
99
  watch_raw = raw.get("watch", {})
100
100
  watch = WatchConfig(
101
101
  paths=watch_raw.get("paths", []),
102
- exclude_patterns=watch_raw.get("exclude_patterns", ["*.md", "*.txt"])
102
+ exclude_patterns=watch_raw.get("exclude_patterns", ["*.md", "*.txt"]),
103
+ file_types=watch_raw.get("file_types", [])
103
104
  )
104
105
 
105
106
  # Parse services
@@ -117,6 +118,7 @@ def validate_config(raw: dict) -> WupConfig:
117
118
  name=svc_raw["name"],
118
119
  root=svc_raw.get("root", ""),
119
120
  paths=svc_raw.get("paths", []),
121
+ type=svc_raw.get("type", "auto"),
120
122
  quick_tests=ServiceTestConfig(
121
123
  scope=quick_tests_raw.get("scope", "all"),
122
124
  max_endpoints=quick_tests_raw.get("max_endpoints", 10)
@@ -147,7 +149,8 @@ def validate_config(raw: dict) -> WupConfig:
147
149
  scenario_dir=testql_raw.get("scenario_dir", "scenarios/tests"),
148
150
  smoke_scenario=testql_raw.get("smoke_scenario", "smoke.testql.toon.yaml"),
149
151
  output_format=testql_raw.get("output_format", "json"),
150
- extra_args=testql_raw.get("extra_args", ["--timeout 10s"])
152
+ extra_args=testql_raw.get("extra_args", ["--timeout 10s"]),
153
+ endpoint_discovery=testql_raw.get("endpoint_discovery", True)
151
154
  )
152
155
 
153
156
  return WupConfig(
@@ -107,9 +107,7 @@ class WupWatcher:
107
107
  """
108
108
  Infer service name from file path.
109
109
 
110
- Examples:
111
- app/users/routes.py → "app/users"
112
- src/components/auth.ts → "src/components"
110
+ Uses config services first, then dependency mapper, then heuristics.
113
111
  """
114
112
  rel_path = self._to_relative_path(file_path)
115
113
  parts = rel_path.parts
@@ -117,10 +115,17 @@ class WupWatcher:
117
115
  # Try to match against configured services first
118
116
  if self.config.services:
119
117
  for svc in self.config.services:
120
- for svc_path in svc.paths:
121
- # Convert glob pattern to simple check
122
- if str(rel_path).startswith(svc_path.replace("**", "")):
123
- return svc.name
118
+ if svc.paths:
119
+ # Use explicit paths if provided
120
+ for svc_path in svc.paths:
121
+ if str(rel_path).startswith(svc_path.replace("**", "")):
122
+ return svc.name
123
+ else:
124
+ # Auto-detect: check if service name appears in path
125
+ service_name_parts = svc.name.replace("/", " ").replace("-", " ").split()
126
+ for part in service_name_parts:
127
+ if part.lower() in str(rel_path).lower():
128
+ return svc.name
124
129
 
125
130
  # Use dependency mapper if available
126
131
  service = self.dependency_mapper.get_service_for_file(file_path)
@@ -133,6 +138,75 @@ class WupWatcher:
133
138
 
134
139
  return None
135
140
 
141
+ def detect_service_coincidences(self, changed_service: str) -> List[str]:
142
+ """
143
+ Detect coincidences between services (e.g., shell <-> web).
144
+
145
+ When a service changes, this finds related services that should also be tested.
146
+
147
+ Args:
148
+ changed_service: The service that changed
149
+
150
+ Returns:
151
+ List of related services that should also be tested
152
+ """
153
+ related_services = []
154
+
155
+ if not self.config.services:
156
+ return related_services
157
+
158
+ # Get the changed service config
159
+ changed_svc_config = None
160
+ for svc in self.config.services:
161
+ if svc.name == changed_service:
162
+ changed_svc_config = svc
163
+ break
164
+
165
+ if not changed_svc_config:
166
+ return related_services
167
+
168
+ # Find coincidences based on service type
169
+ for svc in self.config.services:
170
+ if svc.name == changed_service:
171
+ continue
172
+
173
+ # Coincidence: shell <-> web for same domain
174
+ if changed_svc_config.type != "auto" and svc.type != "auto":
175
+ # If both have explicit types, check for opposites
176
+ if (changed_svc_config.type == "shell" and svc.type == "web") or \
177
+ (changed_svc_config.type == "web" and svc.type == "shell"):
178
+ # Check if they share a common domain (same base name)
179
+ if self._services_share_domain(changed_service, svc.name):
180
+ related_services.append(svc.name)
181
+
182
+ # Coincidence: auto-detect by name similarity
183
+ elif changed_svc_config.type == "auto" or svc.type == "auto":
184
+ if self._services_share_domain(changed_service, svc.name):
185
+ related_services.append(svc.name)
186
+
187
+ return related_services
188
+
189
+ def _services_share_domain(self, service1: str, service2: str) -> bool:
190
+ """
191
+ Check if two services share a common domain/base name.
192
+
193
+ Examples:
194
+ users-shell and users-web -> True
195
+ api/auth and api/users -> False
196
+ payments and payments-shell -> True
197
+ """
198
+ # Extract base names (remove type suffixes like -shell, -web)
199
+ def extract_base(name: str) -> str:
200
+ for suffix in ["-shell", "-web", "_shell", "_web"]:
201
+ if name.endswith(suffix):
202
+ return name[:-len(suffix)]
203
+ return name
204
+
205
+ base1 = extract_base(service1)
206
+ base2 = extract_base(service2)
207
+
208
+ return base1 == base2
209
+
136
210
  def get_service_config(self, service_name: str) -> Optional[ServiceConfig]:
137
211
  """
138
212
  Get service configuration by name.
@@ -316,6 +390,17 @@ class WupWatcher:
316
390
  if pattern in str(rel_path):
317
391
  return
318
392
 
393
+ # Filter by file type if specified in config
394
+ if self.config.watch.file_types:
395
+ # Ensure file extensions start with dot
396
+ file_ext = rel_path.suffix if rel_path.suffix else ""
397
+ if not file_ext.startswith("."):
398
+ file_ext = f".{file_ext}"
399
+
400
+ # Check if file extension matches any of the configured types
401
+ if file_ext not in self.config.watch.file_types:
402
+ return
403
+
319
404
  # Infer service from file path
320
405
  service = self.infer_service(file_path)
321
406
 
@@ -14,6 +14,7 @@ from pathlib import Path
14
14
  from typing import Dict, List, Set, Optional
15
15
  from collections import defaultdict
16
16
  import re
17
+ from .testql_discovery import TestQLEndpointDiscovery
17
18
 
18
19
 
19
20
  class DependencyMapper:
@@ -257,3 +258,27 @@ class DependencyMapper:
257
258
  self.service_to_files[service] = set(info.get("files", []))
258
259
 
259
260
  self.file_to_endpoints = defaultdict(list, data.get("files", {}))
261
+
262
+ def build_from_testql_scenarios(self, scenarios_dir: str, testql_bin: str = "testql") -> Dict:
263
+ """
264
+ Build dependency map from TestQL scenario files.
265
+
266
+ Args:
267
+ scenarios_dir: Path to TestQL scenarios directory
268
+ testql_bin: TestQL executable name or path
269
+
270
+ Returns:
271
+ Dictionary containing the full dependency map
272
+ """
273
+ discovery = TestQLEndpointDiscovery(scenarios_dir, testql_bin)
274
+ dependency_map = discovery.to_dependency_map()
275
+
276
+ # Merge with existing mappings
277
+ for service, info in dependency_map.get("services", {}).items():
278
+ self.service_to_endpoints[service].extend(info["endpoints"])
279
+ self.service_to_files[service].update(info["files"])
280
+
281
+ for file_path, endpoints in dependency_map.get("files", {}).items():
282
+ self.file_to_endpoints[file_path].extend(endpoints)
283
+
284
+ return self.to_dict()
@@ -27,8 +27,9 @@ class ServiceTestConfig:
27
27
  class ServiceConfig:
28
28
  """Configuration for a single service."""
29
29
  name: str
30
- root: str
31
- paths: List[str] = field(default_factory=list)
30
+ root: str = "" # Optional - auto-detected if empty
31
+ paths: List[str] = field(default_factory=list) # Optional - auto-detected if empty
32
+ type: str = "auto" # "web", "shell", "auto" - for coincidence detection
32
33
  quick_tests: ServiceTestConfig = field(default_factory=ServiceTestConfig)
33
34
  detail_tests: ServiceTestConfig = field(default_factory=ServiceTestConfig)
34
35
  cpu_throttle: float = 0.8
@@ -40,6 +41,7 @@ class WatchConfig:
40
41
  """Configuration for file watching."""
41
42
  paths: List[str] = field(default_factory=list)
42
43
  exclude_patterns: List[str] = field(default_factory=lambda: ["*.md", "*.txt"])
44
+ file_types: List[str] = field(default_factory=list) # e.g., [".py", ".ts", ".jsx"]
43
45
 
44
46
 
45
47
  @dataclass
@@ -56,6 +58,7 @@ class TestQLConfig:
56
58
  smoke_scenario: str = "smoke.testql.toon.yaml"
57
59
  output_format: str = "json"
58
60
  extra_args: List[str] = field(default_factory=lambda: ["--timeout 10s"])
61
+ endpoint_discovery: bool = True # Enable automatic endpoint discovery from scenarios
59
62
 
60
63
 
61
64
  @dataclass
@@ -0,0 +1,229 @@
1
+ """
2
+ TestQL endpoint discovery module.
3
+
4
+ Discovers API endpoints from TestQL scenario files (.testql.toon.yaml)
5
+ and maps them to services for intelligent testing.
6
+ """
7
+
8
+ import re
9
+ import subprocess
10
+ from pathlib import Path
11
+ from typing import Dict, List, Optional, Set
12
+
13
+ import yaml
14
+
15
+
16
+ class TestQLEndpointDiscovery:
17
+ """Discover endpoints from TestQL scenario files."""
18
+
19
+ def __init__(self, scenarios_dir: str, testql_bin: str = "testql"):
20
+ """
21
+ Initialize TestQL endpoint discovery.
22
+
23
+ Args:
24
+ scenarios_dir: Path to TestQL scenarios directory
25
+ testql_bin: TestQL executable name or path
26
+ """
27
+ self.scenarios_dir = Path(scenarios_dir)
28
+ self.testql_bin = testql_bin
29
+ self.endpoints_by_service: Dict[str, Set[str]] = {}
30
+ self.scenarios_by_service: Dict[str, List[Path]] = {}
31
+
32
+ def discover_scenarios(self) -> List[Path]:
33
+ """
34
+ Find all TestQL scenario files.
35
+
36
+ Returns:
37
+ List of paths to .testql.toon.yaml files
38
+ """
39
+ if not self.scenarios_dir.exists():
40
+ return []
41
+
42
+ return sorted(self.scenarios_dir.rglob("*.testql.toon.yaml"))
43
+
44
+ def parse_scenario_endpoints(self, scenario_path: Path) -> List[str]:
45
+ """
46
+ Extract endpoints from a TestQL scenario file.
47
+
48
+ Args:
49
+ scenario_path: Path to scenario file
50
+
51
+ Returns:
52
+ List of endpoint paths found in the scenario
53
+ """
54
+ endpoints = []
55
+
56
+ try:
57
+ with open(scenario_path, 'r') as f:
58
+ content = f.read()
59
+
60
+ # Parse TestQL API blocks
61
+ # Pattern: METHOD, /path
62
+ api_pattern = re.compile(r'^\s*(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS)\s*,\s*([^\s,]+)', re.MULTILINE)
63
+ matches = api_pattern.findall(content)
64
+
65
+ for method, path in matches:
66
+ endpoints.append(f"{method.upper()} {path}")
67
+
68
+ # Also try parsing as YAML to extract structured data
69
+ try:
70
+ data = yaml.safe_load(content)
71
+ if data and isinstance(data, dict):
72
+ # Look for API sections
73
+ if 'API' in data:
74
+ api_data = data['API']
75
+ if isinstance(api_data, list):
76
+ for item in api_data:
77
+ if isinstance(item, (list, tuple)) and len(item) >= 2:
78
+ method = item[0]
79
+ path = item[1]
80
+ endpoints.append(f"{method.upper()} {path}")
81
+ except:
82
+ pass
83
+
84
+ except Exception as e:
85
+ print(f"Warning: Could not parse {scenario_path}: {e}")
86
+
87
+ return list(set(endpoints)) # Remove duplicates
88
+
89
+ def infer_service_from_scenario(self, scenario_path: Path) -> Optional[str]:
90
+ """
91
+ Infer service name from scenario file path.
92
+
93
+ Args:
94
+ scenario_path: Path to scenario file
95
+
96
+ Returns:
97
+ Service name or None
98
+ """
99
+ # Extract service from path
100
+ # e.g., scenarios/tests/users/users-api.testql.toon.yaml -> users
101
+ rel_path = scenario_path.relative_to(self.scenarios_dir)
102
+ parts = rel_path.parts
103
+
104
+ # Look for service-like patterns in the path
105
+ for part in parts:
106
+ if part not in ['tests', 'scenarios', 'api', 'views']:
107
+ # Clean up the name
108
+ service = part.replace('-', '/').replace('_', '/')
109
+ return service
110
+
111
+ # Fallback to parent directory name
112
+ if scenario_path.parent.name != self.scenarios_dir.name:
113
+ return scenario_path.parent.name
114
+
115
+ return None
116
+
117
+ def discover_all_endpoints(self) -> Dict[str, Dict]:
118
+ """
119
+ Discover all endpoints from scenarios.
120
+
121
+ Returns:
122
+ Dictionary mapping service names to endpoint info:
123
+ {
124
+ "service_name": {
125
+ "endpoints": ["GET /api/users", ...],
126
+ "scenarios": [path1, path2, ...]
127
+ }
128
+ }
129
+ """
130
+ scenarios = self.discover_scenarios()
131
+
132
+ for scenario in scenarios:
133
+ endpoints = self.parse_scenario_endpoints(scenario)
134
+ service = self.infer_service_from_scenario(scenario)
135
+
136
+ if not service:
137
+ continue
138
+
139
+ if service not in self.endpoints_by_service:
140
+ self.endpoints_by_service[service] = set()
141
+ self.scenarios_by_service[service] = []
142
+
143
+ self.endpoints_by_service[service].update(endpoints)
144
+ self.scenarios_by_service[service].append(scenario)
145
+
146
+ # Convert to output format
147
+ result = {}
148
+ for service in self.endpoints_by_service:
149
+ result[service] = {
150
+ "endpoints": sorted(list(self.endpoints_by_service[service])),
151
+ "scenarios": [str(s) for s in self.scenarios_by_service[service]]
152
+ }
153
+
154
+ return result
155
+
156
+ def discover_via_testql_cli(self, service: Optional[str] = None) -> List[str]:
157
+ """
158
+ Use TestQL CLI to discover endpoints.
159
+
160
+ Args:
161
+ service: Optional service name to filter
162
+
163
+ Returns:
164
+ List of discovered endpoints
165
+ """
166
+ try:
167
+ cmd = [self.testql_bin, "endpoints", str(self.scenarios_dir)]
168
+ if service:
169
+ cmd.extend(["--service", service])
170
+
171
+ result = subprocess.run(
172
+ cmd,
173
+ capture_output=True,
174
+ text=True,
175
+ timeout=30
176
+ )
177
+
178
+ if result.returncode == 0:
179
+ # Parse output - assuming one endpoint per line
180
+ endpoints = [line.strip() for line in result.stdout.split('\n') if line.strip()]
181
+ return endpoints
182
+ else:
183
+ print(f"TestQL CLI error: {result.stderr}")
184
+ return []
185
+
186
+ except FileNotFoundError:
187
+ print(f"TestQL binary '{self.testql_bin}' not found. Using file-based discovery.")
188
+ return []
189
+ except subprocess.TimeoutExpired:
190
+ print("TestQL CLI timeout. Using file-based discovery.")
191
+ return []
192
+ except Exception as e:
193
+ print(f"TestQL CLI error: {e}")
194
+ return []
195
+
196
+ def to_dependency_map(self) -> Dict:
197
+ """
198
+ Convert discovered endpoints to dependency map format.
199
+
200
+ Returns:
201
+ Dictionary in dependency map format:
202
+ {
203
+ "services": {
204
+ "service_name": {
205
+ "endpoints": [...],
206
+ "files": [...]
207
+ }
208
+ },
209
+ "files": {...}
210
+ }
211
+ """
212
+ discovery = self.discover_all_endpoints()
213
+
214
+ dependency_map = {
215
+ "services": {},
216
+ "files": {}
217
+ }
218
+
219
+ for service, info in discovery.items():
220
+ dependency_map["services"][service] = {
221
+ "endpoints": info["endpoints"],
222
+ "files": [str(s) for s in info["scenarios"]]
223
+ }
224
+
225
+ # Map files to endpoints
226
+ for scenario in info["scenarios"]:
227
+ dependency_map["files"][scenario] = info["endpoints"]
228
+
229
+ return dependency_map
@@ -1,11 +1,11 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wup
3
- Version: 0.2.1
3
+ Version: 0.2.6
4
4
  Summary: WUP (What's Up) - Intelligent file watcher for regression testing in large projects
5
5
  Author-email: Tom Sapletta <tom@sapletta.com>
6
6
  License-Expression: Apache-2.0
7
- Project-URL: Homepage, https://github.com/yourusername/wup
8
- Project-URL: Repository, https://github.com/yourusername/wup
7
+ Project-URL: Homepage, https://github.com/semcod/wup
8
+ Project-URL: Repository, https://github.com/semcod/wup
9
9
  Keywords: wup,watcher,testing,regression,file-monitoring
10
10
  Classifier: Development Status :: 3 - Alpha
11
11
  Classifier: Intended Audience :: Developers
@@ -28,17 +28,17 @@ Dynamic: license-file
28
28
 
29
29
  ## AI Cost Tracking
30
30
 
31
- ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.2.1-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
32
- ![AI Cost](https://img.shields.io/badge/AI%20Cost-$0.75-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-2.0h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
31
+ ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.2.6-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
32
+ ![AI Cost](https://img.shields.io/badge/AI%20Cost-$1.05-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-2.0h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
33
33
 
34
- - 🤖 **LLM usage:** $0.7500 (5 commits)
34
+ - 🤖 **LLM usage:** $1.0500 (7 commits)
35
35
  - 👤 **Human dev:** ~$200 (2.0h @ $100/h, 30min dedup)
36
36
 
37
37
  Generated on 2026-04-29 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
38
38
 
39
39
  ---
40
40
 
41
- ![PyPI](https://img.shields.io/badge/pypi-wup-blue) ![Version](https://img.shields.io/badge/version-0.2.1-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
41
+ ![PyPI](https://img.shields.io/badge/pypi-wup-blue) ![Version](https://img.shields.io/badge/version-0.2.6-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
42
42
 
43
43
  **WUP (What's Up)** - Intelligent file watcher for regression testing in large projects.
44
44
 
@@ -56,6 +56,9 @@ WUP monitors file changes and runs intelligent regression tests using a 3-layer
56
56
  - 🔍 **Dependency Mapping**: Automatic detection of files → endpoints → services
57
57
  - 🚀 **Framework Support**: FastAPI, Flask, Django, Express.js, and more
58
58
  - 📝 **Blame Reports**: Detailed regression reports with file/line/commit info
59
+ - ⚙️ **Configuration System**: Declarative configuration via `wup.yaml` file
60
+ - 🎛️ **Per-Service Settings**: Custom test strategies per service
61
+ - 🧪 **TestQL Integration**: Native support for TestQL scenarios
59
62
 
60
63
  ## Installation
61
64
 
@@ -70,13 +73,16 @@ pip install -e ".[dev]"
70
73
  ## Quick Start
71
74
 
72
75
  ```bash
73
- # 1. Build dependency map (one-time setup)
76
+ # 1. Initialize configuration (optional)
77
+ wup init
78
+
79
+ # 2. Build dependency map (one-time setup)
74
80
  wup map-deps ./my-project
75
81
 
76
- # 2. Start watching for changes
82
+ # 3. Start watching for changes
77
83
  wup watch ./my-project
78
84
 
79
- # 3. Start with live dashboard
85
+ # 4. Start with live dashboard
80
86
  wup watch ./my-project --dashboard
81
87
  ```
82
88
 
@@ -98,7 +104,7 @@ wup map-deps ./my-project --output my-deps.json
98
104
  ### Watch Project
99
105
 
100
106
  ```bash
101
- # Basic watching
107
+ # Basic watching (uses wup.yaml if present)
102
108
  wup watch ./my-project
103
109
 
104
110
  # With custom settings
@@ -109,6 +115,25 @@ wup watch ./my-project \
109
115
 
110
116
  # With live dashboard
111
117
  wup watch ./my-project --dashboard
118
+
119
+ # Use specific config file
120
+ wup watch ./my-project --config custom-config.yaml
121
+
122
+ # TestQL mode
123
+ wup watch ./my-project --mode testql
124
+
125
+ # Discover endpoints from TestQL scenarios
126
+ wup testql-endpoints /path/to/scenarios --output testql-deps.json
127
+ ```
128
+
129
+ ### Initialize Configuration
130
+
131
+ ```bash
132
+ # Generate default wup.yaml configuration
133
+ wup init
134
+
135
+ # Generate with custom output path
136
+ wup init --output .wup.yaml
112
137
  ```
113
138
 
114
139
  ### Check Status
@@ -127,24 +152,24 @@ wup status --deps my-deps.json
127
152
 
128
153
  ```
129
154
  ┌─────────────────────────────────────────────────────────────┐
130
- │ DETECTION LAYER
155
+ │ DETECTION LAYER
131
156
  │ File watching with watchdog + heuristics │
132
157
  │ Skips: .git, __pycache__, node_modules, .venv │
133
158
  └──────────────────────┬──────────────────────────────────────┘
134
159
  │ File change
135
160
 
136
161
  ┌─────────────────────────────────────────────────────────────┐
137
- │ PRIORITY LAYER
162
+ │ PRIORITY LAYER
138
163
  │ Quick test: 3 endpoints max per service │
139
- │ Duration: ~1-2 seconds
140
- │ Result: Pass → Done, Fail → Escalate
164
+ │ Duration: ~1-2 seconds
165
+ │ Result: Pass → Done, Fail → Escalate
141
166
  └──────────────────────┬──────────────────────────────────────┘
142
167
  │ Failure
143
168
 
144
169
  ┌─────────────────────────────────────────────────────────────┐
145
- │ DETAIL LAYER
170
+ │ DETAIL LAYER
146
171
  │ Full test: All endpoints with blame report │
147
- │ Duration: ~3-5 seconds
172
+ │ Duration: ~3-5 seconds
148
173
  │ Result: Regression report with file/line/commit │
149
174
  └─────────────────────────────────────────────────────────────┘
150
175
  ```
@@ -160,15 +185,94 @@ Full regression: 15s test → 15% CPU spike
160
185
 
161
186
  ## Configuration
162
187
 
188
+ ### wup.yaml Configuration File
189
+
190
+ WUP supports declarative configuration via `wup.yaml` (or `.wup.yaml`) in your project root. This allows you to define watch paths, service-specific settings, and test strategies.
191
+
192
+ Generate a default configuration:
193
+
194
+ ```bash
195
+ wup init
196
+ ```
197
+
198
+ Example `wup.yaml`:
199
+
200
+ ```yaml
201
+ project:
202
+ name: "my-project"
203
+ description: "My awesome project"
204
+
205
+ watch:
206
+ # Folders to watch (supports glob patterns)
207
+ paths:
208
+ - "app/**"
209
+ - "src/**"
210
+ - "routes/**"
211
+ - "!tests/**" # exclusion
212
+ - "!node_modules/**"
213
+
214
+ # Global exclude patterns
215
+ exclude_patterns:
216
+ - "*.md"
217
+ - "*.txt"
218
+ - "migrations/**"
219
+
220
+ # File types to watch (empty = watch all files)
221
+ # Only changes to these file extensions will trigger tests
222
+ file_types:
223
+ - ".py"
224
+ - ".ts"
225
+ - ".jsx"
226
+
227
+ services:
228
+ # Service configurations - simplified with auto-detection
229
+ # If paths are empty, WUP auto-detects files by service name
230
+
231
+ - name: "users-shell"
232
+ type: "shell"
233
+ # Auto-detects files containing "users-shell"
234
+
235
+ - name: "users-web"
236
+ type: "web"
237
+ # Auto-detects files containing "users-web"
238
+ # Will detect coincidence with users-shell
239
+
240
+ # Or use explicit paths (old style still works)
241
+ - name: "payments"
242
+ paths:
243
+ - "app/payments/**"
244
+ type: "auto"
245
+
246
+ test_strategy:
247
+ quick:
248
+ debounce_s: 2
249
+ max_queue: 5
250
+ timeout_s: 10
251
+ detail:
252
+ debounce_s: 10
253
+ max_queue: 1
254
+ timeout_s: 30
255
+
256
+ testql:
257
+ # TestQL-specific configuration
258
+ scenario_dir: "scenarios/tests"
259
+ smoke_scenario: "smoke.testql.toon.yaml"
260
+ output_format: "json"
261
+ extra_args:
262
+ - "--timeout 10s"
263
+ ```
264
+
163
265
  ### CLI Options
164
266
 
165
267
  | Option | Default | Description |
166
268
  |--------|---------|-------------|
269
+ | `--config` | auto | Path to wup.yaml config file |
167
270
  | `--cpu-throttle` | 0.8 | CPU usage threshold (0.0-1.0) |
168
271
  | `--debounce` | 2 | Debounce time in seconds |
169
272
  | `--cooldown` | 300 | Test cooldown in seconds |
170
273
  | `--dashboard` | false | Enable live dashboard |
171
274
  | `--deps` | deps.json | Dependency map file path |
275
+ | `--mode` | default | Watcher mode: default or testql |
172
276
 
173
277
  ### Environment Variables
174
278
 
@@ -213,11 +317,21 @@ async def run_detail_test(self, service: str, endpoints: List[str]) -> Dict:
213
317
  wup/
214
318
  ├── wup/
215
319
  │ ├── __init__.py # Package exports
320
+ │ ├── config.py # Configuration loader
321
+ │ ├── models/
322
+ │ │ ├── __init__.py # Models package
323
+ │ │ └── config.py # Configuration dataclasses
216
324
  │ ├── core.py # WupWatcher implementation
217
325
  │ ├── dependency_mapper.py # Dependency mapping logic
326
+ │ ├── testql_watcher.py # TestQL integration
218
327
  │ └── cli.py # CLI interface
219
328
  ├── tests/
220
329
  │ └── test_wup.py # Unit tests
330
+ ├── docs/
331
+ │ ├── 2.md # Refactoring documentation
332
+ │ ├── 3.md # Configuration plan
333
+ │ └── TESTQL_INTEGRATION.md # TestQL integration docs
334
+ ├── wup.yaml.example # Example configuration
221
335
  ├── pyproject.toml # Package configuration
222
336
  └── README.md # This file
223
337
  ```
@@ -8,6 +8,7 @@ wup/cli.py
8
8
  wup/config.py
9
9
  wup/core.py
10
10
  wup/dependency_mapper.py
11
+ wup/testql_discovery.py
11
12
  wup/testql_watcher.py
12
13
  wup.egg-info/PKG-INFO
13
14
  wup.egg-info/SOURCES.txt
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes