wup 0.2.78__tar.gz → 0.2.80__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.
- {wup-0.2.78 → wup-0.2.80}/PKG-INFO +60 -7
- {wup-0.2.78 → wup-0.2.80}/README.md +59 -6
- {wup-0.2.78 → wup-0.2.80}/pyproject.toml +1 -1
- wup-0.2.80/tests/test_intent_monitor.py +208 -0
- {wup-0.2.78 → wup-0.2.80}/tests/test_multi_project.py +42 -0
- {wup-0.2.78 → wup-0.2.80}/wup/__init__.py +1 -1
- {wup-0.2.78 → wup-0.2.80}/wup/aql.py +88 -50
- {wup-0.2.78 → wup-0.2.80}/wup/config.py +84 -2
- {wup-0.2.78 → wup-0.2.80}/wup/core.py +29 -2
- wup-0.2.80/wup/intent_monitor.py +260 -0
- {wup-0.2.78 → wup-0.2.80}/wup/models/config.py +28 -0
- {wup-0.2.78 → wup-0.2.80}/wup/testql_watcher.py +23 -0
- {wup-0.2.78 → wup-0.2.80}/wup.egg-info/PKG-INFO +60 -7
- {wup-0.2.78 → wup-0.2.80}/wup.egg-info/SOURCES.txt +2 -0
- {wup-0.2.78 → wup-0.2.80}/LICENSE +0 -0
- {wup-0.2.78 → wup-0.2.80}/setup.cfg +0 -0
- {wup-0.2.78 → wup-0.2.80}/tests/test_aql.py +0 -0
- {wup-0.2.78 → wup-0.2.80}/tests/test_assistant.py +0 -0
- {wup-0.2.78 → wup-0.2.80}/tests/test_auto_detection.py +0 -0
- {wup-0.2.78 → wup-0.2.80}/tests/test_bootstrap.py +0 -0
- {wup-0.2.78 → wup-0.2.80}/tests/test_cli_bridge.py +0 -0
- {wup-0.2.78 → wup-0.2.80}/tests/test_cli_filtering.py +0 -0
- {wup-0.2.78 → wup-0.2.80}/tests/test_control.py +0 -0
- {wup-0.2.78 → wup-0.2.80}/tests/test_discovery_adapters.py +0 -0
- {wup-0.2.78 → wup-0.2.80}/tests/test_e2e.py +0 -0
- {wup-0.2.78 → wup-0.2.80}/tests/test_endpoints_init_cli.py +0 -0
- {wup-0.2.78 → wup-0.2.80}/tests/test_genericity.py +0 -0
- {wup-0.2.78 → wup-0.2.80}/tests/test_health_summary_passed.py +0 -0
- {wup-0.2.78 → wup-0.2.80}/tests/test_monitoring_manifest.py +0 -0
- {wup-0.2.78 → wup-0.2.80}/tests/test_oql.py +0 -0
- {wup-0.2.78 → wup-0.2.80}/tests/test_planfile_reporter_dedupe.py +0 -0
- {wup-0.2.78 → wup-0.2.80}/tests/test_probe_mutex.py +0 -0
- {wup-0.2.78 → wup-0.2.80}/tests/test_service_inference.py +0 -0
- {wup-0.2.78 → wup-0.2.80}/tests/test_status_data.py +0 -0
- {wup-0.2.78 → wup-0.2.80}/tests/test_sync.py +0 -0
- {wup-0.2.78 → wup-0.2.80}/tests/test_testql_monitor.py +0 -0
- {wup-0.2.78 → wup-0.2.80}/tests/test_testql_watcher.py +0 -0
- {wup-0.2.78 → wup-0.2.80}/tests/test_visual_diff_periodic_skip.py +0 -0
- {wup-0.2.78 → wup-0.2.80}/tests/test_visual_diff_progress.py +0 -0
- {wup-0.2.78 → wup-0.2.80}/tests/test_watch_exclude.py +0 -0
- {wup-0.2.78 → wup-0.2.80}/tests/test_watch_no_paths_exit.py +0 -0
- {wup-0.2.78 → wup-0.2.80}/tests/test_web_client.py +0 -0
- {wup-0.2.78 → wup-0.2.80}/tests/test_wup.py +0 -0
- {wup-0.2.78 → wup-0.2.80}/tests/test_wup_generate.py +0 -0
- {wup-0.2.78 → wup-0.2.80}/wup/_ast_detector.py +0 -0
- {wup-0.2.78 → wup-0.2.80}/wup/_base_detector.py +0 -0
- {wup-0.2.78 → wup-0.2.80}/wup/_hash_detector.py +0 -0
- {wup-0.2.78 → wup-0.2.80}/wup/_yaml_detector.py +0 -0
- {wup-0.2.78 → wup-0.2.80}/wup/anomaly_detector.py +0 -0
- {wup-0.2.78 → wup-0.2.80}/wup/anomaly_models.py +0 -0
- {wup-0.2.78 → wup-0.2.80}/wup/assistant.py +0 -0
- {wup-0.2.78 → wup-0.2.80}/wup/assistant_discovery.py +0 -0
- {wup-0.2.78 → wup-0.2.80}/wup/assistant_validator.py +0 -0
- {wup-0.2.78 → wup-0.2.80}/wup/bootstrap.py +0 -0
- {wup-0.2.78 → wup-0.2.80}/wup/bus.py +0 -0
- {wup-0.2.78 → wup-0.2.80}/wup/cli.py +0 -0
- {wup-0.2.78 → wup-0.2.80}/wup/cli_bridge.py +0 -0
- {wup-0.2.78 → wup-0.2.80}/wup/cli_config_generator.py +0 -0
- {wup-0.2.78 → wup-0.2.80}/wup/cli_scanner.py +0 -0
- {wup-0.2.78 → wup-0.2.80}/wup/control.py +0 -0
- {wup-0.2.78 → wup-0.2.80}/wup/dependency_mapper.py +0 -0
- {wup-0.2.78 → wup-0.2.80}/wup/discovery.py +0 -0
- {wup-0.2.78 → wup-0.2.80}/wup/endpoints.py +0 -0
- {wup-0.2.78 → wup-0.2.80}/wup/event_store.py +0 -0
- {wup-0.2.78 → wup-0.2.80}/wup/file_watcher/events/file_events.py +0 -0
- {wup-0.2.78 → wup-0.2.80}/wup/generate.py +0 -0
- {wup-0.2.78 → wup-0.2.80}/wup/init_cli.py +0 -0
- {wup-0.2.78 → wup-0.2.80}/wup/models/__init__.py +0 -0
- {wup-0.2.78 → wup-0.2.80}/wup/models/target.py +0 -0
- {wup-0.2.78 → wup-0.2.80}/wup/monitoring_manifest.py +0 -0
- {wup-0.2.78 → wup-0.2.80}/wup/multi.py +0 -0
- {wup-0.2.78 → wup-0.2.80}/wup/oql.py +0 -0
- {wup-0.2.78 → wup-0.2.80}/wup/paths.py +0 -0
- {wup-0.2.78 → wup-0.2.80}/wup/planfile_reporter.py +0 -0
- {wup-0.2.78 → wup-0.2.80}/wup/status_data.py +0 -0
- {wup-0.2.78 → wup-0.2.80}/wup/sync.py +0 -0
- {wup-0.2.78 → wup-0.2.80}/wup/testing/events/health_events.py +0 -0
- {wup-0.2.78 → wup-0.2.80}/wup/testing/events/test_results.py +0 -0
- {wup-0.2.78 → wup-0.2.80}/wup/testing/handlers/event_handlers.py +0 -0
- {wup-0.2.78 → wup-0.2.80}/wup/testing/handlers/health_handlers.py +0 -0
- {wup-0.2.78 → wup-0.2.80}/wup/testing/queries/health_queries.py +0 -0
- {wup-0.2.78 → wup-0.2.80}/wup/testql_cli_generator.py +0 -0
- {wup-0.2.78 → wup-0.2.80}/wup/testql_discovery.py +0 -0
- {wup-0.2.78 → wup-0.2.80}/wup/testql_monitor.py +0 -0
- {wup-0.2.78 → wup-0.2.80}/wup/validate.py +0 -0
- {wup-0.2.78 → wup-0.2.80}/wup/visual_diff.py +0 -0
- {wup-0.2.78 → wup-0.2.80}/wup/web_client.py +0 -0
- {wup-0.2.78 → wup-0.2.80}/wup.egg-info/dependency_links.txt +0 -0
- {wup-0.2.78 → wup-0.2.80}/wup.egg-info/entry_points.txt +0 -0
- {wup-0.2.78 → wup-0.2.80}/wup.egg-info/requires.txt +0 -0
- {wup-0.2.78 → wup-0.2.80}/wup.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: wup
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.80
|
|
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
|
|
@@ -47,17 +47,17 @@ Dynamic: license-file
|
|
|
47
47
|
|
|
48
48
|
## AI Cost Tracking
|
|
49
49
|
|
|
50
|
-
    
|
|
51
|
+
  
|
|
52
52
|
|
|
53
|
-
- 🤖 **LLM usage:** $7.
|
|
54
|
-
- 👤 **Human dev:** ~$
|
|
53
|
+
- 🤖 **LLM usage:** $7.7365 (111 commits)
|
|
54
|
+
- 👤 **Human dev:** ~$5566 (55.7h @ $100/h, 30min dedup)
|
|
55
55
|
|
|
56
|
-
Generated on 2026-
|
|
56
|
+
Generated on 2026-08-01 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
|
|
57
57
|
|
|
58
58
|
---
|
|
59
59
|
|
|
60
|
-
    
|
|
61
61
|
|
|
62
62
|
**WUP (What's Up)** - Intelligent file watcher for regression testing in large projects.
|
|
63
63
|
|
|
@@ -157,6 +157,13 @@ wup testql-endpoints /path/to/scenarios --output testql-deps.json
|
|
|
157
157
|
Pass several project roots to watch them all at once in a single process. Each
|
|
158
158
|
project keeps its own `wup.yaml`, dependency map, file observer and test queue.
|
|
159
159
|
|
|
160
|
+
When the current directory is a workspace containing projects in immediate
|
|
161
|
+
subfolders, plain `wup watch .` auto-detects their common source directories,
|
|
162
|
+
for example `api/src`, `worker/services` and `frontend/app`. This also repairs
|
|
163
|
+
the runtime behaviour of older auto-generated configs that still point only to
|
|
164
|
+
missing root-level `app`, `src` and `routes` directories. The config file is not
|
|
165
|
+
rewritten by this fallback.
|
|
166
|
+
|
|
160
167
|
```bash
|
|
161
168
|
# Watch several projects at the same time
|
|
162
169
|
wup watch ./service-a ./service-b ./service-c
|
|
@@ -176,6 +183,50 @@ path is resolved per project, so each project uses its own `deps.json`.
|
|
|
176
183
|
> The live `--dashboard` and single `--config` options apply to single-project
|
|
177
184
|
> runs only; when watching multiple projects each uses its own `wup.yaml`.
|
|
178
185
|
|
|
186
|
+
### Continuous Intent Monitoring with todo2code
|
|
187
|
+
|
|
188
|
+
WUP can run todo2code on startup, periodically and after debounced source-file
|
|
189
|
+
changes. Diagnostics are projected into the regular service-health stream as
|
|
190
|
+
`<project>:intent`: blocking findings produce `down`, review-required findings
|
|
191
|
+
produce `degraded`, and a clean audit produces `up`.
|
|
192
|
+
|
|
193
|
+
The integration is opt-in and deterministic by default, so merely installing
|
|
194
|
+
WUP never starts paid LLM requests:
|
|
195
|
+
|
|
196
|
+
```yaml
|
|
197
|
+
intent_monitoring:
|
|
198
|
+
enabled: true
|
|
199
|
+
runner: cli
|
|
200
|
+
command:
|
|
201
|
+
- node
|
|
202
|
+
- /path/to/todo2code/dist/src/cli.js
|
|
203
|
+
interval_s: 300
|
|
204
|
+
debounce_s: 10
|
|
205
|
+
run_on_start: true
|
|
206
|
+
run_on_change: true
|
|
207
|
+
mode: deterministic
|
|
208
|
+
task_file: TASK.md
|
|
209
|
+
todo_file: TODO.md
|
|
210
|
+
changelog_file: CHANGELOG.md
|
|
211
|
+
docs:
|
|
212
|
+
- README.md
|
|
213
|
+
- docs/**/*.md
|
|
214
|
+
output_dir: .wup/intent
|
|
215
|
+
fail_severities:
|
|
216
|
+
- blocking
|
|
217
|
+
- review_required
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
Set `runner: python` to use the dependency-free `todo2code-sdk` bridge. Install
|
|
221
|
+
it from todo2code's `sdk/python` directory and set `cli_path` to the built
|
|
222
|
+
`dist/src/cli.js`. The Python package still delegates semantic processing to
|
|
223
|
+
the canonical Node/TypeScript runtime.
|
|
224
|
+
|
|
225
|
+
`mode: prefer-llm` or `mode: require-llm` enables LLM enrichment for NL and
|
|
226
|
+
TODO/CHANGELOG. Documentation and final-summary LLM calls remain separately
|
|
227
|
+
controlled by `docs_llm` and `summary_llm`. Use these modes deliberately: an
|
|
228
|
+
audit may run after every debounce interval and therefore incur provider cost.
|
|
229
|
+
|
|
179
230
|
### Initialize Configuration
|
|
180
231
|
|
|
181
232
|
```bash
|
|
@@ -294,6 +345,8 @@ The generated `wup.yaml` includes:
|
|
|
294
345
|
- **Metadata header**: Version, generation date, documentation links
|
|
295
346
|
- **Dependencies info**: WUP version and optional wupbro dashboard
|
|
296
347
|
- **Quick start guide**: Common commands to get started
|
|
348
|
+
- **Detected watch paths**: Existing common source directories at the project
|
|
349
|
+
root and one level below it
|
|
297
350
|
|
|
298
351
|
Example `wup.yaml`:
|
|
299
352
|
|
|
@@ -3,17 +3,17 @@
|
|
|
3
3
|
|
|
4
4
|
## AI Cost Tracking
|
|
5
5
|
|
|
6
|
-
    
|
|
7
|
+
  
|
|
8
8
|
|
|
9
|
-
- 🤖 **LLM usage:** $7.
|
|
10
|
-
- 👤 **Human dev:** ~$
|
|
9
|
+
- 🤖 **LLM usage:** $7.7365 (111 commits)
|
|
10
|
+
- 👤 **Human dev:** ~$5566 (55.7h @ $100/h, 30min dedup)
|
|
11
11
|
|
|
12
|
-
Generated on 2026-
|
|
12
|
+
Generated on 2026-08-01 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
|
|
13
13
|
|
|
14
14
|
---
|
|
15
15
|
|
|
16
|
-
    
|
|
17
17
|
|
|
18
18
|
**WUP (What's Up)** - Intelligent file watcher for regression testing in large projects.
|
|
19
19
|
|
|
@@ -113,6 +113,13 @@ wup testql-endpoints /path/to/scenarios --output testql-deps.json
|
|
|
113
113
|
Pass several project roots to watch them all at once in a single process. Each
|
|
114
114
|
project keeps its own `wup.yaml`, dependency map, file observer and test queue.
|
|
115
115
|
|
|
116
|
+
When the current directory is a workspace containing projects in immediate
|
|
117
|
+
subfolders, plain `wup watch .` auto-detects their common source directories,
|
|
118
|
+
for example `api/src`, `worker/services` and `frontend/app`. This also repairs
|
|
119
|
+
the runtime behaviour of older auto-generated configs that still point only to
|
|
120
|
+
missing root-level `app`, `src` and `routes` directories. The config file is not
|
|
121
|
+
rewritten by this fallback.
|
|
122
|
+
|
|
116
123
|
```bash
|
|
117
124
|
# Watch several projects at the same time
|
|
118
125
|
wup watch ./service-a ./service-b ./service-c
|
|
@@ -132,6 +139,50 @@ path is resolved per project, so each project uses its own `deps.json`.
|
|
|
132
139
|
> The live `--dashboard` and single `--config` options apply to single-project
|
|
133
140
|
> runs only; when watching multiple projects each uses its own `wup.yaml`.
|
|
134
141
|
|
|
142
|
+
### Continuous Intent Monitoring with todo2code
|
|
143
|
+
|
|
144
|
+
WUP can run todo2code on startup, periodically and after debounced source-file
|
|
145
|
+
changes. Diagnostics are projected into the regular service-health stream as
|
|
146
|
+
`<project>:intent`: blocking findings produce `down`, review-required findings
|
|
147
|
+
produce `degraded`, and a clean audit produces `up`.
|
|
148
|
+
|
|
149
|
+
The integration is opt-in and deterministic by default, so merely installing
|
|
150
|
+
WUP never starts paid LLM requests:
|
|
151
|
+
|
|
152
|
+
```yaml
|
|
153
|
+
intent_monitoring:
|
|
154
|
+
enabled: true
|
|
155
|
+
runner: cli
|
|
156
|
+
command:
|
|
157
|
+
- node
|
|
158
|
+
- /path/to/todo2code/dist/src/cli.js
|
|
159
|
+
interval_s: 300
|
|
160
|
+
debounce_s: 10
|
|
161
|
+
run_on_start: true
|
|
162
|
+
run_on_change: true
|
|
163
|
+
mode: deterministic
|
|
164
|
+
task_file: TASK.md
|
|
165
|
+
todo_file: TODO.md
|
|
166
|
+
changelog_file: CHANGELOG.md
|
|
167
|
+
docs:
|
|
168
|
+
- README.md
|
|
169
|
+
- docs/**/*.md
|
|
170
|
+
output_dir: .wup/intent
|
|
171
|
+
fail_severities:
|
|
172
|
+
- blocking
|
|
173
|
+
- review_required
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Set `runner: python` to use the dependency-free `todo2code-sdk` bridge. Install
|
|
177
|
+
it from todo2code's `sdk/python` directory and set `cli_path` to the built
|
|
178
|
+
`dist/src/cli.js`. The Python package still delegates semantic processing to
|
|
179
|
+
the canonical Node/TypeScript runtime.
|
|
180
|
+
|
|
181
|
+
`mode: prefer-llm` or `mode: require-llm` enables LLM enrichment for NL and
|
|
182
|
+
TODO/CHANGELOG. Documentation and final-summary LLM calls remain separately
|
|
183
|
+
controlled by `docs_llm` and `summary_llm`. Use these modes deliberately: an
|
|
184
|
+
audit may run after every debounce interval and therefore incur provider cost.
|
|
185
|
+
|
|
135
186
|
### Initialize Configuration
|
|
136
187
|
|
|
137
188
|
```bash
|
|
@@ -250,6 +301,8 @@ The generated `wup.yaml` includes:
|
|
|
250
301
|
- **Metadata header**: Version, generation date, documentation links
|
|
251
302
|
- **Dependencies info**: WUP version and optional wupbro dashboard
|
|
252
303
|
- **Quick start guide**: Common commands to get started
|
|
304
|
+
- **Detected watch paths**: Existing common source directories at the project
|
|
305
|
+
root and one level below it
|
|
253
306
|
|
|
254
307
|
Example `wup.yaml`:
|
|
255
308
|
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
"""Tests for continuous todo2code Intent-vs-Reality monitoring."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import sys
|
|
7
|
+
import types
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from types import SimpleNamespace
|
|
10
|
+
|
|
11
|
+
from wup.config import load_config, save_config
|
|
12
|
+
from wup.intent_monitor import IntentAuditResult, Todo2CodeIntentMonitor
|
|
13
|
+
from wup.models.config import IntentMonitoringConfig, ProjectConfig, WupConfig
|
|
14
|
+
from wup.testql_watcher import TestQLWatcher
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _diagnostics(tmp_path: Path, items: list[dict]) -> Path:
|
|
18
|
+
path = tmp_path / "diagnostics.json"
|
|
19
|
+
path.write_text(json.dumps({"diagnostics": items}), encoding="utf-8")
|
|
20
|
+
return path
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def test_config_round_trip_preserves_intent_monitoring(tmp_path: Path) -> None:
|
|
24
|
+
config = WupConfig(
|
|
25
|
+
project=ProjectConfig(name="demo"),
|
|
26
|
+
intent_monitoring=IntentMonitoringConfig(
|
|
27
|
+
enabled=True,
|
|
28
|
+
runner="python",
|
|
29
|
+
cli_path="/opt/todo2code/dist/src/cli.js",
|
|
30
|
+
interval_s=120,
|
|
31
|
+
mode="require-llm",
|
|
32
|
+
docs_llm=True,
|
|
33
|
+
fail_codes=["PLANNED_NOT_IMPLEMENTED"],
|
|
34
|
+
),
|
|
35
|
+
)
|
|
36
|
+
save_config(config, tmp_path / "wup.yaml")
|
|
37
|
+
|
|
38
|
+
loaded = load_config(tmp_path)
|
|
39
|
+
|
|
40
|
+
assert loaded.intent_monitoring.enabled is True
|
|
41
|
+
assert loaded.intent_monitoring.runner == "python"
|
|
42
|
+
assert loaded.intent_monitoring.interval_s == 120
|
|
43
|
+
assert loaded.intent_monitoring.mode == "require-llm"
|
|
44
|
+
assert loaded.intent_monitoring.docs_llm is True
|
|
45
|
+
assert loaded.intent_monitoring.fail_codes == ["PLANNED_NOT_IMPLEMENTED"]
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def test_cli_monitor_maps_blocking_diagnostics_to_down(tmp_path: Path, monkeypatch) -> None:
|
|
49
|
+
path = _diagnostics(
|
|
50
|
+
tmp_path,
|
|
51
|
+
[{"severity": "blocking", "code": "PLANNED_NOT_IMPLEMENTED"}],
|
|
52
|
+
)
|
|
53
|
+
calls = []
|
|
54
|
+
|
|
55
|
+
def fake_run(command, **kwargs):
|
|
56
|
+
calls.append((command, kwargs))
|
|
57
|
+
payload = {"diagnosticsPath": str(path), "runDirectory": str(tmp_path / "run")}
|
|
58
|
+
return SimpleNamespace(returncode=0, stdout=json.dumps(payload), stderr="")
|
|
59
|
+
|
|
60
|
+
monkeypatch.setattr("wup.intent_monitor.subprocess.run", fake_run)
|
|
61
|
+
results = []
|
|
62
|
+
config = IntentMonitoringConfig(
|
|
63
|
+
enabled=True,
|
|
64
|
+
command=["node", "/opt/todo2code/cli.js"],
|
|
65
|
+
mode="deterministic",
|
|
66
|
+
)
|
|
67
|
+
monitor = Todo2CodeIntentMonitor(tmp_path, config, results.append)
|
|
68
|
+
|
|
69
|
+
result = monitor.run_once()
|
|
70
|
+
|
|
71
|
+
assert result.status == "down"
|
|
72
|
+
assert len(result.diagnostics) == 1
|
|
73
|
+
assert results == [result]
|
|
74
|
+
command, kwargs = calls[0]
|
|
75
|
+
assert command[:3] == ["node", "/opt/todo2code/cli.js", "pipeline"]
|
|
76
|
+
assert "--no-docs-llm" in command
|
|
77
|
+
assert "--no-summary-llm" in command
|
|
78
|
+
assert kwargs.get("shell", False) is False
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def test_cli_monitor_resolves_single_javascript_launcher(tmp_path: Path, monkeypatch) -> None:
|
|
82
|
+
path = _diagnostics(tmp_path, [])
|
|
83
|
+
cli_path = tmp_path / "cli.js"
|
|
84
|
+
cli_path.write_text("", encoding="utf-8")
|
|
85
|
+
observed = {}
|
|
86
|
+
|
|
87
|
+
monkeypatch.setattr("wup.intent_monitor.shutil.which", lambda command: str(cli_path))
|
|
88
|
+
|
|
89
|
+
def fake_run(command, **kwargs):
|
|
90
|
+
observed["command"] = command
|
|
91
|
+
return SimpleNamespace(
|
|
92
|
+
returncode=0,
|
|
93
|
+
stdout=json.dumps({"diagnosticsPath": str(path)}),
|
|
94
|
+
stderr="",
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
monkeypatch.setattr("wup.intent_monitor.subprocess.run", fake_run)
|
|
98
|
+
monitor = Todo2CodeIntentMonitor(
|
|
99
|
+
tmp_path,
|
|
100
|
+
IntentMonitoringConfig(enabled=True, command=["t2c"]),
|
|
101
|
+
lambda result: None,
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
result = monitor.run_once()
|
|
105
|
+
|
|
106
|
+
assert result.status == "up"
|
|
107
|
+
assert observed["command"][:2] == ["node", str(cli_path.resolve())]
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def test_monitor_filters_codes_and_maps_review_required_to_degraded(tmp_path: Path) -> None:
|
|
111
|
+
path = _diagnostics(
|
|
112
|
+
tmp_path,
|
|
113
|
+
[
|
|
114
|
+
{"severity": "review_required", "code": "CHANGELOG_WITHOUT_IMPLEMENTATION"},
|
|
115
|
+
{"severity": "blocking", "code": "UNRELATED"},
|
|
116
|
+
],
|
|
117
|
+
)
|
|
118
|
+
config = IntentMonitoringConfig(
|
|
119
|
+
fail_codes=["CHANGELOG_WITHOUT_IMPLEMENTATION"]
|
|
120
|
+
)
|
|
121
|
+
monitor = Todo2CodeIntentMonitor(tmp_path, config, lambda result: None)
|
|
122
|
+
|
|
123
|
+
result = monitor._result_from_payload({"diagnosticsPath": str(path)})
|
|
124
|
+
|
|
125
|
+
assert result.status == "degraded"
|
|
126
|
+
assert [item["code"] for item in result.diagnostics] == [
|
|
127
|
+
"CHANGELOG_WITHOUT_IMPLEMENTATION"
|
|
128
|
+
]
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def test_monitor_reports_runner_failure_as_down(tmp_path: Path, monkeypatch) -> None:
|
|
132
|
+
monkeypatch.setattr(
|
|
133
|
+
"wup.intent_monitor.subprocess.run",
|
|
134
|
+
lambda *args, **kwargs: SimpleNamespace(
|
|
135
|
+
returncode=2, stdout="", stderr="structured output invalid"
|
|
136
|
+
),
|
|
137
|
+
)
|
|
138
|
+
results = []
|
|
139
|
+
monitor = Todo2CodeIntentMonitor(
|
|
140
|
+
tmp_path, IntentMonitoringConfig(enabled=True), results.append
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
result = monitor.run_once()
|
|
144
|
+
|
|
145
|
+
assert result.status == "down"
|
|
146
|
+
assert "structured output invalid" in result.message
|
|
147
|
+
assert results == [result]
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def test_python_runner_uses_todo2code_sdk_bridge(tmp_path: Path, monkeypatch) -> None:
|
|
151
|
+
path = _diagnostics(tmp_path, [])
|
|
152
|
+
observed = {}
|
|
153
|
+
|
|
154
|
+
class FakeRuntime:
|
|
155
|
+
def __init__(self, root, **kwargs):
|
|
156
|
+
observed["root"] = root
|
|
157
|
+
observed["init"] = kwargs
|
|
158
|
+
|
|
159
|
+
def invoke(self, arguments):
|
|
160
|
+
observed["arguments"] = arguments
|
|
161
|
+
payload = {"diagnosticsPath": str(path), "runDirectory": "python-run"}
|
|
162
|
+
return SimpleNamespace(stdout=json.dumps(payload))
|
|
163
|
+
|
|
164
|
+
module = types.ModuleType("todo2code")
|
|
165
|
+
module.TypeScriptRuntime = FakeRuntime
|
|
166
|
+
monkeypatch.setitem(sys.modules, "todo2code", module)
|
|
167
|
+
config = IntentMonitoringConfig(
|
|
168
|
+
enabled=True,
|
|
169
|
+
runner="python",
|
|
170
|
+
cli_path="/opt/todo2code/dist/src/cli.js",
|
|
171
|
+
mode="prefer-llm",
|
|
172
|
+
docs_llm=True,
|
|
173
|
+
)
|
|
174
|
+
monitor = Todo2CodeIntentMonitor(tmp_path, config, lambda result: None)
|
|
175
|
+
|
|
176
|
+
result = monitor.run_once()
|
|
177
|
+
|
|
178
|
+
assert result.status == "up"
|
|
179
|
+
assert observed["root"] == tmp_path.resolve()
|
|
180
|
+
assert observed["init"]["cli_path"] == config.cli_path
|
|
181
|
+
assert observed["arguments"][:2] == ["pipeline", str(tmp_path.resolve())]
|
|
182
|
+
assert observed["arguments"][observed["arguments"].index("--markdown-mode") + 1] == "prefer-llm"
|
|
183
|
+
assert "--no-docs-llm" not in observed["arguments"]
|
|
184
|
+
assert "--no-communication" in observed["arguments"]
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def test_testql_watcher_projects_intent_result_as_project_health() -> None:
|
|
188
|
+
watcher = object.__new__(TestQLWatcher)
|
|
189
|
+
watcher.config = SimpleNamespace(project=SimpleNamespace(name="workspace"))
|
|
190
|
+
transitions = []
|
|
191
|
+
watcher._record_health_transition = lambda **values: transitions.append(values)
|
|
192
|
+
result = IntentAuditResult(
|
|
193
|
+
status="down",
|
|
194
|
+
message="todo2code found 3 intent issue(s), 1 blocking",
|
|
195
|
+
diagnostics_path="/tmp/diagnostics.json",
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
watcher._record_intent_audit(result)
|
|
199
|
+
|
|
200
|
+
assert transitions == [
|
|
201
|
+
{
|
|
202
|
+
"service": "workspace:intent",
|
|
203
|
+
"status": "down",
|
|
204
|
+
"stage": "intent",
|
|
205
|
+
"message": result.message,
|
|
206
|
+
"track_file": result.diagnostics_path,
|
|
207
|
+
}
|
|
208
|
+
]
|
|
@@ -58,6 +58,29 @@ def test_detect_watch_paths_backend_frontend(tmp_path: Path) -> None:
|
|
|
58
58
|
assert "app/**" not in paths
|
|
59
59
|
|
|
60
60
|
|
|
61
|
+
def test_detect_watch_paths_finds_source_dirs_in_project_subfolders(
|
|
62
|
+
tmp_path: Path,
|
|
63
|
+
) -> None:
|
|
64
|
+
(tmp_path / "api" / "src").mkdir(parents=True)
|
|
65
|
+
(tmp_path / "worker" / "services").mkdir(parents=True)
|
|
66
|
+
|
|
67
|
+
assert detect_watch_paths(tmp_path) == ["api/src/**", "worker/services/**"]
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def test_detect_watch_paths_combines_root_and_project_subfolders(tmp_path: Path) -> None:
|
|
71
|
+
(tmp_path / "src").mkdir()
|
|
72
|
+
(tmp_path / "worker" / "src").mkdir(parents=True)
|
|
73
|
+
|
|
74
|
+
assert detect_watch_paths(tmp_path) == ["src/**", "worker/src/**"]
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def test_detect_watch_paths_skips_nested_vendor_and_hidden_dirs(tmp_path: Path) -> None:
|
|
78
|
+
(tmp_path / "node_modules" / "package" / "src").mkdir(parents=True)
|
|
79
|
+
(tmp_path / ".cache" / "src").mkdir(parents=True)
|
|
80
|
+
|
|
81
|
+
assert detect_watch_paths(tmp_path) == ["app/**", "src/**", "routes/**"]
|
|
82
|
+
|
|
83
|
+
|
|
61
84
|
def test_default_config_watches_only_real_dirs(tmp_path: Path) -> None:
|
|
62
85
|
(tmp_path / "services").mkdir()
|
|
63
86
|
cfg = get_default_config(tmp_path)
|
|
@@ -122,6 +145,25 @@ def test_prepare_observer_none_when_no_valid_paths(tmp_path: Path) -> None:
|
|
|
122
145
|
assert watcher.prepare_observer() is None
|
|
123
146
|
|
|
124
147
|
|
|
148
|
+
def test_watcher_falls_back_to_source_dirs_in_project_subfolders(
|
|
149
|
+
tmp_path: Path,
|
|
150
|
+
) -> None:
|
|
151
|
+
nested_src = tmp_path / "api" / "src"
|
|
152
|
+
nested_services = tmp_path / "worker" / "services"
|
|
153
|
+
nested_src.mkdir(parents=True)
|
|
154
|
+
nested_services.mkdir(parents=True)
|
|
155
|
+
watcher = _watcher(tmp_path, paths=["app/**", "src/**", "routes/**"])
|
|
156
|
+
|
|
157
|
+
assert watcher.build_watched_paths() == [str(nested_src), str(nested_services)]
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def test_watcher_does_not_replace_invalid_custom_paths(tmp_path: Path) -> None:
|
|
161
|
+
(tmp_path / "src").mkdir()
|
|
162
|
+
watcher = _watcher(tmp_path, paths=["misspelled-source/**"])
|
|
163
|
+
|
|
164
|
+
assert watcher.build_watched_paths() == []
|
|
165
|
+
|
|
166
|
+
|
|
125
167
|
def test_multi_watcher_returns_false_when_all_invalid(tmp_path: Path) -> None:
|
|
126
168
|
w1 = _watcher(tmp_path, paths=["missing-a/**"])
|
|
127
169
|
w2 = _watcher(tmp_path, paths=["missing-b/**"])
|
|
@@ -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.
|
|
10
|
+
__version__ = "0.2.80"
|
|
11
11
|
__author__ = "Tom Sapletta"
|
|
12
12
|
|
|
13
13
|
from .config import load_config, save_config, get_default_config
|