todo-agent 0.3.5__tar.gz → 0.3.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.
Files changed (61) hide show
  1. {todo_agent-0.3.5 → todo_agent-0.3.6}/PKG-INFO +1 -1
  2. {todo_agent-0.3.5 → todo_agent-0.3.6}/tests/test_infrastructure/test_todo_shell.py +13 -0
  3. {todo_agent-0.3.5 → todo_agent-0.3.6}/tests/test_interface/test_cli.py +91 -0
  4. {todo_agent-0.3.5 → todo_agent-0.3.6}/todo_agent/_version.py +3 -3
  5. {todo_agent-0.3.5 → todo_agent-0.3.6}/todo_agent/infrastructure/prompts/system_prompt.txt +13 -8
  6. {todo_agent-0.3.5 → todo_agent-0.3.6}/todo_agent/infrastructure/todo_shell.py +4 -0
  7. {todo_agent-0.3.5 → todo_agent-0.3.6}/todo_agent/interface/cli.py +53 -0
  8. {todo_agent-0.3.5 → todo_agent-0.3.6}/todo_agent/interface/formatters.py +1 -0
  9. {todo_agent-0.3.5 → todo_agent-0.3.6}/todo_agent/interface/tools.py +6 -5
  10. {todo_agent-0.3.5 → todo_agent-0.3.6}/todo_agent/main.py +25 -6
  11. {todo_agent-0.3.5 → todo_agent-0.3.6}/todo_agent.egg-info/PKG-INFO +1 -1
  12. {todo_agent-0.3.5 → todo_agent-0.3.6}/.gitignore +0 -0
  13. {todo_agent-0.3.5 → todo_agent-0.3.6}/LICENSE +0 -0
  14. {todo_agent-0.3.5 → todo_agent-0.3.6}/MANIFEST.in +0 -0
  15. {todo_agent-0.3.5 → todo_agent-0.3.6}/Makefile +0 -0
  16. {todo_agent-0.3.5 → todo_agent-0.3.6}/README.md +0 -0
  17. {todo_agent-0.3.5 → todo_agent-0.3.6}/docs/publishing.md +0 -0
  18. {todo_agent-0.3.5 → todo_agent-0.3.6}/pyproject.toml +0 -0
  19. {todo_agent-0.3.5 → todo_agent-0.3.6}/requirements-dev.txt +0 -0
  20. {todo_agent-0.3.5 → todo_agent-0.3.6}/requirements.txt +0 -0
  21. {todo_agent-0.3.5 → todo_agent-0.3.6}/setup.cfg +0 -0
  22. {todo_agent-0.3.5 → todo_agent-0.3.6}/tests/__init__.py +0 -0
  23. {todo_agent-0.3.5 → todo_agent-0.3.6}/tests/test_core/__init__.py +0 -0
  24. {todo_agent-0.3.5 → todo_agent-0.3.6}/tests/test_core/test_conversation_manager.py +0 -0
  25. {todo_agent-0.3.5 → todo_agent-0.3.6}/tests/test_core/test_todo_manager.py +0 -0
  26. {todo_agent-0.3.5 → todo_agent-0.3.6}/tests/test_infrastructure/__init__.py +0 -0
  27. {todo_agent-0.3.5 → todo_agent-0.3.6}/tests/test_infrastructure/test_calendar_utils.py +0 -0
  28. {todo_agent-0.3.5 → todo_agent-0.3.6}/tests/test_infrastructure/test_config.py +0 -0
  29. {todo_agent-0.3.5 → todo_agent-0.3.6}/tests/test_infrastructure/test_inference.py +0 -0
  30. {todo_agent-0.3.5 → todo_agent-0.3.6}/tests/test_infrastructure/test_llm_client_factory.py +0 -0
  31. {todo_agent-0.3.5 → todo_agent-0.3.6}/tests/test_infrastructure/test_ollama_client.py +0 -0
  32. {todo_agent-0.3.5 → todo_agent-0.3.6}/tests/test_infrastructure/test_openrouter_client.py +0 -0
  33. {todo_agent-0.3.5 → todo_agent-0.3.6}/tests/test_infrastructure/test_token_counter.py +0 -0
  34. {todo_agent-0.3.5 → todo_agent-0.3.6}/tests/test_interface/__init__.py +0 -0
  35. {todo_agent-0.3.5 → todo_agent-0.3.6}/tests/test_interface/test_formatters.py +0 -0
  36. {todo_agent-0.3.5 → todo_agent-0.3.6}/tests/test_interface/test_tools.py +0 -0
  37. {todo_agent-0.3.5 → todo_agent-0.3.6}/tests/test_linting.py +0 -0
  38. {todo_agent-0.3.5 → todo_agent-0.3.6}/tests/test_logger.py +0 -0
  39. {todo_agent-0.3.5 → todo_agent-0.3.6}/tests/test_main.py +0 -0
  40. {todo_agent-0.3.5 → todo_agent-0.3.6}/todo_agent/__init__.py +0 -0
  41. {todo_agent-0.3.5 → todo_agent-0.3.6}/todo_agent/core/__init__.py +0 -0
  42. {todo_agent-0.3.5 → todo_agent-0.3.6}/todo_agent/core/conversation_manager.py +0 -0
  43. {todo_agent-0.3.5 → todo_agent-0.3.6}/todo_agent/core/exceptions.py +0 -0
  44. {todo_agent-0.3.5 → todo_agent-0.3.6}/todo_agent/core/todo_manager.py +0 -0
  45. {todo_agent-0.3.5 → todo_agent-0.3.6}/todo_agent/infrastructure/__init__.py +0 -0
  46. {todo_agent-0.3.5 → todo_agent-0.3.6}/todo_agent/infrastructure/calendar_utils.py +0 -0
  47. {todo_agent-0.3.5 → todo_agent-0.3.6}/todo_agent/infrastructure/config.py +0 -0
  48. {todo_agent-0.3.5 → todo_agent-0.3.6}/todo_agent/infrastructure/inference.py +0 -0
  49. {todo_agent-0.3.5 → todo_agent-0.3.6}/todo_agent/infrastructure/llm_client.py +0 -0
  50. {todo_agent-0.3.5 → todo_agent-0.3.6}/todo_agent/infrastructure/llm_client_factory.py +0 -0
  51. {todo_agent-0.3.5 → todo_agent-0.3.6}/todo_agent/infrastructure/logger.py +0 -0
  52. {todo_agent-0.3.5 → todo_agent-0.3.6}/todo_agent/infrastructure/ollama_client.py +0 -0
  53. {todo_agent-0.3.5 → todo_agent-0.3.6}/todo_agent/infrastructure/openrouter_client.py +0 -0
  54. {todo_agent-0.3.5 → todo_agent-0.3.6}/todo_agent/infrastructure/token_counter.py +0 -0
  55. {todo_agent-0.3.5 → todo_agent-0.3.6}/todo_agent/interface/__init__.py +0 -0
  56. {todo_agent-0.3.5 → todo_agent-0.3.6}/todo_agent/interface/progress.py +0 -0
  57. {todo_agent-0.3.5 → todo_agent-0.3.6}/todo_agent.egg-info/SOURCES.txt +0 -0
  58. {todo_agent-0.3.5 → todo_agent-0.3.6}/todo_agent.egg-info/dependency_links.txt +0 -0
  59. {todo_agent-0.3.5 → todo_agent-0.3.6}/todo_agent.egg-info/entry_points.txt +0 -0
  60. {todo_agent-0.3.5 → todo_agent-0.3.6}/todo_agent.egg-info/requires.txt +0 -0
  61. {todo_agent-0.3.5 → todo_agent-0.3.6}/todo_agent.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: todo-agent
