forgex-cli 0.1.5__tar.gz → 0.1.6__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.
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/PKG-INFO +1 -1
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/__init__.py +1 -1
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/agents/architect.py +14 -2
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/agents/base.py +37 -8
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/models/registry.py +36 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/prompts/architect.md +1 -1
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/research/engine.py +20 -8
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/research/sources.py +1 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_builder_agent.py +30 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_registry.py +7 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_research.py +23 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/.github/workflows/ci.yml +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/.github/workflows/release.yml +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/.gitignore +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/CHANGELOG.md +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/LICENSE +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/README.md +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/SECURITY.md +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/docs/agents.md +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/docs/architecture.md +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/docs/configuration.md +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/docs/decisions.md +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/docs/providers.md +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/docs/quickstart.md +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/docs/sandbox.md +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/docs/security.md +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/examples/demo-project/README.md +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/examples/demo-project/pyproject.toml +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/examples/demo-project/src/demo/__init__.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/examples/demo-project/src/demo/calc.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/examples/demo-project/tests/test_calc.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/pyproject.toml +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/__main__.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/agents/__init__.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/agents/builder.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/agents/ensemble.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/agents/expert_coder.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/agents/supervisor.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/cli/__init__.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/cli/app.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/cli/build_views.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/cli/commands/__init__.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/cli/commands/build.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/cli/commands/checkpoint.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/cli/commands/config.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/cli/commands/context.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/cli/commands/diff.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/cli/commands/do_cmd.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/cli/commands/doctor.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/cli/commands/fix.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/cli/commands/init.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/cli/commands/logs.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/cli/commands/models.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/cli/commands/plan.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/cli/commands/review.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/cli/commands/rollback.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/cli/commands/run.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/cli/commands/status.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/cli/commands/supervise.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/cli/commands/task.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/cli/commands/tools.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/cli/commands/verify.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/cli/context_memory.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/cli/intent.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/cli/progress.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/cli/repl.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/cli/runtime.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/cli/session.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/cli/ui.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/config/__init__.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/config/loader.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/config/schema.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/context/__init__.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/context/chunker.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/context/dependency_graph.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/context/indexer.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/context/manager.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/context/retriever.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/models/__init__.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/models/base.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/models/capabilities.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/models/factory.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/models/groq.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/models/local.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/models/openai_compatible.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/models/router.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/orchestration/__init__.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/orchestration/checkpoints.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/orchestration/engine.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/orchestration/events.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/orchestration/lockfile.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/orchestration/retry.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/orchestration/state.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/orchestration/stores.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/orchestration/workflow.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/project.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/prompts/__init__.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/prompts/builder.md +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/prompts/expert_coder.md +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/prompts/reviewer.md +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/prompts/supervisor.md +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/research/__init__.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/research/citations.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/sandbox/__init__.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/sandbox/executor.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/sandbox/permissions.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/sandbox/policies.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/schemas/__init__.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/schemas/agent.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/schemas/checkpoint.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/schemas/error.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/schemas/event.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/schemas/plan.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/schemas/project.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/schemas/research.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/schemas/result.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/schemas/task.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/storage/__init__.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/storage/database.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/storage/migrations.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/storage/repositories.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/tools/__init__.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/tools/base.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/tools/dependencies.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/tools/filesystem.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/tools/git.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/tools/project.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/tools/search.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/tools/terminal.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/tools/testing.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/tools/verification.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/utils/__init__.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/utils/logging.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/utils/paths.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/utils/redact.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/__init__.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/conftest.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/__init__.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_agents_architect.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_checkpoints.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_cli.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_cli_build.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_cli_checkpoint.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_cli_do.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_cli_models.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_cli_plan.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_cli_review.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_cli_surface.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_cli_task.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_cli_tools.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_cli_verify.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_config.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_config_env.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_config_setup.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_context_agents.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_context_manager.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_context_memory.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_engine.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_ensemble.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_events.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_executor.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_expert_coder.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_fix.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_indexer.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_init.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_intent.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_large_repo.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_offline.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_packaging.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_paths.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_permissions.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_plan_schemas.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_policies.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_project.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_providers.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_recovery.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_redact.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_repl.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_retriever.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_retry.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_router.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_run_cli.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_runtime.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_security.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_session.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_state.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_storage.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_stores.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_supervisor.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_tasks.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_tools_dependencies.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_tools_filesystem.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_tools_git.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_tools_search.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_tools_terminal.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_verification.py +0 -0
- {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_workflow.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: forgex-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.6
|
|
4
4
|
Summary: FORGEX — multi-agent AI software engineering. Plan. Build. Refine. Verify.
|
|
5
5
|
Project-URL: Homepage, https://github.com/B-max12/Forgex
|
|
6
6
|
Project-URL: Repository, https://github.com/B-max12/Forgex
|
|
@@ -93,7 +93,11 @@ class ArchitectAgent(BaseAgent):
|
|
|
93
93
|
stage1_msg = (
|
|
94
94
|
f"<user_request>\n{user_request}\n</user_request>\n\n"
|
|
95
95
|
f"<repository>\n{repo_context}\n</repository>\n\n"
|
|
96
|
-
"Analyze this request and repository. State the goal, requirements, candidate libraries, and research topics
|
|
96
|
+
"Analyze this request and repository. State the goal, requirements, candidate libraries, and research topics.\n"
|
|
97
|
+
"IMPORTANT: candidate_libraries and research_topics MUST be concrete package identifiers with their registry prefix "
|
|
98
|
+
"(e.g. 'npm:react', 'npm:tailwindcss', 'pypi:flask', 'crates:serde', 'go:github.com/gin-gonic/gin'). "
|
|
99
|
+
"Do NOT output natural language conceptual phrases like 'Front-end frameworks' or 'Responsive web design' — "
|
|
100
|
+
"research checks official package registries (pypi, npm, crates, go) which only accept package names."
|
|
97
101
|
)
|
|
98
102
|
analysis: AnalysisResult = await self._generate_structured(
|
|
99
103
|
[Message(role="user", content=stage1_msg)], AnalysisResult
|
|
@@ -102,7 +106,15 @@ class ArchitectAgent(BaseAgent):
|
|
|
102
106
|
# Stage 2: Dependency research
|
|
103
107
|
if on_stage:
|
|
104
108
|
on_stage("researching")
|
|
105
|
-
|
|
109
|
+
# Combine candidate_libraries and research_topics, prioritizing candidates
|
|
110
|
+
raw_candidates = [lib.strip() for lib in analysis.candidate_libraries if lib and lib.strip()]
|
|
111
|
+
raw_topics = [t.strip() for t in analysis.research_topics if t and t.strip()]
|
|
112
|
+
combined_topics: list[str] = []
|
|
113
|
+
for item in raw_candidates + raw_topics:
|
|
114
|
+
if item not in combined_topics:
|
|
115
|
+
combined_topics.append(item)
|
|
116
|
+
|
|
117
|
+
notes = await self._research.research(combined_topics)
|
|
106
118
|
if self._events:
|
|
107
119
|
self._events.record(
|
|
108
120
|
EventType.RESEARCH_COMPLETED,
|
|
@@ -49,10 +49,10 @@ _SOFT_BUDGET_RATIO = 0.75
|
|
|
49
49
|
PROTOCOL_MODE_DOC = """
|
|
50
50
|
Tool protocol — this model does not support native tool calls.
|
|
51
51
|
To call a tool, respond with ONLY:
|
|
52
|
-
{"action": "tool", "tool": "<tool name>", "arguments": {<arguments>}}
|
|
52
|
+
{{"action": "tool", "tool": "<tool name>", "arguments": {{<arguments>}}}}
|
|
53
53
|
The tool result is returned to you as JSON in the next message.
|
|
54
54
|
To finish, respond with ONLY:
|
|
55
|
-
{"action": "done", "result": {<final report matching the schema>}}
|
|
55
|
+
{{"action": "done", "result": {{<final report matching the schema>}}}}
|
|
56
56
|
|
|
57
57
|
Available tools (JSON Schemas):
|
|
58
58
|
{tool_schemas}
|
|
@@ -358,10 +358,25 @@ class BaseAgent(ABC):
|
|
|
358
358
|
messages.append(Message(role="user", content="Please reply with a valid JSON action: {\"action\": \"tool\", ...} or {\"action\": \"done\", ...}."))
|
|
359
359
|
continue
|
|
360
360
|
|
|
361
|
-
|
|
361
|
+
if not isinstance(action_data, dict):
|
|
362
|
+
protocol_errors += 1
|
|
363
|
+
if protocol_errors >= MAX_PROTOCOL_ERRORS:
|
|
364
|
+
raise AgentOutputError(f"{self.role.value} protocol errors exceeded: expected JSON object")
|
|
365
|
+
messages.append(Message(role="assistant", content=raw))
|
|
366
|
+
messages.append(Message(role="user", content="Please reply with a valid JSON object: {\"action\": \"tool\", ...} or {\"action\": \"done\", ...}."))
|
|
367
|
+
continue
|
|
368
|
+
|
|
369
|
+
# Normalize keys (strip quotes, whitespace, lowercase)
|
|
370
|
+
normalized: dict[str, Any] = {}
|
|
371
|
+
for k, v in action_data.items():
|
|
372
|
+
normalized[str(k).strip().strip('"\'').lower()] = v
|
|
373
|
+
|
|
374
|
+
action = str(normalized.get("action") or "").strip().lower()
|
|
362
375
|
if action == "tool":
|
|
363
|
-
tool_name =
|
|
364
|
-
args =
|
|
376
|
+
tool_name = normalized.get("tool") or normalized.get("name")
|
|
377
|
+
args = normalized.get("arguments") or normalized.get("args") or {}
|
|
378
|
+
if not isinstance(args, dict):
|
|
379
|
+
args = {}
|
|
365
380
|
total_tool_calls += 1
|
|
366
381
|
t_res = await tools.execute(SystemToolCall(name=tool_name, arguments=args))
|
|
367
382
|
out_text = t_res.output or (f"Error: {t_res.error}" if t_res.error else "done")
|
|
@@ -370,9 +385,23 @@ class BaseAgent(ABC):
|
|
|
370
385
|
messages.append(Message(role="assistant", content=raw))
|
|
371
386
|
messages.append(Message(role="user", content=json.dumps({"tool_result": out_text})))
|
|
372
387
|
elif action == "done":
|
|
373
|
-
res_data =
|
|
374
|
-
|
|
375
|
-
|
|
388
|
+
res_data = normalized.get("result") or normalized.get("report")
|
|
389
|
+
if res_data is None or not isinstance(res_data, dict):
|
|
390
|
+
res_data = {k: v for k, v in normalized.items() if k not in ("action", "tool", "arguments", "args")}
|
|
391
|
+
try:
|
|
392
|
+
report = target_schema.model_validate(res_data)
|
|
393
|
+
return ToolLoopResult(report=report, rounds=rounds, tool_calls_count=total_tool_calls)
|
|
394
|
+
except (ValidationError, ValueError) as val_exc:
|
|
395
|
+
protocol_errors += 1
|
|
396
|
+
if protocol_errors >= MAX_PROTOCOL_ERRORS:
|
|
397
|
+
raise AgentOutputError(f"{self.role.value} schema validation failed: {val_exc}") from val_exc
|
|
398
|
+
messages.append(Message(role="assistant", content=raw))
|
|
399
|
+
messages.append(
|
|
400
|
+
Message(
|
|
401
|
+
role="user",
|
|
402
|
+
content=f"Your 'done' result did not match the JSON schema ({val_exc}). Please return valid JSON: {{\"action\": \"done\", \"result\": {{...}}}}.",
|
|
403
|
+
)
|
|
404
|
+
)
|
|
376
405
|
else:
|
|
377
406
|
protocol_errors += 1
|
|
378
407
|
messages.append(Message(role="assistant", content=raw))
|
|
@@ -52,6 +52,39 @@ _DEFAULT_CATALOG: dict[str, ModelCapabilities] = {
|
|
|
52
52
|
supports_streaming=False,
|
|
53
53
|
known=True,
|
|
54
54
|
),
|
|
55
|
+
# Meta Llama models (OpenRouter / Groq / Ollama)
|
|
56
|
+
"meta-llama/llama-3.3-70b-instruct": ModelCapabilities(
|
|
57
|
+
model="meta-llama/llama-3.3-70b-instruct",
|
|
58
|
+
context_window=131072,
|
|
59
|
+
supports_tools=True,
|
|
60
|
+
supports_json=True,
|
|
61
|
+
supports_streaming=True,
|
|
62
|
+
known=True,
|
|
63
|
+
),
|
|
64
|
+
"meta-llama/llama-3.1-70b-instruct": ModelCapabilities(
|
|
65
|
+
model="meta-llama/llama-3.1-70b-instruct",
|
|
66
|
+
context_window=131072,
|
|
67
|
+
supports_tools=True,
|
|
68
|
+
supports_json=True,
|
|
69
|
+
supports_streaming=True,
|
|
70
|
+
known=True,
|
|
71
|
+
),
|
|
72
|
+
"meta-llama/llama-3.1-8b-instruct": ModelCapabilities(
|
|
73
|
+
model="meta-llama/llama-3.1-8b-instruct",
|
|
74
|
+
context_window=131072,
|
|
75
|
+
supports_tools=True,
|
|
76
|
+
supports_json=True,
|
|
77
|
+
supports_streaming=True,
|
|
78
|
+
known=True,
|
|
79
|
+
),
|
|
80
|
+
"qwen/qwen-2.5-coder-32b-instruct": ModelCapabilities(
|
|
81
|
+
model="qwen/qwen-2.5-coder-32b-instruct",
|
|
82
|
+
context_window=131072,
|
|
83
|
+
supports_tools=True,
|
|
84
|
+
supports_json=True,
|
|
85
|
+
supports_streaming=True,
|
|
86
|
+
known=True,
|
|
87
|
+
),
|
|
55
88
|
}
|
|
56
89
|
|
|
57
90
|
|
|
@@ -65,8 +98,11 @@ class ModelRegistry:
|
|
|
65
98
|
self._catalog[k] = cap
|
|
66
99
|
|
|
67
100
|
def lookup(self, model: str) -> ModelCapabilities:
|
|
101
|
+
clean_model = model.split(":", 1)[-1] if ":" in model else model
|
|
68
102
|
if model in self._catalog:
|
|
69
103
|
return self._catalog[model]
|
|
104
|
+
if clean_model in self._catalog:
|
|
105
|
+
return self._catalog[clean_model]
|
|
70
106
|
# Conservative defaults for unknown models
|
|
71
107
|
return ModelCapabilities(
|
|
72
108
|
model=model,
|
|
@@ -9,7 +9,7 @@ Principles:
|
|
|
9
9
|
3. Respect the repository's existing languages, frameworks, conventions, and dependency choices. Prefer the smallest set of dependencies that satisfies the requirements. Avoid speculative features.
|
|
10
10
|
4. Task ids use the TASK-001 format. depends_on may only reference other task ids in the same plan. Dependency cycles are forbidden.
|
|
11
11
|
5. Include an explicit test strategy and project-level acceptance criteria.
|
|
12
|
-
6. For library choices, emit research topics so versions can be verified against official registries.
|
|
12
|
+
6. For library choices and dependencies, emit concrete package names as research topics and candidate libraries so versions can be verified against official registries. Always prefix with the registry: "pypi:fastapi", "npm:react", "npm:tailwindcss", "crates:serde", "go:github.com/gin-gonic/gin". Never emit generic concepts or sentences as research topics (e.g. do not emit "Front-end frameworks" or "Responsive web design"). Every topic must be a queryable package name.
|
|
13
13
|
|
|
14
14
|
Output rules:
|
|
15
15
|
Respond with ONLY a single JSON object that validates against the provided JSON Schema. No prose, no markdown, no code fences.
|
|
@@ -49,25 +49,37 @@ class ResearchEngine:
|
|
|
49
49
|
self._enabled = config.research.enabled if config is not None else True
|
|
50
50
|
|
|
51
51
|
async def research(self, topics: list[str]) -> list[ResearchNote]:
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
cleaned = [t.strip() for t in topics if t and t.strip()]
|
|
53
|
+
# Prioritize package-like identifiers over long descriptive sentences
|
|
54
|
+
package_like = [t for t in cleaned if " " not in t]
|
|
55
|
+
phrase_like = [t for t in cleaned if " " in t]
|
|
56
|
+
selected = (package_like + phrase_like)[:MAX_TOPICS]
|
|
57
|
+
|
|
58
|
+
if not selected:
|
|
54
59
|
return []
|
|
55
60
|
if not self._enabled:
|
|
56
61
|
return [
|
|
57
62
|
_unavailable(topic, "research disabled in configuration (offline mode)")
|
|
58
|
-
for topic in
|
|
63
|
+
for topic in selected
|
|
59
64
|
]
|
|
60
|
-
return [await self._lookup(topic) for topic in
|
|
65
|
+
return [await self._lookup(topic) for topic in selected]
|
|
61
66
|
|
|
62
67
|
async def _lookup(self, topic: str) -> ResearchNote:
|
|
63
|
-
|
|
68
|
+
clean = topic.strip()
|
|
69
|
+
ecosystem, sep, name = clean.partition(":")
|
|
64
70
|
if sep and ecosystem in self._by_ecosystem:
|
|
65
|
-
return await self._safe_lookup(self._by_ecosystem[ecosystem], name,
|
|
71
|
+
return await self._safe_lookup(self._by_ecosystem[ecosystem], name.strip(), clean)
|
|
72
|
+
# Plain multi-word phrases cannot be package names in any registry
|
|
73
|
+
if " " in clean:
|
|
74
|
+
return _unavailable(
|
|
75
|
+
clean,
|
|
76
|
+
"topic is a descriptive phrase; research queries package registries for package names",
|
|
77
|
+
)
|
|
66
78
|
for source in self._sources:
|
|
67
|
-
note = await self._safe_lookup(source,
|
|
79
|
+
note = await self._safe_lookup(source, clean, clean)
|
|
68
80
|
if note.available:
|
|
69
81
|
return note
|
|
70
|
-
return _unavailable(
|
|
82
|
+
return _unavailable(clean, "not found in any registry source (pypi, npm, crates, go)")
|
|
71
83
|
|
|
72
84
|
async def _safe_lookup(
|
|
73
85
|
self, source: RegistrySource, name: str, topic: str
|
|
@@ -41,6 +41,7 @@ class RegistrySource(ABC):
|
|
|
41
41
|
return bool(_VALID_NAME.match(name)) and ".." not in name
|
|
42
42
|
|
|
43
43
|
async def lookup(self, name: str, client: httpx.AsyncClient) -> ResearchNote:
|
|
44
|
+
name = name.strip()
|
|
44
45
|
if not self.validate_name(name):
|
|
45
46
|
return ResearchNote(
|
|
46
47
|
topic=name,
|
|
@@ -64,3 +64,33 @@ async def test_builder_executes_task_and_records_events(tmp_path):
|
|
|
64
64
|
res = await execute_task(stores=stores, task=task, config=load_config(ws), builder=builder)
|
|
65
65
|
assert res.status == "completed"
|
|
66
66
|
assert (ws / "demo.py").exists()
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
async def test_builder_json_protocol_mode(tmp_path):
|
|
70
|
+
ws = tmp_path / "ws"
|
|
71
|
+
ws.mkdir()
|
|
72
|
+
initialize_project(ws)
|
|
73
|
+
with open_stores(ws) as stores:
|
|
74
|
+
state = stores.state.load()
|
|
75
|
+
task = Task(id="TASK-001", description="create demo file", acceptance_criteria=["exists"])
|
|
76
|
+
stores.save_tasks([task], project_id=state.project_id)
|
|
77
|
+
|
|
78
|
+
router = FakeRouter([
|
|
79
|
+
ModelResponse(content=json.dumps({
|
|
80
|
+
"action": "tool",
|
|
81
|
+
"tool": "write_file",
|
|
82
|
+
"arguments": {"path": "proto.py", "content": "print('protocol')"},
|
|
83
|
+
})),
|
|
84
|
+
ModelResponse(content=json.dumps({
|
|
85
|
+
"action": "done",
|
|
86
|
+
"result": {"status": "completed", "summary": "done via protocol", "created_files": ["proto.py"], "modified_files": [], "tests_written": []},
|
|
87
|
+
})),
|
|
88
|
+
], supports_tools=False)
|
|
89
|
+
ctx = create_context(ws, load_config(ws), agent=AgentRole.BUILDER, events=stores.recorder)
|
|
90
|
+
tools = ToolExecutor(build_tool_registry(), permission_set_for(AgentRole.BUILDER), ctx)
|
|
91
|
+
builder = BuilderAgent(router=router, tools=tools, events=stores.recorder)
|
|
92
|
+
|
|
93
|
+
res = await execute_task(stores=stores, task=task, config=load_config(ws), builder=builder)
|
|
94
|
+
assert res.status == "completed"
|
|
95
|
+
assert (ws / "proto.py").exists()
|
|
96
|
+
|
|
@@ -22,3 +22,10 @@ def test_user_override_is_authoritative():
|
|
|
22
22
|
|
|
23
23
|
def test_catalog_lists_known_models():
|
|
24
24
|
assert any(c.model == "llama-3.3-70b-versatile" for c in ModelRegistry().catalog())
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def test_provider_prefix_lookup():
|
|
28
|
+
reg = ModelRegistry()
|
|
29
|
+
caps = reg.lookup("openrouter:meta-llama/llama-3.3-70b-instruct")
|
|
30
|
+
assert caps.known and caps.supports_tools
|
|
31
|
+
|
|
@@ -87,3 +87,26 @@ async def test_disabled_config_offline_notes():
|
|
|
87
87
|
assert not notes[0].available
|
|
88
88
|
assert "disabled" in notes[0].detail
|
|
89
89
|
|
|
90
|
+
|
|
91
|
+
async def test_descriptive_phrases_marked_unavailable_with_clear_reason():
|
|
92
|
+
engine = make_engine(registry_handler)
|
|
93
|
+
notes = await engine.research(["Front-end frameworks for web development"])
|
|
94
|
+
assert not notes[0].available
|
|
95
|
+
assert "descriptive phrase" in notes[0].detail
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
async def test_package_prioritization_over_phrases():
|
|
99
|
+
engine = make_engine(registry_handler)
|
|
100
|
+
topics = [
|
|
101
|
+
"Front-end frameworks for web development",
|
|
102
|
+
"pypi:fastapi",
|
|
103
|
+
"Responsive web design techniques",
|
|
104
|
+
"express",
|
|
105
|
+
]
|
|
106
|
+
notes = await engine.research(topics)
|
|
107
|
+
# The two package-like topics should be researched first
|
|
108
|
+
assert notes[0].topic == "fastapi" and notes[0].available
|
|
109
|
+
assert notes[1].topic == "express" and notes[1].available
|
|
110
|
+
assert not notes[2].available
|
|
111
|
+
|
|
112
|
+
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|