feat: QGo multi-agent system, copyright headers, logo, enhanced README#1
Conversation
Agent-Logs-Url: https://github.com/Rahulchaube1/QGo/sessions/18f64b0a-1c03-4cfb-a2cc-17e2eead44a9 Co-authored-by: Rahulchaube1 <157899057+Rahulchaube1@users.noreply.github.com>
Agent-Logs-Url: https://github.com/Rahulchaube1/QGo/sessions/34e96495-3546-445a-8818-3b0848e42b80 Co-authored-by: Rahulchaube1 <157899057+Rahulchaube1@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR turns QGo into a more complete terminal-based AI coding assistant by adding a multi-agent orchestration layer, expanding core coding/editing utilities (repo map, diff/whole-file coders, token counting, web scraping, UI/REPL), and updating packaging/docs/branding with a logo and a much richer README.
Changes:
- Added multi-agent system (
qgo/agents/*) with an orchestrator coordinating specialist agents and report compilation. - Implemented core assistant building blocks: coders (editblock/udiff/whole/architect), repo mapping, git integration, terminal UI + REPL commands, token counting, and web scraping.
- Added CI + packaging metadata, extensive pytest coverage, and updated README/logo/branding.
Reviewed changes
Copilot reviewed 42 out of 45 changed files in this pull request and generated 11 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/test_repo_map.py | Adds symbol-extraction and repo-map behavior tests |
| tests/test_llm.py | Tests model metadata/aliasing and token counter utilities |
| tests/test_file_utils.py | Tests file utilities (read/write/diff/language detection) |
| tests/test_editblock_coder.py | Tests SEARCH/REPLACE parsing and edit application |
| tests/test_config.py | Tests config loading/saving via YAML + env vars |
| tests/test_agents.py | Tests multi-agent core types, agents, and orchestrator behavior |
| tests/init.py | Defines tests package marker/docstring |
| requirements.txt | Adds runtime + dev dependency list |
| qgo/utils/web_scraper.py | Adds URL fetching and HTML-to-text/Markdown extraction |
| qgo/utils/token_counter.py | Adds token counting + truncation helpers |
| qgo/utils/file_utils.py | Adds filesystem helpers and unified diff generation |
| qgo/utils/init.py | Exposes utility functions as public imports |
| qgo/ui/terminal.py | Implements Rich-based terminal output + streaming |
| qgo/ui/repl.py | Adds prompt_toolkit REPL with history and completion |
| qgo/ui/commands.py | Adds slash commands for context/files/git/web/run/etc. |
| qgo/ui/init.py | Exposes UI entry points |
| qgo/repo/repo_map.py | Implements regex-based repository symbol mapping |
| qgo/repo/git_repo.py | Implements git operations via subprocess |
| qgo/repo/file_watcher.py | Adds watchdog-based file watching (with stub fallback) |
| qgo/repo/init.py | Exposes repo helpers |
| qgo/models.py | Adds core dataclasses/enums for messages/files/edits/session |
| qgo/main.py | Adds Click CLI entry point with config + REPL/one-shot flow |
| qgo/llm/streaming.py | Adds streaming helpers for litellm responses |
| qgo/llm/model_info.py | Adds model metadata registry, aliasing, and formatting |
| qgo/llm/litellm_provider.py | Implements BaseLLM provider on top of litellm |
| qgo/llm/base.py | Defines BaseLLM interface + capability properties |
| qgo/llm/init.py | Exposes LLM provider + model info helpers |
| qgo/coders/whole_coder.py | Whole-file editing parser + apply logic |
| qgo/coders/udiff_coder.py | Unified-diff parsing + application logic |
| qgo/coders/editblock_coder.py | SEARCH/REPLACE parsing + fuzzy replacement logic |
| qgo/coders/base_coder.py | Core chat loop, prompt construction, auto-commit/lint hooks |
| qgo/coders/architect_coder.py | Two-pass plan+implement “architect” mode |
| qgo/coders/init.py | Coder factory + exports |
| qgo/agents/specialist_agents.py | Defines 8 specialist agents and their system prompts |
| qgo/agents/orchestrator.py | Implements planner-driven execution + message bus + report |
| qgo/agents/base_agent.py | Defines base agent, message/result dataclasses, and run logic |
| qgo/agents/init.py | Exposes multi-agent system public API |
| qgo/main.py | Enables python -m qgo entry |
| qgo/init.py | Package metadata/version |
| pyproject.toml | Packaging metadata, dependencies, and tool configs |
| assets/logo.svg | Adds project logo asset |
| README.md | Expands documentation: features, architecture, usage, commands |
| .gitignore | Adds standard Python + QGo-specific ignores |
| .github/workflows/ci.yml | Adds CI workflow for lint/test/build |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| except ImportError: | ||
| # tiktoken not installed — rough estimate: ~4 characters per token | ||
| return max(1, len(text) // 4) | ||
| except Exception: | ||
| # Network error downloading encoding files, or other runtime error |
There was a problem hiding this comment.
Fallback token estimation uses max(1, len(text)//4), so count_tokens("") becomes 1 when tiktoken isn’t installed, while tiktoken returns 0. Return 0 for empty input in the fallback path to keep behavior consistent.
| except ImportError: | ||
| return _fetch_plain(url, timeout) | ||
| except Exception as exc: | ||
| return f"[Error fetching {url}: {exc}]" | ||
|
|
There was a problem hiding this comment.
On exceptions, fetch_url returns a non-empty error string ([Error fetching ...]), which contradicts the docstring contract that failures return an empty string. Returning "" (and optionally logging via io) would make callers easier to reason about.
| "click>=8.1", | ||
| "rich>=13.0", | ||
| "prompt_toolkit>=3.0", | ||
| "gitpython>=3.1", |
There was a problem hiding this comment.
gitpython>=3.1 is listed as a core dependency but there are no GitPython imports/usages in qgo/ (git integration is via subprocess). Consider removing it from dependencies to avoid pulling an unused heavy dependency unless it’s needed in this PR.
| "gitpython>=3.1", |
| **kwargs, | ||
| ) -> None: | ||
| self._model = resolve_model(model) | ||
| self._api_key = api_key or os.environ.get("OPENAI_API_KEY") |
There was a problem hiding this comment.
self._api_key defaults to OPENAI_API_KEY even when the chosen model/provider is not OpenAI, which can prevent provider-specific env var detection and send the wrong key. Prefer self._api_key = api_key and only fall back to provider-specific env vars based on get_model_info(self._model).
| self._api_key = api_key or os.environ.get("OPENAI_API_KEY") | |
| self._api_key = api_key |
| # ```python path/to/file.py\n...``` | ||
| # path/to/file.py\n```python\n...``` | ||
| _FENCE_RE = re.compile( | ||
| r"```[\w]*\s*\n(.*?)```", |
There was a problem hiding this comment.
The comment says the parser supports fences like python path/to/file.py\n..., but _FENCE_RE requires a newline immediately after the language token (it won’t match a filename on the same fence line). Either widen the regex to accept an optional filename after the language or update the documented supported formats.
| r"```[\w]*\s*\n(.*?)```", | |
| r"```[\w]*?(?:\s+[^\n`]+)?\s*\n(.*?)```", |
| try: | ||
| path.parent.mkdir(parents=True, exist_ok=True) | ||
| path.write_text(block.updated, encoding="utf-8") | ||
| action = "Created" if block.is_new_file or not path.exists() else "Updated" |
There was a problem hiding this comment.
action is computed after writing the file, so not path.exists() will always be false here. Also, EditBlock(original="") marks every whole-file block as is_new_file=True, so updates will be logged as "Created". Capture existed_before = path.exists() before writing and base the action on that (or set is_new_file explicitly for whole-file edits).
| try: | |
| path.parent.mkdir(parents=True, exist_ok=True) | |
| path.write_text(block.updated, encoding="utf-8") | |
| action = "Created" if block.is_new_file or not path.exists() else "Updated" | |
| existed_before = path.exists() | |
| try: | |
| path.parent.mkdir(parents=True, exist_ok=True) | |
| path.write_text(block.updated, encoding="utf-8") | |
| # Determine action based on whether the file existed before writing | |
| block.is_new_file = not existed_before | |
| action = "Created" if not existed_before else "Updated" |
| if stream and self.io: | ||
| iterator = self.llm.complete(messages, stream=True) | ||
| if hasattr(iterator, "__iter__"): | ||
| response = self.io.stream_response(iterator) | ||
| else: |
There was a problem hiding this comment.
hasattr(iterator, "__iter__") will be true for strings, so if an LLM provider ever returns a plain string while stream=True, it will be streamed character-by-character. Consider checking isinstance(iterator, str) (or collections.abc.Iterator) before calling io.stream_response.
| Returns: | ||
| Cleaned text content suitable for LLM context. | ||
| Empty string if fetching fails. | ||
| """ |
There was a problem hiding this comment.
Docstring promises "Empty string if fetching fails", but the function later returns a formatted error string on exceptions. Either adjust the docstring or change the error-path return value to "" to match the contract.
| @click.option("--model", "-m", default=None, help="LLM model to use (e.g. gpt-4o, claude-3-7-sonnet-20250219)") | ||
| @click.option("--api-key", default=None, envvar="QGO_API_KEY", help="LLM API key") | ||
| @click.option("--api-base", default=None, envvar="QGO_API_BASE", help="Custom API base URL (for Ollama, vLLM, etc.)") | ||
| @click.option("--edit-format", "-f", |
There was a problem hiding this comment.
-f is registered for --edit-format here, but it’s also used for --file later in the same command, which will cause a Click option conflict and prevent the CLI from starting. Use distinct short flags (or remove one).
| @click.option("--edit-format", "-f", | |
| @click.option("--edit-format", "-e", |
| if stream and self.io and hasattr(self.io, "stream_response"): | ||
| iterator = self.llm.complete(messages, stream=True) | ||
| # Consume iterator | ||
| if hasattr(iterator, "__iter__"): | ||
| return self.io.stream_response(iterator) |
There was a problem hiding this comment.
Same streaming detection issue as in BaseCoder._send: hasattr(iterator, "__iter__") is true for strings, which can cause character-by-character streaming if llm.complete(..., stream=True) returns a string. Prefer an explicit iterator check and guard against str/bytes.
Builds out QGo into a full AI coding assistant with a multi-agent orchestration layer, Rahul Chaube copyright attribution across the codebase, an SVG logo, and an updated README.
Multi-Agent System (
qgo/agents/)Eight specialist agents coordinated by
AgentOrchestrator:PlannerAgentCoderAgentReviewerAgentTesterAgentDebuggerAgentDocWriterAgentSecurityAgentRefactorAgentAgentOrchestrator.run()drives the pipeline: planner emits a JSON plan, agents execute sequentially with accumulated context passed forward, results compiled into a Markdown report.Message-bus broadcasts (
_broadcast) let agents receive outputs from peers viaAgentMessage.Copyright & Branding
Copyright (c) 2024 Rahul Chaube. All Rights Reserved.headers added to all 28 source filesassets/logo.svgTests
61 new tests in
tests/test_agents.pycoveringAgentMessage,AgentResult,BaseAgent, all 8 specialist agents (parametrised), andAgentOrchestrator(plan parsing, routing,max_agentscap, error paths, report compilation).