fluxloop-cli 0.2.28__tar.gz → 0.2.29__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.
- {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/PKG-INFO +1 -1
- {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/__init__.py +1 -1
- {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/commands/run.py +113 -133
- {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli.egg-info/PKG-INFO +1 -1
- {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/pyproject.toml +1 -1
- {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/README.md +0 -0
- {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/arg_binder.py +0 -0
- {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/commands/__init__.py +0 -0
- {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/commands/config.py +0 -0
- {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/commands/doctor.py +0 -0
- {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/commands/evaluate.py +0 -0
- {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/commands/generate.py +0 -0
- {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/commands/init.py +0 -0
- {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/commands/parse.py +0 -0
- {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/commands/record.py +0 -0
- {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/commands/status.py +0 -0
- {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/config_loader.py +0 -0
- {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/config_schema.py +0 -0
- {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/constants.py +0 -0
- {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/conversation_supervisor.py +0 -0
- {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/environment.py +0 -0
- {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/evaluation/__init__.py +0 -0
- {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/evaluation/artifacts.py +0 -0
- {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/evaluation/config.py +0 -0
- {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/evaluation/engine/__init__.py +0 -0
- {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/evaluation/engine/analysis.py +0 -0
- {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/evaluation/engine/core.py +0 -0
- {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/evaluation/engine/reporting/__init__.py +0 -0
- {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/evaluation/engine/reporting/html.py +0 -0
- {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/evaluation/engine/reporting/markdown.py +0 -0
- {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/evaluation/engine/success.py +0 -0
- {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/evaluation/llm.py +0 -0
- {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/evaluation/prompts/__init__.py +0 -0
- {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/evaluation/prompts/base.py +0 -0
- {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/evaluation/prompts/information_completeness.py +0 -0
- {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/evaluation/prompts/intent_recognition.py +0 -0
- {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/evaluation/prompts/response_clarity.py +0 -0
- {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/evaluation/prompts/response_consistency.py +0 -0
- {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/evaluation/rules.py +0 -0
- {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/input_generator.py +0 -0
- {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/llm_generator.py +0 -0
- {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/main.py +0 -0
- {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/project_paths.py +0 -0
- {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/runner.py +0 -0
- {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/target_loader.py +0 -0
- {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/templates.py +0 -0
- {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/validators.py +0 -0
- {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli.egg-info/SOURCES.txt +0 -0
- {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli.egg-info/dependency_links.txt +0 -0
- {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli.egg-info/entry_points.txt +0 -0
- {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli.egg-info/requires.txt +0 -0
- {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli.egg-info/top_level.txt +0 -0
- {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/setup.cfg +0 -0
- {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/tests/test_analysis_recommendations.py +0 -0
- {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/tests/test_arg_binder.py +0 -0
- {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/tests/test_config_command.py +0 -0
- {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/tests/test_conversation_supervisor.py +0 -0
- {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/tests/test_evaluate_command.py +0 -0
- {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/tests/test_evaluation_llm.py +0 -0
- {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/tests/test_input_generator.py +0 -0
- {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/tests/test_prompt_library.py +0 -0
- {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/tests/test_run_command.py +0 -0
- {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/tests/test_runner_multi_turn.py +0 -0
- {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/tests/test_success_criteria.py +0 -0
- {fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/tests/test_target_loader.py +0 -0
|
@@ -3,10 +3,9 @@ Run command for executing experiments and simulations.
|
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
import asyncio
|
|
6
|
-
import json
|
|
7
6
|
import sys
|
|
8
7
|
from pathlib import Path
|
|
9
|
-
from typing import Optional
|
|
8
|
+
from typing import Callable, Optional
|
|
10
9
|
|
|
11
10
|
import typer
|
|
12
11
|
from rich.console import Console
|
|
@@ -28,16 +27,6 @@ app = typer.Typer()
|
|
|
28
27
|
console = Console()
|
|
29
28
|
|
|
30
29
|
|
|
31
|
-
def _emit_progress_event(event: str, **payload: object) -> None:
|
|
32
|
-
"""Emit a structured progress event to stderr for machine consumption."""
|
|
33
|
-
data = {"event": event, **payload}
|
|
34
|
-
try:
|
|
35
|
-
print(json.dumps(data, default=str), file=sys.stderr, flush=True)
|
|
36
|
-
except Exception:
|
|
37
|
-
# Fall back to repr if serialization fails
|
|
38
|
-
print({"event": event, **{k: repr(v) for k, v in payload.items()}}, file=sys.stderr, flush=True)
|
|
39
|
-
|
|
40
|
-
|
|
41
30
|
@app.command()
|
|
42
31
|
def experiment(
|
|
43
32
|
config_file: Path = typer.Option(
|
|
@@ -124,6 +113,11 @@ def experiment(
|
|
|
124
113
|
"--dry-run",
|
|
125
114
|
help="Show what would be run without executing",
|
|
126
115
|
),
|
|
116
|
+
display: bool = typer.Option(
|
|
117
|
+
True,
|
|
118
|
+
"--display/--no-display",
|
|
119
|
+
help="Show rich console output (disable for plain log streaming)",
|
|
120
|
+
),
|
|
127
121
|
):
|
|
128
122
|
"""
|
|
129
123
|
Run an experiment based on configuration file.
|
|
@@ -239,19 +233,8 @@ def experiment(
|
|
|
239
233
|
|
|
240
234
|
console.print(summary)
|
|
241
235
|
|
|
242
|
-
_emit_progress_event(
|
|
243
|
-
"experiment_summary",
|
|
244
|
-
name=config.name,
|
|
245
|
-
iterations=config.iterations,
|
|
246
|
-
personas=len(config.personas),
|
|
247
|
-
total_runs=total_runs,
|
|
248
|
-
multi_turn_enabled=bool(config.multi_turn and config.multi_turn.enabled),
|
|
249
|
-
output_dir=config.output_directory,
|
|
250
|
-
)
|
|
251
|
-
|
|
252
236
|
if dry_run:
|
|
253
237
|
console.print("\n[yellow]Dry run mode - no execution will occur[/yellow]")
|
|
254
|
-
_emit_progress_event("experiment_status", status="dry_run", name=config.name)
|
|
255
238
|
return
|
|
256
239
|
|
|
257
240
|
# Confirm execution
|
|
@@ -265,128 +248,134 @@ def experiment(
|
|
|
265
248
|
f"\nThis will execute {total_runs} runs."
|
|
266
249
|
)
|
|
267
250
|
|
|
268
|
-
_emit_progress_event(
|
|
269
|
-
"experiment_ready",
|
|
270
|
-
name=config.name,
|
|
271
|
-
total_runs=total_runs,
|
|
272
|
-
)
|
|
273
|
-
|
|
274
251
|
if not typer.confirm("Continue?"):
|
|
275
|
-
_emit_progress_event("experiment_status", status="aborted", reason="user_declined", name=config.name)
|
|
276
252
|
raise typer.Abort()
|
|
277
253
|
|
|
278
254
|
# Create runner
|
|
279
255
|
# Run experiment with progress tracking
|
|
280
256
|
console.print("\n[bold green]▶️ Starting experiment...[/bold green]\n")
|
|
281
257
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
else None
|
|
302
|
-
)
|
|
303
|
-
turn_task = progress.add_task(
|
|
304
|
-
"[cyan]Turn 0/0",
|
|
305
|
-
total=multi_turn_total or 1,
|
|
306
|
-
visible=False,
|
|
307
|
-
)
|
|
258
|
+
def _execute_with_callbacks(
|
|
259
|
+
progress_callback: Optional[Callable[[], None]],
|
|
260
|
+
turn_progress_callback: Optional[Callable[[int, int, Optional[str]], None]],
|
|
261
|
+
):
|
|
262
|
+
try:
|
|
263
|
+
return asyncio.run(
|
|
264
|
+
runner.run_experiment(
|
|
265
|
+
progress_callback=progress_callback,
|
|
266
|
+
turn_progress_callback=turn_progress_callback,
|
|
267
|
+
)
|
|
268
|
+
)
|
|
269
|
+
except KeyboardInterrupt:
|
|
270
|
+
console.print("\n[yellow]Experiment interrupted by user[/yellow]")
|
|
271
|
+
raise typer.Exit(1)
|
|
272
|
+
except Exception as e:
|
|
273
|
+
console.print(f"\n[red]Error during experiment:[/red] {e}")
|
|
274
|
+
if "--debug" in sys.argv:
|
|
275
|
+
console.print_exception()
|
|
276
|
+
raise typer.Exit(1)
|
|
308
277
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
278
|
+
if display and console.is_terminal:
|
|
279
|
+
with Progress(
|
|
280
|
+
SpinnerColumn(),
|
|
281
|
+
TextColumn("[progress.description]{task.description}"),
|
|
282
|
+
BarColumn(),
|
|
283
|
+
TextColumn("[progress.percentage]{task.percentage:>3.0f}%"),
|
|
284
|
+
TimeRemainingColumn(),
|
|
285
|
+
TextColumn("({task.completed} of {task.total})"),
|
|
286
|
+
console=console,
|
|
287
|
+
) as progress:
|
|
288
|
+
main_task = progress.add_task(
|
|
289
|
+
f"Running {config.name}",
|
|
290
|
+
total=total_runs,
|
|
291
|
+
)
|
|
292
|
+
multi_turn_total = (
|
|
293
|
+
config.multi_turn.max_turns
|
|
294
|
+
if config.multi_turn and config.multi_turn.max_turns
|
|
295
|
+
else None
|
|
296
|
+
)
|
|
297
|
+
turn_task = progress.add_task(
|
|
298
|
+
"[cyan]Turn 0/0",
|
|
299
|
+
total=multi_turn_total or 1,
|
|
300
|
+
visible=False,
|
|
318
301
|
)
|
|
319
302
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
303
|
+
def _progress_callback() -> None:
|
|
304
|
+
progress.advance(main_task)
|
|
305
|
+
|
|
306
|
+
def _turn_progress_callback(
|
|
307
|
+
current_turn: int,
|
|
308
|
+
total_turns: int,
|
|
309
|
+
message: Optional[str] = None,
|
|
310
|
+
) -> None:
|
|
311
|
+
total = total_turns or 1
|
|
312
|
+
clamped_turn = max(0, current_turn)
|
|
313
|
+
if message is None:
|
|
314
|
+
completed = min(clamped_turn, total)
|
|
315
|
+
description = f"[cyan]Turn {min(clamped_turn, total)}/{total}: complete"
|
|
316
|
+
progress.update(
|
|
317
|
+
turn_task,
|
|
318
|
+
total=total,
|
|
319
|
+
completed=completed,
|
|
320
|
+
description=description,
|
|
321
|
+
visible=False,
|
|
322
|
+
)
|
|
323
|
+
return
|
|
324
|
+
|
|
325
|
+
if not progress.tasks[turn_task].visible:
|
|
326
|
+
progress.update(turn_task, visible=True)
|
|
327
|
+
|
|
328
|
+
preview = message.replace("\n", " ")
|
|
329
|
+
if len(preview) > 40:
|
|
330
|
+
preview = preview[:37] + "..."
|
|
331
|
+
|
|
332
|
+
completed = max(0, min(clamped_turn - 1, total))
|
|
333
|
+
description = f"[cyan]Turn {min(clamped_turn, total)}/{total}: {preview}"
|
|
330
334
|
progress.update(
|
|
331
335
|
turn_task,
|
|
332
336
|
total=total,
|
|
333
337
|
completed=completed,
|
|
334
338
|
description=description,
|
|
335
|
-
visible=False,
|
|
336
339
|
)
|
|
337
|
-
_emit_progress_event(
|
|
338
|
-
"turn_progress",
|
|
339
|
-
state="complete",
|
|
340
|
-
current=min(clamped_turn, total),
|
|
341
|
-
total=total,
|
|
342
|
-
experiment=config.name,
|
|
343
|
-
)
|
|
344
|
-
return
|
|
345
|
-
|
|
346
|
-
if not progress.tasks[turn_task].visible:
|
|
347
|
-
progress.update(turn_task, visible=True)
|
|
348
|
-
|
|
349
|
-
preview = message.replace("\n", " ")
|
|
350
|
-
if len(preview) > 40:
|
|
351
|
-
preview = preview[:37] + "..."
|
|
352
340
|
|
|
353
|
-
|
|
354
|
-
|
|
341
|
+
results = _execute_with_callbacks(_progress_callback, _turn_progress_callback)
|
|
342
|
+
else:
|
|
343
|
+
completed_runs = 0
|
|
355
344
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
_emit_progress_event(
|
|
363
|
-
"turn_progress",
|
|
364
|
-
state="in_progress",
|
|
365
|
-
current=clamped_turn,
|
|
366
|
-
total=total,
|
|
367
|
-
preview=preview,
|
|
368
|
-
experiment=config.name,
|
|
345
|
+
def _progress_callback() -> None:
|
|
346
|
+
nonlocal completed_runs
|
|
347
|
+
completed_runs += 1
|
|
348
|
+
print(
|
|
349
|
+
f"[fluxloop] run progress {completed_runs}/{total_runs}",
|
|
350
|
+
flush=True,
|
|
369
351
|
)
|
|
370
352
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
353
|
+
def _turn_progress_callback(
|
|
354
|
+
current_turn: int,
|
|
355
|
+
total_turns: int,
|
|
356
|
+
message: Optional[str] = None,
|
|
357
|
+
) -> None:
|
|
358
|
+
total = total_turns or 1
|
|
359
|
+
clamped_turn = max(0, current_turn)
|
|
360
|
+
if message:
|
|
361
|
+
preview = message.replace("\n", " ")
|
|
362
|
+
if len(preview) > 80:
|
|
363
|
+
preview = preview[:77] + "..."
|
|
364
|
+
print(
|
|
365
|
+
f"[fluxloop] turn progress {min(clamped_turn, total)}/{total}: {preview}",
|
|
366
|
+
flush=True,
|
|
377
367
|
)
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
raise typer.Exit(1)
|
|
368
|
+
else:
|
|
369
|
+
print(
|
|
370
|
+
f"[fluxloop] turn progress {min(clamped_turn, total)}/{total}: complete",
|
|
371
|
+
flush=True,
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
print(
|
|
375
|
+
f"[fluxloop] running {config.name} ({total_runs} total runs)",
|
|
376
|
+
flush=True,
|
|
377
|
+
)
|
|
378
|
+
results = _execute_with_callbacks(_progress_callback, _turn_progress_callback)
|
|
390
379
|
|
|
391
380
|
config.set_resolved_input_count(results.get("input_count", config.get_input_count()))
|
|
392
381
|
|
|
@@ -399,15 +388,6 @@ def experiment(
|
|
|
399
388
|
)
|
|
400
389
|
|
|
401
390
|
_display_results(results)
|
|
402
|
-
_emit_progress_event(
|
|
403
|
-
"experiment_status",
|
|
404
|
-
status="completed",
|
|
405
|
-
name=config.name,
|
|
406
|
-
total_runs=results.get("total_runs", total_runs),
|
|
407
|
-
successful=results.get("successful"),
|
|
408
|
-
failed=results.get("failed"),
|
|
409
|
-
success_rate=results.get("success_rate"),
|
|
410
|
-
)
|
|
411
391
|
|
|
412
392
|
|
|
413
393
|
@app.command()
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/evaluation/engine/reporting/__init__.py
RENAMED
|
File without changes
|
{fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/evaluation/engine/reporting/html.py
RENAMED
|
File without changes
|
{fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/evaluation/engine/reporting/markdown.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/evaluation/prompts/intent_recognition.py
RENAMED
|
File without changes
|
{fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/evaluation/prompts/response_clarity.py
RENAMED
|
File without changes
|
{fluxloop_cli-0.2.28 → fluxloop_cli-0.2.29}/fluxloop_cli/evaluation/prompts/response_consistency.py
RENAMED
|
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
|
|
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
|