forgex-cli 0.1.5__tar.gz → 0.1.7__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.7}/PKG-INFO +1 -1
  2. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/__init__.py +1 -1
  3. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/agents/architect.py +14 -2
  4. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/agents/base.py +43 -8
  5. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/cli/repl.py +3 -3
  6. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/models/registry.py +36 -0
  7. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/prompts/architect.md +1 -1
  8. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/research/engine.py +20 -8
  9. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/research/sources.py +1 -0
  10. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/tests/unit/test_builder_agent.py +34 -1
  11. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/tests/unit/test_registry.py +7 -0
  12. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/tests/unit/test_research.py +23 -0
  13. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/.github/workflows/ci.yml +0 -0
  14. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/.github/workflows/release.yml +0 -0
  15. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/.gitignore +0 -0
  16. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/CHANGELOG.md +0 -0
  17. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/LICENSE +0 -0
  18. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/README.md +0 -0
  19. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/SECURITY.md +0 -0
  20. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/docs/agents.md +0 -0
  21. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/docs/architecture.md +0 -0
  22. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/docs/configuration.md +0 -0
  23. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/docs/decisions.md +0 -0
  24. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/docs/providers.md +0 -0
  25. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/docs/quickstart.md +0 -0
  26. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/docs/sandbox.md +0 -0
  27. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/docs/security.md +0 -0
  28. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/examples/demo-project/README.md +0 -0
  29. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/examples/demo-project/pyproject.toml +0 -0
  30. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/examples/demo-project/src/demo/__init__.py +0 -0
  31. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/examples/demo-project/src/demo/calc.py +0 -0
  32. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/examples/demo-project/tests/test_calc.py +0 -0
  33. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/pyproject.toml +0 -0
  34. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/__main__.py +0 -0
  35. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/agents/__init__.py +0 -0
  36. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/agents/builder.py +0 -0
  37. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/agents/ensemble.py +0 -0
  38. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/agents/expert_coder.py +0 -0
  39. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/agents/supervisor.py +0 -0
  40. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/cli/__init__.py +0 -0
  41. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/cli/app.py +0 -0
  42. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/cli/build_views.py +0 -0
  43. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/cli/commands/__init__.py +0 -0
  44. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/cli/commands/build.py +0 -0
  45. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/cli/commands/checkpoint.py +0 -0
  46. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/cli/commands/config.py +0 -0
  47. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/cli/commands/context.py +0 -0
  48. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/cli/commands/diff.py +0 -0
  49. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/cli/commands/do_cmd.py +0 -0
  50. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/cli/commands/doctor.py +0 -0
  51. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/cli/commands/fix.py +0 -0
  52. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/cli/commands/init.py +0 -0
  53. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/cli/commands/logs.py +0 -0
  54. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/cli/commands/models.py +0 -0
  55. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/cli/commands/plan.py +0 -0
  56. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/cli/commands/review.py +0 -0
  57. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/cli/commands/rollback.py +0 -0
  58. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/cli/commands/run.py +0 -0
  59. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/cli/commands/status.py +0 -0
  60. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/cli/commands/supervise.py +0 -0
  61. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/cli/commands/task.py +0 -0
  62. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/cli/commands/tools.py +0 -0
  63. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/cli/commands/verify.py +0 -0
  64. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/cli/context_memory.py +0 -0
  65. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/cli/intent.py +0 -0
  66. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/cli/progress.py +0 -0
  67. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/cli/runtime.py +0 -0
  68. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/cli/session.py +0 -0
  69. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/cli/ui.py +0 -0
  70. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/config/__init__.py +0 -0
  71. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/config/loader.py +0 -0
  72. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/config/schema.py +0 -0
  73. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/context/__init__.py +0 -0
  74. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/context/chunker.py +0 -0
  75. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/context/dependency_graph.py +0 -0
  76. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/context/indexer.py +0 -0
  77. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/context/manager.py +0 -0
  78. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/context/retriever.py +0 -0
  79. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/models/__init__.py +0 -0
  80. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/models/base.py +0 -0
  81. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/models/capabilities.py +0 -0
  82. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/models/factory.py +0 -0
  83. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/models/groq.py +0 -0
  84. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/models/local.py +0 -0
  85. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/models/openai_compatible.py +0 -0
  86. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/models/router.py +0 -0
  87. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/orchestration/__init__.py +0 -0
  88. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/orchestration/checkpoints.py +0 -0
  89. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/orchestration/engine.py +0 -0
  90. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/orchestration/events.py +0 -0
  91. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/orchestration/lockfile.py +0 -0
  92. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/orchestration/retry.py +0 -0
  93. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/orchestration/state.py +0 -0
  94. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/orchestration/stores.py +0 -0
  95. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/orchestration/workflow.py +0 -0
  96. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/project.py +0 -0
  97. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/prompts/__init__.py +0 -0
  98. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/prompts/builder.md +0 -0
  99. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/prompts/expert_coder.md +0 -0
  100. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/prompts/reviewer.md +0 -0
  101. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/prompts/supervisor.md +0 -0
  102. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/research/__init__.py +0 -0
  103. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/research/citations.py +0 -0
  104. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/sandbox/__init__.py +0 -0
  105. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/sandbox/executor.py +0 -0
  106. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/sandbox/permissions.py +0 -0
  107. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/sandbox/policies.py +0 -0
  108. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/schemas/__init__.py +0 -0
  109. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/schemas/agent.py +0 -0
  110. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/schemas/checkpoint.py +0 -0
  111. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/schemas/error.py +0 -0
  112. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/schemas/event.py +0 -0
  113. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/schemas/plan.py +0 -0
  114. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/schemas/project.py +0 -0
  115. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/schemas/research.py +0 -0
  116. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/schemas/result.py +0 -0
  117. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/schemas/task.py +0 -0
  118. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/storage/__init__.py +0 -0
  119. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/storage/database.py +0 -0
  120. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/storage/migrations.py +0 -0
  121. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/storage/repositories.py +0 -0
  122. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/tools/__init__.py +0 -0
  123. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/tools/base.py +0 -0
  124. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/tools/dependencies.py +0 -0
  125. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/tools/filesystem.py +0 -0
  126. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/tools/git.py +0 -0
  127. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/tools/project.py +0 -0
  128. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/tools/search.py +0 -0
  129. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/tools/terminal.py +0 -0
  130. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/tools/testing.py +0 -0
  131. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/tools/verification.py +0 -0
  132. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/utils/__init__.py +0 -0
  133. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/utils/logging.py +0 -0
  134. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/utils/paths.py +0 -0
  135. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/src/forgex/utils/redact.py +0 -0
  136. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/tests/__init__.py +0 -0
  137. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/tests/conftest.py +0 -0
  138. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/tests/unit/__init__.py +0 -0
  139. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/tests/unit/test_agents_architect.py +0 -0
  140. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/tests/unit/test_checkpoints.py +0 -0
  141. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/tests/unit/test_cli.py +0 -0
  142. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/tests/unit/test_cli_build.py +0 -0
  143. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/tests/unit/test_cli_checkpoint.py +0 -0
  144. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/tests/unit/test_cli_do.py +0 -0
  145. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/tests/unit/test_cli_models.py +0 -0
  146. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/tests/unit/test_cli_plan.py +0 -0
  147. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/tests/unit/test_cli_review.py +0 -0
  148. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/tests/unit/test_cli_surface.py +0 -0
  149. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/tests/unit/test_cli_task.py +0 -0
  150. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/tests/unit/test_cli_tools.py +0 -0
  151. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/tests/unit/test_cli_verify.py +0 -0
  152. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/tests/unit/test_config.py +0 -0
  153. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/tests/unit/test_config_env.py +0 -0
  154. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/tests/unit/test_config_setup.py +0 -0
  155. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/tests/unit/test_context_agents.py +0 -0
  156. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/tests/unit/test_context_manager.py +0 -0
  157. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/tests/unit/test_context_memory.py +0 -0
  158. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/tests/unit/test_engine.py +0 -0
  159. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/tests/unit/test_ensemble.py +0 -0
  160. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/tests/unit/test_events.py +0 -0
  161. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/tests/unit/test_executor.py +0 -0
  162. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/tests/unit/test_expert_coder.py +0 -0
  163. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/tests/unit/test_fix.py +0 -0
  164. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/tests/unit/test_indexer.py +0 -0
  165. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/tests/unit/test_init.py +0 -0
  166. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/tests/unit/test_intent.py +0 -0
  167. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/tests/unit/test_large_repo.py +0 -0
  168. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/tests/unit/test_offline.py +0 -0
  169. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/tests/unit/test_packaging.py +0 -0
  170. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/tests/unit/test_paths.py +0 -0
  171. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/tests/unit/test_permissions.py +0 -0
  172. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/tests/unit/test_plan_schemas.py +0 -0
  173. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/tests/unit/test_policies.py +0 -0
  174. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/tests/unit/test_project.py +0 -0
  175. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/tests/unit/test_providers.py +0 -0
  176. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/tests/unit/test_recovery.py +0 -0
  177. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/tests/unit/test_redact.py +0 -0
  178. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/tests/unit/test_repl.py +0 -0
  179. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/tests/unit/test_retriever.py +0 -0
  180. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/tests/unit/test_retry.py +0 -0
  181. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/tests/unit/test_router.py +0 -0
  182. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/tests/unit/test_run_cli.py +0 -0
  183. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/tests/unit/test_runtime.py +0 -0
  184. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/tests/unit/test_security.py +0 -0
  185. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/tests/unit/test_session.py +0 -0
  186. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/tests/unit/test_state.py +0 -0
  187. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/tests/unit/test_storage.py +0 -0
  188. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/tests/unit/test_stores.py +0 -0
  189. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/tests/unit/test_supervisor.py +0 -0
  190. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/tests/unit/test_tasks.py +0 -0
  191. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/tests/unit/test_tools_dependencies.py +0 -0
  192. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/tests/unit/test_tools_filesystem.py +0 -0
  193. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/tests/unit/test_tools_git.py +0 -0
  194. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/tests/unit/test_tools_search.py +0 -0
  195. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/tests/unit/test_tools_terminal.py +0 -0
  196. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/tests/unit/test_verification.py +0 -0
  197. {forgex_cli-0.1.5 → forgex_cli-0.1.7}/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.7
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.7"
@@ -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,31 @@ 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
+ raw_tool = normalized.get("tool") or normalized.get("name")
377
+ if not raw_tool or not isinstance(raw_tool, str):
378
+ protocol_errors += 1
379
+ messages.append(Message(role="assistant", content=raw))
380
+ messages.append(Message(role="user", content="Tool name missing or invalid. Use {\"action\": \"tool\", \"tool\": \"<name>\", \"arguments\": {...}}."))
381
+ continue
382
+ tool_name: str = raw_tool.strip()
383
+ args = normalized.get("arguments") or normalized.get("args") or {}
384
+ if not isinstance(args, dict):
385
+ args = {}
365
386
  total_tool_calls += 1
366
387
  t_res = await tools.execute(SystemToolCall(name=tool_name, arguments=args))
367
388
  out_text = t_res.output or (f"Error: {t_res.error}" if t_res.error else "done")
@@ -370,9 +391,23 @@ class BaseAgent(ABC):
370
391
  messages.append(Message(role="assistant", content=raw))
371
392
  messages.append(Message(role="user", content=json.dumps({"tool_result": out_text})))
372
393
  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)
394
+ res_data = normalized.get("result") or normalized.get("report")
395
+ if res_data is None or not isinstance(res_data, dict):
396
+ res_data = {k: v for k, v in normalized.items() if k not in ("action", "tool", "arguments", "args")}
397
+ try:
398
+ report = target_schema.model_validate(res_data)
399
+ return ToolLoopResult(report=report, rounds=rounds, tool_calls_count=total_tool_calls)
400
+ except (ValidationError, ValueError) as val_exc:
401
+ protocol_errors += 1
402
+ if protocol_errors >= MAX_PROTOCOL_ERRORS:
403
+ raise AgentOutputError(f"{self.role.value} schema validation failed: {val_exc}") from val_exc
404
+ messages.append(Message(role="assistant", content=raw))
405
+ messages.append(
406
+ Message(
407
+ role="user",
408
+ content=f"Your 'done' result did not match the JSON schema ({val_exc}). Please return valid JSON: {{\"action\": \"done\", \"result\": {{...}}}}.",
409
+ )
410
+ )
376
411
  else:
377
412
  protocol_errors += 1
378
413
  messages.append(Message(role="assistant", content=raw))
@@ -9,7 +9,6 @@ import sys
9
9
  from pathlib import Path
10
10
  from typing import Any
11
11
 
12
- import typer
13
12
  from prompt_toolkit import PromptSession
14
13
  from prompt_toolkit.completion import WordCompleter
15
14
  from prompt_toolkit.formatted_text import HTML
@@ -169,11 +168,12 @@ class ForgexREPL:
169
168
  def _run_setup_wizard(self) -> None:
170
169
  """Run the setup wizard directly (not through CliRunner) so it can read real stdin."""
171
170
  import click
171
+ import typer
172
172
  from forgex.cli.commands.config import setup_command
173
173
 
174
174
  try:
175
- # Create a plain click Context — setup_command only needs ctx.obj for ui_from()
176
- ctx = click.Context(click.Command("setup"))
175
+ # Create a typer Context — setup_command only needs ctx.obj for ui_from()
176
+ ctx = typer.Context(click.Command("setup"))
177
177
  ctx.obj = {"verbose": False, "quiet": False, "json": False, "plain": False}
178
178
  setup_command(ctx)
179
179
  self.session.refresh()
@@ -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,
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import json
4
+ from typing import Any, cast
4
5
 
5
6
  from forgex.agents.builder import BuilderAgent, execute_task
6
7
  from forgex.config import load_config
@@ -50,6 +51,7 @@ async def test_builder_executes_task_and_records_events(tmp_path):
50
51
  initialize_project(ws)
51
52
  with open_stores(ws) as stores:
52
53
  state = stores.state.load()
54
+ assert state is not None
53
55
  task = Task(id="TASK-001", description="create demo file", acceptance_criteria=["exists"])
54
56
  stores.save_tasks([task], project_id=state.project_id)
55
57
 
@@ -59,8 +61,39 @@ async def test_builder_executes_task_and_records_events(tmp_path):
59
61
  ])
60
62
  ctx = create_context(ws, load_config(ws), agent=AgentRole.BUILDER, events=stores.recorder)
61
63
  tools = ToolExecutor(build_tool_registry(), permission_set_for(AgentRole.BUILDER), ctx)
62
- builder = BuilderAgent(router=router, tools=tools, events=stores.recorder)
64
+ builder = BuilderAgent(router=cast(Any, router), tools=tools, events=stores.recorder)
63
65
 
64
66
  res = await execute_task(stores=stores, task=task, config=load_config(ws), builder=builder)
65
67
  assert res.status == "completed"
66
68
  assert (ws / "demo.py").exists()
69
+
70
+
71
+ async def test_builder_json_protocol_mode(tmp_path):
72
+ ws = tmp_path / "ws"
73
+ ws.mkdir()
74
+ initialize_project(ws)
75
+ with open_stores(ws) as stores:
76
+ state = stores.state.load()
77
+ assert state is not None
78
+ task = Task(id="TASK-001", description="create demo file", acceptance_criteria=["exists"])
79
+ stores.save_tasks([task], project_id=state.project_id)
80
+
81
+ router = FakeRouter([
82
+ ModelResponse(content=json.dumps({
83
+ "action": "tool",
84
+ "tool": "write_file",
85
+ "arguments": {"path": "proto.py", "content": "print('protocol')"},
86
+ })),
87
+ ModelResponse(content=json.dumps({
88
+ "action": "done",
89
+ "result": {"status": "completed", "summary": "done via protocol", "created_files": ["proto.py"], "modified_files": [], "tests_written": []},
90
+ })),
91
+ ], supports_tools=False)
92
+ ctx = create_context(ws, load_config(ws), agent=AgentRole.BUILDER, events=stores.recorder)
93
+ tools = ToolExecutor(build_tool_registry(), permission_set_for(AgentRole.BUILDER), ctx)
94
+ builder = BuilderAgent(router=cast(Any, router), tools=tools, events=stores.recorder)
95
+
96
+ res = await execute_task(stores=stores, task=task, config=load_config(ws), builder=builder)
97
+ assert res.status == "completed"
98
+ assert (ws / "proto.py").exists()
99
+
@@ -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