3
- Version: 0.3.5
3
+ Version: 0.3.6
4
4
  Summary: A natural language interface for todo.sh task management
5
5
  Author: codeprimate
6
6
  Maintainer: codeprimate
@@ -810,3 +810,16 @@ class TestTodoShell:
810
810
  # Should not duplicate +work, only add +new
811
811
  mock_replace.assert_called_once_with(1, "Test task +work +new @context")
812
812
  assert result == "Task updated"
813
+
814
+ def test_get_help_constructs_correct_command(self):
815
+ """Test getting help constructs the correct todo.sh command."""
816
+ with patch.object(
817
+ self.todo_shell, "execute", return_value="Todo.sh help output"
818
+ ) as mock_execute:
819
+ result = self.todo_shell.get_help()
820
+
821
+ # Verify the correct command was constructed
822
+ mock_execute.assert_called_once_with(
823
+ ["todo.sh", "help"], suppress_color=False
824
+ )
825
+ assert result == "Todo.sh help output"
@@ -377,6 +377,97 @@ class TestCLI:
377
377
  expected_line in str(call) for call in mock_print.call_args_list
378
378
  )
379
379
 
380
+ def test_todo_passthrough_command_success(self):
381
+ """Test successful todo.sh passthrough command execution."""
382
+ with patch("builtins.input", return_value="/add test task"), patch("sys.exit"):
383
+ # Mock the todo_shell execute method
384
+ mock_todo_shell = Mock()
385
+ mock_todo_shell.execute.return_value = "1 test task"
386
+
387
+ # Create CLI instance with mocked dependencies
388
+ with patch("todo_agent.interface.cli.Config"), patch(
389
+ "todo_agent.interface.cli.TodoShell", return_value=mock_todo_shell
390
+ ), patch("todo_agent.interface.cli.TodoManager"), patch(
391
+ "todo_agent.interface.cli.ToolCallHandler"
392
+ ), patch("todo_agent.interface.cli.Inference"), patch(
393
+ "todo_agent.interface.cli.Logger"
394
+ ), patch("todo_agent.interface.cli.Console") as mock_console:
395
+ CLI()
396
+
397
+ # Mock the console methods
398
+ mock_console.return_value.input.return_value = "/add test task"
399
+ mock_console.return_value.print = Mock()
400
+
401
+ # Test the passthrough logic directly
402
+ user_input = "/add test task"
403
+ if user_input.startswith("/"):
404
+ todo_command = user_input[1:].strip()
405
+ output = mock_todo_shell.execute(["todo.sh", *todo_command.split()])
406
+
407
+ # Verify the command was executed correctly
408
+ mock_todo_shell.execute.assert_called_once_with(
409
+ ["todo.sh", "add", "test", "task"]
410
+ )
411
+ assert output == "1 test task"
412
+
413
+ def test_todo_passthrough_command_empty(self):
414
+ """Test todo.sh passthrough with empty command."""
415
+ with patch("builtins.input", return_value="/"), patch("sys.exit"):
416
+ # Mock the todo_shell execute method
417
+ mock_todo_shell = Mock()
418
+
419
+ # Create CLI instance with mocked dependencies
420
+ with patch("todo_agent.interface.cli.Config"), patch(
421
+ "todo_agent.interface.cli.TodoShell", return_value=mock_todo_shell
422
+ ), patch("todo_agent.interface.cli.TodoManager"), patch(
423
+ "todo_agent.interface.cli.ToolCallHandler"
424
+ ), patch("todo_agent.interface.cli.Inference"), patch(
425
+ "todo_agent.interface.cli.Logger"
426
+ ), patch("todo_agent.interface.cli.Console") as mock_console:
427
+ CLI()
428
+
429
+ # Mock the console methods
430
+ mock_console.return_value.input.return_value = "/"
431
+ mock_console.return_value.print = Mock()
432
+
433
+ # Test the passthrough logic with empty command
434
+ user_input = "/"
435
+ if user_input.startswith("/"):
436
+ todo_command = user_input[1:].strip()
437
+ if not todo_command:
438
+ # Should handle empty command gracefully
439
+ assert todo_command == ""
440
+
441
+ def test_todo_help_command(self):
442
+ """Test todo-help command execution."""
443
+ with patch("builtins.input", return_value="todo-help"), patch("sys.exit"):
444
+ # Mock the todo_shell get_help method
445
+ mock_todo_shell = Mock()
446
+ mock_todo_shell.get_help.return_value = "Todo.sh help output"
447
+
448
+ # Create CLI instance with mocked dependencies
449
+ with patch("todo_agent.interface.cli.Config"), patch(
450
+ "todo_agent.interface.cli.TodoShell", return_value=mock_todo_shell
451
+ ), patch("todo_agent.interface.cli.TodoManager"), patch(
452
+ "todo_agent.interface.cli.ToolCallHandler"
453
+ ), patch("todo_agent.interface.cli.Inference"), patch(
454
+ "todo_agent.interface.cli.Logger"
455
+ ), patch("todo_agent.interface.cli.Console") as mock_console:
456
+ CLI()
457
+
458
+ # Mock the console methods
459
+ mock_console.return_value.input.return_value = "todo-help"
460
+ mock_console.return_value.print = Mock()
461
+
462
+ # Test the todo-help command
463
+ user_input = "todo-help"
464
+ if user_input.lower() == "todo-help":
465
+ help_output = mock_todo_shell.get_help()
466
+
467
+ # Verify the help was retrieved
468
+ mock_todo_shell.get_help.assert_called_once()
469
+ assert help_output == "Todo.sh help output"
470
+
380
471
  def test_empty_input_handling(self):
