diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 24e716b..36e9bab 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -28,3 +28,17 @@ jobs: pip install -e ".[dev]" - name: Run the suite run: python -m pytest -q + + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - uses: actions/setup-python@v6 + with: + python-version: "3.12" + - name: Install ruff + run: | + python -m pip install --upgrade pip + pip install "ruff>=0.1.0,<1.0.0" + - name: Lint src/ (ruff) + run: ruff check src/ diff --git a/.gitignore b/.gitignore index dfb7fb6..409a8dc 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,11 @@ RE_FINDINGS*.md *-assessment-*.html *reverse-engineering* *-RE-*.md +# Ghidra / binary RE projects (pyghidra-mcp output) — must never enter the public tree +pyghidra_mcp_projects/ +*ghidra* +*.gpr +*.rep # Python __pycache__/ @@ -37,7 +42,6 @@ secrets* *.swp *.swo *~ -.DS_Store # Seluj .seluj/ @@ -55,3 +59,9 @@ ecosystem_audit_*.html # playwright MCP test artifacts .playwright-mcp/ + +# Cloudflare wrangler local state +.wrangler/ + +# uv lock (dev convenience; project builds via setuptools/pip from pyproject) +uv.lock diff --git a/AGENTS.md b/AGENTS.md index 20dcb56..13027fa 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -5,9 +5,9 @@ Your purpose is to assist users by completing coding tasks, such as solving bugs implementing features, and writing tests. ## Core Directives -1. PLAN FIRST: Explore the codebase (list_files, read_file). Read this file and README.md. - Ask clarifying questions. Articulate the plan using set_plan. -2. VERIFY WORK: After every modification, use read_file or list_files to confirm success. +1. PLAN FIRST: Explore the codebase (Glob, Grep, Read). Read this file and README.md. + Ask clarifying questions. Articulate the plan (track steps with TaskCreate/TaskUpdate). +2. VERIFY WORK: After every modification, use Read or Grep to confirm success. Do NOT mark a plan step complete until you've verified. 3. EDIT SOURCE, NOT ARTIFACTS: If a file is a build artifact (dist/, build/, node_modules/, __pycache__/, .next/), trace back to its source. diff --git a/README.md b/README.md index 6f63e15..a750df6 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ PyPI version Python versions CI - tests + tests MCP registry License: MIT

@@ -541,7 +541,7 @@ korgex/ │ └── ... ├── docs/ # CLI reference, comparison, getting-started, tools-reference, … ├── spec/korg-ledger-v1/ # the ledger spec (SPEC.md, EVENTS.md) -├── tests/ # ~1,571 tests +├── tests/ # ~1,586 tests ├── .github/workflows/ # Linux CI (3.10–3.13) + PyPI publisher (OIDC) ├── pyproject.toml └── README.md @@ -560,7 +560,7 @@ ruff check src/ # lint pytest -q # the full suite ``` -The suite is **~1,285 tests** with no live LLM calls (everything is unit-tested) and runs on Linux CI across **Python 3.10, 3.11, 3.12, 3.13** on every push and PR. Major areas: the agent loop (routing, provider schemas, mode/model resolution, loop guards, the stall classifier, compaction), tools (fuzzy Edit, edit-freshness, background Bash, web), the verifiable ledger (hash-chain + causal DAG, redaction, the Ed25519 signed bus), CodeAct (kernel isolation, fuel, the tool bridge), MCP (namespaced multi-server router, OAuth refresh, full round-trip), prompt caching, skills (trust tiers, self-learning, the curator), and the REPL. +The suite is **~1,586 tests** with no live LLM calls (everything is unit-tested) and runs on Linux CI across **Python 3.10, 3.11, 3.12, 3.13** on every push and PR. Major areas: the agent loop (routing, provider schemas, mode/model resolution, loop guards, the stall classifier, compaction), tools (fuzzy Edit, edit-freshness, background Bash, web), the verifiable ledger (hash-chain + causal DAG, redaction, the Ed25519 signed bus), CodeAct (kernel isolation, fuel, the tool bridge), MCP (namespaced multi-server router, OAuth refresh, full round-trip), prompt caching, skills (trust tiers, self-learning, the curator), and the REPL. --- @@ -594,7 +594,8 @@ To build locally for inspection: `python -m build` then `python -m twine check d These exist today; PRs welcome. -- **OpenAI streaming has fewer rendered events than Anthropic.** Anthropic emits thinking blocks and message-delta usage; OpenAI emits only text and tool-call chunks. Both render correctly, but the TUI is richer for Anthropic.- **Dashboard authentication is not implemented.** Don't expose port 8090 publicly without an auth-terminating reverse proxy in front. +- **OpenAI streaming has fewer rendered events than Anthropic.** Anthropic emits thinking blocks and message-delta usage; OpenAI emits only text and tool-call chunks. Both render correctly, but the TUI is richer for Anthropic. +- **Dashboard authentication is not implemented.** Don't expose port 8090 publicly without an auth-terminating reverse proxy in front. - **The VS Code sidecar is a legacy companion** to the dashboard; korgex's primary interface is the terminal REPL. --- diff --git a/ROADMAP.md b/ROADMAP.md index 7595631..889f5aa 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -1,5 +1,12 @@ # Korgex — Next Frontier Roadmap +> ⚠️ **HISTORICAL — kept for reference; do not treat as current.** +> This roadmap predates korgex's v0.x pivot. It describes a pre-pivot plan (korg-bridge / PyO3 +> → KorgChat, "27 tests"). korgex has since shipped through **v0.35.0** (~1,571 tests) as a +> provider-agnostic, MCP-native coding agent. For the current state see +> [`CHANGELOG.md`](CHANGELOG.md) and [`README.md`](README.md). (The AlphaEvolve appendix below +> is still useful.) + ## Release Sequence | Version | Status | Scope | diff --git a/docs/images/korgex-chrome-light.png b/docs/images/korgex-chrome-light.png new file mode 100644 index 0000000..e98e1bc Binary files /dev/null and b/docs/images/korgex-chrome-light.png differ diff --git a/docs/korgex-explained.html b/docs/korgex-explained.html new file mode 100644 index 0000000..c021188 --- /dev/null +++ b/docs/korgex-explained.html @@ -0,0 +1,237 @@ + + + + + +korgex, explained — the AI coding helper that keeps the receipts + + + + +
+ +
+ Plain-English guide +
korgex
+

The AI coding helper that keeps the receipts.

+

Tell it what you want in everyday words — “fix the failing test,” “add a sign-up page” — and it reads your code, makes the change, runs your tests, and shows its work. Then it hands you a record you can actually check. Free, open-source, and it works with whatever AI you like.

+ +
+ free & open-source + works in your terminal + any AI: Claude · ChatGPT · Gemini · local + no lock-in +
+
+ +
+ +
+

What is it, really?

+

Think of a very capable junior developer who lives in your terminal.

+

You describe a job in plain language. korgex looks through your project, figures out a plan, edits the right files, runs the tests to check it worked, and tells you exactly what it did. You stay in control — it can ask before making big changes, and you can undo anything.

+

The twist that nothing else has: everything it does is written down in a way that can’t be quietly changed later. That’s the “receipts.”

+
+ +
+

The big idea: AI you can check, not just trust

+

Most AI tools ask you to take their word for it. korgex gives you proof.

+

As it works, korgex keeps a logbook. Every step — every file it read, every command it ran — is sealed to the step before it, like links in a chain. If anyone later changes, removes, or reorders even one entry, the chain visibly breaks and korgex points to exactly where.

+ +
+
step 1
your request
+
+
step 2
read the code
+
+
step 3
make the change
+
+
step 4
run the tests
+
+
+
Nothing touched — the record checks out, start to finish.
+
Something changed — the chain breaks and shows the exact spot.
+
+

Why care? Audits, compliance, debugging, or just peace of mind — you get honest, checkable proof of what the AI actually did, instead of hoping it behaved.

+
+ +
+

What it can do

+

The everyday stuff, plus a few things no other coding assistant offers.

+
+
⌨️

Writes & fixes code

Reads your project, makes the change, and edits the exact lines — not a wall of copy-paste.

+

Runs your tests

It checks its own work by running your test suite, so you get changes that actually pass.

+
🔌

Any AI you like

Claude, ChatGPT, Gemini, Grok, or a private model on your own computer. Switch anytime — no lock-in.

+
🧾

Keeps the receipts

A tamper-evident record of every step you can verify later with one command.

+
🧩

Plugs into your tools

Connects to GitHub, your files, databases and more through open “MCP” connectors.

+
↩️

Undo anything

A rewind log lets you roll files back to before any step. Nothing is one-way.

+
+
+ +
+

Try it in 60 seconds

+

If you can open a terminal, you can run korgex.

+
+
+
1
+
+

Install it

+

One line. (Needs Python 3.10 or newer.)

+
pip install -U korgex
+
+
+
+
2
+
+

Connect an AI

+

Run korgex setup and paste a key from whichever provider you use — or set one and go.

+
export ANTHROPIC_API_KEY="…"     # or OPENAI_API_KEY, or a local model
+
+
+
+
3
+
+

Ask for something — then check the receipt

+

Describe the job in plain words. When it’s done, prove the run wasn’t altered.

+
+
+
+ + +
+ +
+

Is it safe to let it loose?

+

It’s built cautious. The powerful stuff is off until you turn it on.

+
+
Asks before risky edits

Big or sensitive changes show you a diff and wait for your “yes.”

+
Blocks dangerous commands

A built-in guard refuses obviously destructive shell commands — and records that it did.

+
Runs code in a sandbox

When it executes code for you, it can be boxed off from your network and files.

+
Watches what leaves

An optional guard flags secrets or large data trying to leave your machine.

+
+
+ +
+

Why people choose it

+

Two reasons, in plain terms. You’re never locked to one AI company — korgex speaks to all the major ones and to private models, so you can pick the best (or cheapest) for the job and switch whenever. And you can prove what happened — the checkable record means you don’t have to take an AI’s word for anything. As far as we know, no other coding assistant gives you that.

+
+ +
+ + + +
+ + diff --git a/scripts/githooks/pre-commit b/scripts/githooks/pre-commit index 55c801f..d132ef6 100755 --- a/scripts/githooks/pre-commit +++ b/scripts/githooks/pre-commit @@ -18,11 +18,11 @@ set -u # so bare "hermes"/"nousresearch" are intentionally absent; only the RE-specific # module `hermes_tools` is blocked. \bMITM\b (word-bounded) avoids the "comMITMent" # false positive; the trailing grep -v strips other benign substrings. -PATTERN='mitmproxy|manual mitm|\bmitm\b|reverse[- ]?engineer|captured from (claude|the api|api\.)|claude max|mahoraga|\bjules\b|hermes_tools|kikkaskills|kabukiskills|intercepted from' +PATTERN='mitmproxy|manual mitm|\bmitm\b|reverse[- ]?engineer|captured from (claude|the api|api\.)|claude max|mahoraga|\bjules\b|hermes_tools|kikkaskills|kabukiskills|intercepted from|ghidra|pyghidra' # 1) Sensitive file NAMES being added. bad_files=$(git diff --cached --name-only --diff-filter=A \ - | grep -iE 'RE[_-]?FINDINGS|reverse.?eng|[-_]assessment[-_].*\.html$|_mitm' || true) + | grep -iE 'RE[_-]?FINDINGS|reverse.?eng|[-_]assessment[-_].*\.html$|_mitm|ghidra|\.gpr$' || true) # 2) Sensitive ADDED lines (only new content, not context). The guard's own files # legitimately contain these patterns (the definition + its docs), so exclude them. diff --git a/src/agent.py b/src/agent.py index 783ddc2..5a7b5cf 100644 --- a/src/agent.py +++ b/src/agent.py @@ -14,8 +14,6 @@ import os import sys import time -from pathlib import Path -from typing import Optional from src.tool_abstraction import USER_TOOLS, route_tool_call from src import tool_abstraction as _TA @@ -91,9 +89,9 @@ def codeact_unconfined_warning(platform: str) -> str: f"Bash; raw stdlib bypasses the command + egress guards ({hint}).") -from src.agent_resolve import ( # resolution helpers, extracted to keep agent.py focused - _looks_anthropic, _OAUTH_BASE_URLS, _oauth_provider_for, _oauth_token_and_base, - _READONLY_SUBAGENT_TOOLS, _MODEL_ALIASES, subagent_tools, _resolve_params, _resolve_model, +from src.agent_resolve import ( # noqa: E402 — extracted helpers, imported after defs to avoid a circular import + _looks_anthropic, _oauth_provider_for, _oauth_token_and_base, + _MODEL_ALIASES, subagent_tools, _resolve_params, _resolve_model, ) @@ -711,7 +709,8 @@ def _call_anthropic_streaming(self, client, messages: list, tools: list, ) as stream: for event in stream: if on_first is not None: - on_first(); on_first = None # first event → clear the thinking spinner + on_first() + on_first = None # first event → clear the thinking spinner ev_type = getattr(event, "type", None) if not ev_type: continue @@ -756,7 +755,8 @@ def _call_openai_streaming(self, client, messages: list, tools: list, on_first=N for chunk in stream: if on_first is not None: - on_first(); on_first = None # first chunk → clear the thinking spinner + on_first() + on_first = None # first chunk → clear the thinking spinner if not chunk.choices: usage = getattr(chunk, "usage", None) or usage # usage-only final chunk continue diff --git a/src/cli.py b/src/cli.py index 90fa8cc..fe8a2ae 100644 --- a/src/cli.py +++ b/src/cli.py @@ -298,15 +298,15 @@ def cmd_default(): subprocess.Popen([code, str(ext_path)]) print() - print(f" ┌─────────────────────────────────────────────┐") - print(f" │ Korgex is live │") - print(f" │ │") + print(" ┌─────────────────────────────────────────────┐") + print(" │ Korgex is live │") + print(" │ │") print(f" │ Dashboard → http://localhost:{DASHBOARD_PORT:<4} │") - print(f" │ VS Code → Press F5 in the new window │") - print(f" │ Commands → Cmd+Shift+P → 'Korgex:' │") - print(f" │ │") - print(f" │ korgex stop to shut down │") - print(f" └─────────────────────────────────────────────┘") + print(" │ VS Code → Press F5 in the new window │") + print(" │ Commands → Cmd+Shift+P → 'Korgex:' │") + print(" │ │") + print(" │ korgex stop to shut down │") + print(" └─────────────────────────────────────────────┘") def cmd_init(): @@ -335,7 +335,7 @@ def cmd_dashboard(): """Start just the web dashboard.""" _start_background_server() print(f" Dashboard: http://localhost:{DASHBOARD_PORT}") - print(f" Press Ctrl+C to stop.") + print(" Press Ctrl+C to stop.") def cmd_status(): @@ -346,7 +346,7 @@ def cmd_status(): print(f" Dashboard: http://localhost:{DASHBOARD_PORT}") else: print(" Korgex is not running.") - print(f" Run `korgex` to start.") + print(" Run `korgex` to start.") def cmd_stop(): @@ -411,7 +411,7 @@ def cmd_verify(): if not Path(path).exists(): print(f" No ledger journal at {path}") - print(f" (set KORG_JOURNAL_PATH or pass: korgex verify )") + print(" (set KORG_JOURNAL_PATH or pass: korgex verify )") return 1 n = len(load_journal_raw(path)) # real event count (array OR jsonl), not line count @@ -1435,7 +1435,7 @@ def cmd_repl(resume=False): # ── Entry Point ────────────────────────────────────────────────────────── -import argparse +import argparse # noqa: E402 (imported beside the entry point) # Map subcommand name → handler. Existing bodies untouched. def cmd_providers(): diff --git a/src/dashboard.py b/src/dashboard.py index 97f105f..59224d1 100644 --- a/src/dashboard.py +++ b/src/dashboard.py @@ -9,15 +9,11 @@ - Subagent swarm dashboard """ -import json -import os import threading -import uuid -from pathlib import Path from typing import Optional try: - from fastapi import FastAPI, HTTPException, WebSocket, WebSocketDisconnect + from fastapi import FastAPI, WebSocket, WebSocketDisconnect from fastapi.responses import HTMLResponse, JSONResponse import uvicorn FASTAPI_AVAILABLE = True @@ -368,7 +364,7 @@ def start_dashboard(host: str = "0.0.0.0", port: int = 8090): # Tool registration -from src.tool_base import register_tool, ToolParam +from src.tool_base import register_tool, ToolParam # noqa: E402 (registered after the module's HTML/handlers) @register_tool("start_dashboard", "Starts the Korgex web steering dashboard.", [ diff --git a/src/dependency_graph.py b/src/dependency_graph.py index 25187f7..0436908 100644 --- a/src/dependency_graph.py +++ b/src/dependency_graph.py @@ -20,7 +20,7 @@ import os import ast from pathlib import Path -from typing import Dict, List, Set, Optional, Any +from typing import Dict, List, Set, Optional class DependencyAnalyzer: diff --git a/src/diff_engine.py b/src/diff_engine.py index 45c8e75..b34b623 100644 --- a/src/diff_engine.py +++ b/src/diff_engine.py @@ -71,7 +71,7 @@ def apply_git_three_way(filepath: str, new_content: str) -> dict: "conflicts": False, } - except Exception as e: + except Exception: # Fall back to SEARCH/REPLACE return DiffEngine.apply_search_replace(filepath, f"<<<<<<< SEARCH\n>>>>>>> REPLACE\n{new_content}") @@ -110,11 +110,7 @@ def apply_search_replace(filepath: str, merge_diff: str) -> dict: content_normalized = re.sub(r'\s+', ' ', modified) if search_normalized in content_normalized: - # Find the actual location and replace - idx = content_normalized.index(search_normalized) - # Map back to original content - actual_start = len(modified[:idx]) # approximate - # Try to find by line proximity + # Find the actual location by line proximity search_lines = search_part.split('\n') for i, line in enumerate(modified.split('\n')): if search_lines[0].strip() in line: @@ -128,9 +124,9 @@ def apply_search_replace(filepath: str, merge_diff: str) -> dict: changes += 1 break else: - errors.append(f"Could not locate SEARCH block (fuzzy match failed)") + errors.append("Could not locate SEARCH block (fuzzy match failed)") else: - errors.append(f"SEARCH block not found in file (exact or fuzzy)") + errors.append("SEARCH block not found in file (exact or fuzzy)") if changes == 0: return {"error": "No changes applied.", "details": errors} @@ -189,7 +185,7 @@ def _apply_js_ts(filepath: str, new_content: str) -> dict: # Tool registration -from src.tool_base import register_tool, ToolParam +from src.tool_base import register_tool, ToolParam # noqa: E402 (registered after the module's functions) def apply_patch(filepath: str, patch_path: str) -> dict: diff --git a/src/feature_flags.py b/src/feature_flags.py index ca1f455..368dea4 100644 --- a/src/feature_flags.py +++ b/src/feature_flags.py @@ -7,9 +7,7 @@ Flags also control runtime behavior: tool availability, model settings, etc. """ -import os -from dataclasses import dataclass, field -from typing import Optional +from dataclasses import dataclass @dataclass diff --git a/src/github_api.py b/src/github_api.py index 6faaec7..f1bc1c2 100644 --- a/src/github_api.py +++ b/src/github_api.py @@ -5,9 +5,7 @@ """ import os -import json import subprocess -from typing import Optional GITHUB_TOKEN_ENV = "KORGEX_GITHUB_TOKEN" GITHUB_API = "https://api.github.com" diff --git a/src/hooks.py b/src/hooks.py index 790b38a..4698bca 100644 --- a/src/hooks.py +++ b/src/hooks.py @@ -34,11 +34,9 @@ import hashlib import json import logging -import os import re import subprocess from pathlib import Path -from typing import Any logger = logging.getLogger(__name__) diff --git a/src/interactive.py b/src/interactive.py index d7a5c00..b2cec20 100644 --- a/src/interactive.py +++ b/src/interactive.py @@ -331,8 +331,7 @@ def should_confirm_edit(file_path: str, old_content: str, ] old_lines = len(old_content.splitlines()) - new_lines = len(new_content.splitlines()) - + # Check if it's a critical file basename = os.path.basename(file_path) if any(crit in basename for crit in CRITICAL_FILES): diff --git a/src/korg_ledger.py b/src/korg_ledger.py index 6c6ec5b..9b7a63f 100644 --- a/src/korg_ledger.py +++ b/src/korg_ledger.py @@ -65,13 +65,11 @@ from __future__ import annotations import hashlib -import hmac import json import logging import os import queue import threading -import time from pathlib import Path from typing import Any diff --git a/src/korgantic.py b/src/korgantic.py index 675c458..805b4d1 100644 --- a/src/korgantic.py +++ b/src/korgantic.py @@ -257,11 +257,11 @@ def _record_best_of_n(ledger, prompt, attempts, winner, parent_seq) -> int: def multi_modal_sweep(lenses, runner, base_prompt: str) -> list: """Run one understand-agent per lens CONCURRENTLY — each blind to the others. - Lenses are independent, so this is a genuine fan-out. `l=lens` binds the loop + Lenses are independent, so this is a genuine fan-out. `ln=lens` binds the loop var per-thunk (avoids late-binding closure capture). """ thunks = [ - (lambda l=lens: runner("understand", f"[{l} lens] Analyze for this task: {base_prompt}")) + (lambda ln=lens: runner("understand", f"[{ln} lens] Analyze for this task: {base_prompt}")) for lens in lenses ] return parallel(thunks) diff --git a/src/mcp_client.py b/src/mcp_client.py index 33133c5..2fffe6f 100644 --- a/src/mcp_client.py +++ b/src/mcp_client.py @@ -25,13 +25,11 @@ import json import os import subprocess -import sys import threading -import time import uuid from collections import deque from dataclasses import dataclass, field -from typing import Any, Optional +from typing import Optional # Cap MCP server stderr capture so a chatty server can't grow memory unbounded. # 1000 lines is plenty for post-mortem diagnostics; oldest are dropped first. @@ -209,7 +207,7 @@ def connect(self) -> dict: text=True, bufsize=1, # Line-buffered ) - except FileNotFoundError as e: + except FileNotFoundError: return {"error": f"Command not found: {self.config.command}", "status": "failed"} except Exception as e: return {"error": f"Failed to spawn: {e}", "status": "failed"} @@ -657,20 +655,20 @@ def get_manager() -> MCPServerManager: print("=== MCP Client Module Test ===\n") # Verify all components load - print(f" MCPClient class: ✓") - print(f" MCPServerManager class: ✓") - print(f" MCPTool dataclass: ✓") - print(f" MCPServerConfig dataclass: ✓") - print(f" make_request: ✓") - print(f" parse_response: ✓") - print(f" load_mcp_config: ✓") + print(" MCPClient class: ✓") + print(" MCPServerManager class: ✓") + print(" MCPTool dataclass: ✓") + print(" MCPServerConfig dataclass: ✓") + print(" make_request: ✓") + print(" parse_response: ✓") + print(" load_mcp_config: ✓") # Test JSON-RPC message format req = make_request("initialize", {"protocolVersion": "2025-03-26"}) parsed = json.loads(req) assert parsed["jsonrpc"] == "2.0" assert parsed["method"] == "initialize" - print(f"\n ✓ JSON-RPC 2.0 message format verified") + print("\n ✓ JSON-RPC 2.0 message format verified") # Test config loading test_config = """{ @@ -692,5 +690,5 @@ def get_manager() -> MCPServerManager: assert configs["github"].command == "npx" assert configs["github"].args == ["-y", "@modelcontextprotocol/server-github"] os.unlink(tmp_path) - print(f" ✓ MCP config parsing verified") + print(" ✓ MCP config parsing verified") print(f"\n Ready: {len(configs)} servers from config") \ No newline at end of file diff --git a/src/memory.py b/src/memory.py index c84b3e9..657a21d 100644 --- a/src/memory.py +++ b/src/memory.py @@ -31,7 +31,6 @@ import re import yaml from datetime import datetime -from pathlib import Path from typing import Optional MEMORY_DIR = None # Set during init @@ -284,7 +283,7 @@ def _remove_from_index(name: str): with open(index_path) as f: lines = f.readlines() - lines = [l for l in lines if f"]({name}.md)" not in l] + lines = [ln for ln in lines if f"]({name}.md)" not in ln] with open(index_path, "w") as f: f.writelines(lines) diff --git a/src/mode_schemas.py b/src/mode_schemas.py index 742ad21..ae8bb04 100644 --- a/src/mode_schemas.py +++ b/src/mode_schemas.py @@ -17,7 +17,6 @@ [Agent Loop] → determines mode → selects tool subset → sends to LLM """ -from typing import Optional # ── Tool Subset Definitions ───────────────────────────────────────────── diff --git a/src/model_router.py b/src/model_router.py index 2e1e5d1..0fd3f97 100644 --- a/src/model_router.py +++ b/src/model_router.py @@ -23,12 +23,10 @@ import json import os import time -from dataclasses import dataclass, field -from enum import Enum -from typing import Any, Optional, Protocol +from dataclasses import dataclass +from typing import Optional, Protocol # For cost tracking and display -from src.feature_flags import is_enabled def _to_epoch(value, ms: bool = False) -> float: @@ -1519,5 +1517,5 @@ def on_mode_change(mode: str): p3 = router.get_current_params() assert p1["model"] == p3["model"], "Context lost during plan→execute→plan swap" - print(f"\n ✓ Context preserved across plan→execute→plan swap") + print("\n ✓ Context preserved across plan→execute→plan swap") print(f" ✓ Model swap count: {router._swap_count}") \ No newline at end of file diff --git a/src/sandbox.py b/src/sandbox.py index 8f7e25e..eba336e 100644 --- a/src/sandbox.py +++ b/src/sandbox.py @@ -13,12 +13,10 @@ """ import os -import json import subprocess import tempfile import shutil from pathlib import Path -from typing import Optional class SandboxBase: @@ -143,9 +141,8 @@ def _setup_modal(self): @self.app.function(image=image, timeout=600) def run_in_cloud(cmd: str, repo_url: str = None): - import subprocess, os, tempfile - - results = {"stdout": "", "stderr": "", "exit_code": 0} + import subprocess + import os if repo_url: repo_name = repo_url.split("/")[-1].replace(".git", "") diff --git a/src/self_healing.py b/src/self_healing.py index 89ee0ac..f7c9eb5 100644 --- a/src/self_healing.py +++ b/src/self_healing.py @@ -18,7 +18,6 @@ """ import json -import os import re from typing import Callable, Optional @@ -212,7 +211,6 @@ def _apply_patch_in_sandbox(self, filepath: str, patch: str) -> dict: temp_patch = ".korgex_heal.patch" # Write patch file to sandbox - escaped = patch.replace("'", "'\\''") self.sandbox.run( f"cat << 'KORGEOF' > {temp_patch}\n{patch}\nKORGEOF" ) diff --git a/src/strict_pairing.py b/src/strict_pairing.py index 6119aa8..abbe8c4 100644 --- a/src/strict_pairing.py +++ b/src/strict_pairing.py @@ -26,7 +26,6 @@ import hashlib import json -import os import secrets import time from typing import Any, Optional diff --git a/src/swarm.py b/src/swarm.py index 16a4045..26427a2 100644 --- a/src/swarm.py +++ b/src/swarm.py @@ -15,7 +15,6 @@ import json import os -import threading import time import uuid from concurrent.futures import ThreadPoolExecutor, as_completed @@ -258,7 +257,7 @@ def analyze_pr(self, repo_root: str, pr_description: str) -> dict: # Tool registration -from src.tool_base import register_tool, ToolParam +from src.tool_base import register_tool, ToolParam # noqa: E402 (registered after the module's functions) @register_tool("swarm_analyze_pr", "Runs multiple specialist agents (test, security, refactor) in parallel on a PR.", [ diff --git a/src/system_prompt.py b/src/system_prompt.py index bc03513..0178281 100644 --- a/src/system_prompt.py +++ b/src/system_prompt.py @@ -14,7 +14,6 @@ import os import platform import subprocess -from pathlib import Path def build_system_prompt(memory_text: str = "", workdir: str = None, @@ -127,7 +126,6 @@ def _build_session_block(memory_text: str, workdir: str = None, """.strip()) # Environment context - env_info = _get_environment_info(workdir) parts.append(f""" # Environment You have been invoked in the following environment: diff --git a/src/tool_abstraction.py b/src/tool_abstraction.py index 693491d..bf068cf 100644 --- a/src/tool_abstraction.py +++ b/src/tool_abstraction.py @@ -16,7 +16,6 @@ import json import os -from typing import Any, Callable, Dict, Optional # ── User-Facing Tool Definitions ──────────────────────────────────────── # These are the ~12 tools the LLM sees. Each has deep descriptions. @@ -448,7 +447,7 @@ def tool_search(query: str, limit: int = 5) -> dict: # Additionally, tools registered via register_mcp_tool() route through the # MCP server manager instead of an in-process handler. -import inspect +import inspect # noqa: E402 (kept beside the MCP-tool registry it supports) # Track which tool names came from MCP servers (vs. native handlers) _MCP_TOOLS: set[str] = set() diff --git a/src/tool_base.py b/src/tool_base.py index 65db9b4..ce943ef 100644 --- a/src/tool_base.py +++ b/src/tool_base.py @@ -7,13 +7,9 @@ Param types: STRING, BOOLEAN, ARRAY, OBJECT (a compact schema, not full JSON Schema). """ -import json import os -import shlex import subprocess -import tempfile from functools import lru_cache -from pathlib import Path from typing import Any, Callable, Optional TOOL_REGISTRY = {} diff --git a/src/tools_impl.py b/src/tools_impl.py index 7904328..3f51fc5 100644 --- a/src/tools_impl.py +++ b/src/tools_impl.py @@ -834,7 +834,7 @@ def tool_analyze_image(filepath: str, question: str = None, context: dict = None def tool_grok_imagine(prompt: str, aspect_ratio: str = "1:1", resolution: str = "1k", n: int = 1, context: dict = None): """Generate images via xAI Grok Imagine API using grok-build OAuth.""" - import base64, json as _json + import base64 import httpx from src.model_router import GrokClient @@ -842,13 +842,6 @@ def tool_grok_imagine(prompt: str, aspect_ratio: str = "1:1", resolution: str = client = GrokClient() token = client._ensure_token() - # Map aspect ratio to width,height arrays - ar_map = { - "1:1": [1, 1], "3:2": [3, 2], "2:3": [2, 3], - "16:9": [16, 9], "9:16": [9, 16], - } - ar = ar_map.get(aspect_ratio, [1, 1]) - body = { "model": "grok-imagine-image", "prompt": prompt, diff --git a/src/tui_app.py b/src/tui_app.py index 46dd7e3..20da8ef 100644 --- a/src/tui_app.py +++ b/src/tui_app.py @@ -63,7 +63,6 @@ def run_app(repl) -> None: import shutil from prompt_toolkit.application import Application - from prompt_toolkit.application.current import get_app from prompt_toolkit.key_binding import KeyBindings from prompt_toolkit.layout import HSplit, Layout, Window from prompt_toolkit.layout.controls import FormattedTextControl diff --git a/src/vision.py b/src/vision.py index 19653c9..0924a8b 100644 --- a/src/vision.py +++ b/src/vision.py @@ -4,12 +4,9 @@ Integrates with browser automation for visual testing and screenshot capture. """ -import os -import json import base64 import tempfile from pathlib import Path -from typing import Optional # Vision backends try: diff --git a/src/webhook_server.py b/src/webhook_server.py index c0ff70b..b5bb0e8 100644 --- a/src/webhook_server.py +++ b/src/webhook_server.py @@ -105,13 +105,12 @@ def _process_webhook(event: str, data: dict): elif event == "pull_request" and data.get("action") in ("opened", "labeled"): pr = data.get("pull_request", {}) - labels = [l.get("name", "").lower() for l in pr.get("labels", [])] + labels = [lbl.get("name", "").lower() for lbl in pr.get("labels", [])] if "korgex" in labels: title = pr.get("title", "") body = pr.get("body", "") number = pr.get("number", "") - head_sha = pr.get("head", {}).get("sha", "") task = f"Review PR #{number}: {title}\n{body}" _run_korgex(task, repo, clone_url) diff --git a/tests/test_opsec_guard.py b/tests/test_opsec_guard.py index 97e765f..418140c 100644 --- a/tests/test_opsec_guard.py +++ b/tests/test_opsec_guard.py @@ -66,3 +66,10 @@ def test_guard_blocks_reverse_engineering_and_mitm(tmp_path): def test_guard_blocks_re_findings_filename(tmp_path): assert _run_guard(tmp_path, "RE_FINDINGS.md", "anything\n") == 1 + + +def test_guard_blocks_ghidra_project_files(tmp_path): + # Ghidra / pyghidra-mcp RE output must not enter the public repo. + # Blocked by filename (.gpr) and by content ("ghidra"). + assert _run_guard(tmp_path, "my_project.gpr", "binary\n") == 1 + assert _run_guard(tmp_path, "notes.py", "# opened in ghidra to inspect the binary\n") == 1