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.
Files changed (197) hide show
  1. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/PKG-INFO +1 -1
  2. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/__init__.py +1 -1
  3. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/agents/architect.py +14 -2
  4. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/agents/base.py +37 -8
  5. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/models/registry.py +36 -0
  6. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/prompts/architect.md +1 -1
  7. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/research/engine.py +20 -8
  8. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/research/sources.py +1 -0
  9. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_builder_agent.py +30 -0
  10. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_registry.py +7 -0
  11. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_research.py +23 -0
  12. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/.github/workflows/ci.yml +0 -0
  13. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/.github/workflows/release.yml +0 -0
  14. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/.gitignore +0 -0
  15. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/CHANGELOG.md +0 -0
  16. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/LICENSE +0 -0
  17. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/README.md +0 -0
  18. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/SECURITY.md +0 -0
  19. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/docs/agents.md +0 -0
  20. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/docs/architecture.md +0 -0
  21. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/docs/configuration.md +0 -0
  22. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/docs/decisions.md +0 -0
  23. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/docs/providers.md +0 -0
  24. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/docs/quickstart.md +0 -0
  25. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/docs/sandbox.md +0 -0
  26. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/docs/security.md +0 -0
  27. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/examples/demo-project/README.md +0 -0
  28. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/examples/demo-project/pyproject.toml +0 -0
  29. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/examples/demo-project/src/demo/__init__.py +0 -0
  30. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/examples/demo-project/src/demo/calc.py +0 -0
  31. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/examples/demo-project/tests/test_calc.py +0 -0
  32. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/pyproject.toml +0 -0
  33. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/__main__.py +0 -0
  34. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/agents/__init__.py +0 -0
  35. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/agents/builder.py +0 -0
  36. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/agents/ensemble.py +0 -0
  37. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/agents/expert_coder.py +0 -0
  38. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/agents/supervisor.py +0 -0
  39. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/cli/__init__.py +0 -0
  40. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/cli/app.py +0 -0
  41. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/cli/build_views.py +0 -0
  42. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/cli/commands/__init__.py +0 -0
  43. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/cli/commands/build.py +0 -0
  44. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/cli/commands/checkpoint.py +0 -0
  45. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/cli/commands/config.py +0 -0
  46. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/cli/commands/context.py +0 -0
  47. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/cli/commands/diff.py +0 -0
  48. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/cli/commands/do_cmd.py +0 -0
  49. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/cli/commands/doctor.py +0 -0
  50. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/cli/commands/fix.py +0 -0
  51. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/cli/commands/init.py +0 -0
  52. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/cli/commands/logs.py +0 -0
  53. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/cli/commands/models.py +0 -0
  54. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/cli/commands/plan.py +0 -0
  55. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/cli/commands/review.py +0 -0
  56. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/cli/commands/rollback.py +0 -0
  57. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/cli/commands/run.py +0 -0
  58. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/cli/commands/status.py +0 -0
  59. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/cli/commands/supervise.py +0 -0
  60. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/cli/commands/task.py +0 -0
  61. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/cli/commands/tools.py +0 -0
  62. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/cli/commands/verify.py +0 -0
  63. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/cli/context_memory.py +0 -0
  64. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/cli/intent.py +0 -0
  65. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/cli/progress.py +0 -0
  66. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/cli/repl.py +0 -0
  67. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/cli/runtime.py +0 -0
  68. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/cli/session.py +0 -0
  69. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/cli/ui.py +0 -0
  70. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/config/__init__.py +0 -0
  71. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/config/loader.py +0 -0
  72. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/config/schema.py +0 -0
  73. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/context/__init__.py +0 -0
  74. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/context/chunker.py +0 -0
  75. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/context/dependency_graph.py +0 -0
  76. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/context/indexer.py +0 -0
  77. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/context/manager.py +0 -0
  78. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/context/retriever.py +0 -0
  79. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/models/__init__.py +0 -0
  80. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/models/base.py +0 -0
  81. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/models/capabilities.py +0 -0
  82. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/models/factory.py +0 -0
  83. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/models/groq.py +0 -0
  84. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/models/local.py +0 -0
  85. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/models/openai_compatible.py +0 -0
  86. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/models/router.py +0 -0
  87. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/orchestration/__init__.py +0 -0
  88. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/orchestration/checkpoints.py +0 -0
  89. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/orchestration/engine.py +0 -0
  90. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/orchestration/events.py +0 -0
  91. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/orchestration/lockfile.py +0 -0
  92. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/orchestration/retry.py +0 -0
  93. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/orchestration/state.py +0 -0
  94. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/orchestration/stores.py +0 -0
  95. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/orchestration/workflow.py +0 -0
  96. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/project.py +0 -0
  97. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/prompts/__init__.py +0 -0
  98. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/prompts/builder.md +0 -0
  99. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/prompts/expert_coder.md +0 -0
  100. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/prompts/reviewer.md +0 -0
  101. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/prompts/supervisor.md +0 -0
  102. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/research/__init__.py +0 -0
  103. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/research/citations.py +0 -0
  104. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/sandbox/__init__.py +0 -0
  105. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/sandbox/executor.py +0 -0
  106. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/sandbox/permissions.py +0 -0
  107. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/sandbox/policies.py +0 -0
  108. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/schemas/__init__.py +0 -0
  109. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/schemas/agent.py +0 -0
  110. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/schemas/checkpoint.py +0 -0
  111. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/schemas/error.py +0 -0
  112. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/schemas/event.py +0 -0
  113. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/schemas/plan.py +0 -0
  114. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/schemas/project.py +0 -0
  115. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/schemas/research.py +0 -0
  116. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/schemas/result.py +0 -0
  117. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/schemas/task.py +0 -0
  118. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/storage/__init__.py +0 -0
  119. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/storage/database.py +0 -0
  120. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/storage/migrations.py +0 -0
  121. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/storage/repositories.py +0 -0
  122. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/tools/__init__.py +0 -0
  123. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/tools/base.py +0 -0
  124. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/tools/dependencies.py +0 -0
  125. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/tools/filesystem.py +0 -0
  126. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/tools/git.py +0 -0
  127. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/tools/project.py +0 -0
  128. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/tools/search.py +0 -0
  129. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/tools/terminal.py +0 -0
  130. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/tools/testing.py +0 -0
  131. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/tools/verification.py +0 -0
  132. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/utils/__init__.py +0 -0
  133. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/utils/logging.py +0 -0
  134. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/utils/paths.py +0 -0
  135. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/src/forgex/utils/redact.py +0 -0
  136. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/__init__.py +0 -0
  137. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/conftest.py +0 -0
  138. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/__init__.py +0 -0
  139. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_agents_architect.py +0 -0
  140. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_checkpoints.py +0 -0
  141. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_cli.py +0 -0
  142. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_cli_build.py +0 -0
  143. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_cli_checkpoint.py +0 -0
  144. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_cli_do.py +0 -0
  145. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_cli_models.py +0 -0
  146. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_cli_plan.py +0 -0
  147. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_cli_review.py +0 -0
  148. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_cli_surface.py +0 -0
  149. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_cli_task.py +0 -0
  150. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_cli_tools.py +0 -0
  151. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_cli_verify.py +0 -0
  152. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_config.py +0 -0
  153. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_config_env.py +0 -0
  154. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_config_setup.py +0 -0
  155. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_context_agents.py +0 -0
  156. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_context_manager.py +0 -0
  157. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_context_memory.py +0 -0
  158. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_engine.py +0 -0
  159. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_ensemble.py +0 -0
  160. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_events.py +0 -0
  161. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_executor.py +0 -0
  162. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_expert_coder.py +0 -0
  163. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_fix.py +0 -0
  164. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_indexer.py +0 -0
  165. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_init.py +0 -0
  166. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_intent.py +0 -0
  167. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_large_repo.py +0 -0
  168. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_offline.py +0 -0
  169. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_packaging.py +0 -0
  170. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_paths.py +0 -0
  171. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_permissions.py +0 -0
  172. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_plan_schemas.py +0 -0
  173. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_policies.py +0 -0
  174. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_project.py +0 -0
  175. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_providers.py +0 -0
  176. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_recovery.py +0 -0
  177. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_redact.py +0 -0
  178. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_repl.py +0 -0
  179. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_retriever.py +0 -0
  180. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_retry.py +0 -0
  181. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_router.py +0 -0
  182. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_run_cli.py +0 -0
  183. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_runtime.py +0 -0
  184. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_security.py +0 -0
  185. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_session.py +0 -0
  186. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_state.py +0 -0
  187. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_storage.py +0 -0
  188. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_stores.py +0 -0
  189. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_supervisor.py +0 -0
  190. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_tasks.py +0 -0
  191. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_tools_dependencies.py +0 -0
  192. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_tools_filesystem.py +0 -0
  193. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_tools_git.py +0 -0
  194. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_tools_search.py +0 -0
  195. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_tools_terminal.py +0 -0
  196. {forgex_cli-0.1.5 → forgex_cli-0.1.6}/tests/unit/test_verification.py +0 -0
  197. {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.5
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
@@ -1,3 +1,3 @@
1
1
  """FORGEX — Plan. Build. Refine. Verify."""
2
2
 
3
- __version__ = "0.1.5"
3
+ __version__ = "0.1.6"
@@ -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
- notes = await self._research.research(analysis.research_topics)
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
- action = action_data.get("action")
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 = action_data.get("tool")
364
- args = action_data.get("arguments") or {}
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 = action_data.get("result") or {}
374
- report = target_schema.model_validate(res_data)
375
- return ToolLoopResult(report=report, rounds=rounds, tool_calls_count=total_tool_calls)
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. Prefix with the registry when you know it: "pypi:fastapi", "npm:express", "crates:serde", "go:github.com/gin-gonic/gin".
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
- topics = topics[:MAX_TOPICS]
53
- if not topics:
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 topics
63
+ for topic in selected
59
64
  ]
60
- return [await self._lookup(topic) for topic in topics]
65
+ return [await self._lookup(topic) for topic in selected]
61
66
 
62
67
  async def _lookup(self, topic: str) -> ResearchNote:
63
- ecosystem, sep, name = topic.partition(":")
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, topic)
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, topic, topic)
79
+ note = await self._safe_lookup(source, clean, clean)
68
80
  if note.available:
69
81
  return note
70
- return _unavailable(topic, "not found in any registry source (pypi, npm, crates, go)")
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