381
472
  """Test that empty input is handled gracefully."""
382
473
  # This would be tested in the main run loop
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '0.3.5'
32
- __version_tuple__ = version_tuple = (0, 3, 5)
31
+ __version__ = version = '0.3.6'
32
+ __version_tuple__ = version_tuple = (0, 3, 6)
33
33
 
34
- __commit_id__ = commit_id = 'g698d691a3'
34
+ __commit_id__ = commit_id = 'g30b41eb2b'
@@ -104,23 +104,28 @@ Example: add_task(description='Put out trash and recycling', project='weekly', c
104
104
 
105
105
  **Parent Task Format**: `(A) Child task description +project @context due:YYYY-MM-DD duration:2h parent:12`
106
106
 
107
- ### Task Completion Protocol
107
+ ### Task Completion Protocol
108
+ NOTE: use complete_task() for existing non-recurring tasks
108
109
  1. **Discovery**: Use `list_tasks()` to search semantically in active tasks
109
- 2. **For Recurring Tasks** (containing +daily, +weekly, +monthly, +weekdays, etc.):
110
+ 2. **For Non-Recurring Tasks**:
111
+ - The task will NOT have the tag "rec:"
112
+ - Single match → use `complete_task(task_number='XX')` to mark existing active tasks complete
113
+ - Multiple/fuzzy → show options
114
+ - No match → suggest alternatives
115
+ 3. **For Recurring Tasks** (containing +daily, +weekly, +monthly, +weekdays, etc.):
116
+ - The task will have the tag "rec:"
110
117
  - **USE** `create_completed_task()` with the original task number as parent_number
111
118
  - **PRESERVE** the original recurring task for future occurrences. **IMPORTANT** DO NOT MARK COMPLETE! DO NOT MODIFY!
112
119
  - **TOOL CALL**: `create_completed_task(description='Task description', parent_number='XX', completion_date='YYYY-MM-DD', context='context', project='project')`
113
- 3. **For Non-Recurring Tasks**:
114
- - Single match → use `complete_task(task_number='XX')`
115
- - Multiple/fuzzy → show options
116
- - No match → suggest alternatives
120
+
117
121
 
118
122
  ### Recurring Task Completion Examples
123
+ NOTE: ONLY USE create_completed_task() for RECURRING TASKS!
119
124
  - **User says**: "I put out the trash" → Find `(B) Put out trash and recycling +weekly +thursday @home duration:5m`
120
- - **Tool Call**: `create_completed_task(description='Put out trash and recycling', parent_number='B', completion_date='YYYY-MM-DD', context='home', project='weekly')`
125
+ - **Tool Call**: ONLY FOR EXISTING TASKS! `create_completed_task(description='Put out trash and recycling', parent_number='B', completion_date='YYYY-MM-DD', context='home', project='weekly')`
121
126
  - **Result**: Original task remains active for next Thursday
122
127
  - **User says**: "Done with standup" → Find `Standup meeting at 9:00AM +daily +weekdays @office duration:10m`
123
- - **Tool Call**: `create_completed_task(description='Standup meeting at 9:00AM', parent_number='XX', completion_date='YYYY-MM-DD', context='office', project='daily')`
128
+ - **Tool Call**: ONLY FOR EXISTING TASKS! `create_completed_task(description='Standup meeting at 9:00AM', parent_number='XX', completion_date='YYYY-MM-DD', context='office', project='daily')`
124
129
  - **Result**: Original task remains active for next weekday
125
130
 
126
131
  ### Task Suggestions
@@ -182,6 +182,10 @@ class TodoShell:
182
182
  """Archive completed tasks."""
183
183
  return self.execute(["todo.sh", "-f", "archive"])
184
184
 
185
+ def get_help(self) -> str:
186
+ """Get todo.sh help output."""
187
+ return self.execute(["todo.sh", "help"], suppress_color=False)
188
+
185
189
  def set_due_date(self, task_number: int, due_date: str) -> str:
186
190
  """
187
191
  Set or update due date for a task by intelligently rewriting it.
@@ -225,6 +225,23 @@ class CLI:
225
225
  self.console.print(table)
226
226
  self.console.print("Or just type your request naturally!", style="italic green")
227
227
 
228
+ def _print_todo_help(self) -> None:
229
+ """Print todo.sh help information."""
230
+ try:
231
+ # Get todo.sh help output
232
+ help_output = self.todo_shell.get_help()
233
+ formatted_output = TaskFormatter.format_task_list(help_output)
234
+ help_panel = PanelFormatter.create_task_panel(
235
+ formatted_output, title="📋 Todo.sh Help"
236
+ )
237
+ self.console.print(help_panel)
238
+ except Exception as e:
239
+ self.logger.error(f"Error getting todo.sh help: {e!s}")
240
+ error_msg = ResponseFormatter.format_error(
241
+ f"Failed to get todo.sh help: {e!s}"
242
+ )
243
+ self.console.print(error_msg)
244
+
228
245
  def _print_about(self) -> None:
229
246
  """Print about information in a formatted panel."""
230
247
  about_panel = PanelFormatter.create_about_panel()
@@ -280,6 +297,37 @@ class CLI:
280
297
  if not user_input:
281
298
  continue
282
299
 
300
+ # Handle todo.sh passthrough commands (starting with /)
301
+ if user_input.startswith("/"):
302
+ self.logger.debug(
303
+ f"Processing todo.sh passthrough command: {user_input}"
304
+ )
305
+ try:
306
+ # Remove the leading / and execute as todo.sh command
307
+ todo_command = user_input[1:].strip()
308
+ if not todo_command:
309
+ self.console.print(
310
+ ResponseFormatter.format_error("Empty todo.sh command")
311
+ )
312
+ continue
313
+
314
+ # Execute the todo.sh command directly
315
+ output = self.todo_shell.execute(
316
+ ["todo.sh", *todo_command.split()]
317
+ )
318
+ formatted_output = TaskFormatter.format_task_list(output)
319
+ task_panel = PanelFormatter.create_task_panel(
320
+ formatted_output, title="📋 Todo.sh Output"
321
+ )
322
+ self.console.print(task_panel)
323
+ except Exception as e:
324
+ self.logger.error(f"Error executing todo.sh command: {e!s}")
325
+ error_msg = ResponseFormatter.format_error(
326
+ f"Todo.sh command failed: {e!s}"
327
+ )
328
+ self.console.print(error_msg)
329
+ continue
330
+
283
331
  # Handle special commands
284
332
  if user_input.lower() == "clear":
285
333
  self.logger.info("User requested conversation clear")
@@ -302,6 +350,11 @@ class CLI:
302
350
  self._print_help()
303
351
  continue
304
352
 
353
+ if user_input.lower() == "todo-help":
354
+ self.logger.debug("User requested todo.sh help")
355
+ self._print_todo_help()
356
+ continue
357
+
305
358
  if user_input.lower() == "about":
306
359
  self.logger.debug("User requested about information")
307
360
  self._print_about()
@@ -357,6 +357,7 @@ class TableFormatter:
357
357
  ("clear", "Clear conversation history"),
358
358
  ("stats", "Show conversation statistics"),
359
359
  ("help", "Show this help message"),
360
+ ("todo-help", "Show todo.sh help"),
360
361
  ("about", "Show application information"),
361
362
  ("list", "List all tasks (no LLM interaction)"),
362
363
  ("done", "List completed tasks (no LLM interaction)"),
@@ -626,13 +626,14 @@ class ToolCallHandler:
626
626
  "name": "create_completed_task",
627
627
  "description": (
628
628
  "Create a task and immediately mark it as completed. "
629
- "USE CASE: Call this when user says they completed something on a specific date (e.g., 'I did the laundry today', 'I finished the report yesterday', 'I cleaned the garage last week') "
629
+ "USE CASE: WHEN NO MATCH IS FOUND! Call this when user says they completed something on a specific date (e.g., 'I did the laundry today', 'I finished the report yesterday', 'I cleaned the garage last week') "
630
630
  "and you have already researched existing tasks to determine no match exists. "
631
- "WORKFLOW: 1) Use list_tasks() to search for existing tasks, 2) Use list_completed_tasks() to verify it's not already done, "
632
- "3) If no match found, call this tool to create and complete the task in one operation. "
631
+ "WORKFLOW: 1) Use list_tasks() to search for existing tasks, "
632
+ "2) Use list_completed_tasks() to verify it's not already done, "
633
+ "3) If a match is found, use complete_task() to mark it complete, "
634
+ "4) If no match found, call this tool to create and complete the task in one operation. "
633
635
  "STRATEGIC CONTEXT: This is a convenience tool for the common pattern of 'I did X on [date]' - "
634
- "it creates a task with the specified completion date and immediately marks it complete. "
635
- "The LLM should handle the research and decision-making about whether to use this tool."
636
+ "when no task match is found, it creates a task with the specified completion date and immediately marks it complete. "
636
637
  ),
637
638
  "parameters": {
638
639
  "type": "object",
@@ -12,7 +12,7 @@ from .interface.cli import CLI
12
12
  def main() -> None:
13
13
  """Main application entry point."""
14
14
  from ._version import __version__
15
-
15
+
16
16
  parser = argparse.ArgumentParser(
17
17
  description=f"Todo.sh LLM Agent - Natural language task management (v{__version__})",
18
18
  formatter_class=argparse.RawDescriptionHelpFormatter,
@@ -27,18 +27,20 @@ Examples:
27
27
  )
28
28
 
29
29
  parser.add_argument(
30
- "--version", "-v",
30
+ "--version",
31
+ "-v",
31
32
  action="version",
32
33
  version=f"%(prog)s {__version__}",
33
34
  help="Show version information and exit",
34
35
  )
35
-
36
+
36
37
  parser.add_argument(
37
- "--help", "-h",
38
+ "--help",
39
+ "-h",
38
40
  action="help",
39
41
  help="Show this help message and exit",
40
42
  )
41
-
43
+
42
44
  parser.add_argument(
43
45
  "command",
44
46
  nargs="?",
@@ -53,11 +55,28 @@ Examples:
53
55
  if args.command:
54
56
  # Single command mode
55
57
  # Handle special commands that don't need LLM processing
56
- if args.command.lower() in ["help", "about"]:
58
+ if args.command.lower() in ["help", "about", "todo-help"]:
57
59
  if args.command.lower() == "help":
58
60
  cli._print_help()
59
61
  elif args.command.lower() == "about":
60
62
  cli._print_about()
63
+ elif args.command.lower() == "todo-help":
64
+ cli._print_todo_help()
65
+ elif args.command.startswith("/"):
66
+ # Handle todo.sh passthrough commands
67
+ try:
68
+ # Remove the leading / and execute as todo.sh command
69
+ todo_command = args.command[1:].strip()
70
+ if not todo_command:
71
+ print("Error: Empty todo.sh command")
72
+ sys.exit(1)
73
+
74
+ # Execute the todo.sh command directly
75
+ output = cli.todo_shell.execute(["todo.sh", *todo_command.split()])
76
+ print(output)
77
+ except Exception as e:
78
+ print(f"Error: Todo.sh command failed: {e}")
79
+ sys.exit(1)
61
80
  else:
62
81
  # Process through LLM
63
82
  response = cli.run_single_request(args.command)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: todo-agent
3
- Version: 0.3.5
3
+ Version: 0.3.6
4
4
  Summary: A natural language interface for todo.sh task management
5
5
  Author: codeprimate
6
6
  Maintainer: codeprimate
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