wup 0.2.11__tar.gz → 0.2.12__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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wup
3
- Version: 0.2.11
3
+ Version: 0.2.12
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: Apache-2.0
@@ -29,17 +29,17 @@ Dynamic: license-file
29
29
 
30
30
  ## AI Cost Tracking
31
31
 
32
- ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.2.11-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
33
- ![AI Cost](https://img.shields.io/badge/AI%20Cost-$1.80-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-2.9h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
32
+ ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.2.12-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
33
+ ![AI Cost](https://img.shields.io/badge/AI%20Cost-$1.95-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-3.1h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
34
34
 
35
- - 🤖 **LLM usage:** $1.8000 (12 commits)
36
- - 👤 **Human dev:** ~$290 (2.9h @ $100/h, 30min dedup)
35
+ - 🤖 **LLM usage:** $1.9500 (13 commits)
36
+ - 👤 **Human dev:** ~$312 (3.1h @ $100/h, 30min dedup)
37
37
 
38
38
  Generated on 2026-04-29 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
39
39
 
40
40
  ---
41
41
 
42
- ![PyPI](https://img.shields.io/badge/pypi-wup-blue) ![Version](https://img.shields.io/badge/version-0.2.11-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
42
+ ![PyPI](https://img.shields.io/badge/pypi-wup-blue) ![Version](https://img.shields.io/badge/version-0.2.12-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
43
43
 
44
44
  **WUP (What's Up)** - Intelligent file watcher for regression testing in large projects.
45
45
 
@@ -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.11-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.80-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-2.9h-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.12-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.95-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-3.1h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
8
8
 
9
- - 🤖 **LLM usage:** $1.8000 (12 commits)
10
- - 👤 **Human dev:** ~$290 (2.9h @ $100/h, 30min dedup)
9
+ - 🤖 **LLM usage:** $1.9500 (13 commits)
10
+ - 👤 **Human dev:** ~$312 (3.1h @ $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.11-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.12-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
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "wup"
7
- version = "0.2.11"
7
+ version = "0.2.12"
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"
@@ -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.11"
10
+ __version__ = "0.2.12"
11
11
  __author__ = "Tom Sapletta"
12
12
 
13
13
  from .config import load_config, save_config, get_default_config
@@ -173,152 +173,122 @@ def status(
173
173
  config: Optional[str] = typer.Option(None, "--config", "-C", help="Path to wup.yaml config file"),
174
174
  delta_seconds: int = typer.Option(0, "--delta-seconds", help="Show only service health transitions from last N seconds"),
175
175
  failed_only: bool = typer.Option(False, "--failed-only", help="Show only currently failing services"),
176
+ watch: bool = typer.Option(False, "--watch", "-w", help="Live mode: refresh display in real time"),
177
+ interval: int = typer.Option(5, "--interval", "-i", help="Refresh interval in seconds for --watch mode"),
176
178
  ):
177
179
  """
178
180
  Show dependency map status and configuration.
179
181
  """
182
+ import json
183
+ import time
184
+
180
185
  project_path = Path(".").resolve()
181
-
182
- # Load configuration
183
186
  config_path = Path(config) if config else None
184
187
  wup_config = load_config(project_path, config_path)
185
-
186
- console.print(f"[bold cyan]📊 WUP Status[/bold cyan]")
187
- console.print(f"[dim]Project: {wup_config.project.name}[/dim]")
188
- console.print(f"[dim]Description: {wup_config.project.description}[/dim]")
189
- console.print()
190
-
191
- # Show watch configuration
192
- console.print("[bold]Watch Configuration:[/bold]")
193
- console.print(f" Paths: {', '.join(wup_config.watch.paths) if wup_config.watch.paths else 'default'}")
194
- console.print(f" Excludes: {', '.join(wup_config.watch.exclude_patterns)}")
195
- console.print()
196
-
197
- # Show services
198
- if wup_config.services:
199
- console.print(f"[bold]Services ({len(wup_config.services)}):[/bold]")
200
- for svc in wup_config.services:
201
- console.print(f" [cyan]{svc.name}[/cyan]")
202
- console.print(f" Root: {svc.root}")
203
- console.print(f" Quick: scope={svc.quick_tests.scope}, max={svc.quick_tests.max_endpoints}")
204
- console.print(f" Detail: scope={svc.detail_tests.scope}, max={svc.detail_tests.max_endpoints}")
205
- console.print()
206
-
207
- # Show dependency map status
208
- deps_path = Path(deps_file)
209
-
210
- if not deps_path.exists():
211
- console.print(f"[yellow]Warning: Dependency file '{deps_file}' does not exist[/yellow]")
212
- console.print(f"[dim]Run 'wup map-deps' to create it[/dim]")
213
- console.print()
214
- return
215
-
216
- import json
217
- with open(deps_file) as f:
218
- deps = json.load(f)
219
-
220
- services = deps.get("services", {})
221
- files = deps.get("files", {})
222
-
223
- console.print(f"[bold]Dependency Map:[/bold]")
224
- console.print(f" Services: {len(services)}")
225
- console.print(f" Files: {len(files)}")
226
- console.print()
227
-
228
- if services:
229
- console.print("[bold]Service Details:[/bold]")
230
- for service, info in sorted(services.items()):
231
- # Handle both dict format (new) and list format (legacy)
232
- if isinstance(info, dict):
233
- endpoints = info.get("endpoints", [])
234
- service_files = info.get("files", [])
235
- elif isinstance(info, list):
236
- endpoints = info
237
- service_files = []
238
- else:
239
- endpoints = []
240
- service_files = []
241
-
242
- console.print(f" [cyan]{service}[/cyan]")
243
- console.print(f" Endpoints: {len(endpoints)}")
244
- console.print(f" Files: {len(service_files)}")
245
- if endpoints:
246
- console.print(f" Sample endpoints: {', '.join(endpoints[:3])}")
247
-
248
- # Show service health state and recent transitions (TestQL watcher)
249
188
  health_state_path = project_path / ".wup" / "service-health.json"
250
189
  health_events_path = project_path / ".wup" / "service-health-events.jsonl"
251
190
 
252
- health_state = {}
253
- if health_state_path.exists():
254
- import json
191
+ deps_path = Path(deps_file)
192
+
193
+ def _build_panel(ts: float) -> "Group":
194
+ from rich.console import Group
195
+ from rich.text import Text
196
+ from rich.padding import Padding
197
+ lines: list = []
255
198
 
256
- try:
257
- payload = json.loads(health_state_path.read_text(encoding="utf-8"))
258
- if isinstance(payload, dict):
259
- health_state = payload
260
- except json.JSONDecodeError:
261
- health_state = {}
199
+ # header
200
+ lines.append(Text.from_markup(
201
+ f"[bold cyan]📊 WUP Status[/bold cyan] "
202
+ f"[dim]{wup_config.project.name}[/dim] "
203
+ f"[dim]updated {time.strftime('%H:%M:%S', time.localtime(ts))}[/dim]"
204
+ ))
262
205
 
263
- if failed_only:
264
- failing = [
265
- (svc, data)
266
- for svc, data in sorted(health_state.items())
267
- if isinstance(data, dict) and data.get("status") == "down"
268
- ]
269
- console.print()
270
- console.print("[bold]Currently failing services:[/bold]")
271
- if not failing:
272
- console.print(" [green]None[/green]")
273
- else:
274
- for svc, data in failing:
275
- updated_at = data.get("updated_at", 0)
276
- track_file = data.get("track_file", "")
277
- stage = data.get("stage", "")
278
- message = data.get("message", "")
279
- console.print(f" [red]{svc}[/red] stage={stage} updated_at={updated_at}")
280
- if track_file:
281
- console.print(f" track: {track_file}")
282
- if message:
283
- console.print(f" message: {message}")
206
+ # --- failing services ---
207
+ health_state: dict = {}
208
+ if health_state_path.exists():
209
+ try:
210
+ payload = json.loads(health_state_path.read_text(encoding="utf-8"))
211
+ if isinstance(payload, dict):
212
+ health_state = payload
213
+ except json.JSONDecodeError:
214
+ pass
215
+
216
+ if failed_only or watch:
217
+ failing = [
218
+ (svc, data)
219
+ for svc, data in sorted(health_state.items())
220
+ if isinstance(data, dict) and data.get("status") == "down"
221
+ ]
222
+ lines.append(Text(""))
223
+ lines.append(Text.from_markup("[bold]Currently failing services:[/bold]"))
224
+ if not failing:
225
+ lines.append(Text.from_markup(" [green]✓ None[/green]"))
226
+ else:
227
+ for svc, data in failing:
228
+ stage = data.get("stage", "")
229
+ message = data.get("message", "")
230
+ track_file = data.get("track_file", "")
231
+ lines.append(Text.from_markup(f" [red]✗ {svc}[/red] [dim]{stage}[/dim]"))
232
+ if message:
233
+ lines.append(Text.from_markup(f" [dim]{message}[/dim]"))
234
+ if track_file:
235
+ lines.append(Text.from_markup(f" [dim]track: {track_file}[/dim]"))
284
236
 
285
- if delta_seconds > 0:
286
- import json
287
- import time
237
+ # --- delta ---
238
+ effective_delta = delta_seconds if delta_seconds > 0 else (30 if watch else 0)
239
+ if effective_delta > 0:
240
+ cutoff = int(ts) - effective_delta
241
+ recent_events: list = []
242
+ if health_events_path.exists():
243
+ with health_events_path.open("r", encoding="utf-8") as handle:
244
+ for line in handle:
245
+ line = line.strip()
246
+ if not line:
247
+ continue
248
+ try:
249
+ event = json.loads(line)
250
+ except json.JSONDecodeError:
251
+ continue
252
+ if int(event.get("timestamp", 0)) >= cutoff:
253
+ recent_events.append(event)
288
254
 
289
- cutoff = int(time.time()) - delta_seconds
290
- recent_events = []
291
- if health_events_path.exists():
292
- with health_events_path.open("r", encoding="utf-8") as handle:
293
- for line in handle:
294
- line = line.strip()
295
- if not line:
296
- continue
297
- try:
298
- event = json.loads(line)
299
- except json.JSONDecodeError:
300
- continue
301
- if int(event.get("timestamp", 0)) >= cutoff:
302
- recent_events.append(event)
255
+ lines.append(Text(""))
256
+ lines.append(Text.from_markup(f"[bold]Service health delta (last {effective_delta}s):[/bold]"))
257
+ if not recent_events:
258
+ lines.append(Text.from_markup(" [yellow]No health transitions in selected window[/yellow]"))
259
+ else:
260
+ recent_events.sort(key=lambda e: int(e.get("timestamp", 0)), reverse=True)
261
+ for event in recent_events:
262
+ svc = event.get("service", "unknown")
263
+ prev = event.get("previous_status", "unknown")
264
+ curr = event.get("status", "unknown")
265
+ stage = event.get("stage", "")
266
+ message = event.get("message", "")
267
+ track_file = event.get("track_file", "")
268
+ arrow_color = "green" if curr == "up" else "red"
269
+ lines.append(Text.from_markup(
270
+ f" [cyan]{svc}[/cyan]: {prev} [bold {arrow_color}]→ {curr}[/bold {arrow_color}] [dim]({stage})[/dim]"
271
+ ))
272
+ if message:
273
+ lines.append(Text.from_markup(f" [dim]{message}[/dim]"))
274
+ if track_file:
275
+ lines.append(Text.from_markup(f" [dim]track: {track_file}[/dim]"))
276
+
277
+ return Group(*lines)
278
+
279
+ if not watch:
280
+ console.print(_build_panel(time.time()))
281
+ return
303
282
 
304
- console.print()
305
- console.print(f"[bold]Service health delta (last {delta_seconds}s):[/bold]")
306
- if not recent_events:
307
- console.print(" [yellow]No health transitions in selected window[/yellow]")
308
- else:
309
- recent_events.sort(key=lambda item: int(item.get("timestamp", 0)), reverse=True)
310
- for event in recent_events:
311
- svc = event.get("service", "unknown")
312
- prev = event.get("previous_status", "unknown")
313
- curr = event.get("status", "unknown")
314
- stage = event.get("stage", "")
315
- message = event.get("message", "")
316
- track_file = event.get("track_file", "")
317
- console.print(f" [cyan]{svc}[/cyan]: {prev} -> {curr} ({stage})")
318
- if message:
319
- console.print(f" message: {message}")
320
- if track_file:
321
- console.print(f" track: {track_file}")
283
+ # --- live / watch mode ---
284
+ from rich.live import Live
285
+ try:
286
+ with Live(_build_panel(time.time()), refresh_per_second=1, console=console) as live:
287
+ while True:
288
+ time.sleep(interval)
289
+ live.update(_build_panel(time.time()))
290
+ except KeyboardInterrupt:
291
+ pass
322
292
 
323
293
 
324
294
  @app.command()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wup
3
- Version: 0.2.11
3
+ Version: 0.2.12
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: Apache-2.0
@@ -29,17 +29,17 @@ Dynamic: license-file
29
29
 
30
30
  ## AI Cost Tracking
31
31
 
32
- ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.2.11-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
33
- ![AI Cost](https://img.shields.io/badge/AI%20Cost-$1.80-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-2.9h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
32
+ ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.2.12-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
33
+ ![AI Cost](https://img.shields.io/badge/AI%20Cost-$1.95-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-3.1h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
34
34
 
35
- - 🤖 **LLM usage:** $1.8000 (12 commits)
36
- - 👤 **Human dev:** ~$290 (2.9h @ $100/h, 30min dedup)
35
+ - 🤖 **LLM usage:** $1.9500 (13 commits)
36
+ - 👤 **Human dev:** ~$312 (3.1h @ $100/h, 30min dedup)
37
37
 
38
38
  Generated on 2026-04-29 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
39
39
 
40
40
  ---
41
41
 
42
- ![PyPI](https://img.shields.io/badge/pypi-wup-blue) ![Version](https://img.shields.io/badge/version-0.2.11-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
42
+ ![PyPI](https://img.shields.io/badge/pypi-wup-blue) ![Version](https://img.shields.io/badge/version-0.2.12-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
43
43
 
44
44
  **WUP (What's Up)** - Intelligent file watcher for regression testing in large projects.
45
45
 
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
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes