minitest-cli 0.27.2__tar.gz → 0.28.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 (169) hide show
  1. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/PKG-INFO +1 -1
  2. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/pyproject.toml +1 -1
  3. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/api/client.py +11 -4
  4. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/commands/draft_feature.py +7 -0
  5. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/commands/run_display.py +5 -1
  6. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/commands/test_profile.py +22 -0
  7. minitest_cli-0.28.1/src/minitest_cli/commands/test_profile_card.py +52 -0
  8. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/commands/test_profile_update.py +38 -2
  9. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/core/config.py +8 -0
  10. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/models/story_run.py +3 -1
  11. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/tests/test_run_commands.py +63 -0
  12. minitest_cli-0.28.1/tests/test_suite_change_provenance.py +108 -0
  13. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/tests/test_test_profile_commands.py +61 -0
  14. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/uv.lock +1 -1
  15. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/.env.example +0 -0
  16. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/.github/workflows/ci.yml +0 -0
  17. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/.github/workflows/install-scripts.yml +0 -0
  18. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/.github/workflows/release.yml +0 -0
  19. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/.gitignore +0 -0
  20. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/.opencode/skill/release/SKILL.md +0 -0
  21. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/AGENTS.md +0 -0
  22. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/README.md +0 -0
  23. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/RELEASE.md +0 -0
  24. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/install.ps1 +0 -0
  25. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/install.sh +0 -0
  26. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/pyrightconfig.json +0 -0
  27. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/renovate.json +0 -0
  28. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/__init__.py +0 -0
  29. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/api/__init__.py +0 -0
  30. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/api/apps_manager_client.py +0 -0
  31. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/api/errors.py +0 -0
  32. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/assets/__init__.py +0 -0
  33. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/assets/callback.html +0 -0
  34. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/commands/__init__.py +0 -0
  35. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/commands/_response_errors.py +0 -0
  36. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/commands/app_knowledge.py +0 -0
  37. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/commands/app_knowledge_helpers.py +0 -0
  38. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/commands/apps.py +0 -0
  39. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/commands/apps_dependencies.py +0 -0
  40. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/commands/apps_get.py +0 -0
  41. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/commands/apps_helpers.py +0 -0
  42. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/commands/auth.py +0 -0
  43. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/commands/auth_api_key.py +0 -0
  44. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/commands/batch.py +0 -0
  45. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/commands/batch_helpers.py +0 -0
  46. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/commands/build.py +0 -0
  47. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/commands/build_commit.py +0 -0
  48. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/commands/build_fix_prompts.py +0 -0
  49. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/commands/build_helpers.py +0 -0
  50. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/commands/capabilities.py +0 -0
  51. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/commands/commit_helpers.py +0 -0
  52. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/commands/draft_feature_apply.py +0 -0
  53. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/commands/draft_feature_conflicts.py +0 -0
  54. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/commands/draft_feature_helpers.py +0 -0
  55. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/commands/draft_feature_show.py +0 -0
  56. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/commands/env.py +0 -0
  57. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/commands/env_helpers.py +0 -0
  58. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/commands/flow_types.py +0 -0
  59. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/commands/flow_types_helpers.py +0 -0
  60. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/commands/flow_types_write.py +0 -0
  61. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/commands/init.py +0 -0
  62. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/commands/init_helpers.py +0 -0
  63. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/commands/init_playbook.py +0 -0
  64. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/commands/issues.py +0 -0
  65. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/commands/issues_fix.py +0 -0
  66. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/commands/issues_helpers.py +0 -0
  67. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/commands/issues_projection.py +0 -0
  68. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/commands/issues_service.py +0 -0
  69. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/commands/maintenance.py +0 -0
  70. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/commands/maintenance_callbacks.py +0 -0
  71. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/commands/maintenance_helpers.py +0 -0
  72. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/commands/maintenance_state.py +0 -0
  73. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/commands/run.py +0 -0
  74. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/commands/run_commit.py +0 -0
  75. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/commands/run_commit_helpers.py +0 -0
  76. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/commands/run_feedback.py +0 -0
  77. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/commands/run_helpers.py +0 -0
  78. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/commands/run_targets.py +0 -0
  79. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/commands/screens.py +0 -0
  80. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/commands/screens_detail.py +0 -0
  81. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/commands/screens_format.py +0 -0
  82. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/commands/screens_helpers.py +0 -0
  83. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/commands/screens_tree.py +0 -0
  84. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/commands/skill.py +0 -0
  85. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/commands/test_file.py +0 -0
  86. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/commands/test_file_helpers.py +0 -0
  87. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/commands/test_file_list.py +0 -0
  88. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/commands/test_profile_default.py +0 -0
  89. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/commands/test_profile_helpers.py +0 -0
  90. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/commands/test_profile_list.py +0 -0
  91. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/commands/upgrade.py +0 -0
  92. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/commands/user_story.py +0 -0
  93. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/commands/user_story_bindings.py +0 -0
  94. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/commands/user_story_camera.py +0 -0
  95. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/commands/user_story_create.py +0 -0
  96. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/commands/user_story_criteria.py +0 -0
  97. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/commands/user_story_criterion_edits.py +0 -0
  98. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/commands/user_story_delete.py +0 -0
  99. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/commands/user_story_device_count.py +0 -0
  100. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/commands/user_story_helpers.py +0 -0
  101. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/commands/user_story_modify.py +0 -0
  102. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/commands/user_story_overrides.py +0 -0
  103. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/commands/user_story_profiles.py +0 -0
  104. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/commands/user_story_update.py +0 -0
  105. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/commands/verdicts.py +0 -0
  106. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/commands/verdicts_helpers.py +0 -0
  107. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/commands/verdicts_projection.py +0 -0
  108. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/core/__init__.py +0 -0
  109. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/core/app_context.py +0 -0
  110. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/core/auth.py +0 -0
  111. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/core/credentials.py +0 -0
  112. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/core/oauth.py +0 -0
  113. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/core/tenants.py +0 -0
  114. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/core/token_exchange.py +0 -0
  115. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/main.py +0 -0
  116. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/models/__init__.py +0 -0
  117. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/models/app.py +0 -0
  118. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/models/app_env_vars.py +0 -0
  119. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/models/app_failure.py +0 -0
  120. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/models/app_screen.py +0 -0
  121. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/models/base.py +0 -0
  122. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/models/batch.py +0 -0
  123. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/models/build.py +0 -0
  124. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/models/commit_build.py +0 -0
  125. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/models/draft_feature.py +0 -0
  126. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/models/flow_type.py +0 -0
  127. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/models/issues.py +0 -0
  128. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/models/targets.py +0 -0
  129. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/models/user_story.py +0 -0
  130. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/utils/__init__.py +0 -0
  131. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/utils/confirm.py +0 -0
  132. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/utils/dependency_sim.py +0 -0
  133. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/utils/mermaid.py +0 -0
  134. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/utils/output.py +0 -0
  135. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/utils/skill_refresh.py +0 -0
  136. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/src/minitest_cli/utils/update_check.py +0 -0
  137. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/tests/__init__.py +0 -0
  138. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/tests/_commit_transport.py +0 -0
  139. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/tests/test_app_knowledge_commands.py +0 -0
  140. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/tests/test_apps_commands.py +0 -0
  141. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/tests/test_apps_dependencies.py +0 -0
  142. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/tests/test_auth.py +0 -0
  143. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/tests/test_auth_api_key.py +0 -0
  144. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/tests/test_auth_commands.py +0 -0
  145. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/tests/test_batch_commands.py +0 -0
  146. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/tests/test_build_commands.py +0 -0
  147. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/tests/test_build_fix_prompts.py +0 -0
  148. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/tests/test_capabilities_command.py +0 -0
  149. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/tests/test_channel_header.py +0 -0
  150. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/tests/test_code_quality.py +0 -0
  151. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/tests/test_commit_commands.py +0 -0
  152. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/tests/test_draft_feature_commands.py +0 -0
  153. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/tests/test_env.py +0 -0
  154. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/tests/test_flow_types_commands.py +0 -0
  155. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/tests/test_init_command.py +0 -0
  156. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/tests/test_issues.py +0 -0
  157. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/tests/test_issues_commands.py +0 -0
  158. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/tests/test_mermaid.py +0 -0
  159. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/tests/test_network_errors.py +0 -0
  160. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/tests/test_response_errors.py +0 -0
  161. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/tests/test_screens_commands.py +0 -0
  162. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/tests/test_skill_command.py +0 -0
  163. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/tests/test_test_file_commands.py +0 -0
  164. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/tests/test_upgrade_command.py +0 -0
  165. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/tests/test_user_story_bindings_commands.py +0 -0
  166. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/tests/test_user_story_commands.py +0 -0
  167. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/tests/test_verdicts.py +0 -0
  168. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/tests/test_verdicts_actionable.py +0 -0
  169. {minitest_cli-0.27.2 → minitest_cli-0.28.1}/tests/test_version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: minitest-cli
3
- Version: 0.27.2
3
+ Version: 0.28.1
4
4
  Summary: Minitest CLI – command-line interface for the Minitest testing platform
5
5
  Project-URL: Homepage, https://minitap.ai/
6
6
  Project-URL: Source, https://github.com/minitap-ai/minitest-cli
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "minitest-cli"
3
- version = "0.27.2"
3
+ version = "0.28.1"
4
4
  description = "Minitest CLI – command-line interface for the Minitest testing platform"
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -8,6 +8,8 @@ from minitest_cli.core.auth import load_token
8
8
  from minitest_cli.core.config import Settings
9
9
 
10
10
  CHANNEL_HEADER = "X-Minitest-Channel"
11
+ CONVERSATION_ID_HEADER = "X-Minitest-Conversation-Id"
12
+ CAUSE_HEADER = "X-Minitest-Cause"
11
13
  DEFAULT_TIMEOUT = 30.0
12
14
  UPLOAD_TIMEOUT = 300.0 # 5 minutes for large file uploads
13
15
  # Creating a batch can block on server-side maintenance before it answers, and the
@@ -31,12 +33,17 @@ class ApiClient:
31
33
 
32
34
  async def __aenter__(self) -> "ApiClient":
33
35
  token = self._token_override or load_token(self._settings)
36
+ headers: dict[str, str] = {
37
+ "Authorization": f"Bearer {token}",
38
+ CHANNEL_HEADER: self._settings.channel,
39
+ }
40
+ if self._settings.conversation_id is not None:
41
+ headers[CONVERSATION_ID_HEADER] = self._settings.conversation_id
42
+ if self._settings.cause is not None:
43
+ headers[CAUSE_HEADER] = self._settings.cause
34
44
  self._client = httpx.AsyncClient(
35
45
  base_url=self._settings.api_url,
36
- headers={
37
- "Authorization": f"Bearer {token}",
38
- CHANNEL_HEADER: self._settings.channel,
39
- },
46
+ headers=headers,
40
47
  timeout=DEFAULT_TIMEOUT,
41
48
  )
42
49
  return self
@@ -4,6 +4,7 @@ A draft feature is a branch of the app's test suite — it holds a delta against
4
4
  main rather than a copy of it, so `show` is the only way to see what it changes.
5
5
  """
6
6
 
7
+ import json
7
8
  from typing import Annotated
8
9
 
9
10
  import typer
@@ -66,12 +67,18 @@ def create_draft_feature(
66
67
  str | None,
67
68
  typer.Option("--description", help="What product change the branch describes."),
68
69
  ] = None,
70
+ source_refs: Annotated[
71
+ str | None,
72
+ typer.Option("--source-refs", help="JSON array of provenance refs (passed as sourceRefs)."),
73
+ ] = None,
69
74
  ) -> None:
70
75
  """Open a branch off the app's main suite."""
71
76
  settings, app_id, json_mode = resolve_app()
72
77
  body: dict[str, object] = {"title": title}
73
78
  if description is not None:
74
79
  body["description"] = description
80
+ if source_refs is not None:
81
+ body["sourceRefs"] = json.loads(source_refs)
75
82
 
76
83
  async def _create() -> DraftFeatureResponse:
77
84
  async with ApiClient(settings) as client:
@@ -27,7 +27,9 @@ def _derive_run_status(run: StoryRunResponse) -> str:
27
27
  Mirrors the heuristic used by the cockpit / webapp: any platform
28
28
  with a stamped ``cancellation_requested_at`` short-circuits to
29
29
  ``cancelled``; otherwise we pick the worst platform state in
30
- lifecycle order (running > pending/blocked > failed > completed).
30
+ lifecycle order (running > pending/blocked > evaluating > failed >
31
+ completed). ``evaluating`` is a platform that finished executing and
32
+ is waiting for its verdict, so it outranks every terminal state.
31
33
  """
32
34
  if any(p.cancellation_requested_at is not None for p in run.platforms):
33
35
  return "cancelled"
@@ -38,6 +40,8 @@ def _derive_run_status(run: StoryRunResponse) -> str:
38
40
  return "running"
39
41
  if "pending" in states or "blocked" in states:
40
42
  return "pending"
43
+ if "evaluating" in states:
44
+ return "evaluating"
41
45
  if "failed" in states:
42
46
  return "failed"
43
47
  if all(s in _TERMINAL_EXEC_STATES for s in states) and any(s == "completed" for s in states):
@@ -4,6 +4,14 @@ import typer
4
4
 
5
5
  from minitest_cli.api.client import ApiClient
6
6
  from minitest_cli.commands import test_profile_default, test_profile_list, test_profile_update
7
+ from minitest_cli.commands.test_profile_card import (
8
+ CardCvcOption,
9
+ CardExpiryOption,
10
+ CardHolderNameOption,
11
+ CardNumberOption,
12
+ CardPostalCodeOption,
13
+ card_fields,
14
+ )
7
15
  from minitest_cli.commands.test_profile_helpers import (
8
16
  app_base_path,
9
17
  get_app_flag,
@@ -65,6 +73,11 @@ def create_profile(
65
73
  about: Annotated[
66
74
  str | None, typer.Option("--about", help="Free-text notes about the account.")
67
75
  ] = None,
76
+ test_card_number: CardNumberOption = None,
77
+ test_card_expiry: CardExpiryOption = None,
78
+ test_card_cvc: CardCvcOption = None,
79
+ test_card_holder_name: CardHolderNameOption = None,
80
+ test_card_postal_code: CardPostalCodeOption = None,
68
81
  ) -> None:
69
82
  settings = get_settings()
70
83
  json_mode = is_json_mode()
@@ -91,6 +104,15 @@ def create_profile(
91
104
  body["static_otp_code"] = otp
92
105
  if about is not None:
93
106
  body["about"] = about
107
+ card = card_fields(
108
+ test_card_number,
109
+ test_card_expiry,
110
+ test_card_cvc,
111
+ test_card_holder_name,
112
+ test_card_postal_code,
113
+ )
114
+ if card:
115
+ body["test_card"] = card
94
116
 
95
117
  async def _run() -> dict[str, Any]:
96
118
  async with ApiClient(settings) as client:
@@ -0,0 +1,52 @@
1
+ from typing import Annotated, Any
2
+
3
+ import typer
4
+
5
+ CardNumberOption = Annotated[
6
+ str | None,
7
+ typer.Option(
8
+ "--test-card-number",
9
+ help="Sandbox payment card number the persona pays with (e.g. 4242 4242 4242 4242).",
10
+ ),
11
+ ]
12
+ CardExpiryOption = Annotated[
13
+ str | None,
14
+ typer.Option("--test-card-expiry", help="Test card expiry, exactly as the app expects it."),
15
+ ]
16
+ CardCvcOption = Annotated[
17
+ str | None,
18
+ typer.Option("--test-card-cvc", help="Test card security code, exactly as the app expects it."),
19
+ ]
20
+ CardHolderNameOption = Annotated[
21
+ str | None, typer.Option("--test-card-holder-name", help="Name on the test card.")
22
+ ]
23
+ CardPostalCodeOption = Annotated[
24
+ str | None, typer.Option("--test-card-postal-code", help="Test card billing postal code.")
25
+ ]
26
+
27
+ _API_KEYS: dict[str, str] = {"holderName": "holder_name", "postalCode": "postal_code"}
28
+
29
+
30
+ def card_fields(
31
+ number: str | None,
32
+ expiry: str | None,
33
+ cvc: str | None,
34
+ holder_name: str | None,
35
+ postal_code: str | None,
36
+ ) -> dict[str, str]:
37
+ """The card fields the command was given, in the API's shape."""
38
+ given = {
39
+ "number": number,
40
+ "expiry": expiry,
41
+ "cvc": cvc,
42
+ "holder_name": holder_name,
43
+ "postal_code": postal_code,
44
+ }
45
+ return {key: value for key, value in given.items() if value is not None}
46
+
47
+
48
+ def merged_card(stored: dict[str, Any] | None, changes: dict[str, str]) -> dict[str, str]:
49
+ """The stored card with ``changes`` applied, since the API replaces a card whole."""
50
+ items: dict[str, Any] = stored or {}
51
+ current = {_API_KEYS.get(key, key): str(value) for key, value in items.items() if value}
52
+ return {**current, **changes}
@@ -3,6 +3,15 @@ from typing import Annotated, Any
3
3
  import typer
4
4
 
5
5
  from minitest_cli.api.client import ApiClient
6
+ from minitest_cli.commands.test_profile_card import (
7
+ CardCvcOption,
8
+ CardExpiryOption,
9
+ CardHolderNameOption,
10
+ CardNumberOption,
11
+ CardPostalCodeOption,
12
+ card_fields,
13
+ merged_card,
14
+ )
6
15
  from minitest_cli.commands.test_profile_helpers import (
7
16
  app_base_path,
8
17
  get_app_flag,
@@ -66,6 +75,15 @@ def register(app: typer.Typer) -> None:
66
75
  bool,
67
76
  typer.Option("--clear-about", help="Remove the stored about text."),
68
77
  ] = False,
78
+ test_card_number: CardNumberOption = None,
79
+ test_card_expiry: CardExpiryOption = None,
80
+ test_card_cvc: CardCvcOption = None,
81
+ test_card_holder_name: CardHolderNameOption = None,
82
+ test_card_postal_code: CardPostalCodeOption = None,
83
+ clear_test_card: Annotated[
84
+ bool,
85
+ typer.Option("--clear-test-card", help="Remove the stored test card."),
86
+ ] = False,
69
87
  ) -> None:
70
88
  settings = get_settings()
71
89
  json_mode = is_json_mode()
@@ -107,13 +125,31 @@ def register(app: typer.Typer) -> None:
107
125
  elif clear:
108
126
  body[key] = None
109
127
 
110
- if not body:
128
+ card = card_fields(
129
+ test_card_number,
130
+ test_card_expiry,
131
+ test_card_cvc,
132
+ test_card_holder_name,
133
+ test_card_postal_code,
134
+ )
135
+ if card and clear_test_card:
136
+ print_error("Use either --test-card-* options or --clear-test-card, not both.")
137
+ raise typer.Exit(code=1)
138
+ if clear_test_card:
139
+ body["test_card"] = None
140
+
141
+ if not body and not card:
111
142
  print_error("Provide at least one field to update.")
112
143
  raise typer.Exit(code=1)
113
144
 
114
145
  async def _run() -> dict[str, Any]:
115
146
  async with ApiClient(settings) as client:
116
- resp = await client.patch(f"{app_base_path(app_id)}/{profile_id}", json=body)
147
+ path = f"{app_base_path(app_id)}/{profile_id}"
148
+ if card:
149
+ current = await client.get(path)
150
+ handle_profile_response(current)
151
+ body["test_card"] = merged_card(current.json().get("testCard"), card)
152
+ resp = await client.patch(path, json=body)
117
153
  handle_profile_response(resp)
118
154
  return resp.json()
119
155
 
@@ -48,6 +48,14 @@ class Settings(BaseSettings):
48
48
  default="cli",
49
49
  description="X-Minitest-Channel header value sent on every API call (MINITEST_CHANNEL)",
50
50
  )
