qaas-python 0.2.0__tar.gz → 0.2.1__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 (166) hide show
  1. {qaas_python-0.2.0 → qaas_python-0.2.1}/PKG-INFO +1 -1
  2. {qaas_python-0.2.0 → qaas_python-0.2.1}/pyproject.toml +1 -1
  3. {qaas_python-0.2.0 → qaas_python-0.2.1}/src/qaas/cli.py +6 -8
  4. {qaas_python-0.2.0 → qaas_python-0.2.1}/src/qaas/conductor.py +22 -0
  5. {qaas_python-0.2.0 → qaas_python-0.2.1}/src/qaas/target.py +18 -0
  6. {qaas_python-0.2.0 → qaas_python-0.2.1}/tests/test_conductor.py +20 -0
  7. {qaas_python-0.2.0 → qaas_python-0.2.1}/.gitignore +0 -0
  8. {qaas_python-0.2.0 → qaas_python-0.2.1}/ARCHITECTURE.md +0 -0
  9. {qaas_python-0.2.0 → qaas_python-0.2.1}/BUILD_PLAN.md +0 -0
  10. {qaas_python-0.2.0 → qaas_python-0.2.1}/CLAUDE.md +0 -0
  11. {qaas_python-0.2.0 → qaas_python-0.2.1}/LICENSE +0 -0
  12. {qaas_python-0.2.0 → qaas_python-0.2.1}/README.md +0 -0
  13. {qaas_python-0.2.0 → qaas_python-0.2.1}/config/targets/corvid.yaml +0 -0
  14. {qaas_python-0.2.0 → qaas_python-0.2.1}/src/qaas/adapters/__init__.py +0 -0
  15. {qaas_python-0.2.0 → qaas_python-0.2.1}/src/qaas/adapters/tracker.py +0 -0
  16. {qaas_python-0.2.0 → qaas_python-0.2.1}/src/qaas/adapters/vcs.py +0 -0
  17. {qaas_python-0.2.0 → qaas_python-0.2.1}/src/qaas/config.py +0 -0
  18. {qaas_python-0.2.0 → qaas_python-0.2.1}/src/qaas/defaults/config/agents/arbiter.yaml +0 -0
  19. {qaas_python-0.2.0 → qaas_python-0.2.1}/src/qaas/defaults/config/agents/cartographer.yaml +0 -0
  20. {qaas_python-0.2.0 → qaas_python-0.2.1}/src/qaas/defaults/config/agents/clerk.yaml +0 -0
  21. {qaas_python-0.2.0 → qaas_python-0.2.1}/src/qaas/defaults/config/agents/conduit.yaml +0 -0
  22. {qaas_python-0.2.0 → qaas_python-0.2.1}/src/qaas/defaults/config/agents/forge.yaml +0 -0
  23. {qaas_python-0.2.0 → qaas_python-0.2.1}/src/qaas/defaults/config/agents/mender.yaml +0 -0
  24. {qaas_python-0.2.0 → qaas_python-0.2.1}/src/qaas/defaults/config/agents/proof.yaml +0 -0
  25. {qaas_python-0.2.0 → qaas_python-0.2.1}/src/qaas/defaults/config/agents/surface.yaml +0 -0
  26. {qaas_python-0.2.0 → qaas_python-0.2.1}/src/qaas/defaults/config/agents/vault.yaml +0 -0
  27. {qaas_python-0.2.0 → qaas_python-0.2.1}/src/qaas/defaults/config/agents/warden.yaml +0 -0
  28. {qaas_python-0.2.0 → qaas_python-0.2.1}/src/qaas/defaults/config/system.yaml +0 -0
  29. {qaas_python-0.2.0 → qaas_python-0.2.1}/src/qaas/discover.py +0 -0
  30. {qaas_python-0.2.0 → qaas_python-0.2.1}/src/qaas/envelope.py +0 -0
  31. {qaas_python-0.2.0 → qaas_python-0.2.1}/src/qaas/guardrails.py +0 -0
  32. {qaas_python-0.2.0 → qaas_python-0.2.1}/src/qaas/mcp/__init__.py +0 -0
  33. {qaas_python-0.2.0 → qaas_python-0.2.1}/src/qaas/mcp/context.py +0 -0
  34. {qaas_python-0.2.0 → qaas_python-0.2.1}/src/qaas/mcp/contract_diff.py +0 -0
  35. {qaas_python-0.2.0 → qaas_python-0.2.1}/src/qaas/mcp/defect_memory.py +0 -0
  36. {qaas_python-0.2.0 → qaas_python-0.2.1}/src/qaas/mcp/env_control.py +0 -0
  37. {qaas_python-0.2.0 → qaas_python-0.2.1}/src/qaas/mcp/envelope_server.py +0 -0
  38. {qaas_python-0.2.0 → qaas_python-0.2.1}/src/qaas/mcp/test_runner.py +0 -0
  39. {qaas_python-0.2.0 → qaas_python-0.2.1}/src/qaas/mcp/tracker.py +0 -0
  40. {qaas_python-0.2.0 → qaas_python-0.2.1}/src/qaas/mcp/vcs.py +0 -0
  41. {qaas_python-0.2.0 → qaas_python-0.2.1}/src/qaas/paths.py +0 -0
  42. {qaas_python-0.2.0 → qaas_python-0.2.1}/src/qaas/plugin/.claude-plugin/plugin.json +0 -0
  43. {qaas_python-0.2.0 → qaas_python-0.2.1}/src/qaas/plugin/skills/a11y-audit/SKILL.md +0 -0
  44. {qaas_python-0.2.0 → qaas_python-0.2.1}/src/qaas/plugin/skills/adversarial-review/SKILL.md +0 -0
  45. {qaas_python-0.2.0 → qaas_python-0.2.1}/src/qaas/plugin/skills/api-surface-extraction/SKILL.md +0 -0
  46. {qaas_python-0.2.0 → qaas_python-0.2.1}/src/qaas/plugin/skills/authz-matrix-check/SKILL.md +0 -0
  47. {qaas_python-0.2.0 → qaas_python-0.2.1}/src/qaas/plugin/skills/console-error-triage/SKILL.md +0 -0
  48. {qaas_python-0.2.0 → qaas_python-0.2.1}/src/qaas/plugin/skills/contract-test-generation/SKILL.md +0 -0
  49. {qaas_python-0.2.0 → qaas_python-0.2.1}/src/qaas/plugin/skills/dedupe-strategy/SKILL.md +0 -0
  50. {qaas_python-0.2.0 → qaas_python-0.2.1}/src/qaas/plugin/skills/environment-pinning/SKILL.md +0 -0
  51. {qaas_python-0.2.0 → qaas_python-0.2.1}/src/qaas/plugin/skills/error-taxonomy/SKILL.md +0 -0
  52. {qaas_python-0.2.0 → qaas_python-0.2.1}/src/qaas/plugin/skills/exploratory-ui-walk/SKILL.md +0 -0
  53. {qaas_python-0.2.0 → qaas_python-0.2.1}/src/qaas/plugin/skills/failing-test-authoring/SKILL.md +0 -0
  54. {qaas_python-0.2.0 → qaas_python-0.2.1}/src/qaas/plugin/skills/flake-detection/SKILL.md +0 -0
  55. {qaas_python-0.2.0 → qaas_python-0.2.1}/src/qaas/plugin/skills/form-state-probe/SKILL.md +0 -0
  56. {qaas_python-0.2.0 → qaas_python-0.2.1}/src/qaas/plugin/skills/minimal-diff-discipline/SKILL.md +0 -0
  57. {qaas_python-0.2.0 → qaas_python-0.2.1}/src/qaas/plugin/skills/openapi-diff/SKILL.md +0 -0
  58. {qaas_python-0.2.0 → qaas_python-0.2.1}/src/qaas/plugin/skills/ownership-resolution/SKILL.md +0 -0
  59. {qaas_python-0.2.0 → qaas_python-0.2.1}/src/qaas/plugin/skills/product-task-graph/SKILL.md +0 -0
  60. {qaas_python-0.2.0 → qaas_python-0.2.1}/src/qaas/plugin/skills/regression-risk-scoring/SKILL.md +0 -0
  61. {qaas_python-0.2.0 → qaas_python-0.2.1}/src/qaas/plugin/skills/regression-suite-selection/SKILL.md +0 -0
  62. {qaas_python-0.2.0 → qaas_python-0.2.1}/src/qaas/plugin/skills/repo-cartography/SKILL.md +0 -0
  63. {qaas_python-0.2.0 → qaas_python-0.2.1}/src/qaas/plugin/skills/repro-minimisation/SKILL.md +0 -0
  64. {qaas_python-0.2.0 → qaas_python-0.2.1}/src/qaas/plugin/skills/rollback-plan-authoring/SKILL.md +0 -0
  65. {qaas_python-0.2.0 → qaas_python-0.2.1}/src/qaas/plugin/skills/root-cause-vs-symptom/SKILL.md +0 -0
  66. {qaas_python-0.2.0 → qaas_python-0.2.1}/src/qaas/plugin/skills/routing-rules/SKILL.md +0 -0
  67. {qaas_python-0.2.0 → qaas_python-0.2.1}/src/qaas/plugin/skills/severity-rubric/SKILL.md +0 -0
  68. {qaas_python-0.2.0 → qaas_python-0.2.1}/src/qaas/plugin/skills/test-first-fix/SKILL.md +0 -0
  69. {qaas_python-0.2.0 → qaas_python-0.2.1}/src/qaas/plugin/skills/test-quality-audit/SKILL.md +0 -0
  70. {qaas_python-0.2.0 → qaas_python-0.2.1}/src/qaas/plugin/skills/ticket-writer/SKILL.md +0 -0
  71. {qaas_python-0.2.0 → qaas_python-0.2.1}/src/qaas/plugin/skills/verdict-reporting/SKILL.md +0 -0
  72. {qaas_python-0.2.0 → qaas_python-0.2.1}/src/qaas/plugin/skills/verification-protocol/SKILL.md +0 -0
  73. {qaas_python-0.2.0 → qaas_python-0.2.1}/src/qaas/prompts/ARBITER.md +0 -0
  74. {qaas_python-0.2.0 → qaas_python-0.2.1}/src/qaas/prompts/CARTOGRAPHER.md +0 -0
  75. {qaas_python-0.2.0 → qaas_python-0.2.1}/src/qaas/prompts/CLERK.md +0 -0
  76. {qaas_python-0.2.0 → qaas_python-0.2.1}/src/qaas/prompts/CONDUIT.md +0 -0
  77. {qaas_python-0.2.0 → qaas_python-0.2.1}/src/qaas/prompts/FORGE.md +0 -0
  78. {qaas_python-0.2.0 → qaas_python-0.2.1}/src/qaas/prompts/MENDER.md +0 -0
  79. {qaas_python-0.2.0 → qaas_python-0.2.1}/src/qaas/prompts/PROOF.md +0 -0
  80. {qaas_python-0.2.0 → qaas_python-0.2.1}/src/qaas/prompts/SURFACE.md +0 -0
  81. {qaas_python-0.2.0 → qaas_python-0.2.1}/src/qaas/prompts/VAULT.md +0 -0
  82. {qaas_python-0.2.0 → qaas_python-0.2.1}/src/qaas/prompts/WARDEN.md +0 -0
  83. {qaas_python-0.2.0 → qaas_python-0.2.1}/src/qaas/prompts/_shared.md +0 -0
  84. {qaas_python-0.2.0 → qaas_python-0.2.1}/src/qaas/registry.py +0 -0
  85. {qaas_python-0.2.0 → qaas_python-0.2.1}/src/qaas/runner.py +0 -0
  86. {qaas_python-0.2.0 → qaas_python-0.2.1}/src/qaas/scorecard.py +0 -0
  87. {qaas_python-0.2.0 → qaas_python-0.2.1}/src/qaas/sdk_compat.py +0 -0
  88. {qaas_python-0.2.0 → qaas_python-0.2.1}/src/qaas/store.py +0 -0
  89. {qaas_python-0.2.0 → qaas_python-0.2.1}/src/qaas/tasks.py +0 -0
  90. {qaas_python-0.2.0 → qaas_python-0.2.1}/src/qaas/trace.py +0 -0
  91. {qaas_python-0.2.0 → qaas_python-0.2.1}/target-app/CODEOWNERS +0 -0
  92. {qaas_python-0.2.0 → qaas_python-0.2.1}/target-app/api/Dockerfile +0 -0
  93. {qaas_python-0.2.0 → qaas_python-0.2.1}/target-app/api/app/__init__.py +0 -0
  94. {qaas_python-0.2.0 → qaas_python-0.2.1}/target-app/api/app/auth.py +0 -0
  95. {qaas_python-0.2.0 → qaas_python-0.2.1}/target-app/api/app/config.py +0 -0
  96. {qaas_python-0.2.0 → qaas_python-0.2.1}/target-app/api/app/db.py +0 -0
  97. {qaas_python-0.2.0 → qaas_python-0.2.1}/target-app/api/app/errors.py +0 -0
  98. {qaas_python-0.2.0 → qaas_python-0.2.1}/target-app/api/app/main.py +0 -0
  99. {qaas_python-0.2.0 → qaas_python-0.2.1}/target-app/api/app/models.py +0 -0
  100. {qaas_python-0.2.0 → qaas_python-0.2.1}/target-app/api/app/routes/__init__.py +0 -0
  101. {qaas_python-0.2.0 → qaas_python-0.2.1}/target-app/api/app/routes/auth.py +0 -0
  102. {qaas_python-0.2.0 → qaas_python-0.2.1}/target-app/api/app/routes/invoices.py +0 -0
  103. {qaas_python-0.2.0 → qaas_python-0.2.1}/target-app/api/app/routes/orders.py +0 -0
  104. {qaas_python-0.2.0 → qaas_python-0.2.1}/target-app/api/app/routes/stream.py +0 -0
  105. {qaas_python-0.2.0 → qaas_python-0.2.1}/target-app/api/app/schemas.py +0 -0
  106. {qaas_python-0.2.0 → qaas_python-0.2.1}/target-app/api/migrations/001_init.sql +0 -0
  107. {qaas_python-0.2.0 → qaas_python-0.2.1}/target-app/api/pyproject.toml +0 -0
  108. {qaas_python-0.2.0 → qaas_python-0.2.1}/target-app/api/seed/fixtures.sql +0 -0
  109. {qaas_python-0.2.0 → qaas_python-0.2.1}/target-app/defects.yaml +0 -0
  110. {qaas_python-0.2.0 → qaas_python-0.2.1}/target-app/docker-compose.yml +0 -0
  111. {qaas_python-0.2.0 → qaas_python-0.2.1}/target-app/openapi.yaml +0 -0
  112. {qaas_python-0.2.0 → qaas_python-0.2.1}/target-app/web/.gitignore +0 -0
  113. {qaas_python-0.2.0 → qaas_python-0.2.1}/target-app/web/Dockerfile +0 -0
  114. {qaas_python-0.2.0 → qaas_python-0.2.1}/target-app/web/index.html +0 -0
  115. {qaas_python-0.2.0 → qaas_python-0.2.1}/target-app/web/package-lock.json +0 -0
  116. {qaas_python-0.2.0 → qaas_python-0.2.1}/target-app/web/package.json +0 -0
  117. {qaas_python-0.2.0 → qaas_python-0.2.1}/target-app/web/src/api.ts +0 -0
  118. {qaas_python-0.2.0 → qaas_python-0.2.1}/target-app/web/src/components/Button.tsx +0 -0
  119. {qaas_python-0.2.0 → qaas_python-0.2.1}/target-app/web/src/components/Layout.tsx +0 -0
  120. {qaas_python-0.2.0 → qaas_python-0.2.1}/target-app/web/src/components/SearchInput.tsx +0 -0
  121. {qaas_python-0.2.0 → qaas_python-0.2.1}/target-app/web/src/main.tsx +0 -0
  122. {qaas_python-0.2.0 → qaas_python-0.2.1}/target-app/web/src/routes/CheckoutReview.tsx +0 -0
  123. {qaas_python-0.2.0 → qaas_python-0.2.1}/target-app/web/src/routes/Login.tsx +0 -0
  124. {qaas_python-0.2.0 → qaas_python-0.2.1}/target-app/web/src/routes/NewOrder.tsx +0 -0
  125. {qaas_python-0.2.0 → qaas_python-0.2.1}/target-app/web/src/routes/OrderDetail.tsx +0 -0
  126. {qaas_python-0.2.0 → qaas_python-0.2.1}/target-app/web/src/routes/OrdersList.tsx +0 -0
  127. {qaas_python-0.2.0 → qaas_python-0.2.1}/target-app/web/src/styles.css +0 -0
  128. {qaas_python-0.2.0 → qaas_python-0.2.1}/target-app/web/tsconfig.json +0 -0
  129. {qaas_python-0.2.0 → qaas_python-0.2.1}/target-app/web/vite.config.ts +0 -0
  130. {qaas_python-0.2.0 → qaas_python-0.2.1}/tests/adapters/test_github_vcs.py +0 -0
  131. {qaas_python-0.2.0 → qaas_python-0.2.1}/tests/adapters/test_jira_tracker.py +0 -0
  132. {qaas_python-0.2.0 → qaas_python-0.2.1}/tests/conftest.py +0 -0
  133. {qaas_python-0.2.0 → qaas_python-0.2.1}/tests/mcp/conftest.py +0 -0
  134. {qaas_python-0.2.0 → qaas_python-0.2.1}/tests/mcp/test_contract_diff.py +0 -0
  135. {qaas_python-0.2.0 → qaas_python-0.2.1}/tests/mcp/test_defect_memory.py +0 -0
  136. {qaas_python-0.2.0 → qaas_python-0.2.1}/tests/mcp/test_env_control.py +0 -0
  137. {qaas_python-0.2.0 → qaas_python-0.2.1}/tests/mcp/test_test_runner.py +0 -0
  138. {qaas_python-0.2.0 → qaas_python-0.2.1}/tests/mcp/test_tracker.py +0 -0
  139. {qaas_python-0.2.0 → qaas_python-0.2.1}/tests/mcp/test_vcs.py +0 -0
  140. {qaas_python-0.2.0 → qaas_python-0.2.1}/tests/support.py +0 -0
  141. {qaas_python-0.2.0 → qaas_python-0.2.1}/tests/target_app/test_seeded_defects.py +0 -0
  142. {qaas_python-0.2.0 → qaas_python-0.2.1}/tests/test_cli.py +0 -0
  143. {qaas_python-0.2.0 → qaas_python-0.2.1}/tests/test_config.py +0 -0
  144. {qaas_python-0.2.0 → qaas_python-0.2.1}/tests/test_discover.py +0 -0
  145. {qaas_python-0.2.0 → qaas_python-0.2.1}/tests/test_envelope.py +0 -0
  146. {qaas_python-0.2.0 → qaas_python-0.2.1}/tests/test_guardrails.py +0 -0
  147. {qaas_python-0.2.0 → qaas_python-0.2.1}/tests/test_hooks_and_skills.py +0 -0
  148. {qaas_python-0.2.0 → qaas_python-0.2.1}/tests/test_paths.py +0 -0
  149. {qaas_python-0.2.0 → qaas_python-0.2.1}/tests/test_prompt_overrides.py +0 -0
  150. {qaas_python-0.2.0 → qaas_python-0.2.1}/tests/test_registry.py +0 -0
  151. {qaas_python-0.2.0 → qaas_python-0.2.1}/tests/test_scorecard.py +0 -0
  152. {qaas_python-0.2.0 → qaas_python-0.2.1}/tests/test_skills_actually_load.py +0 -0
  153. {qaas_python-0.2.0 → qaas_python-0.2.1}/tests/test_store.py +0 -0
  154. {qaas_python-0.2.0 → qaas_python-0.2.1}/tests/test_target_profile_is_honoured.py +0 -0
  155. {qaas_python-0.2.0 → qaas_python-0.2.1}/tests/test_trace.py +0 -0
  156. {qaas_python-0.2.0 → qaas_python-0.2.1}/tests/test_user_mcp_servers.py +0 -0
  157. {qaas_python-0.2.0 → qaas_python-0.2.1}/tutorial/01-code-structure.md +0 -0
  158. {qaas_python-0.2.0 → qaas_python-0.2.1}/tutorial/02-how-agents-communicate.md +0 -0
  159. {qaas_python-0.2.0 → qaas_python-0.2.1}/tutorial/03-skills-and-hooks.md +0 -0
  160. {qaas_python-0.2.0 → qaas_python-0.2.1}/tutorial/04-mcp-servers.md +0 -0
  161. {qaas_python-0.2.0 → qaas_python-0.2.1}/tutorial/05-prompt-configuration.md +0 -0
  162. {qaas_python-0.2.0 → qaas_python-0.2.1}/tutorial/06-jira-integration.md +0 -0
  163. {qaas_python-0.2.0 → qaas_python-0.2.1}/tutorial/07-github-actions.md +0 -0
  164. {qaas_python-0.2.0 → qaas_python-0.2.1}/tutorial/08-packaging-and-publishing.md +0 -0
  165. {qaas_python-0.2.0 → qaas_python-0.2.1}/tutorial/09-guardrails-and-safety.md +0 -0
  166. {qaas_python-0.2.0 → qaas_python-0.2.1}/tutorial/README.md +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: qaas-python
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Summary: A multi-agent QA system: finds real defects, reproduces them, files tickets, fixes them, and proves the fix
5
5
  Project-URL: Homepage, https://github.com/allaabdella2-us/qa-multi-agent-system
6
6
  Project-URL: Repository, https://github.com/allaabdella2-us/qa-multi-agent-system
@@ -2,7 +2,7 @@
2
2
  # Distribution name is `qaas-python` (`qaas` was taken); the import package and
3
3
  # the CLI are both `qaas`.
4
4
  name = "qaas-python"
5
- version = "0.2.0"
5
+ version = "0.2.1"
6
6
  description = "A multi-agent QA system: finds real defects, reproduces them, files tickets, fixes them, and proves the fix"
7
7
  readme = "README.md"
8
8
  license = "MIT"
@@ -449,16 +449,14 @@ def doctor(
449
449
 
450
450
 
451
451
  def _agent_usable(spec, caps: dict[str, bool]) -> bool:
452
- """Whether an agent can do useful work with the capabilities available.
452
+ """Delegates to `target.agent_usable`, which the conductor also uses.
453
453
 
454
- SURFACE without a browser-reachable UI has nothing to do; the rest can all
455
- contribute from static analysis alone, at lower confidence.
454
+ Two copies of this rule meant `qaas doctor` could report an agent unusable
455
+ while a run dispatched it anyway.
456
456
  """
457
- if spec.name == "SURFACE":
458
- return caps["live_ui"]
459
- if spec.name == "PROOF":
460
- return caps["live_api"] or caps["static_analysis"]
461
- return True
457
+ from qaas.target import agent_usable
458
+
459
+ return agent_usable(spec.name, caps)
462
460
 
463
461
 
464
462
  @app.command()
@@ -25,6 +25,7 @@ from typing import Any, Callable
25
25
 
26
26
  from qaas.config import AgentSpec, SystemConfig, load_config
27
27
  from qaas.envelope import DefectEnvelope
28
+ from qaas.target import agent_usable
28
29
  from qaas.mcp.context import ToolContext
29
30
  from qaas.runner import RunOutcome, run_agent
30
31
  from qaas.store import RunStore, SystemMapStore
@@ -269,6 +270,27 @@ class Conductor:
269
270
  async def _phase_discover(self, specs, store, budget, report, mode, map_version) -> None:
270
271
  """Discovery agents are independent. Run them concurrently, bounded."""
271
272
  discovery = [s for name, s in specs.items() if s.layer == "discovery"]
273
+
274
+ # Skip agents this target cannot support. `qaas doctor` has always
275
+ # reported these ("agents that cannot: SURFACE"), but nothing acted on
276
+ # it, so a run against a target with no reachable UI would still
277
+ # dispatch SURFACE and spend its entire budget hunting a browser that
278
+ # was never there. Being told an agent cannot work and then watching it
279
+ # run is worse than not being told.
280
+ profile = getattr(self.config, "profile", None)
281
+ if profile is not None:
282
+ caps = profile.capabilities()
283
+ unusable = [s for s in discovery if not agent_usable(s.name, caps)]
284
+ if unusable:
285
+ discovery = [s for s in discovery if s not in unusable]
286
+ store.log(
287
+ "skipped",
288
+ reason="target cannot support these agents",
289
+ agents=[s.name for s in unusable],
290
+ )
291
+ for spec in unusable:
292
+ self._emit("skipped", agent=spec.name, reason="target lacks the capability")
293
+
272
294
  if not discovery:
273
295
  return
274
296
 
@@ -259,3 +259,21 @@ def load_target(name: str, targets_dir: Path | str = "config/targets") -> Target
259
259
  # directory is the bug that hid `<project>/config/targets/` the moment anything
260
260
  # wrote into `.qaas/config/targets/`; profiles layer across every config
261
261
  # directory, and `config.target_files(dirs)` is the one place that knows it.
262
+
263
+
264
+ def agent_usable(agent_name: str, caps: dict[str, bool]) -> bool:
265
+ """Whether an agent can do useful work with the capabilities available.
266
+
267
+ Lives here, beside `capabilities()`, because it has two callers that must
268
+ agree: `qaas doctor` reports it, and the conductor acts on it. They did not
269
+ agree for a while -- doctor would say "agents that cannot: SURFACE" and then
270
+ a run would dispatch SURFACE anyway and spend its whole budget looking for a
271
+ browser that was never there. Being told an agent cannot work and then
272
+ watching it run is worse than not being told.
273
+
274
+ Everything except SURFACE can contribute from static analysis alone, at
275
+ lower confidence. SURFACE without a reachable UI has nothing to do at all.
276
+ """
277
+ if agent_name == "SURFACE":
278
+ return caps.get("live_ui", False)
279
+ return True
@@ -561,3 +561,23 @@ async def test_a_new_discovery_agent_runs_without_a_hand_written_task(cfg, tmp_p
561
561
  report = await make_conductor(cfg, tmp_path).run("nightly")
562
562
  ran = {name for name, _ in calls}
563
563
  assert {"VAULT", "WARDEN"} <= ran, f"a config-only agent was skipped: {sorted(ran)}"
564
+
565
+
566
+ async def test_an_agent_the_target_cannot_support_is_not_dispatched(cfg, tmp_path, fake_agents):
567
+ """`qaas doctor` has always reported "agents that cannot: SURFACE" for a
568
+ target with no reachable UI. Nothing acted on it: the run dispatched SURFACE
569
+ anyway and spent its budget looking for a browser that was never there.
570
+
571
+ Being told an agent cannot work and then watching it run is worse than not
572
+ being told, which is why this asserts the dispatch, not the report.
573
+ """
574
+ calls, behaviour = fake_agents
575
+ from qaas.target import Environment, TargetProfile
576
+
577
+ static_only = TargetProfile(name="static", root=".", environment=Environment(mode="none"))
578
+ cfg2 = cfg.model_copy(update={"profile": static_only})
579
+
580
+ await make_conductor(cfg2, tmp_path).run("pr-check")
581
+ ran = {name for name, _ in calls}
582
+ assert "SURFACE" not in ran, "dispatched SURFACE at a target with no UI"
583
+ assert "CONDUIT" in ran, "static analysis agents must still run"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes