fixos 2.2.27__tar.gz → 2.2.28__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 (145) hide show
  1. {fixos-2.2.27 → fixos-2.2.28}/CHANGELOG.md +25 -0
  2. {fixos-2.2.27 → fixos-2.2.28}/PKG-INFO +6 -6
  3. {fixos-2.2.27 → fixos-2.2.28}/README.md +5 -5
  4. {fixos-2.2.27 → fixos-2.2.28}/fixos/__init__.py +1 -1
  5. {fixos-2.2.27 → fixos-2.2.28}/fixos/agent/session_core.py +34 -0
  6. {fixos-2.2.27 → fixos-2.2.28}/fixos.egg-info/PKG-INFO +6 -6
  7. {fixos-2.2.27 → fixos-2.2.28}/fixos.egg-info/top_level.txt +1 -0
  8. {fixos-2.2.27 → fixos-2.2.28}/pyproject.toml +1 -1
  9. {fixos-2.2.27 → fixos-2.2.28}/setup.py +1 -1
  10. {fixos-2.2.27 → fixos-2.2.28}/tests/unit/test_core.py +87 -0
  11. {fixos-2.2.27 → fixos-2.2.28}/.env.example +0 -0
  12. {fixos-2.2.27 → fixos-2.2.28}/LICENSE +0 -0
  13. {fixos-2.2.27 → fixos-2.2.28}/MANIFEST.in +0 -0
  14. {fixos-2.2.27 → fixos-2.2.28}/docker/README.md +0 -0
  15. {fixos-2.2.27 → fixos-2.2.28}/docker/TEST_RESULTS.md +0 -0
  16. {fixos-2.2.27 → fixos-2.2.28}/docker/TEST_RESULTS_V2.md +0 -0
  17. {fixos-2.2.27 → fixos-2.2.28}/docker/alpine/Dockerfile +0 -0
  18. {fixos-2.2.27 → fixos-2.2.28}/docker/arch/Dockerfile +0 -0
  19. {fixos-2.2.27 → fixos-2.2.28}/docker/base/Dockerfile +0 -0
  20. {fixos-2.2.27 → fixos-2.2.28}/docker/broken-audio/Dockerfile +0 -0
  21. {fixos-2.2.27 → fixos-2.2.28}/docker/broken-full/Dockerfile +0 -0
  22. {fixos-2.2.27 → fixos-2.2.28}/docker/broken-network/Dockerfile +0 -0
  23. {fixos-2.2.27 → fixos-2.2.28}/docker/broken-thumbnails/Dockerfile +0 -0
  24. {fixos-2.2.27 → fixos-2.2.28}/docker/debian/Dockerfile +0 -0
  25. {fixos-2.2.27 → fixos-2.2.28}/docker/docker-compose.multi-system.yml +0 -0
  26. {fixos-2.2.27 → fixos-2.2.28}/docker/docker-compose.yml +0 -0
  27. {fixos-2.2.27 → fixos-2.2.28}/docker/fedora/Dockerfile +0 -0
  28. {fixos-2.2.27 → fixos-2.2.28}/docker/test-multi-system.sh +0 -0
  29. {fixos-2.2.27 → fixos-2.2.28}/docker/test-scenarios.sh +0 -0
  30. {fixos-2.2.27 → fixos-2.2.28}/docker/ubuntu/Dockerfile +0 -0
  31. {fixos-2.2.27 → fixos-2.2.28}/docker/validate-scenario.py +0 -0
  32. {fixos-2.2.27 → fixos-2.2.28}/docs/examples/advanced_usage.py +0 -0
  33. {fixos-2.2.27 → fixos-2.2.28}/docs/examples/quickstart.py +0 -0
  34. {fixos-2.2.27 → fixos-2.2.28}/fixos/agent/__init__.py +0 -0
  35. {fixos-2.2.27 → fixos-2.2.28}/fixos/agent/autonomous.py +0 -0
  36. {fixos-2.2.27 → fixos-2.2.28}/fixos/agent/autonomous_session.py +0 -0
  37. {fixos-2.2.27 → fixos-2.2.28}/fixos/agent/hitl.py +0 -0
  38. {fixos-2.2.27 → fixos-2.2.28}/fixos/agent/hitl_session.py +0 -0
  39. {fixos-2.2.27 → fixos-2.2.28}/fixos/agent/session_handlers.py +0 -0
  40. {fixos-2.2.27 → fixos-2.2.28}/fixos/agent/session_io.py +0 -0
  41. {fixos-2.2.27 → fixos-2.2.28}/fixos/anonymizer.py +0 -0
  42. {fixos-2.2.27 → fixos-2.2.28}/fixos/cli/__init__.py +0 -0
  43. {fixos-2.2.27 → fixos-2.2.28}/fixos/cli/ask_cmd.py +0 -0
  44. {fixos-2.2.27 → fixos-2.2.28}/fixos/cli/cleanup_cmd.py +0 -0
  45. {fixos-2.2.27 → fixos-2.2.28}/fixos/cli/config_cmd.py +0 -0
  46. {fixos-2.2.27 → fixos-2.2.28}/fixos/cli/features_cmd.py +0 -0
  47. {fixos-2.2.27 → fixos-2.2.28}/fixos/cli/fix_cmd.py +0 -0
  48. {fixos-2.2.27 → fixos-2.2.28}/fixos/cli/history_cmd.py +0 -0
  49. {fixos-2.2.27 → fixos-2.2.28}/fixos/cli/main.py +0 -0
  50. {fixos-2.2.27 → fixos-2.2.28}/fixos/cli/orchestrate_cmd.py +0 -0
  51. {fixos-2.2.27 → fixos-2.2.28}/fixos/cli/output_formatter.py +0 -0
  52. {fixos-2.2.27 → fixos-2.2.28}/fixos/cli/profile_cmd.py +0 -0
  53. {fixos-2.2.27 → fixos-2.2.28}/fixos/cli/provider_cmd.py +0 -0
  54. {fixos-2.2.27 → fixos-2.2.28}/fixos/cli/quickfix_cmd.py +0 -0
  55. {fixos-2.2.27 → fixos-2.2.28}/fixos/cli/report_cmd.py +0 -0
  56. {fixos-2.2.27 → fixos-2.2.28}/fixos/cli/rollback_cmd.py +0 -0
  57. {fixos-2.2.27 → fixos-2.2.28}/fixos/cli/scan_cmd.py +0 -0
  58. {fixos-2.2.27 → fixos-2.2.28}/fixos/cli/shared.py +0 -0
  59. {fixos-2.2.27 → fixos-2.2.28}/fixos/cli/token_cmd.py +0 -0
  60. {fixos-2.2.27 → fixos-2.2.28}/fixos/cli/watch_cmd.py +0 -0
  61. {fixos-2.2.27 → fixos-2.2.28}/fixos/cli.py +0 -0
  62. {fixos-2.2.27 → fixos-2.2.28}/fixos/config.py +0 -0
  63. {fixos-2.2.27 → fixos-2.2.28}/fixos/config_interactive.py +0 -0
  64. {fixos-2.2.27 → fixos-2.2.28}/fixos/constants.py +0 -0
  65. {fixos-2.2.27 → fixos-2.2.28}/fixos/diagnostics/__init__.py +0 -0
  66. {fixos-2.2.27 → fixos-2.2.28}/fixos/diagnostics/checks/__init__.py +0 -0
  67. {fixos-2.2.27 → fixos-2.2.28}/fixos/diagnostics/checks/_shared.py +0 -0
  68. {fixos-2.2.27 → fixos-2.2.28}/fixos/diagnostics/checks/audio.py +0 -0
  69. {fixos-2.2.27 → fixos-2.2.28}/fixos/diagnostics/checks/hardware.py +0 -0
  70. {fixos-2.2.27 → fixos-2.2.28}/fixos/diagnostics/checks/resources.py +0 -0
  71. {fixos-2.2.27 → fixos-2.2.28}/fixos/diagnostics/checks/security.py +0 -0
  72. {fixos-2.2.27 → fixos-2.2.28}/fixos/diagnostics/checks/system_core.py +0 -0
  73. {fixos-2.2.27 → fixos-2.2.28}/fixos/diagnostics/checks/thumbnails.py +0 -0
  74. {fixos-2.2.27 → fixos-2.2.28}/fixos/diagnostics/dev_project_analyzer.py +0 -0
  75. {fixos-2.2.27 → fixos-2.2.28}/fixos/diagnostics/disk_analyzer.py +0 -0
  76. {fixos-2.2.27 → fixos-2.2.28}/fixos/diagnostics/flatpak_analyzer.py +0 -0
  77. {fixos-2.2.27 → fixos-2.2.28}/fixos/diagnostics/service_cleanup.py +0 -0
  78. {fixos-2.2.27 → fixos-2.2.28}/fixos/diagnostics/service_details.py +0 -0
  79. {fixos-2.2.27 → fixos-2.2.28}/fixos/diagnostics/service_scanner.py +0 -0
  80. {fixos-2.2.27 → fixos-2.2.28}/fixos/diagnostics/storage_analyzer.py +0 -0
  81. {fixos-2.2.27 → fixos-2.2.28}/fixos/diagnostics/system_checks.py +0 -0
  82. {fixos-2.2.27 → fixos-2.2.28}/fixos/features/__init__.py +0 -0
  83. {fixos-2.2.27 → fixos-2.2.28}/fixos/features/auditor.py +0 -0
  84. {fixos-2.2.27 → fixos-2.2.28}/fixos/features/catalog.py +0 -0
  85. {fixos-2.2.27 → fixos-2.2.28}/fixos/features/installer.py +0 -0
  86. {fixos-2.2.27 → fixos-2.2.28}/fixos/features/profiles.py +0 -0
  87. {fixos-2.2.27 → fixos-2.2.28}/fixos/features/renderer.py +0 -0
  88. {fixos-2.2.27 → fixos-2.2.28}/fixos/fixes/__init__.py +0 -0
  89. {fixos-2.2.27 → fixos-2.2.28}/fixos/interactive/__init__.py +0 -0
  90. {fixos-2.2.27 → fixos-2.2.28}/fixos/interactive/cleanup_planner.py +0 -0
  91. {fixos-2.2.27 → fixos-2.2.28}/fixos/llm_shell.py +0 -0
  92. {fixos-2.2.27 → fixos-2.2.28}/fixos/orchestrator/__init__.py +0 -0
  93. {fixos-2.2.27 → fixos-2.2.28}/fixos/orchestrator/executor.py +0 -0
  94. {fixos-2.2.27 → fixos-2.2.28}/fixos/orchestrator/graph.py +0 -0
  95. {fixos-2.2.27 → fixos-2.2.28}/fixos/orchestrator/orchestrator.py +0 -0
  96. {fixos-2.2.27 → fixos-2.2.28}/fixos/orchestrator/rollback.py +0 -0
  97. {fixos-2.2.27 → fixos-2.2.28}/fixos/platform_utils.py +0 -0
  98. {fixos-2.2.27 → fixos-2.2.28}/fixos/plugins/__init__.py +0 -0
  99. {fixos-2.2.27 → fixos-2.2.28}/fixos/plugins/base.py +0 -0
  100. {fixos-2.2.27 → fixos-2.2.28}/fixos/plugins/builtin/__init__.py +0 -0
  101. {fixos-2.2.27 → fixos-2.2.28}/fixos/plugins/builtin/audio.py +0 -0
  102. {fixos-2.2.27 → fixos-2.2.28}/fixos/plugins/builtin/disk.py +0 -0
  103. {fixos-2.2.27 → fixos-2.2.28}/fixos/plugins/builtin/hardware.py +0 -0
  104. {fixos-2.2.27 → fixos-2.2.28}/fixos/plugins/builtin/resources.py +0 -0
  105. {fixos-2.2.27 → fixos-2.2.28}/fixos/plugins/builtin/security.py +0 -0
  106. {fixos-2.2.27 → fixos-2.2.28}/fixos/plugins/builtin/thumbnails.py +0 -0
  107. {fixos-2.2.27 → fixos-2.2.28}/fixos/plugins/registry.py +0 -0
  108. {fixos-2.2.27 → fixos-2.2.28}/fixos/profiles/__init__.py +0 -0
  109. {fixos-2.2.27 → fixos-2.2.28}/fixos/providers/__init__.py +0 -0
  110. {fixos-2.2.27 → fixos-2.2.28}/fixos/providers/llm.py +0 -0
  111. {fixos-2.2.27 → fixos-2.2.28}/fixos/providers/llm_analyzer.py +0 -0
  112. {fixos-2.2.27 → fixos-2.2.28}/fixos/providers/schemas.py +0 -0
  113. {fixos-2.2.27 → fixos-2.2.28}/fixos/system_checks.py +0 -0
  114. {fixos-2.2.27 → fixos-2.2.28}/fixos/utils/__init__.py +0 -0
  115. {fixos-2.2.27 → fixos-2.2.28}/fixos/utils/anonymizer.py +0 -0
  116. {fixos-2.2.27 → fixos-2.2.28}/fixos/utils/terminal.py +0 -0
  117. {fixos-2.2.27 → fixos-2.2.28}/fixos/utils/timeout.py +0 -0
  118. {fixos-2.2.27 → fixos-2.2.28}/fixos/utils/web_search.py +0 -0
  119. {fixos-2.2.27 → fixos-2.2.28}/fixos/watch.py +0 -0
  120. {fixos-2.2.27 → fixos-2.2.28}/fixos.egg-info/SOURCES.txt +0 -0
  121. {fixos-2.2.27 → fixos-2.2.28}/fixos.egg-info/dependency_links.txt +0 -0
  122. {fixos-2.2.27 → fixos-2.2.28}/fixos.egg-info/entry_points.txt +0 -0
  123. {fixos-2.2.27 → fixos-2.2.28}/fixos.egg-info/requires.txt +0 -0
  124. {fixos-2.2.27 → fixos-2.2.28}/pytest.ini +0 -0
  125. {fixos-2.2.27 → fixos-2.2.28}/requirements-dev.txt +0 -0
  126. {fixos-2.2.27 → fixos-2.2.28}/requirements.txt +0 -0
  127. {fixos-2.2.27 → fixos-2.2.28}/scripts/pyqual-calibrate.py +0 -0
  128. {fixos-2.2.27 → fixos-2.2.28}/setup.cfg +0 -0
  129. {fixos-2.2.27 → fixos-2.2.28}/tests/__init__.py +0 -0
  130. {fixos-2.2.27 → fixos-2.2.28}/tests/conftest.py +0 -0
  131. {fixos-2.2.27 → fixos-2.2.28}/tests/e2e/__init__.py +0 -0
  132. {fixos-2.2.27 → fixos-2.2.28}/tests/e2e/test_anonymization_layers.py +0 -0
  133. {fixos-2.2.27 → fixos-2.2.28}/tests/e2e/test_audio_broken.py +0 -0
  134. {fixos-2.2.27 → fixos-2.2.28}/tests/e2e/test_cli.py +0 -0
  135. {fixos-2.2.27 → fixos-2.2.28}/tests/e2e/test_executor.py +0 -0
  136. {fixos-2.2.27 → fixos-2.2.28}/tests/e2e/test_multi_system.py +0 -0
  137. {fixos-2.2.27 → fixos-2.2.28}/tests/e2e/test_network_broken.py +0 -0
  138. {fixos-2.2.27 → fixos-2.2.28}/tests/e2e/test_thumbnails_broken.py +0 -0
  139. {fixos-2.2.27 → fixos-2.2.28}/tests/test_fixos.py +0 -0
  140. {fixos-2.2.27 → fixos-2.2.28}/tests/unit/__init__.py +0 -0
  141. {fixos-2.2.27 → fixos-2.2.28}/tests/unit/test_anonymizer.py +0 -0
  142. {fixos-2.2.27 → fixos-2.2.28}/tests/unit/test_executor.py +0 -0
  143. {fixos-2.2.27 → fixos-2.2.28}/tests/unit/test_orchestrator.py +0 -0
  144. {fixos-2.2.27 → fixos-2.2.28}/tests/unit/test_service_cleanup.py +0 -0
  145. {fixos-2.2.27 → fixos-2.2.28}/tests/unit/test_service_scanner.py +0 -0
@@ -150,6 +150,31 @@ fix(goal): code analysis engine
150
150
  - **refactor(cli):** Usunięto zduplikowany kod ujednolicając funkcje analizy dysku do wspólnego helpera `_run_disk_analysis`.
151
151
  - **refactor(ui):** Usunięto ikony Unicode z CLI i sformatowano wyjście `stderr` oraz standardowego logowania na czysty kod Markdown dla poprawy czytelności w oknach terminalowych.
152
152
 
153
+ ## [2.2.28] - 2026-05-06
154
+
155
+ ### Docs
156
+ - Update README.md
157
+ - Update SUMD.md
158
+ - Update SUMR.md
159
+ - Update project/README.md
160
+ - Update project/context.md
161
+
162
+ ### Test
163
+ - Update tests/unit/test_core.py
164
+
165
+ ### Other
166
+ - Update app.doql.less
167
+ - Update docker/docker-compose.yml
168
+ - Update fixos/agent/session_core.py
169
+ - Update project/analysis.toon.yaml
170
+ - Update project/calls.mmd
171
+ - Update project/calls.png
172
+ - Update project/calls.toon.yaml
173
+ - Update project/calls.yaml
174
+ - Update project/compact_flow.mmd
175
+ - Update project/compact_flow.png
176
+ - ... and 10 more files
177
+
153
178
  ## [2.2.27] - 2026-05-04
154
179
 
155
180
  ### Docs
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fixos
3
- Version: 2.2.27
3
+ Version: 2.2.28
4
4
  Summary: AI-powered Linux/Windows diagnostics and repair – audio, hardware, system issues
5
5
  Home-page: https://github.com/wronai/fixos
6
6
  Author: fixos contributors
@@ -63,13 +63,13 @@ AI-powered OS Diagnostics
63
63
 
64
64
  ## AI Cost Tracking
65
65
 
66
- ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-2.2.27-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
67
- ![AI Cost](https://img.shields.io/badge/AI%20Cost-$7.50-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-26.2h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
66
+ ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-2.2.28-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
67
+ ![AI Cost](https://img.shields.io/badge/AI%20Cost-$7.50-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-26.5h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
68
68
 
69
- - 🤖 **LLM usage:** $7.5000 (126 commits)
70
- - 👤 **Human dev:** ~$2620 (26.2h @ $100/h, 30min dedup)
69
+ - 🤖 **LLM usage:** $7.5000 (127 commits)
70
+ - 👤 **Human dev:** ~$2647 (26.5h @ $100/h, 30min dedup)
71
71
 
72
- Generated on 2026-05-04 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
72
+ Generated on 2026-05-06 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
73
73
 
74
74
  ---
75
75
 
@@ -19,13 +19,13 @@ AI-powered OS Diagnostics
19
19
 
20
20
  ## AI Cost Tracking
21
21
 
22
- ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-2.2.27-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
23
- ![AI Cost](https://img.shields.io/badge/AI%20Cost-$7.50-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-26.2h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
22
+ ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-2.2.28-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
23
+ ![AI Cost](https://img.shields.io/badge/AI%20Cost-$7.50-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-26.5h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
24
24
 
25
- - 🤖 **LLM usage:** $7.5000 (126 commits)
26
- - 👤 **Human dev:** ~$2620 (26.2h @ $100/h, 30min dedup)
25
+ - 🤖 **LLM usage:** $7.5000 (127 commits)
26
+ - 👤 **Human dev:** ~$2647 (26.5h @ $100/h, 30min dedup)
27
27
 
28
- Generated on 2026-05-04 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
28
+ Generated on 2026-05-06 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
29
29
 
30
30
  ---
31
31
 
@@ -1,2 +1,2 @@
1
1
  """fixos – AI-powered Linux/Windows diagnostics and repair."""
2
- __version__ = "2.2.27"
2
+ __version__ = "2.2.28"
@@ -111,9 +111,17 @@ def _is_part_diagnostic_only(part: str) -> bool:
111
111
  return normalized.startswith(diagnostic_prefixes)
112
112
 
113
113
 
114
+ def _extract_co_robi(text: str) -> str:
115
+ """Extract 'Co robi:' comment from text following a command match."""
116
+ m = re.search(r"\s*\*{0,2}Co robi:\*{0,2}\s*(.+?)(?:\n|$)", text, re.IGNORECASE)
117
+ return m.group(1).strip() if m else ""
118
+
119
+
114
120
  def extract_fixes(reply: str) -> List[Tuple[str, str]]:
115
121
  """Extract (command, comment) pairs from LLM reply."""
116
122
  fixes: List[Tuple[str, str]] = []
123
+
124
+ # Pattern 1: **Komenda:** `command` (strict: bold + backticks)
117
125
  for m in re.finditer(
118
126
  r"\*\*Komenda:\*\*\s*`([^`]+)`(?:[^\n]*?\*\*Co robi:\*\*\s*(.+?))?(?=\n|$)",
119
127
  reply, re.IGNORECASE,
@@ -121,12 +129,38 @@ def extract_fixes(reply: str) -> List[Tuple[str, str]]:
121
129
  cmd = m.group(1).strip()
122
130
  if cmd:
123
131
  fixes.append((cmd, (m.group(2) or "").strip()))
132
+
133
+ # Pattern 2: Komenda: `command` (backticks, optional bold)
134
+ if not fixes:
135
+ for m in re.finditer(
136
+ r"\*{0,2}Komenda:\*{0,2}\s*`([^`]+)`",
137
+ reply, re.IGNORECASE,
138
+ ):
139
+ cmd = m.group(1).strip()
140
+ if cmd:
141
+ fixes.append((cmd, _extract_co_robi(reply[m.end():])))
142
+
143
+ # Pattern 3: Komenda: command (no backticks — command until Co robi:/next problem/section)
144
+ if not fixes:
145
+ for m in re.finditer(
146
+ r"\*{0,2}Komenda:\*{0,2}\s*"
147
+ r"(.+?)"
148
+ r"(?=\n\s*\*{0,2}Co robi:|\n[🔴🟡🟢]|\n━|\n─|\n\[[\dA-Z]|\Z)",
149
+ reply, re.IGNORECASE | re.DOTALL,
150
+ ):
151
+ cmd = re.sub(r"\s*\n\s*", " ", m.group(1)).strip()
152
+ if cmd:
153
+ fixes.append((cmd, _extract_co_robi(reply[m.end():])))
154
+
155
+ # Fallback: → Fix: `command`
124
156
  if not fixes:
125
157
  for m in re.finditer(r"→\s*Fix:\s*`([^`]+)`", reply, re.IGNORECASE):
126
158
  fixes.append((m.group(1).strip(), ""))
159
+ # Fallback: [N] ... `command`
127
160
  if not fixes:
128
161
  for m in re.finditer(r"\[(\d+)\][^`\n]+`([^`]+)`", reply):
129
162
  fixes.append((m.group(2).strip(), f"Fix #{m.group(1)}"))
163
+ # Fallback: EXEC: `command`
130
164
  if not fixes:
131
165
  for m in re.finditer(r"EXEC:\s*`([^`]+)`", reply, re.IGNORECASE):
132
166
  fixes.append((m.group(1).strip(), ""))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fixos
3
- Version: 2.2.27
3
+ Version: 2.2.28
4
4
  Summary: AI-powered Linux/Windows diagnostics and repair – audio, hardware, system issues
5
5
  Home-page: https://github.com/wronai/fixos
6
6
  Author: fixos contributors
@@ -63,13 +63,13 @@ AI-powered OS Diagnostics
63
63
 
64
64
  ## AI Cost Tracking
65
65
 
66
- ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-2.2.27-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
67
- ![AI Cost](https://img.shields.io/badge/AI%20Cost-$7.50-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-26.2h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
66
+ ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-2.2.28-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
67
+ ![AI Cost](https://img.shields.io/badge/AI%20Cost-$7.50-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-26.5h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
68
68
 
69
- - 🤖 **LLM usage:** $7.5000 (126 commits)
70
- - 👤 **Human dev:** ~$2620 (26.2h @ $100/h, 30min dedup)
69
+ - 🤖 **LLM usage:** $7.5000 (127 commits)
70
+ - 👤 **Human dev:** ~$2647 (26.5h @ $100/h, 30min dedup)
71
71
 
72
- Generated on 2026-05-04 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
72
+ Generated on 2026-05-06 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
73
73
 
74
74
  ---
75
75
 
@@ -3,6 +3,7 @@ TODO
3
3
  dist
4
4
  docs
5
5
  fixos
6
+ htmlcov
6
7
  project
7
8
  scratch
8
9
  scripts
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "fixos"
7
- version = "2.2.27"
7
+ version = "2.2.28"
8
8
  description = "AI-powered Linux/Windows diagnostics and repair – audio, hardware, system issues"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -5,7 +5,7 @@ long_description = (Path(__file__).parent / "README.md").read_text(encoding="utf
5
5
 
6
6
  setup(
7
7
  name="fixos",
8
- version="2.2.26",
8
+ version="2.2.27",
9
9
  description="AI-powered Linux/Windows diagnostics and repair with anonymization",
10
10
  long_description=long_description,
11
11
  long_description_content_type="text/markdown",
@@ -191,6 +191,93 @@ class TestDiagnosticOnlyCommand:
191
191
  assert _is_diagnostic_only_command("df -h && free -h") is True
192
192
 
193
193
 
194
+ class TestExtractFixes:
195
+ """Regression tests for extract_fixes – covers multiple LLM output formats."""
196
+
197
+ def test_strict_bold_backticks(self):
198
+ """Pattern 1: **Komenda:** `command` **Co robi:** explanation"""
199
+ from fixos.agent.session_core import extract_fixes
200
+ reply = (
201
+ "🔴 **Problem 1: disk full**\n"
202
+ " **Komenda:** `sudo dnf autoremove -y`\n"
203
+ " **Co robi:** removes unused packages\n"
204
+ )
205
+ fixes = extract_fixes(reply)
206
+ assert len(fixes) == 1
207
+ assert fixes[0][0] == "sudo dnf autoremove -y"
208
+
209
+ def test_backticks_no_bold(self):
210
+ """Pattern 2: Komenda: `command` (backticks, no bold)"""
211
+ from fixos.agent.session_core import extract_fixes
212
+ reply = (
213
+ "🔴 Problem 1: disk full\n"
214
+ "Komenda: `sudo dnf autoremove -y`\n"
215
+ "Co robi: removes unused packages\n"
216
+ )
217
+ fixes = extract_fixes(reply)
218
+ assert len(fixes) == 1
219
+ assert fixes[0][0] == "sudo dnf autoremove -y"
220
+
221
+ def test_no_backticks_no_bold(self):
222
+ """Pattern 3: Komenda: command (plain text – deepseek bug scenario)"""
223
+ from fixos.agent.session_core import extract_fixes
224
+ reply = (
225
+ "🔴 **Problem 1: Krytyczne zapełnienie dysku.**\n"
226
+ "Komenda: sudo journalctl --vacuum-size=200M && sudo dnf autoremove -y\n"
227
+ "Co robi: Oczyszcza logi i pakiety.\n"
228
+ "🟡 **Problem 2: swap failed.**\n"
229
+ "Komenda: sudo systemctl restart swapfile.swap\n"
230
+ "Co robi: Restartuje swap.\n"
231
+ )
232
+ fixes = extract_fixes(reply)
233
+ assert len(fixes) == 2
234
+ assert "journalctl" in fixes[0][0]
235
+ assert "swapfile" in fixes[1][0]
236
+
237
+ def test_multiline_command_collapsed(self):
238
+ """Pattern 3 with multiline command – should be collapsed to single line."""
239
+ from fixos.agent.session_core import extract_fixes
240
+ reply = (
241
+ "🔴 **Problem 1: disk full.**\n"
242
+ "Komenda: sudo journalctl --vacuum-size=200M && sudo rm -rf\n"
243
+ "/var/cache/abrt-diag/* && sudo dnf autoremove -y\n"
244
+ "Co robi: cleanup\n"
245
+ )
246
+ fixes = extract_fixes(reply)
247
+ assert len(fixes) >= 1
248
+ cmd = fixes[0][0]
249
+ assert "\n" not in cmd
250
+ assert "journalctl" in cmd
251
+ assert "dnf autoremove" in cmd
252
+
253
+ def test_co_robi_extracted_as_comment(self):
254
+ from fixos.agent.session_core import extract_fixes
255
+ reply = (
256
+ "🔴 Problem 1: disk\n"
257
+ "Komenda: `sudo dnf autoremove -y`\n"
258
+ "Co robi: removes unused packages\n"
259
+ )
260
+ fixes = extract_fixes(reply)
261
+ assert len(fixes) == 1
262
+ assert "removes" in fixes[0][1]
263
+
264
+ def test_diagnostic_only_filtered(self):
265
+ """Read-only commands should be filtered out."""
266
+ from fixos.agent.session_core import extract_fixes
267
+ reply = (
268
+ "🔴 Problem 1: check disk\n"
269
+ " **Komenda:** `df -h`\n"
270
+ " **Co robi:** shows disk usage\n"
271
+ )
272
+ fixes = extract_fixes(reply)
273
+ assert len(fixes) == 0
274
+
275
+ def test_empty_reply(self):
276
+ from fixos.agent.session_core import extract_fixes
277
+ assert extract_fixes("") == []
278
+ assert extract_fixes("No problems found.") == []
279
+
280
+
194
281
  class TestInteractiveBlocker:
195
282
  def test_newgrp_blocked(self):
196
283
  from fixos.platform_utils import is_interactive_blocker
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
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
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