51
+ conversation_id: str | None = Field(
52
+ default=None,
53
+ description="X-Minitest-Conversation-Id header value (MINITEST_CONVERSATION_ID)",
54
+ )
55
+ cause: str | None = Field(
56
+ default=None,
57
+ description="X-Minitest-Cause header value sent on every API call (MINITEST_CAUSE)",
58
+ )
51
59
  token: str | None = Field(
52
60
  default=None,
53
61
  description="API authentication token (MINITEST_TOKEN)",
@@ -33,7 +33,9 @@ class PlatformRun(CamelModel):
33
33
  Every previously-flat ``ios_*`` / ``android_*`` field on
34
34
  :class:`StoryRunResponse` now lives here, one row per platform that
35
35
  was in scope for the run. ``execution_state`` is the lifecycle axis
36
- (pending/running/completed/failed/blocked/skipped); ``verdict`` is
36
+ (pending/running/evaluating/completed/failed/blocked/skipped);
37
+ ``evaluating`` is a platform that finished executing and is waiting
38
+ for its verdict. ``verdict`` is
37
39
  the criticality-aware outcome populated once the platform reaches a
38
40
  non-skipped terminal state. ``cancellation_requested_at`` is
39
41
  stamped when a user requested cancellation on this platform; UI
@@ -11,6 +11,7 @@ from click.exceptions import Exit
11
11
  from typer.testing import CliRunner
12
12
 
13
13
  from minitest_cli.commands.run import app as run_app
14
+ from minitest_cli.commands.run_display import _derive_run_status
14
15
  from minitest_cli.commands.run_helpers import (
15
16
  display_run_result,
16
17
  extract_detail,
@@ -1273,3 +1274,65 @@ class TestRunFeedback:
1273
1274
  tmp_path, ["feedback", "0e4a4b4f-6f0e-4a3b-9e0a-000000000042", "text"], resp=resp
1274
1275
  )
1275
1276
  assert result.exit_code == 4
1277
+
1278
+
1279
+ _EVALUATING_RUN = {
1280
+ **_PENDING_RUN,
1281
+ "platforms": [
1282
+ {**p, "executionState": "evaluating", "finishedAt": "2025-06-01T10:05:00Z"}
1283
+ for p in _PENDING_RUN["platforms"]
1284
+ ],
1285
+ }
1286
+
1287
+
1288
+ class TestEvaluatingRunStatus:
1289
+ def test_evaluating_lane_derives_evaluating(self) -> None:
1290
+ run = StoryRunResponse.model_validate(_EVALUATING_RUN)
1291
+ assert _derive_run_status(run) == "evaluating"
1292
+
1293
+ def test_evaluating_outranks_a_failed_sibling(self) -> None:
1294
+ run = StoryRunResponse.model_validate(
1295
+ {
1296
+ **_EVALUATING_RUN,
1297
+ "platforms": [
1298
+ _EVALUATING_RUN["platforms"][0],
1299
+ {**_EVALUATING_RUN["platforms"][1], "executionState": "failed"},
1300
+ ],
1301
+ }
1302
+ )
1303
+ assert _derive_run_status(run) == "evaluating"
1304
+
1305
+ def test_pending_sibling_still_wins(self) -> None:
1306
+ run = StoryRunResponse.model_validate(
1307
+ {
1308
+ **_EVALUATING_RUN,
1309
+ "platforms": [
1310
+ _EVALUATING_RUN["platforms"][0],
1311
+ _PENDING_RUN["platforms"][1],
1312
+ ],
1313
+ }
1314
+ )
1315
+ assert _derive_run_status(run) == "pending"
1316
+
1317
+ def test_status_with_watch_polls_through_evaluating(self, tmp_path) -> None:
1318
+ settings = _make_settings(tmp_path)
1319
+ client = _mock_client()
1320
+ client.get = AsyncMock(
1321
+ side_effect=[
1322
+ _mock_response(200, _EVALUATING_RUN),
1323
+ _mock_response(200, _EVALUATING_RUN),
1324
+ _mock_response(200, _COMPLETED_RUN),
1325
+ ]
1326
+ )
1327
+
1328
+ with (
1329
+ patch("minitest_cli.commands.run.ApiClient", return_value=client),
1330
+ patch("minitest_cli.commands.run_helpers.asyncio.sleep", new_callable=AsyncMock),
1331
+ patch("minitest_cli.commands.run_helpers.err_console"),
1332
+ ):
1333
+ result = _run_with_context(["status", _RUN_UUID, "--watch"], settings, json_mode=True)
1334
+
1335
+ assert result.exit_code == 0
1336
+ assert client.get.call_count == 3
1337
+ data = json.loads(result.output)
1338
+ assert all(p["executionState"] == "completed" for p in data["platforms"])
@@ -0,0 +1,108 @@
1
+ """Tests for suite-change provenance contract (section 3).
2
+
3
+ Uses real httpx request cycles via MockTransport so headers are proven at
4
+ the HTTP wire level rather than by inspecting mock call args.
5
+ """
6
+
7
+ import json
8
+
9
+ import httpx
10
+ from typer.testing import CliRunner
11
+
12
+ from minitest_cli.commands.draft_feature import app as df_app
13
+ from tests._commit_transport import cli_context, make_settings, routed
14
+
15
+ runner = CliRunner()
16
+
17
+ _APP_ID = "a0d9820f-5136-4f70-b46b-e5966f56bfb5"
18
+ _FEATURE_PAYLOAD = {
19
+ "id": "b1",
20
+ "tenantId": "t1",
21
+ "appId": _APP_ID,
22
+ "title": "Checkout",
23
+ "description": "test branch",
24
+ "status": "open",
25
+ "rebaseState": "in_sync",
26
+ "rebasedToMainRev": 1,
27
+ "sourceRefs": [],
28
+ "createdAt": "2026-01-01T00:00:00Z",
29
+ "updatedAt": "2026-01-01T00:00:00Z",
30
+ "mergedAt": None,
31
+ }
32
+
33
+
34
+ class TestProvenanceHeaders:
35
+ """X-Minitest-Conversation-Id and X-Minitest-Cause are sent on every request."""
36
+
37
+ def test_headers_present_when_env_vars_set(self, tmp_path):
38
+ """Both provenance headers appear when their env vars are set."""
39
+ received: dict[str, str] = {}
40
+
41
+ def handler(request: httpx.Request) -> httpx.Response:
42
+ received["conversation_id"] = request.headers.get("x-minitest-conversation-id", "")
43
+ received["cause"] = request.headers.get("x-minitest-cause", "")
44
+ return httpx.Response(201, json=_FEATURE_PAYLOAD)
45
+
46
+ settings = make_settings(
47
+ tmp_path,
48
+ conversation_id="conv-abc-123",
49
+ cause="ci/pipeline",
50
+ )
51
+ with routed(handler), cli_context(settings, json_mode=True):
52
+ result = runner.invoke(df_app, ["create", "--title", "Checkout"])
53
+
54
+ assert result.exit_code == 0, result.output
55
+ assert received["conversation_id"] == "conv-abc-123"
56
+ assert received["cause"] == "ci/pipeline"
57
+
58
+ def test_headers_absent_when_env_vars_not_set(self, tmp_path):
59
+ """Neither header is sent when the env vars are absent."""
60
+ received_keys: list[str] = []
61
+
62
+ def handler(request: httpx.Request) -> httpx.Response:
63
+ received_keys.extend(request.headers.keys())
64
+ return httpx.Response(201, json=_FEATURE_PAYLOAD)
65
+
66
+ settings = make_settings(tmp_path)
67
+ with routed(handler), cli_context(settings, json_mode=True):
68
+ result = runner.invoke(df_app, ["create", "--title", "Checkout"])
69
+
70
+ assert result.exit_code == 0, result.output
71
+ assert "x-minitest-conversation-id" not in received_keys
72
+ assert "x-minitest-cause" not in received_keys
73
+
74
+
75
+ class TestDfCreateSourceRefs:
76
+ """--source-refs flag is passed as sourceRefs in the POST body."""
77
+
78
+ def test_source_refs_included_in_post_body(self, tmp_path):
79
+ received_body: dict = {}
80
+
81
+ def handler(request: httpx.Request) -> httpx.Response:
82
+ received_body.update(json.loads(request.content))
83
+ return httpx.Response(201, json=_FEATURE_PAYLOAD)
84
+
85
+ source_refs = [{"type": "github_pr", "ref": "https://github.com/org/repo/pull/42"}]
86
+ settings = make_settings(tmp_path)
87
+ with routed(handler), cli_context(settings, json_mode=True):
88
+ result = runner.invoke(
89
+ df_app,
90
+ ["create", "--title", "Checkout", "--source-refs", json.dumps(source_refs)],
91
+ )
92
+
93
+ assert result.exit_code == 0, result.output
94
+ assert received_body["sourceRefs"] == source_refs
95
+
96
+ def test_source_refs_absent_when_flag_not_given(self, tmp_path):
97
+ received_body: dict = {}
98
+
99
+ def handler(request: httpx.Request) -> httpx.Response:
100
+ received_body.update(json.loads(request.content))
101
+ return httpx.Response(201, json=_FEATURE_PAYLOAD)
102
+
103
+ settings = make_settings(tmp_path)
104
+ with routed(handler), cli_context(settings, json_mode=True):
105
+ result = runner.invoke(df_app, ["create", "--title", "Checkout"])
106
+
107
+ assert result.exit_code == 0, result.output
108
+ assert "sourceRefs" not in received_body
@@ -177,6 +177,67 @@ class TestCreate:
177
177
  assert result.exit_code == 1
178
178
 
179
179
 
180
+ class TestTestCard:
181
+ def test_create_sends_only_the_card_fields_given(self, tmp_path):
182
+ settings = _make_settings(tmp_path)
183
+ client = _mock_client(post=_mock_response(201, _PROFILE))
184
+ with patch("minitest_cli.commands.test_profile.ApiClient", return_value=client):
185
+ result = _run(
186
+ [
187
+ "create",
188
+ "--name",
189
+ "Shopper",
190
+ "--test-card-number",
191
+ "4242 4242 4242 4242",
192
+ "--test-card-expiry",
193
+ "12/34",
194
+ ],
195
+ settings,
196
+ json_mode=True,
197
+ )
198
+ assert result.exit_code == 0
199
+ body = client.post.await_args.kwargs["json"]
200
+ assert body["test_card"] == {"number": "4242 4242 4242 4242", "expiry": "12/34"}
201
+
202
+ def test_update_changes_one_card_field_and_keeps_the_rest(self, tmp_path):
203
+ settings = _make_settings(tmp_path)
204
+ stored = {
205
+ **_PROFILE,
206
+ "testCard": {"number": "4242 4242 4242 4242", "cvc": "123", "holderName": "Ada"},
207
+ }
208
+ client = _mock_client(get=_mock_response(200, stored), patch_=_mock_response(200, stored))
209
+ with patch("minitest_cli.commands.test_profile_update.ApiClient", return_value=client):
210
+ result = _run(
211
+ ["update", "p-111", "--test-card-cvc", "999"],
212
+ settings,
213
+ json_mode=True,
214
+ )
215
+ assert result.exit_code == 0
216
+ body = client.patch.await_args.kwargs["json"]
217
+ assert body == {
218
+ "test_card": {"number": "4242 4242 4242 4242", "cvc": "999", "holder_name": "Ada"}
219
+ }
220
+
221
+ def test_update_clear_test_card(self, tmp_path):
222
+ settings = _make_settings(tmp_path)
223
+ client = _mock_client(patch_=_mock_response(200, _PROFILE))
224
+ with patch("minitest_cli.commands.test_profile_update.ApiClient", return_value=client):
225
+ result = _run(["update", "p-111", "--clear-test-card"], settings, json_mode=True)
226
+ assert result.exit_code == 0
227
+ assert client.patch.await_args.kwargs["json"] == {"test_card": None}
228
+ client.get.assert_not_awaited()
229
+
230
+ def test_update_card_field_and_clear_rejected(self, tmp_path):
231
+ settings = _make_settings(tmp_path)
232
+ client = _mock_client()
233
+ with patch("minitest_cli.commands.test_profile_update.ApiClient", return_value=client):
234
+ result = _run(
235
+ ["update", "p-111", "--test-card-cvc", "999", "--clear-test-card"], settings
236
+ )
237
+ assert result.exit_code == 1
238
+ client.patch.assert_not_awaited()
239
+
240
+
180
241
  class TestUpdate:
181
242
  def test_update_clear_password(self, tmp_path):
182
243
  settings = _make_settings(tmp_path)
@@ -141,7 +141,7 @@ wheels = [
141
141
 
142
142
  [[package]]
143
143
  name = "minitest-cli"
144
- version = "0.27.2"
144
+ version = "0.28.1"
145
145
  source = { editable = "." }
146
146
  dependencies = [
147
147
  { name = "httpx" },
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes