fixos 2.2.26__tar.gz → 2.2.27__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.
- {fixos-2.2.26 → fixos-2.2.27}/CHANGELOG.md +16 -0
- {fixos-2.2.26 → fixos-2.2.27}/PKG-INFO +5 -5
- {fixos-2.2.26 → fixos-2.2.27}/README.md +4 -4
- {fixos-2.2.26 → fixos-2.2.27}/docker/docker-compose.yml +25 -10
- fixos-2.2.27/docker/test-scenarios.sh +147 -0
- fixos-2.2.27/docker/validate-scenario.py +158 -0
- {fixos-2.2.26 → fixos-2.2.27}/fixos/__init__.py +1 -1
- {fixos-2.2.26 → fixos-2.2.27}/fixos/cli/fix_cmd.py +33 -16
- fixos-2.2.27/fixos/cli/output_formatter.py +158 -0
- {fixos-2.2.26 → fixos-2.2.27}/fixos/cli/report_cmd.py +13 -1
- {fixos-2.2.26 → fixos-2.2.27}/fixos/cli/scan_cmd.py +92 -38
- {fixos-2.2.26 → fixos-2.2.27}/fixos/cli/shared.py +2 -0
- {fixos-2.2.26 → fixos-2.2.27}/fixos.egg-info/PKG-INFO +5 -5
- {fixos-2.2.26 → fixos-2.2.27}/fixos.egg-info/SOURCES.txt +3 -0
- {fixos-2.2.26 → fixos-2.2.27}/pyproject.toml +1 -1
- {fixos-2.2.26 → fixos-2.2.27}/setup.py +1 -1
- {fixos-2.2.26 → fixos-2.2.27}/.env.example +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/LICENSE +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/MANIFEST.in +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/docker/README.md +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/docker/TEST_RESULTS.md +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/docker/TEST_RESULTS_V2.md +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/docker/alpine/Dockerfile +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/docker/arch/Dockerfile +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/docker/base/Dockerfile +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/docker/broken-audio/Dockerfile +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/docker/broken-full/Dockerfile +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/docker/broken-network/Dockerfile +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/docker/broken-thumbnails/Dockerfile +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/docker/debian/Dockerfile +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/docker/docker-compose.multi-system.yml +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/docker/fedora/Dockerfile +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/docker/test-multi-system.sh +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/docker/ubuntu/Dockerfile +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/docs/examples/advanced_usage.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/docs/examples/quickstart.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/fixos/agent/__init__.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/fixos/agent/autonomous.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/fixos/agent/autonomous_session.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/fixos/agent/hitl.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/fixos/agent/hitl_session.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/fixos/agent/session_core.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/fixos/agent/session_handlers.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/fixos/agent/session_io.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/fixos/anonymizer.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/fixos/cli/__init__.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/fixos/cli/ask_cmd.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/fixos/cli/cleanup_cmd.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/fixos/cli/config_cmd.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/fixos/cli/features_cmd.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/fixos/cli/history_cmd.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/fixos/cli/main.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/fixos/cli/orchestrate_cmd.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/fixos/cli/profile_cmd.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/fixos/cli/provider_cmd.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/fixos/cli/quickfix_cmd.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/fixos/cli/rollback_cmd.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/fixos/cli/token_cmd.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/fixos/cli/watch_cmd.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/fixos/cli.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/fixos/config.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/fixos/config_interactive.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/fixos/constants.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/fixos/diagnostics/__init__.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/fixos/diagnostics/checks/__init__.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/fixos/diagnostics/checks/_shared.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/fixos/diagnostics/checks/audio.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/fixos/diagnostics/checks/hardware.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/fixos/diagnostics/checks/resources.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/fixos/diagnostics/checks/security.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/fixos/diagnostics/checks/system_core.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/fixos/diagnostics/checks/thumbnails.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/fixos/diagnostics/dev_project_analyzer.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/fixos/diagnostics/disk_analyzer.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/fixos/diagnostics/flatpak_analyzer.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/fixos/diagnostics/service_cleanup.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/fixos/diagnostics/service_details.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/fixos/diagnostics/service_scanner.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/fixos/diagnostics/storage_analyzer.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/fixos/diagnostics/system_checks.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/fixos/features/__init__.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/fixos/features/auditor.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/fixos/features/catalog.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/fixos/features/installer.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/fixos/features/profiles.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/fixos/features/renderer.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/fixos/fixes/__init__.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/fixos/interactive/__init__.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/fixos/interactive/cleanup_planner.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/fixos/llm_shell.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/fixos/orchestrator/__init__.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/fixos/orchestrator/executor.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/fixos/orchestrator/graph.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/fixos/orchestrator/orchestrator.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/fixos/orchestrator/rollback.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/fixos/platform_utils.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/fixos/plugins/__init__.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/fixos/plugins/base.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/fixos/plugins/builtin/__init__.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/fixos/plugins/builtin/audio.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/fixos/plugins/builtin/disk.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/fixos/plugins/builtin/hardware.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/fixos/plugins/builtin/resources.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/fixos/plugins/builtin/security.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/fixos/plugins/builtin/thumbnails.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/fixos/plugins/registry.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/fixos/profiles/__init__.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/fixos/providers/__init__.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/fixos/providers/llm.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/fixos/providers/llm_analyzer.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/fixos/providers/schemas.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/fixos/system_checks.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/fixos/utils/__init__.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/fixos/utils/anonymizer.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/fixos/utils/terminal.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/fixos/utils/timeout.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/fixos/utils/web_search.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/fixos/watch.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/fixos.egg-info/dependency_links.txt +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/fixos.egg-info/entry_points.txt +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/fixos.egg-info/requires.txt +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/fixos.egg-info/top_level.txt +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/pytest.ini +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/requirements-dev.txt +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/requirements.txt +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/scripts/pyqual-calibrate.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/setup.cfg +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/tests/__init__.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/tests/conftest.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/tests/e2e/__init__.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/tests/e2e/test_anonymization_layers.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/tests/e2e/test_audio_broken.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/tests/e2e/test_cli.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/tests/e2e/test_executor.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/tests/e2e/test_multi_system.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/tests/e2e/test_network_broken.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/tests/e2e/test_thumbnails_broken.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/tests/test_fixos.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/tests/unit/__init__.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/tests/unit/test_anonymizer.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/tests/unit/test_core.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/tests/unit/test_executor.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/tests/unit/test_orchestrator.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/tests/unit/test_service_cleanup.py +0 -0
- {fixos-2.2.26 → fixos-2.2.27}/tests/unit/test_service_scanner.py +0 -0
|
@@ -150,6 +150,22 @@ 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.27] - 2026-05-04
|
|
154
|
+
|
|
155
|
+
### Docs
|
|
156
|
+
- Update README.md
|
|
157
|
+
|
|
158
|
+
### Other
|
|
159
|
+
- Update docker/docker-compose.yml
|
|
160
|
+
- Update docker/test-scenarios.sh
|
|
161
|
+
- Update docker/validate-scenario.py
|
|
162
|
+
- Update fixos/cli/fix_cmd.py
|
|
163
|
+
- Update fixos/cli/output_formatter.py
|
|
164
|
+
- Update fixos/cli/report_cmd.py
|
|
165
|
+
- Update fixos/cli/scan_cmd.py
|
|
166
|
+
- Update fixos/cli/shared.py
|
|
167
|
+
- Update uv.lock
|
|
168
|
+
|
|
153
169
|
## [2.2.26] - 2026-05-04
|
|
154
170
|
|
|
155
171
|
### Docs
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fixos
|
|
3
|
-
Version: 2.2.
|
|
3
|
+
Version: 2.2.27
|
|
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,11 +63,11 @@ AI-powered OS Diagnostics
|
|
|
63
63
|
|
|
64
64
|
## AI Cost Tracking
|
|
65
65
|
|
|
66
|
-
     
|
|
67
|
+
  
|
|
68
68
|
|
|
69
|
-
- 🤖 **LLM usage:** $7.5000 (
|
|
70
|
-
- 👤 **Human dev:** ~$
|
|
69
|
+
- 🤖 **LLM usage:** $7.5000 (126 commits)
|
|
70
|
+
- 👤 **Human dev:** ~$2620 (26.2h @ $100/h, 30min dedup)
|
|
71
71
|
|
|
72
72
|
Generated on 2026-05-04 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
|
|
73
73
|
|
|
@@ -19,11 +19,11 @@ AI-powered OS Diagnostics
|
|
|
19
19
|
|
|
20
20
|
## AI Cost Tracking
|
|
21
21
|
|
|
22
|
-
     
|
|
23
|
+
  
|
|
24
24
|
|
|
25
|
-
- 🤖 **LLM usage:** $7.5000 (
|
|
26
|
-
- 👤 **Human dev:** ~$
|
|
25
|
+
- 🤖 **LLM usage:** $7.5000 (126 commits)
|
|
26
|
+
- 👤 **Human dev:** ~$2620 (26.2h @ $100/h, 30min dedup)
|
|
27
27
|
|
|
28
28
|
Generated on 2026-05-04 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
|
|
29
29
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
# docker-compose.yml – fixos test environments
|
|
2
2
|
|
|
3
3
|
# ═══════════════════════════════════════════════════════════
|
|
4
4
|
# fixos – środowiska testowe (Docker)
|
|
@@ -32,7 +32,6 @@ services:
|
|
|
32
32
|
context: ..
|
|
33
33
|
dockerfile: docker/base/Dockerfile
|
|
34
34
|
image: fixos-base:latest
|
|
35
|
-
profiles: ["build-only"]
|
|
36
35
|
command: echo "base image built"
|
|
37
36
|
|
|
38
37
|
# ── Uszkodzony dźwięk ──────────────────────────────────
|
|
@@ -49,9 +48,9 @@ services:
|
|
|
49
48
|
- FIXOS_TEST_MODE=1
|
|
50
49
|
command: >
|
|
51
50
|
sh -c "
|
|
52
|
-
echo '=== Scenariusz: broken-audio ===' &&
|
|
53
|
-
verify-scenario.sh &&
|
|
54
|
-
python3 -m fixos.
|
|
51
|
+
echo '=== Scenariusz: broken-audio ===' >&2 &&
|
|
52
|
+
verify-scenario.sh >&2 &&
|
|
53
|
+
python3 -m fixos.cli.main scan --yaml -M audio --no-banner
|
|
55
54
|
"
|
|
56
55
|
|
|
57
56
|
# ── Uszkodzone thumbnails ──────────────────────────────
|
|
@@ -83,9 +82,9 @@ services:
|
|
|
83
82
|
- FIXOS_TEST_MODE=1
|
|
84
83
|
command: >
|
|
85
84
|
sh -c "
|
|
86
|
-
echo '=== Scenariusz: broken-network ===' &&
|
|
87
|
-
verify-scenario.sh &&
|
|
88
|
-
python3
|
|
85
|
+
echo '=== Scenariusz: broken-network ===' >&2 &&
|
|
86
|
+
verify-scenario.sh >&2 &&
|
|
87
|
+
python3 -m fixos.cli.main scan --yaml -M system --no-banner
|
|
89
88
|
"
|
|
90
89
|
|
|
91
90
|
# ── Pełne uszkodzenie ─────────────────────────────────
|
|
@@ -123,8 +122,7 @@ services:
|
|
|
123
122
|
--cov-report=html:/reports/coverage
|
|
124
123
|
-m 'not real_api'
|
|
125
124
|
"
|
|
126
|
-
|
|
127
|
-
- base
|
|
125
|
+
|
|
128
126
|
|
|
129
127
|
# ── Unit testy (bez Dockera środowisk) ────────────────
|
|
130
128
|
unit-tests:
|
|
@@ -153,6 +151,23 @@ services:
|
|
|
153
151
|
python -m pytest tests/e2e/test_cli.py tests/e2e/test_executor.py -v --tb=short
|
|
154
152
|
"
|
|
155
153
|
|
|
154
|
+
# ── Testy scenariuszy (YAML validation) ────────────────
|
|
155
|
+
scenario-tests:
|
|
156
|
+
<<: *common
|
|
157
|
+
build:
|
|
158
|
+
context: ..
|
|
159
|
+
dockerfile: docker/base/Dockerfile
|
|
160
|
+
container_name: fixos-scenario-tests
|
|
161
|
+
environment:
|
|
162
|
+
- FIXOS_TEST_MODE=1
|
|
163
|
+
command: >
|
|
164
|
+
sh -c "
|
|
165
|
+
echo 'Running scenario validation...' &&
|
|
166
|
+
python3 -m fixos.cli.main scan --yaml -M audio --no-banner |
|
|
167
|
+
python3 docker/validate-scenario.py broken-audio &&
|
|
168
|
+
echo 'All scenario tests passed!'
|
|
169
|
+
"
|
|
170
|
+
|
|
156
171
|
networks:
|
|
157
172
|
fixos-test:
|
|
158
173
|
driver: bridge
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ═══════════════════════════════════════════════════════════
|
|
3
|
+
# fixos – Docker Scenario Test Harness
|
|
4
|
+
#
|
|
5
|
+
# Builds and runs each broken-* scenario container,
|
|
6
|
+
# captures YAML output from `fixos scan --yaml`,
|
|
7
|
+
# and validates results with validate-scenario.py.
|
|
8
|
+
#
|
|
9
|
+
# Usage:
|
|
10
|
+
# ./test-scenarios.sh # all scenarios
|
|
11
|
+
# ./test-scenarios.sh broken-audio # single scenario
|
|
12
|
+
# ═══════════════════════════════════════════════════════════
|
|
13
|
+
|
|
14
|
+
set -euo pipefail
|
|
15
|
+
|
|
16
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
17
|
+
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
|
|
18
|
+
VALIDATOR="$SCRIPT_DIR/validate-scenario.py"
|
|
19
|
+
REPORTS_DIR="${REPORTS_DIR:-/tmp/fixos-test-reports}"
|
|
20
|
+
|
|
21
|
+
# Colors
|
|
22
|
+
RED='\033[0;31m'
|
|
23
|
+
GREEN='\033[0;32m'
|
|
24
|
+
YELLOW='\033[1;33m'
|
|
25
|
+
CYAN='\033[0;36m'
|
|
26
|
+
NC='\033[0m'
|
|
27
|
+
|
|
28
|
+
# Scenarios: service name → modules to scan
|
|
29
|
+
declare -A SCENARIO_MODULES=(
|
|
30
|
+
["broken-audio"]="audio"
|
|
31
|
+
["broken-thumbnails"]="thumbnails"
|
|
32
|
+
["broken-network"]="system"
|
|
33
|
+
["broken-full"]="audio,thumbnails,system"
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
PASSED=0
|
|
37
|
+
FAILED=0
|
|
38
|
+
ERRORS=()
|
|
39
|
+
|
|
40
|
+
log() { echo -e "${CYAN}[fixos-test]${NC} $*"; }
|
|
41
|
+
ok() { echo -e " ${GREEN}✓${NC} $*"; }
|
|
42
|
+
err() { echo -e " ${RED}✗${NC} $*"; }
|
|
43
|
+
warn() { echo -e " ${YELLOW}⚠${NC} $*"; }
|
|
44
|
+
|
|
45
|
+
# ── Build base image ──────────────────────────────────────
|
|
46
|
+
build_base() {
|
|
47
|
+
log "Building base image..."
|
|
48
|
+
docker compose -f "$SCRIPT_DIR/docker-compose.yml" build base 2>&1 | tail -5
|
|
49
|
+
ok "fixos-base:latest built"
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
# ── Run scenario ──────────────────────────────────────────
|
|
53
|
+
run_scenario() {
|
|
54
|
+
local scenario="$1"
|
|
55
|
+
local modules="${SCENARIO_MODULES[$scenario]:-system}"
|
|
56
|
+
local output_file="$REPORTS_DIR/${scenario}.yml"
|
|
57
|
+
|
|
58
|
+
log "━━━ Scenario: ${YELLOW}${scenario}${NC} (modules: $modules) ━━━"
|
|
59
|
+
|
|
60
|
+
# Build scenario image
|
|
61
|
+
log " Building $scenario..."
|
|
62
|
+
if ! docker compose -f "$SCRIPT_DIR/docker-compose.yml" build "$scenario" 2>&1 | tail -3; then
|
|
63
|
+
err "Build failed for $scenario"
|
|
64
|
+
ERRORS+=("$scenario: build failed")
|
|
65
|
+
((FAILED++))
|
|
66
|
+
return 1
|
|
67
|
+
fi
|
|
68
|
+
|
|
69
|
+
# Run fixos scan --yaml inside container
|
|
70
|
+
log " Running fixos scan --yaml -M $modules..."
|
|
71
|
+
mkdir -p "$REPORTS_DIR"
|
|
72
|
+
|
|
73
|
+
local exit_code=0
|
|
74
|
+
docker compose -f "$SCRIPT_DIR/docker-compose.yml" run --rm \
|
|
75
|
+
--no-deps \
|
|
76
|
+
-e FIXOS_TEST_MODE=1 \
|
|
77
|
+
"$scenario" \
|
|
78
|
+
python3 -m fixos.cli.main scan --yaml -M "$modules" --no-banner \
|
|
79
|
+
> "$output_file" 2>/dev/null || exit_code=$?
|
|
80
|
+
|
|
81
|
+
# Check we got output
|
|
82
|
+
if [ ! -s "$output_file" ]; then
|
|
83
|
+
err "No YAML output from $scenario"
|
|
84
|
+
ERRORS+=("$scenario: no output")
|
|
85
|
+
((FAILED++))
|
|
86
|
+
return 1
|
|
87
|
+
fi
|
|
88
|
+
|
|
89
|
+
ok "YAML captured: $output_file ($(wc -l < "$output_file") lines)"
|
|
90
|
+
|
|
91
|
+
# Validate with validate-scenario.py
|
|
92
|
+
if python3 "$VALIDATOR" "$scenario" < "$output_file"; then
|
|
93
|
+
ok "$scenario PASSED"
|
|
94
|
+
((PASSED++))
|
|
95
|
+
else
|
|
96
|
+
err "$scenario FAILED validation"
|
|
97
|
+
ERRORS+=("$scenario: validation failed")
|
|
98
|
+
((FAILED++))
|
|
99
|
+
fi
|
|
100
|
+
|
|
101
|
+
echo ""
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
# ── Main ──────────────────────────────────────────────────
|
|
105
|
+
main() {
|
|
106
|
+
log "fixOS Docker Scenario Test Harness"
|
|
107
|
+
log "═══════════════════════════════════"
|
|
108
|
+
|
|
109
|
+
# Build base first
|
|
110
|
+
build_base
|
|
111
|
+
|
|
112
|
+
# Determine which scenarios to run
|
|
113
|
+
local scenarios=()
|
|
114
|
+
if [ $# -gt 0 ]; then
|
|
115
|
+
scenarios=("$@")
|
|
116
|
+
else
|
|
117
|
+
scenarios=("broken-audio" "broken-thumbnails" "broken-network" "broken-full")
|
|
118
|
+
fi
|
|
119
|
+
|
|
120
|
+
# Run each scenario
|
|
121
|
+
for scenario in "${scenarios[@]}"; do
|
|
122
|
+
if [ -z "${SCENARIO_MODULES[$scenario]+x}" ]; then
|
|
123
|
+
warn "Unknown scenario: $scenario (skipping)"
|
|
124
|
+
continue
|
|
125
|
+
fi
|
|
126
|
+
run_scenario "$scenario" || true
|
|
127
|
+
done
|
|
128
|
+
|
|
129
|
+
# Summary
|
|
130
|
+
echo ""
|
|
131
|
+
log "═══════════════════════════════════"
|
|
132
|
+
log " SUMMARY"
|
|
133
|
+
log "═══════════════════════════════════"
|
|
134
|
+
ok "Passed: $PASSED"
|
|
135
|
+
if [ $FAILED -gt 0 ]; then
|
|
136
|
+
err "Failed: $FAILED"
|
|
137
|
+
for e in "${ERRORS[@]}"; do
|
|
138
|
+
err " $e"
|
|
139
|
+
done
|
|
140
|
+
exit 1
|
|
141
|
+
else
|
|
142
|
+
ok "All scenarios passed!"
|
|
143
|
+
exit 0
|
|
144
|
+
fi
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
main "$@"
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Validates fixOS scan YAML output against expected scenario conditions.
|
|
4
|
+
|
|
5
|
+
Usage:
|
|
6
|
+
fixos scan --yaml --modules audio | python3 validate-scenario.py broken-audio
|
|
7
|
+
cat scan-output.yml | python3 validate-scenario.py broken-full
|
|
8
|
+
|
|
9
|
+
Exit codes:
|
|
10
|
+
0 – all expectations met
|
|
11
|
+
1 – validation failures
|
|
12
|
+
2 – invalid input / unknown scenario
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import sys
|
|
18
|
+
from typing import Any
|
|
19
|
+
|
|
20
|
+
import yaml
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# ── Scenario expectations ─────────────────────────────────
|
|
24
|
+
|
|
25
|
+
SCENARIOS: dict[str, list[dict[str, Any]]] = {
|
|
26
|
+
"broken-audio": [
|
|
27
|
+
{
|
|
28
|
+
"path": "diagnostics.audio.alsa_cards",
|
|
29
|
+
"contains": "no soundcards",
|
|
30
|
+
"desc": "ALSA powinno zgłaszać brak kart dźwiękowych",
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"path": "diagnostics.audio.sof_firmware",
|
|
34
|
+
"contains_any": ["No such file", "not installed", "ERR"],
|
|
35
|
+
"desc": "SOF firmware powinno być niedostępne",
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
"broken-thumbnails": [
|
|
39
|
+
{
|
|
40
|
+
"path": "diagnostics.thumbnails.thumbnail_cache_count",
|
|
41
|
+
"equals": "0",
|
|
42
|
+
"desc": "Cache miniaturek powinien być pusty",
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"path": "diagnostics.thumbnails.ffmpegthumbnailer",
|
|
46
|
+
"contains": "nie zainstalowany",
|
|
47
|
+
"desc": "ffmpegthumbnailer powinien być niezainstalowany",
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
"broken-network": [
|
|
51
|
+
{
|
|
52
|
+
"path": "diagnostics.system.systemctl_failed",
|
|
53
|
+
"contains": "NetworkManager",
|
|
54
|
+
"desc": "NetworkManager powinien być w stanie failed",
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
"broken-full": [
|
|
58
|
+
{
|
|
59
|
+
"path": "diagnostics.audio.alsa_cards",
|
|
60
|
+
"contains": "no soundcards",
|
|
61
|
+
"desc": "ALSA: brak kart dźwiękowych",
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"path": "diagnostics.thumbnails.ffmpegthumbnailer",
|
|
65
|
+
"contains": "nie zainstalowany",
|
|
66
|
+
"desc": "ffmpegthumbnailer niezainstalowany",
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _get_nested(data: dict, path: str) -> Any:
|
|
73
|
+
"""Get a nested value from a dict using dot notation."""
|
|
74
|
+
keys = path.split(".")
|
|
75
|
+
current = data
|
|
76
|
+
for key in keys:
|
|
77
|
+
if isinstance(current, dict) and key in current:
|
|
78
|
+
current = current[key]
|
|
79
|
+
else:
|
|
80
|
+
return None
|
|
81
|
+
return current
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def validate(data: dict, scenario: str) -> list[str]:
|
|
85
|
+
"""Validate data against scenario expectations. Returns list of failures."""
|
|
86
|
+
expectations = SCENARIOS.get(scenario)
|
|
87
|
+
if expectations is None:
|
|
88
|
+
return [f"Nieznany scenariusz: '{scenario}'. Dostępne: {', '.join(SCENARIOS.keys())}"]
|
|
89
|
+
|
|
90
|
+
failures = []
|
|
91
|
+
for exp in expectations:
|
|
92
|
+
path = exp["path"]
|
|
93
|
+
desc = exp.get("desc", path)
|
|
94
|
+
value = _get_nested(data, path)
|
|
95
|
+
|
|
96
|
+
if value is None:
|
|
97
|
+
failures.append(f" ✗ BRAK KLUCZA: {path} — {desc}")
|
|
98
|
+
continue
|
|
99
|
+
|
|
100
|
+
value_str = str(value)
|
|
101
|
+
|
|
102
|
+
if "equals" in exp:
|
|
103
|
+
if value_str.strip() != str(exp["equals"]).strip():
|
|
104
|
+
failures.append(f" ✗ {desc}: oczekiwano '{exp['equals']}', otrzymano '{value_str[:80]}'")
|
|
105
|
+
|
|
106
|
+
if "contains" in exp:
|
|
107
|
+
if exp["contains"].lower() not in value_str.lower():
|
|
108
|
+
failures.append(f" ✗ {desc}: brak '{exp['contains']}' w '{value_str[:80]}'")
|
|
109
|
+
|
|
110
|
+
if "contains_any" in exp:
|
|
111
|
+
if not any(term.lower() in value_str.lower() for term in exp["contains_any"]):
|
|
112
|
+
failures.append(
|
|
113
|
+
f" ✗ {desc}: brak żadnego z {exp['contains_any']} w '{value_str[:80]}'"
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
return failures
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def main() -> None:
|
|
120
|
+
if len(sys.argv) < 2:
|
|
121
|
+
print(f"Użycie: {sys.argv[0]} <scenario>", file=sys.stderr)
|
|
122
|
+
print(f"Dostępne scenariusze: {', '.join(SCENARIOS.keys())}", file=sys.stderr)
|
|
123
|
+
sys.exit(2)
|
|
124
|
+
|
|
125
|
+
scenario = sys.argv[1]
|
|
126
|
+
if scenario not in SCENARIOS:
|
|
127
|
+
print(f"✗ Nieznany scenariusz: '{scenario}'", file=sys.stderr)
|
|
128
|
+
print(f" Dostępne: {', '.join(SCENARIOS.keys())}", file=sys.stderr)
|
|
129
|
+
sys.exit(2)
|
|
130
|
+
|
|
131
|
+
# Read YAML from stdin
|
|
132
|
+
try:
|
|
133
|
+
raw = sys.stdin.read()
|
|
134
|
+
data = yaml.safe_load(raw)
|
|
135
|
+
except yaml.YAMLError as e:
|
|
136
|
+
print(f"✗ Błąd parsowania YAML: {e}", file=sys.stderr)
|
|
137
|
+
sys.exit(2)
|
|
138
|
+
|
|
139
|
+
if not isinstance(data, dict):
|
|
140
|
+
print(f"✗ Oczekiwano dict, otrzymano: {type(data).__name__}", file=sys.stderr)
|
|
141
|
+
sys.exit(2)
|
|
142
|
+
|
|
143
|
+
# Validate
|
|
144
|
+
failures = validate(data, scenario)
|
|
145
|
+
|
|
146
|
+
if failures:
|
|
147
|
+
print(f"✗ Scenariusz '{scenario}' — {len(failures)} błędów:", file=sys.stderr)
|
|
148
|
+
for f in failures:
|
|
149
|
+
print(f, file=sys.stderr)
|
|
150
|
+
sys.exit(1)
|
|
151
|
+
else:
|
|
152
|
+
expectations_count = len(SCENARIOS[scenario])
|
|
153
|
+
print(f"✓ Scenariusz '{scenario}' — {expectations_count}/{expectations_count} OK", file=sys.stderr)
|
|
154
|
+
sys.exit(0)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
if __name__ == "__main__":
|
|
158
|
+
main()
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""fixos – AI-powered Linux/Windows diagnostics and repair."""
|
|
2
|
-
__version__ = "2.2.
|
|
2
|
+
__version__ = "2.2.27"
|
|
@@ -9,6 +9,7 @@ from typing import Dict, Any, List
|
|
|
9
9
|
import click
|
|
10
10
|
|
|
11
11
|
from fixos.cli.shared import add_common_options, add_shared_options, BANNER
|
|
12
|
+
from fixos.cli.output_formatter import OutputFormatter
|
|
12
13
|
from fixos.config import FixOsConfig, interactive_provider_setup
|
|
13
14
|
from fixos.agent.hitl import run_hitl_session
|
|
14
15
|
from fixos.agent.autonomous import run_autonomous_session
|
|
@@ -19,22 +20,20 @@ from fixos.constants import (
|
|
|
19
20
|
)
|
|
20
21
|
|
|
21
22
|
|
|
22
|
-
def _collect_diagnostics(modules: str, disc: bool,
|
|
23
|
+
def _collect_diagnostics(modules: str, disc: bool, fmt: OutputFormatter, output: str) -> dict:
|
|
23
24
|
"""Run diagnostics collection and optionally disk analysis. Returns data dict."""
|
|
24
25
|
selected_modules = modules.split(",") if modules else None
|
|
25
26
|
|
|
26
27
|
if disc and not modules:
|
|
27
28
|
data: dict = {}
|
|
28
29
|
else:
|
|
29
|
-
|
|
30
|
-
def progress(name, desc) -> None:
|
|
31
|
-
click.echo(f" → {desc}...")
|
|
30
|
+
fmt.status("\nZbieranie diagnostyki...", fg="yellow")
|
|
32
31
|
from fixos.diagnostics import get_full_diagnostics
|
|
33
|
-
data = get_full_diagnostics(selected_modules, progress_callback=progress)
|
|
32
|
+
data = get_full_diagnostics(selected_modules, progress_callback=fmt.progress)
|
|
34
33
|
|
|
35
34
|
if disc:
|
|
36
35
|
from fixos.cli.scan_cmd import _run_disk_analysis
|
|
37
|
-
_run_disk_analysis(data,
|
|
36
|
+
_run_disk_analysis(data, fmt=fmt, is_fix_mode=True)
|
|
38
37
|
|
|
39
38
|
if output:
|
|
40
39
|
from fixos.utils.anonymizer import anonymize
|
|
@@ -44,9 +43,9 @@ def _collect_diagnostics(modules: str, disc: bool, json_output: bool, output: st
|
|
|
44
43
|
json.dumps({"anonymized": anon_str, "raw": data}, ensure_ascii=False, indent=2, default=str),
|
|
45
44
|
encoding="utf-8"
|
|
46
45
|
)
|
|
47
|
-
|
|
46
|
+
fmt.status(f"Raport: {output}", fg="green")
|
|
48
47
|
except Exception as e:
|
|
49
|
-
|
|
48
|
+
fmt.status(f"Błąd zapisu: {e}")
|
|
50
49
|
|
|
51
50
|
return data
|
|
52
51
|
|
|
@@ -83,7 +82,7 @@ def _run_agent_session(cfg, data: dict, max_fixes: int) -> None:
|
|
|
83
82
|
help="Maksymalna liczba napraw w sesji")
|
|
84
83
|
@add_shared_options
|
|
85
84
|
def fix(provider, token, model, no_banner, mode, timeout, modules, no_show_data, output, max_fixes,
|
|
86
|
-
disc, dry_run, interactive, json_output, llm_fallback, show_raw) -> None:
|
|
85
|
+
disc, dry_run, interactive, json_output, yaml_output, llm_fallback, show_raw) -> None:
|
|
87
86
|
"""
|
|
88
87
|
Przeprowadza pełną diagnostykę i uruchamia sesję naprawczą z LLM.
|
|
89
88
|
|
|
@@ -92,12 +91,17 @@ def fix(provider, token, model, no_banner, mode, timeout, modules, no_show_data,
|
|
|
92
91
|
hitl – Human-in-the-Loop (pyta o każdą akcję) [domyślny]
|
|
93
92
|
autonomous – Agent sam wykonuje komendy (UWAGA: wymaga potwierdzenia)
|
|
94
93
|
|
|
94
|
+
\b
|
|
95
|
+
Pipeline (synchroniczny, bez interakcji):
|
|
96
|
+
fixos fix --yaml --no-interactive # diagnostyka → YAML
|
|
97
|
+
fixos fix --yaml --no-interactive --disc # diagnostyka + dysk → YAML
|
|
98
|
+
|
|
95
99
|
\b
|
|
96
100
|
Opcje dyskowe:
|
|
97
101
|
--disc – Analiza zajętości dysku + grupowanie przyczyn
|
|
98
102
|
--dry-run – Symulacja bez wykonywania akcji
|
|
99
|
-
--interactive – Tryb interaktywny (domyślnie włączony)
|
|
100
103
|
--json – Wyjście w formacie JSON
|
|
104
|
+
--yaml – Wyjście w formacie YAML (pipe-safe)
|
|
101
105
|
--llm-fallback – Użyj LLM gdy heurystyki nie wystarczą
|
|
102
106
|
|
|
103
107
|
\b
|
|
@@ -107,11 +111,24 @@ def fix(provider, token, model, no_banner, mode, timeout, modules, no_show_data,
|
|
|
107
111
|
fixos fix --disc --dry-run # analiza dysku bez wykonywania
|
|
108
112
|
fixos fix --mode autonomous # tryb autonomiczny
|
|
109
113
|
fixos fix --modules audio,thumbnails # tylko audio i thumbnails
|
|
114
|
+
fixos fix --yaml --no-interactive # pipeline mode → YAML
|
|
110
115
|
fixos fix --provider openai --token sk-...
|
|
111
116
|
"""
|
|
117
|
+
fmt = OutputFormatter.from_flags(yaml_output=yaml_output, json_output=json_output)
|
|
118
|
+
|
|
112
119
|
if not no_banner:
|
|
113
|
-
|
|
120
|
+
fmt.banner(BANNER)
|
|
121
|
+
|
|
122
|
+
# ── Pipeline mode: --yaml/--json + --no-interactive ──────
|
|
123
|
+
if fmt.is_machine and not interactive:
|
|
124
|
+
fmt.status("Pipeline mode: diagnostyka → structured output", fg="cyan")
|
|
125
|
+
data = _collect_diagnostics(modules, disc, fmt, output)
|
|
126
|
+
fmt.status("Diagnostyka gotowa.", fg="green")
|
|
127
|
+
content = fmt.format_diagnostics(data)
|
|
128
|
+
click.echo(content)
|
|
129
|
+
return
|
|
114
130
|
|
|
131
|
+
# ── Interactive / LLM mode ────────────────────────────────
|
|
115
132
|
cfg = FixOsConfig.load(
|
|
116
133
|
provider=provider,
|
|
117
134
|
api_key=token,
|
|
@@ -138,15 +155,15 @@ def fix(provider, token, model, no_banner, mode, timeout, modules, no_show_data,
|
|
|
138
155
|
click.echo(click.style(f"{err}", fg="red"))
|
|
139
156
|
sys.exit(1)
|
|
140
157
|
|
|
141
|
-
|
|
158
|
+
fmt.status("\nKonfiguracja:", fg="cyan")
|
|
142
159
|
click.echo(cfg.summary())
|
|
143
160
|
if dry_run:
|
|
144
|
-
|
|
161
|
+
fmt.status(" Tryb: DRY-RUN (komendy nie będą wykonywane)", fg="yellow")
|
|
145
162
|
if disc:
|
|
146
|
-
|
|
163
|
+
fmt.status(" Analiza dysku: Włączona", fg="blue")
|
|
147
164
|
|
|
148
|
-
data = _collect_diagnostics(modules, disc,
|
|
149
|
-
|
|
165
|
+
data = _collect_diagnostics(modules, disc, fmt, output)
|
|
166
|
+
fmt.status("Diagnostyka gotowa.\n", fg="green")
|
|
150
167
|
|
|
151
168
|
if disc and "disk_analysis" in data:
|
|
152
169
|
return handle_disk_cleanup_mode(data["disk_analysis"], cfg, dry_run, interactive, json_output, llm_fallback)
|