基于 Playwright 和AI实现的闲鱼多任务实时/定时监控与智能分析系统,配备了功能完善的后台管理UI。帮助用户从闲鱼海量商品中,找到心仪产品。
This report summarizes scanner evidence for Usagi-org/ai-goofish-monitor, a Python project with 14,765 GitHub stars. SynthScan examined 35,361 lines of code across 275 source files, recording 378 pattern matches distributed across 8 syntactic categories. The legacy adjusted pattern signal is 11.9 (Moderate pattern signal); it is not an authorship estimate.
Use the findings to ask whether maintainers can explain intent, verify dependencies, simplify unnecessary scaffolding, and focus review effort. Risk Payload v1 adds ownership, supply-chain, over-engineering, and review-effort vectors when available. These are prompts for human verification, not probabilities, security findings, or measured maintenance costs.
Use each vector to guide manual verification of the underlying findings.
Can maintainers explain intent, assumptions, and trade-offs? 239 finding(s).
Are imports declared and package/API names verified? 51 finding(s).
Are wrappers, guards, and repeated structures necessary? 312 finding(s).
Which changes need focused human review? 378 finding(s).
Run additional scans to build a history of the legacy pattern signal. Score changes are not a measure of AI adoption or code quality.
Groups findings by configured severity weight. Severity is not a confidence estimate, security rating, or proof that a package is missing. Verify imports against the project's manifests and inspect each finding in its local design context.
This horizontal bar chart shows where configured pattern scores accumulate across directories. Treat the bars as locations to inspect, not as evidence of authorship or code quality. Use the file-level findings to decide whether a directory needs a focused ownership, dependency, complexity, or review check.
The scanner identified 378 distinct pattern matches across 8 syntactic categories. Each entry below represents a discrete location in the source code where the engine recorded a detector match to inspect in context. Expand any category row to inspect the individual file paths, line numbers, code snippets, and the lexical context (CODE, COMMENT, or STRING) in which each match was detected.
Reading the findings table: The Severity column indicates the configured finding weight (CRITICAL / HIGH / MEDIUM / LOW). The Context column identifies whether the match occurred inside executable code, an inline comment, or a string literal — legacy comment-context matches receive a ×1.5 score weight. The ⚡ bolt icon marks clustered matches: three or more patterns within a 10-line window, each receiving an additional ×1.5 density multiplier under the legacy scoring model.
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | web-ui/src/components/tasks/TaskForm.vue | 177 | function handleAccountStrategyChange(event: Event) { | CODE |
| LOW | web-ui/src/components/tasks/TaskForm.vue | 182 | function handleAccountStateFileChange(event: Event) { | CODE |
| LOW | web-ui/src/components/results/ResultsFilterBar.vue | 79 | function handleToggleAiRecommended(value: boolean) { | CODE |
| LOW | web-ui/src/components/results/ResultsFilterBar.vue | 86 | function handleToggleKeywordRecommended(value: boolean) { | CODE |
| LOW | web-ui/src/api/settings.ts | 114 | export async function updateNotificationSettings(settings: NotificationSettingsUpdate): Promise<{ message: string; confi | CODE |
| LOW | web-ui/src/api/results.ts | 39 | export async function updateResultBlacklistRules(filename: string, keywords: string[]): Promise<{ message: string; keywo | CODE |
| LOW | web-ui/src/i18n/index.ts | 66 | export function formatRelativeTimeFromNow(value: string | null | undefined) { | CODE |
| LOW⚡ | tests/conftest.py | 120 | def override_get_task_service(): | CODE |
| LOW⚡ | tests/conftest.py | 123 | def override_get_process_service(): | CODE |
| LOW⚡ | tests/conftest.py | 126 | def override_get_scheduler_service(): | CODE |
| LOW⚡ | tests/conftest.py | 129 | def override_get_task_generation_service(): | CODE |
| LOW | tests/test_failure_guard.py | 8 | def test_failure_guard_opens_circuit_after_threshold_and_rate_limits(tmp_path): | CODE |
| LOW | tests/test_failure_guard.py | 49 | def test_failure_guard_auto_recovers_on_cookie_change(tmp_path): | CODE |
| LOW | tests/test_frontend_build_paths.py | 14 | def test_frontend_build_output_path_is_consistent_across_configs(): | CODE |
| LOW | tests/unit/test_utils.py | 12 | def test_safe_get_nested_and_default(): | CODE |
| LOW | tests/unit/test_utils.py | 18 | def test_format_registration_days(): | CODE |
| LOW | tests/unit/test_search_pagination.py | 36 | async def scroll_into_view_if_needed(self) -> None: | CODE |
| LOW | tests/unit/test_search_pagination.py | 94 | def test_advance_search_page_stops_when_no_next_button() -> None: | CODE |
| LOW | tests/unit/test_search_pagination.py | 115 | def test_advance_search_page_stops_after_timeout_retries() -> None: | CODE |
| LOW | tests/unit/test_search_pagination.py | 146 | def test_advance_search_page_returns_new_response_on_success() -> None: | CODE |
| LOW | tests/unit/test_search_pagination.py | 170 | def test_advance_search_page_stops_when_click_times_out() -> None: | CODE |
| LOW⚡ | tests/unit/test_search_pagination.py | 195 | def test_is_search_results_response_matches_exact_search_api() -> None: | CODE |
| LOW⚡ | tests/unit/test_search_pagination.py | 204 | def test_is_search_results_response_rejects_search_shade_api() -> None: | CODE |
| LOW⚡ | tests/unit/test_search_pagination.py | 213 | def test_is_search_results_response_rejects_non_post_request() -> None: | CODE |
| LOW | tests/unit/test_item_analysis_dispatcher.py | 9 | def test_item_analysis_dispatcher_uses_bounded_concurrency(): | CODE |
| LOW | tests/unit/test_item_analysis_dispatcher.py | 81 | def test_item_analysis_dispatcher_supports_keyword_mode_without_ai(): | CODE |
| LOW | tests/unit/test_task_log_cleanup_service.py | 23 | def test_cleanup_task_logs_removes_only_expired_top_level_logs(tmp_path): | CODE |
| LOW | tests/unit/test_task_log_cleanup_service.py | 50 | def test_cleanup_task_logs_skips_when_retention_is_invalid(tmp_path): | CODE |
| LOW | tests/unit/test_ai_handler_analysis.py | 18 | def test_get_ai_analysis_stops_after_internal_retries_when_content_is_none( | CODE |
| LOW | tests/unit/test_ai_handler_analysis.py | 45 | def test_get_ai_analysis_returns_parsed_json(monkeypatch, tmp_path): | CODE |
| LOW | tests/unit/test_ai_handler_analysis.py | 75 | def test_get_ai_analysis_retries_without_structured_output_when_model_rejects_it( | CODE |
| LOW | tests/unit/test_ai_handler_analysis.py | 123 | def test_get_ai_analysis_falls_back_to_responses_when_chat_completions_api_is_missing( | CODE |
| LOW | tests/unit/test_ai_handler_analysis.py | 175 | def test_get_ai_analysis_retries_without_temperature_when_gateway_rejects_it( | CODE |
| LOW | tests/unit/test_ai_handler_analysis.py | 216 | def test_get_ai_analysis_uses_first_json_object_when_model_returns_multiple_objects( | CODE |
| LOW | tests/unit/test_seller_profile_cache.py | 6 | def test_seller_profile_cache_reuses_value_and_returns_copy(): | CODE |
| LOW | tests/unit/test_seller_profile_cache.py | 27 | def test_seller_profile_cache_coalesces_inflight_requests(): | CODE |
| LOW | tests/unit/test_domain_task.py | 4 | def test_task_can_start_and_stop(): | CODE |
| LOW | tests/unit/test_domain_task.py | 54 | def test_legacy_keyword_groups_are_flattened_to_keyword_rules(): | CODE |
| LOW | tests/unit/test_domain_task.py | 79 | def test_generate_request_accepts_legacy_group_payload(): | CODE |
| LOW | tests/unit/test_domain_task.py | 90 | def test_generate_request_enables_image_analysis_by_default(): | CODE |
| LOW | tests/unit/test_domain_task.py | 100 | def test_generate_request_infers_fixed_account_strategy_from_state_file(): | CODE |
| LOW | tests/unit/test_domain_task.py | 112 | def test_generate_request_requires_state_file_for_fixed_account_strategy(): | CODE |
| LOW | tests/unit/test_result_blacklist_service.py | 13 | def test_regex_blacklist_rule_matches_aliases_case_insensitively(): | CODE |
| LOW | tests/unit/test_result_blacklist_service.py | 22 | def test_regex_blacklist_rule_does_not_hide_plain_pro_models(): | CODE |
| LOW | tests/unit/test_ai_handler_downloads.py | 7 | def test_download_all_images_runs_with_concurrency(tmp_path, monkeypatch): | CODE |
| LOW | tests/unit/test_scraper_region_filter.py | 9 | def test_region_selectors_ignore_stale_css_module_hashes(): | CODE |
| LOW | tests/unit/test_scraper_region_filter.py | 18 | def test_region_submit_text_matches_live_button_label(): | CODE |
| LOW | tests/unit/test_prompt_utils.py | 9 | def test_generate_criteria_closes_ai_client_after_success(monkeypatch, tmp_path): | CODE |
| LOW | tests/unit/test_prompt_utils.py | 37 | def test_generate_criteria_drops_think_blocks_and_raises_output_limit(monkeypatch, tmp_path): | CODE |
| LOW | tests/unit/test_prompt_utils.py | 67 | def test_generate_criteria_rejects_truncated_think_block(monkeypatch, tmp_path): | CODE |
| LOW | tests/unit/test_prompt_utils.py | 90 | def test_generate_criteria_closes_ai_client_after_ai_failure(monkeypatch, tmp_path): | CODE |
| LOW | tests/unit/test_env_manager.py | 4 | def test_get_value_prefers_env_file_when_key_present(tmp_path, monkeypatch): | CODE |
| LOW | tests/unit/test_env_manager.py | 14 | def test_get_value_falls_back_to_runtime_when_key_missing_from_env_file(tmp_path, monkeypatch): | CODE |
| LOW⚡ | tests/unit/test_scraper_browser_channel.py | 17 | def test_resolve_browser_channel_uses_chromium_in_docker_even_when_edge_requested(monkeypatch, capsys): | CODE |
| LOW⚡ | tests/unit/test_scraper_browser_channel.py | 24 | def test_resolve_browser_channel_uses_msedge_locally_when_requested(monkeypatch): | CODE |
| LOW⚡ | tests/unit/test_scraper_browser_channel.py | 30 | def test_chromium_launch_args_disable_crashpad_and_pin_dump_dir(): | CODE |
| LOW | tests/unit/test_ai_client.py | 22 | def test_build_messages_without_images_uses_text_only_content(): | CODE |
| LOW | tests/unit/test_ai_client.py | 37 | def test_build_messages_with_images_uses_multimodal_content(monkeypatch): | CODE |
| LOW | tests/unit/test_ai_client.py | 53 | def test_build_responses_input_converts_multimodal_messages(): | CODE |
| LOW | tests/unit/test_ai_client.py | 81 | def test_call_ai_retries_without_structured_output_when_model_rejects_it(): | CODE |
| 163 more matches not shown… | ||||
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | spider_v2.py | 136 | except Exception as e: | STRING |
| LOW | spider_v2.py | 147 | except Exception as e: | STRING |
| LOW | tests/live/conftest.py | 78 | except Exception: | CODE |
| LOW | src/scraper.py | 201 | except Exception as e: | CODE |
| LOW | src/scraper.py | 425 | except Exception as e: | CODE |
| LOW | src/scraper.py | 437 | except Exception as e: | CODE |
| LOW | src/scraper.py | 448 | except Exception as e: | CODE |
| LOW | src/scraper.py | 496 | except Exception as e: | CODE |
| LOW | src/scraper.py | 610 | except Exception as e: | CODE |
| LOW | src/scraper.py | 811 | except Exception as e: | STRING |
| LOW | src/scraper.py | 833 | except Exception as e: | STRING |
| LOW | src/scraper.py | 935 | except Exception as e: | STRING |
| LOW | src/scraper.py | 1178 | except Exception as e: | STRING |
| LOW | src/scraper.py | 1186 | except Exception as e: | STRING |
| LOW | src/scraper.py | 1210 | except Exception as e: | STRING |
| LOW | src/scraper.py | 1273 | except Exception as e: | STRING |
| LOW | src/scraper.py | 1332 | except Exception as e: | STRING |
| LOW | src/config.py | 78 | except Exception as e: | CODE |
| LOW | src/ai_handler.py | 193 | except Exception as e: | CODE |
| LOW | src/ai_handler.py | 206 | except Exception as e: | CODE |
| LOW | src/ai_handler.py | 224 | except Exception as e: | CODE |
| LOW | src/ai_handler.py | 235 | except Exception as e: | CODE |
| LOW | src/ai_handler.py | 368 | except Exception as e: | CODE |
| LOW | src/ai_handler.py | 445 | except Exception as e: | CODE |
| LOW | src/parsers.py | 64 | except Exception as e: | CODE |
| LOW | src/utils.py | 38 | except Exception as e: | CODE |
| LOW | src/utils.py | 72 | except Exception: | CODE |
| LOW | src/utils.py | 126 | except Exception as e: | CODE |
| LOW | src/prompt_utils.py | 87 | except Exception as exc: | STRING |
| LOW | src/prompt_utils.py | 102 | except Exception as close_error: | STRING |
| LOW | src/prompt_utils.py | 137 | except Exception as exc: | STRING |
| LOW | src/prompt_utils.py | 186 | except Exception as e: | STRING |
| LOW | src/failure_guard.py | 29 | except Exception: # pragma: no cover | CODE |
| LOW | src/failure_guard.py | 99 | except Exception: | CODE |
| LOW | src/failure_guard.py | 108 | except Exception: | CODE |
| LOW | src/failure_guard.py | 126 | except Exception: | CODE |
| LOW | src/failure_guard.py | 131 | except Exception: | CODE |
| MEDIUM | src/failure_guard.py | 94 | def __enter__(self): | CODE |
| MEDIUM | src/failure_guard.py | 103 | def __exit__(self, exc_type, exc, tb): | CODE |
| LOW | src/api/routes/tasks.py | 124 | except Exception as e: | CODE |
| LOW | src/api/routes/tasks.py | 203 | except Exception as e: | CODE |
| LOW | src/api/routes/tasks.py | 242 | except Exception as e: | CODE |
| LOW | src/api/routes/tasks.py | 249 | except Exception as e: | CODE |
| LOW | src/api/routes/results.py | 115 | except Exception as exc: | CODE |
| LOW | src/api/routes/results.py | 170 | except Exception as exc: | CODE |
| LOW | src/api/routes/login_state.py | 36 | except Exception as e: | CODE |
| LOW | src/api/routes/prompts.py | 65 | except Exception as e: | CODE |
| LOW | src/api/routes/dashboard.py | 21 | except Exception as exc: | CODE |
| LOW | src/api/routes/logs.py | 97 | except Exception as e: | CODE |
| LOW | src/api/routes/logs.py | 152 | except Exception as e: | CODE |
| LOW | src/api/routes/logs.py | 186 | except Exception as e: | CODE |
| LOW | src/api/routes/logs.py | 199 | except Exception as e: | CODE |
| LOW | src/api/routes/settings.py | 325 | except Exception as exc: | CODE |
| LOW | src/api/routes/settings.py | 345 | except Exception as exc: | CODE |
| LOW | src/api/routes/websocket.py | 33 | except Exception as e: | CODE |
| LOW | src/api/routes/websocket.py | 52 | except Exception: | CODE |
| LOW | src/infrastructure/config/env_manager.py | 68 | except Exception as e: | CODE |
| LOW | src/infrastructure/config/env_manager.py | 83 | except Exception as e: | CODE |
| LOW | src/infrastructure/config/env_manager.py | 94 | except Exception as e: | CODE |
| LOW | src/infrastructure/external/ai_client.py | 119 | except Exception as e: | CODE |
| 16 more matches not shown… | ||||
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | tests/conftest.py | 2 | CODE | |
| LOW | tests/test_failure_guard.py | 1 | CODE | |
| LOW | tests/test_frontend_build_paths.py | 1 | CODE | |
| LOW | tests/unit/test_task_log_cleanup_service.py | 1 | CODE | |
| LOW | tests/live/conftest.py | 1 | CODE | |
| LOW | tests/live/_support.py | 1 | CODE | |
| LOW | tests/live/test_live_smoke.py | 1 | CODE | |
| LOW | src/ai_handler.py | 10 | CODE | |
| LOW | src/ai_handler.py | 10 | CODE | |
| LOW | src/ai_handler.py | 10 | CODE | |
| LOW | src/ai_handler.py | 10 | CODE | |
| LOW | src/ai_handler.py | 50 | CODE | |
| LOW | src/app.py | 8 | CODE | |
| LOW | src/failure_guard.py | 12 | CODE | |
| LOW | src/core/cron_utils.py | 4 | CODE | |
| LOW | src/api/dependencies.py | 5 | CODE | |
| LOW | src/api/routes/settings.py | 18 | CODE | |
| LOW | src/infrastructure/config/__init__.py | 1 | CODE | |
| LOW | src/infrastructure/config/__init__.py | 1 | CODE | |
| LOW | src/infrastructure/config/__init__.py | 1 | CODE | |
| LOW | src/infrastructure/config/__init__.py | 1 | CODE | |
| LOW | …c/infrastructure/persistence/sqlite_task_repository.py | 4 | CODE | |
| LOW | src/infrastructure/persistence/sqlite_bootstrap.py | 4 | CODE | |
| LOW | src/infrastructure/persistence/sqlite_connection.py | 4 | CODE | |
| LOW | src/infrastructure/persistence/storage_names.py | 4 | CODE | |
| LOW | src/infrastructure/external/ai_client.py | 10 | CODE | |
| LOW | …frastructure/external/notification_clients/__init__.py | 1 | CODE | |
| LOW | …frastructure/external/notification_clients/__init__.py | 1 | CODE | |
| LOW | …frastructure/external/notification_clients/__init__.py | 2 | CODE | |
| LOW | …frastructure/external/notification_clients/__init__.py | 3 | CODE | |
| LOW | …frastructure/external/notification_clients/__init__.py | 4 | CODE | |
| LOW | …frastructure/external/notification_clients/__init__.py | 5 | CODE | |
| LOW | …frastructure/external/notification_clients/__init__.py | 6 | CODE | |
| LOW | …frastructure/external/notification_clients/__init__.py | 7 | CODE | |
| LOW | …frastructure/external/notification_clients/__init__.py | 8 | CODE | |
| LOW | src/domain/repositories/task_repository.py | 7 | CODE | |
| LOW | src/domain/repositories/task_repository.py | 8 | CODE | |
| LOW | src/domain/models/__init__.py | 1 | CODE | |
| LOW | src/domain/models/__init__.py | 1 | CODE | |
| LOW | src/domain/models/__init__.py | 1 | CODE | |
| LOW | src/domain/models/__init__.py | 1 | CODE | |
| LOW | src/services/price_history_service.py | 4 | CODE | |
| LOW | src/services/dashboard_payloads.py | 4 | CODE | |
| LOW | src/services/dashboard_service.py | 5 | CODE | |
| LOW | src/services/result_blacklist_service.py | 4 | CODE | |
| LOW | src/services/result_storage_service.py | 4 | CODE | |
| LOW | src/services/task_payloads.py | 4 | CODE | |
| LOW | src/services/task_log_cleanup_service.py | 4 | CODE |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| HIGH | spider_v2.py | 186 | except NotImplementedError: | CODE |
| HIGH | src/scraper.py | 760 | except PlaywrightTimeoutError: | CODE |
| HIGH | src/scraper.py | 783 | except PlaywrightTimeoutError: | CODE |
| HIGH | src/scraper.py | 892 | except PlaywrightTimeoutError: | CODE |
| HIGH | src/failure_guard.py | 99 | except Exception: | CODE |
| HIGH | src/failure_guard.py | 108 | except Exception: | CODE |
| HIGH | src/failure_guard.py | 131 | except Exception: | CODE |
| HIGH | src/services/process_service.py | 60 | except Exception: | CODE |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | spider_v2.py | 15 | CODE | |
| LOW | src/scraper.py | 402 | CODE | |
| LOW | src/scraper.py | 506 | CODE | |
| LOW | src/scraper.py | 416 | CODE | |
| LOW | src/scraper.py | 599 | CODE | |
| LOW | src/ai_handler.py | 298 | CODE | |
| LOW | src/parsers.py | 8 | CODE | |
| LOW | src/parsers.py | 69 | CODE | |
| LOW | src/parsers.py | 147 | CODE | |
| LOW | src/prompt_utils.py | 144 | CODE | |
| LOW | src/api/routes/tasks.py | 141 | CODE | |
| LOW | src/infrastructure/persistence/sqlite_bootstrap.py | 116 | CODE | |
| LOW | src/infrastructure/persistence/sqlite_bootstrap.py | 146 | CODE | |
| LOW | src/infrastructure/external/ai_client.py | 197 | CODE | |
| LOW | src/services/task_generation_service.py | 69 | CODE |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| HIGH | src/scraper.py | 666 | Object.defineProperty(navigator, 'webdriver', {get: () => undefined}); | CODE |
| HIGH | src/app.py | 163 | content={"error": "前端构建产物不存在,请先运行 cd web-ui && npm run build"} | CODE |
| HIGH | src/app.py | 184 | content={"error": "前端构建产物不存在,请先运行 cd web-ui && npm run build"} | CODE |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | tests/unit/test_task_log_cleanup_service.py | 14 | def _set_mtime(path: Path, when: datetime) -> None: | CODE |
| LOW | src/infrastructure/config/__init__.py | 3 | __all__ = ["settings", "AppSettings", "AISettings", "NotificationSettings"] | CODE |
| LOW | …frastructure/external/notification_clients/__init__.py | 10 | __all__ = [ | CODE |
| LOW | src/domain/models/__init__.py | 3 | __all__ = ["Task", "TaskCreate", "TaskUpdate", "TaskStatus"] | CODE |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | src/api/routes/results.py | 83 | CODE |