Born as an AI web agent that applied to jobs in bulk. Becoming a general one: an agent you point at the web and tell what to do.
This report presents the forensic synthetic code analysis of feder-cr/AIHawk, a Python project with 30,307 GitHub stars. SynthScan v2.0 examined 10,787 lines of code across 65 source files, recording 252 pattern matches distributed across 9 syntactic categories. The overall adjusted score of 46.3 places this repository in the Strong AI signal band.
The scanner applied 160+ deterministic lexical heuristics, multi-line block detectors, abstract syntax tree depth profilers, and a cross-file Jaccard similarity matrix to construct a statistically normalised synthetic code estimate. All matches are individually weighted by severity coefficient and contextual multiplier before summation, and the resulting headline score is temporally discounted to account for the repository's development history relative to the commercial emergence of large language model coding tooling (November 2022 onward).
Longitudinal tracking requires multiple scan runs. Once this repository is re-scanned after new commits land, this chart will visualise how the synthetic code signal evolves over time — enabling you to detect whether AI authorship is growing, stabilising, or being actively corrected by human engineers.
Classifies detected patterns by their diagnostic confidence and structural impact. CRITICAL patterns (coefficient 10) represent definitive synthetic signatures — hallucinated imports, explicit LLM attribution metadata — virtually never produced by human authors. HIGH (5) indicates strong structural tells such as cross-file repetition or cross-linguistic idioms. MEDIUM (2) covers recognisable conversational padding and AI-specific vocabulary. LOW (1) captures subtle indicators like tautological comments and generic boilerplate that require density to carry independent signal.
This horizontal bar chart decomposes the repository's raw synthetic code score by top-level directory, allowing you to pinpoint precisely which modules or components carry the highest AI authorship density. Directories with disproportionately high scores relative to their size warrant targeted manual review: concentrated AI signatures often trace back to mass-generated configuration layers, auto-ported test suites, LLM-scaffolded boilerplate classes, or entire subsystems authored under heavy copilot assistance. Use this view to prioritise your human code-review effort.
The scanner identified 252 distinct pattern matches across 9 syntactic categories. Each entry below represents a discrete location in the source code where the engine recorded a statistically significant AI authorship indicator. 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 diagnostic confidence level (CRITICAL / HIGH / MEDIUM / LOW). The Context column identifies whether the match occurred inside executable code, an inline comment, or a string literal — comment-context matches receive a ×1.5 weight because LLMs systematically over-annotate. The ⚡ bolt icon marks clustered matches: three or more patterns within a 10-line window, each receiving an additional ×1.5 density multiplier as dense clusters constitute far stronger evidence of synthetic authorship than isolated hits.
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| MEDIUM⚡ | tests/test_cli_surface.py | 95 | # -------------------------------------------------------------------------- | COMMENT |
| MEDIUM⚡ | tests/test_cli_surface.py | 97 | # -------------------------------------------------------------------------- | COMMENT |
| MEDIUM⚡ | tests/test_cli_surface.py | 191 | # -------------------------------------------------------------------------- | COMMENT |
| MEDIUM⚡ | tests/test_cli_surface.py | 193 | # -------------------------------------------------------------------------- | COMMENT |
| MEDIUM⚡ | tests/test_cli_surface.py | 351 | # -------------------------------------------------------------------------- | COMMENT |
| MEDIUM⚡ | tests/test_cli_surface.py | 353 | # -------------------------------------------------------------------------- | COMMENT |
| MEDIUM⚡ | tests/test_cli_surface.py | 386 | # -------------------------------------------------------------------------- | COMMENT |
| MEDIUM⚡ | tests/test_cli_surface.py | 388 | # -------------------------------------------------------------------------- | COMMENT |
| MEDIUM⚡ | tests/test_cli_surface.py | 429 | # -------------------------------------------------------------------------- | COMMENT |
| MEDIUM⚡ | tests/test_cli_surface.py | 431 | # -------------------------------------------------------------------------- | COMMENT |
| MEDIUM⚡ | tests/test_web_service.py | 78 | # -------------------------------------------------------------------------- | COMMENT |
| MEDIUM⚡ | tests/test_web_service.py | 80 | # -------------------------------------------------------------------------- | COMMENT |
| MEDIUM⚡ | tests/test_web_service.py | 146 | # -------------------------------------------------------------------------- | COMMENT |
| MEDIUM⚡ | tests/test_web_service.py | 148 | # -------------------------------------------------------------------------- | COMMENT |
| MEDIUM⚡ | tests/test_web_service.py | 204 | # -------------------------------------------------------------------------- | COMMENT |
| MEDIUM⚡ | tests/test_web_service.py | 206 | # -------------------------------------------------------------------------- | COMMENT |
| MEDIUM | tests/test_web_service.py | 238 | # -------------------------------------------------------------------------- | COMMENT |
| MEDIUM | tests/test_web_service.py | 240 | # -------------------------------------------------------------------------- | COMMENT |
| MEDIUM⚡ | tests/test_openrouter_only.py | 147 | # -------------------------------------------------------------------------- | COMMENT |
| MEDIUM⚡ | tests/test_openrouter_only.py | 149 | # -------------------------------------------------------------------------- | COMMENT |
| MEDIUM⚡ | tests/test_openrouter_only.py | 223 | # -------------------------------------------------------------------------- | COMMENT |
| MEDIUM⚡ | tests/test_openrouter_only.py | 225 | # -------------------------------------------------------------------------- | COMMENT |
| MEDIUM⚡ | tests/test_openrouter_only.py | 280 | # -------------------------------------------------------------------------- | COMMENT |
| MEDIUM⚡ | tests/test_openrouter_only.py | 282 | # -------------------------------------------------------------------------- | COMMENT |
| MEDIUM⚡ | tests/test_openrouter_only.py | 320 | # -------------------------------------------------------------------------- | COMMENT |
| MEDIUM⚡ | tests/test_openrouter_only.py | 327 | # -------------------------------------------------------------------------- | COMMENT |
| MEDIUM⚡ | tests/test_agent_loop.py | 213 | # -------------------------------------------------------------------------- | COMMENT |
| MEDIUM⚡ | tests/test_agent_loop.py | 215 | # -------------------------------------------------------------------------- | COMMENT |
| MEDIUM⚡ | tests/test_agent_loop.py | 364 | # -------------------------------------------------------------------------- | COMMENT |
| MEDIUM⚡ | tests/test_agent_loop.py | 366 | # -------------------------------------------------------------------------- | COMMENT |
| MEDIUM⚡ | tests/test_agent_loop.py | 421 | # -------------------------------------------------------------------------- | COMMENT |
| MEDIUM⚡ | tests/test_agent_loop.py | 423 | # -------------------------------------------------------------------------- | COMMENT |
| MEDIUM | tests/test_agent_loop.py | 64 | # -------------------------------------------------------------------------- | COMMENT |
| MEDIUM | tests/test_agent_loop.py | 66 | # -------------------------------------------------------------------------- | COMMENT |
| MEDIUM⚡ | tests/test_key_isolation.py | 42 | # --------------------------------------------------------------------------- | COMMENT |
| MEDIUM⚡ | tests/test_key_isolation.py | 44 | # --------------------------------------------------------------------------- | COMMENT |
| MEDIUM⚡ | tests/test_key_isolation.py | 93 | # --------------------------------------------------------------------------- | COMMENT |
| MEDIUM⚡ | tests/test_key_isolation.py | 95 | # --------------------------------------------------------------------------- | COMMENT |
| MEDIUM⚡ | tests/test_key_isolation.py | 137 | # ── what the mutation test asked for ──────────────────────────────────────── | COMMENT |
| MEDIUM⚡ | tests/test_key_isolation.py | 215 | # --------------------------------------------------------------------------- | COMMENT |
| MEDIUM⚡ | tests/test_key_isolation.py | 217 | # --------------------------------------------------------------------------- | COMMENT |
| MEDIUM⚡ | tests/test_key_isolation.py | 297 | # --------------------------------------------------------------------------- | COMMENT |
| MEDIUM⚡ | tests/test_key_isolation.py | 299 | # --------------------------------------------------------------------------- | COMMENT |
| MEDIUM | tests/test_key_isolation.py | 329 | # --------------------------------------------------------------------------- | COMMENT |
| MEDIUM | tests/test_key_isolation.py | 331 | # --------------------------------------------------------------------------- | COMMENT |
| MEDIUM | scripts/check_english_only.py | 224 | # ── the selftest ──────────────────────────────────────────────────────────── | COMMENT |
| MEDIUM | .github/workflows/publish-wiki.yml | 1 | # ───────────────────────────────────────────────────────────────────────────── | COMMENT |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | tests/test_readme_offers_both.py | 54 | def test_both_ways_in_are_offered(): | CODE |
| LOW | tests/test_readme_offers_both.py | 85 | def test_the_offer_comes_within_the_first_screen(): | CODE |
| LOW | tests/test_readme_offers_both.py | 105 | def test_the_check_fails_on_the_pages_as_they_used_to_be(): | CODE |
| LOW | tests/test_readme_offers_both.py | 131 | def test_the_two_ways_are_offered_as_one_choice(): | CODE |
| LOW | tests/test_readme_offers_both.py | 146 | def test_the_offer_says_what_each_one_costs_the_reader(): | CODE |
| LOW | tests/test_readme_offers_both.py | 164 | def test_no_tool_name_that_does_not_exist(): | CODE |
| LOW | tests/test_readme_offers_both.py | 184 | def test_no_hand_written_count_of_tools(): | CODE |
| LOW⚡ | tests/test_agent.py | 60 | def test_result_text_falls_back_for_non_text_content(): | CODE |
| LOW⚡ | tests/test_agent.py | 70 | def test_mcp_tools_to_openai_shape(): | CODE |
| LOW⚡ | tests/test_agent.py | 77 | async def test_run_task_drives_tools_then_returns_final_answer(): | CODE |
| LOW | tests/test_real_cli.py | 12 | def test_do_reads_a_data_url_heading(): | CODE |
| LOW | tests/test_llm.py | 5 | def test_key_arg_wins_then_env_then_error(): | CODE |
| LOW | tests/test_llm.py | 12 | def test_model_arg_env_default(): | CODE |
| LOW⚡ | tests/test_cli_surface.py | 99 | def test_missing_key_exits_one_and_names_both_ways_to_supply_one(drive): | CODE |
| LOW⚡ | tests/test_cli_surface.py | 195 | def test_every_option_reaches_drive_with_the_right_name_and_type(drive): | CODE |
| LOW⚡ | tests/test_cli_surface.py | 355 | def test_the_result_is_printed_verbatim_with_one_trailing_newline(monkeypatch): | CODE |
| LOW⚡ | tests/test_cli_surface.py | 390 | def test_group_help_works_and_names_openrouter(): | CODE |
| LOW⚡ | tests/test_cli_surface.py | 421 | def test_bare_invocation_shows_the_do_command(): | CODE |
| LOW⚡ | tests/test_cli_surface.py | 433 | def test_the_key_is_never_echoed_on_any_path(monkeypatch): | CODE |
| LOW | tests/test_cli_surface.py | 116 | def test_explicit_key_wins_and_the_environment_is_not_consulted(drive, monkeypatch): | CODE |
| LOW | tests/test_cli_surface.py | 132 | def test_key_is_taken_from_the_environment_when_the_flag_is_absent(drive, monkeypatch): | CODE |
| LOW | tests/test_cli_surface.py | 144 | def test_an_openai_key_in_the_environment_is_not_accepted(drive, monkeypatch): | CODE |
| LOW | tests/test_cli_surface.py | 161 | def test_the_command_offers_no_second_provider(): | CODE |
| LOW | tests/test_cli_surface.py | 177 | def test_the_cli_does_not_put_the_key_into_its_own_environment(drive, monkeypatch): | CODE |
| LOW | tests/test_cli_surface.py | 230 | def test_defaults_are_none_headless_and_the_default_model(drive): | CODE |
| LOW | tests/test_cli_surface.py | 250 | def test_the_option_names_are_the_ones_the_runner_reads(drive): | CODE |
| LOW | tests/test_cli_surface.py | 280 | def test_without_headed_the_child_is_left_headless(drive): | CODE |
| LOW | tests/test_cli_surface.py | 292 | def test_a_non_numeric_seed_is_a_usage_error(drive): | CODE |
| LOW | tests/test_cli_surface.py | 306 | def test_the_task_argument_is_required(drive): | CODE |
| LOW | tests/test_cli_surface.py | 319 | def test_an_unknown_option_is_a_usage_error(drive): | CODE |
| LOW | tests/test_cli_surface.py | 327 | def test_the_task_reaches_drive_verbatim(drive): | CODE |
| LOW | tests/test_cli_surface.py | 340 | def test_drive_is_called_exactly_once(drive): | CODE |
| LOW | tests/test_cli_surface.py | 372 | def test_an_empty_result_prints_only_a_newline(monkeypatch): | CODE |
| LOW | tests/test_cli_surface.py | 405 | def test_do_help_works_and_names_openrouter_and_the_env_variables(): | CODE |
| LOW | tests/test_cli_surface.py | 480 | def test_the_key_detector_is_not_vacuous(monkeypatch): | CODE |
| LOW | tests/test_cli_surface.py | 499 | def test_the_key_option_cannot_echo_its_own_value_in_a_usage_error(): | CODE |
| LOW⚡ | tests/test_web_service.py | 82 | async def test_the_instruction_is_emitted_by_the_service_not_added_by_the_page(): | CODE |
| LOW⚡ | tests/test_web_service.py | 150 | async def test_stop_cancels_a_run_in_flight_and_says_so(): | CODE |
| LOW⚡ | tests/test_web_service.py | 208 | async def test_the_app_exposes_exactly_the_routes_the_page_calls(): | CODE |
| LOW⚡ | tests/test_web_service.py | 217 | async def test_the_live_view_asks_for_nothing_until_an_instruction_has_been_given(): | CODE |
| LOW | tests/test_web_service.py | 98 | async def test_a_turn_brackets_itself_with_busy(): | CODE |
| LOW | tests/test_web_service.py | 113 | async def test_state_is_not_transcript(): | CODE |
| LOW | tests/test_web_service.py | 128 | async def test_the_replay_flag_is_on_history_and_not_on_live_events(): | CODE |
| LOW | tests/test_web_service.py | 174 | async def test_stop_with_nothing_running_is_false_rather_than_an_error(): | CODE |
| LOW | tests/test_web_service.py | 186 | async def test_a_failing_brain_reports_and_still_clears_busy(): | CODE |
| LOW | tests/test_web_service.py | 257 | async def test_the_address_comes_from_the_active_tab(): | CODE |
| LOW | tests/test_web_service.py | 280 | async def test_an_older_server_leaves_the_strip_empty_instead_of_breaking_the_pane(): | CODE |
| LOW | tests/test_web_service.py | 293 | async def test_the_strip_asks_nothing_before_an_instruction(): | CODE |
| LOW⚡ | tests/test_openrouter_only.py | 151 | def test_explicit_key_wins_over_the_environment(): | CODE |
| LOW⚡ | tests/test_openrouter_only.py | 156 | def test_environment_key_is_used_when_no_argument_is_given(): | CODE |
| LOW⚡ | tests/test_openrouter_only.py | 161 | def test_missing_key_raises_and_names_both_ways_to_supply_one(): | CODE |
| LOW⚡ | tests/test_openrouter_only.py | 227 | def test_base_url_constant_points_at_openrouter_over_https(): | CODE |
| LOW⚡ | tests/test_openrouter_only.py | 234 | def test_no_other_provider_host_appears_in_the_module(): | CODE |
| LOW⚡ | tests/test_openrouter_only.py | 241 | def test_client_base_url_is_openrouter_even_with_openai_base_url_set(foreign_env): | CODE |
| LOW⚡ | tests/test_openrouter_only.py | 284 | def test_model_explicit_wins_over_the_environment(): | CODE |
| LOW⚡ | tests/test_openrouter_only.py | 289 | def test_model_environment_used_when_no_argument(): | CODE |
| LOW⚡ | tests/test_openrouter_only.py | 294 | def test_model_falls_back_to_the_default_constant(): | CODE |
| LOW⚡ | tests/test_openrouter_only.py | 306 | def test_model_ignores_other_environment_names(): | CODE |
| LOW⚡ | tests/test_openrouter_only.py | 313 | def test_empty_model_falls_through_to_the_default(): | CODE |
| LOW⚡ | tests/test_openrouter_only.py | 337 | def test_environment_cannot_replace_the_outgoing_authorization_header(clean_env): | CODE |
| 85 more matches not shown… | ||||
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| HIGH | tests/test_ui_drive.py | 180 | window.clicked.push(index + 1); | CODE |
| HIGH | tests/test_ui_drive.py | 685 | "() => { return document.querySelector('#late') !== null; }", | CODE |
| HIGH | tests/test_ui_drive.py | 710 | " return ids.filter(function (id) { return document.getElementById(id) !== null; }).length; }" | CODE |
| HIGH⚡ | src/aihawk/web.py | 443 | live = null; | CODE |
| HIGH⚡ | src/aihawk/web.py | 451 | if(text.length <= LONG && text.indexOf('\n') < 0){ | CODE |
| HIGH⚡ | src/aihawk/web.py | 461 | p.append(el('span','sr', kind === 'err' ? 'error ' : ''), el('span', null, text)); | CODE |
| HIGH⚡ | src/aihawk/web.py | 481 | if(!busyNow){ flush(true, r); live = null; clearInterval(timer); | CODE |
| HIGH⚡ | src/aihawk/web.py | 482 | if(queued){ const t = queued; queued = null; send(t); } } | CODE |
| HIGH⚡ | src/aihawk/web.py | 484 | case 'you': flush(false, r); live = null; newTurn(); | CODE |
| HIGH | src/aihawk/web.py | 377 | if(c) e.className = c; if(x != null) e.textContent = x; return e; }; | CODE |
| HIGH | src/aihawk/web.py | 394 | let turn = null, live = null, hold = null, n = 0, t0 = 0, timer = 0; | CODE |
| HIGH | src/aihawk/web.py | 395 | let busyNow = false, queued = null, pinned = false, settle = 0; | CODE |
| HIGH | src/aihawk/web.py | 410 | if(hold === null) return; | CODE |
| HIGH | src/aihawk/web.py | 412 | hold = null; | CODE |
| HIGH | src/aihawk/web.py | 423 | lab.appendChild(el('b', null, (VERB[name] || ['Calling','Called'])[0])); | CODE |
| HIGH | src/aihawk/web.py | 424 | if(arg){ lab.append(' ', el('code', null, arg)); } | CODE |
| HIGH | src/aihawk/web.py | 527 | chip.onclick = () => { i.value = queued; queued = null; i.focus(); | CODE |
| HIGH | src/aihawk/web.py | 549 | $('tok').textContent = k(u.last_prompt || 0) + ' ctx / ' + | CODE |
| HIGH | src/aihawk/web.py | 550 | k((u.prompt || 0) + (u.completion || 0)) + ' total'; | CODE |
| HIGH | src/aihawk/web.py | 604 | if(!rows || rows.length < 2){ box.hidden = true; box.textContent = ''; return; } | CODE |
| HIGH | src/aihawk/web.py | 615 | b.appendChild(el('span','t', r.title || host || r.id)); | CODE |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | tests/test_readme_offers_both.py | 21 | CODE | |
| LOW | tests/test_cli_surface.py | 14 | CODE | |
| LOW | tests/test_web_service.py | 13 | CODE | |
| LOW | tests/test_openrouter_only.py | 23 | CODE | |
| LOW | tests/test_ui_drive.py | 28 | CODE | |
| LOW | tests/test_agent_loop.py | 45 | CODE | |
| LOW | tests/test_every_tool_reads_as_english.py | 18 | CODE | |
| LOW | tests/test_key_isolation.py | 18 | CODE | |
| LOW | scripts/check_english_only.py | 27 | CODE | |
| LOW | src/aihawk/link.py | 23 | CODE | |
| LOW | src/aihawk/runner.py | 14 | CODE | |
| LOW | src/aihawk/web.py | 26 | CODE | |
| LOW | src/aihawk/actions_help.py | 11 | CODE | |
| LOW | src/aihawk/llm.py | 2 | CODE | |
| LOW | src/aihawk/brain.py | 20 | CODE | |
| LOW | src/aihawk/cli.py | 2 | CODE | |
| LOW | src/aihawk/agent.py | 15 | CODE | |
| LOW | src/aihawk/agent.py | 18 | CODE |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| MEDIUM | docs/is-openai-operator-still-available.md | 147 | - [OpenAI help: ChatGPT agent](https://help.openai.com/en/articles/11752874-chatgpt-agent) and [OpenAI help: Evolving At | CODE |
| MEDIUM | docs/best-ai-browser-agent.md | 166 | - [Wikipedia: OpenAI Operator](https://en.wikipedia.org/wiki/OpenAI_Operator), retrieved 2026-09-03, and [OpenAI help: E | CODE |
| MEDIUM | docs/openai-operator-alternatives.md | 176 | - [OpenAI help: ChatGPT agent](https://help.openai.com/en/articles/11752874-chatgpt-agent) and [OpenAI help: Evolving At | CODE |
| MEDIUM | src/aihawk/agent.py | 102 | # size IS the current occupancy of the context window; adding them up | COMMENT |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | pyproject.toml | 21 | "Programming Language :: Python :: 3.12", | COMMENT |
| LOW | tests/test_key_isolation.py | 141 | # thinner than it looked: four of five mutations survived, and two of them | COMMENT |
| LOW | scripts/check_english_only.py | 41 | #: add them will come back: | COMMENT |
| LOW | scripts/check_english_only.py | 101 | return {w for w in ITALIAN | COMMENT |
| LOW | scripts/check_english_only.py | 121 | #: for Italian that sat in upstream code below the patch. | COMMENT |
| LOW | .github/workflows/publish.yml | 1 | name: publish | COMMENT |
| LOW | .github/workflows/publish.yml | 81 | # served 14 tools, the checkout offered 15, and the aihawk suite failed on | COMMENT |
| LOW | .github/workflows/publish-wiki.yml | 1 | # ───────────────────────────────────────────────────────────────────────────── | COMMENT |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | src/aihawk/link.py | 77 | except Exception: | CODE |
| LOW | src/aihawk/link.py | 134 | except Exception: | CODE |
| LOW | src/aihawk/web.py | 703 | except Exception as exc: | CODE |
| LOW | src/aihawk/web.py | 753 | except Exception as exc: | CODE |
| LOW | src/aihawk/web.py | 779 | except Exception: | CODE |
| LOW | src/aihawk/agent.py | 153 | except Exception as exc: | CODE |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | scripts/build_wiki.py | 19 | CODE | |
| LOW | src/aihawk/brain.py | 51 | CODE |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | src/aihawk/agent.py | 164 | async def run_task(mcp, task: str, *, client, model: str, max_turns: int = 25, | CODE |