ai-dev-workflow 0.3.1.2__tar.gz → 0.3.1.4__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 (63) hide show
  1. {ai_dev_workflow-0.3.1.2 → ai_dev_workflow-0.3.1.4}/PKG-INFO +2 -1
  2. {ai_dev_workflow-0.3.1.2 → ai_dev_workflow-0.3.1.4}/pyproject.toml +2 -1
  3. {ai_dev_workflow-0.3.1.2 → ai_dev_workflow-0.3.1.4}/tests/test_ai_workflow_cli.py +1 -1
  4. {ai_dev_workflow-0.3.1.2 → ai_dev_workflow-0.3.1.4}/tests/test_setup_wizard.py +80 -0
  5. {ai_dev_workflow-0.3.1.2 → ai_dev_workflow-0.3.1.4}/tools/ai_dev_workflow.egg-info/PKG-INFO +2 -1
  6. ai_dev_workflow-0.3.1.4/tools/ai_dev_workflow.egg-info/requires.txt +2 -0
  7. {ai_dev_workflow-0.3.1.2 → ai_dev_workflow-0.3.1.4}/tools/ai_workflow_cli.py +57 -39
  8. {ai_dev_workflow-0.3.1.2 → ai_dev_workflow-0.3.1.4}/tools/setup_wizard.py +80 -5
  9. ai_dev_workflow-0.3.1.2/tools/ai_dev_workflow.egg-info/requires.txt +0 -1
  10. {ai_dev_workflow-0.3.1.2 → ai_dev_workflow-0.3.1.4}/README.md +0 -0
  11. {ai_dev_workflow-0.3.1.2 → ai_dev_workflow-0.3.1.4}/setup.cfg +0 -0
  12. {ai_dev_workflow-0.3.1.2 → ai_dev_workflow-0.3.1.4}/tests/test_active_work_context.py +0 -0
  13. {ai_dev_workflow-0.3.1.2 → ai_dev_workflow-0.3.1.4}/tests/test_ado_provider.py +0 -0
  14. {ai_dev_workflow-0.3.1.2 → ai_dev_workflow-0.3.1.4}/tests/test_ai_provider.py +0 -0
  15. {ai_dev_workflow-0.3.1.2 → ai_dev_workflow-0.3.1.4}/tests/test_bootstrap_project.py +0 -0
  16. {ai_dev_workflow-0.3.1.2 → ai_dev_workflow-0.3.1.4}/tests/test_codespaces_environment.py +0 -0
  17. {ai_dev_workflow-0.3.1.2 → ai_dev_workflow-0.3.1.4}/tests/test_codex_cloud_executor.py +0 -0
  18. {ai_dev_workflow-0.3.1.2 → ai_dev_workflow-0.3.1.4}/tests/test_composite_work_item.py +0 -0
  19. {ai_dev_workflow-0.3.1.2 → ai_dev_workflow-0.3.1.4}/tests/test_connection_and_mcp.py +0 -0
  20. {ai_dev_workflow-0.3.1.2 → ai_dev_workflow-0.3.1.4}/tests/test_framework_update.py +0 -0
  21. {ai_dev_workflow-0.3.1.2 → ai_dev_workflow-0.3.1.4}/tests/test_general_init.py +0 -0
  22. {ai_dev_workflow-0.3.1.2 → ai_dev_workflow-0.3.1.4}/tests/test_instruction_adapter.py +0 -0
  23. {ai_dev_workflow-0.3.1.2 → ai_dev_workflow-0.3.1.4}/tests/test_issue_publisher.py +0 -0
  24. {ai_dev_workflow-0.3.1.2 → ai_dev_workflow-0.3.1.4}/tests/test_package_install.py +0 -0
  25. {ai_dev_workflow-0.3.1.2 → ai_dev_workflow-0.3.1.4}/tests/test_planning_approval.py +0 -0
  26. {ai_dev_workflow-0.3.1.2 → ai_dev_workflow-0.3.1.4}/tests/test_planning_context.py +0 -0
  27. {ai_dev_workflow-0.3.1.2 → ai_dev_workflow-0.3.1.4}/tests/test_queue_dispatcher.py +0 -0
  28. {ai_dev_workflow-0.3.1.2 → ai_dev_workflow-0.3.1.4}/tests/test_work_breakdown.py +0 -0
  29. {ai_dev_workflow-0.3.1.2 → ai_dev_workflow-0.3.1.4}/tests/test_work_claim.py +0 -0
  30. {ai_dev_workflow-0.3.1.2 → ai_dev_workflow-0.3.1.4}/tests/test_worker_presence.py +0 -0
  31. {ai_dev_workflow-0.3.1.2 → ai_dev_workflow-0.3.1.4}/tests/test_worker_registry.py +0 -0
  32. {ai_dev_workflow-0.3.1.2 → ai_dev_workflow-0.3.1.4}/tests/test_worker_runtime_manager.py +0 -0
  33. {ai_dev_workflow-0.3.1.2 → ai_dev_workflow-0.3.1.4}/tests/test_workflow_handoff.py +0 -0
  34. {ai_dev_workflow-0.3.1.2 → ai_dev_workflow-0.3.1.4}/tests/test_workflow_runtime.py +0 -0
  35. {ai_dev_workflow-0.3.1.2 → ai_dev_workflow-0.3.1.4}/tools/active_work_context.py +0 -0
  36. {ai_dev_workflow-0.3.1.2 → ai_dev_workflow-0.3.1.4}/tools/ado_mcp.py +0 -0
  37. {ai_dev_workflow-0.3.1.2 → ai_dev_workflow-0.3.1.4}/tools/ado_provider.py +0 -0
  38. {ai_dev_workflow-0.3.1.2 → ai_dev_workflow-0.3.1.4}/tools/ai_dev_workflow.egg-info/SOURCES.txt +0 -0
  39. {ai_dev_workflow-0.3.1.2 → ai_dev_workflow-0.3.1.4}/tools/ai_dev_workflow.egg-info/dependency_links.txt +0 -0
  40. {ai_dev_workflow-0.3.1.2 → ai_dev_workflow-0.3.1.4}/tools/ai_dev_workflow.egg-info/entry_points.txt +0 -0
  41. {ai_dev_workflow-0.3.1.2 → ai_dev_workflow-0.3.1.4}/tools/ai_dev_workflow.egg-info/top_level.txt +0 -0
  42. {ai_dev_workflow-0.3.1.2 → ai_dev_workflow-0.3.1.4}/tools/ai_provider.py +0 -0
  43. {ai_dev_workflow-0.3.1.2 → ai_dev_workflow-0.3.1.4}/tools/composite_work_item.py +0 -0
  44. {ai_dev_workflow-0.3.1.2 → ai_dev_workflow-0.3.1.4}/tools/framework_update.py +0 -0
  45. {ai_dev_workflow-0.3.1.2 → ai_dev_workflow-0.3.1.4}/tools/github_work_item_provider.py +0 -0
  46. {ai_dev_workflow-0.3.1.2 → ai_dev_workflow-0.3.1.4}/tools/instruction_adapter.py +0 -0
  47. {ai_dev_workflow-0.3.1.2 → ai_dev_workflow-0.3.1.4}/tools/issue_publisher.py +0 -0
  48. {ai_dev_workflow-0.3.1.2 → ai_dev_workflow-0.3.1.4}/tools/planning_approval.py +0 -0
  49. {ai_dev_workflow-0.3.1.2 → ai_dev_workflow-0.3.1.4}/tools/planning_context.py +0 -0
  50. {ai_dev_workflow-0.3.1.2 → ai_dev_workflow-0.3.1.4}/tools/setup_context.py +0 -0
  51. {ai_dev_workflow-0.3.1.2 → ai_dev_workflow-0.3.1.4}/tools/skill_registry.py +0 -0
  52. {ai_dev_workflow-0.3.1.2 → ai_dev_workflow-0.3.1.4}/tools/source_connection.py +0 -0
  53. {ai_dev_workflow-0.3.1.2 → ai_dev_workflow-0.3.1.4}/tools/work_breakdown.py +0 -0
  54. {ai_dev_workflow-0.3.1.2 → ai_dev_workflow-0.3.1.4}/tools/work_claim.py +0 -0
  55. {ai_dev_workflow-0.3.1.2 → ai_dev_workflow-0.3.1.4}/tools/work_item_provider.py +0 -0
  56. {ai_dev_workflow-0.3.1.2 → ai_dev_workflow-0.3.1.4}/tools/work_item_registry.py +0 -0
  57. {ai_dev_workflow-0.3.1.2 → ai_dev_workflow-0.3.1.4}/tools/worker_identity.py +0 -0
  58. {ai_dev_workflow-0.3.1.2 → ai_dev_workflow-0.3.1.4}/tools/worker_presence.py +0 -0
  59. {ai_dev_workflow-0.3.1.2 → ai_dev_workflow-0.3.1.4}/tools/worker_registry.py +0 -0
  60. {ai_dev_workflow-0.3.1.2 → ai_dev_workflow-0.3.1.4}/tools/worker_runtime_manager.py +0 -0
  61. {ai_dev_workflow-0.3.1.2 → ai_dev_workflow-0.3.1.4}/tools/worker_session.py +0 -0
  62. {ai_dev_workflow-0.3.1.2 → ai_dev_workflow-0.3.1.4}/tools/workflow_handoff.py +0 -0
  63. {ai_dev_workflow-0.3.1.2 → ai_dev_workflow-0.3.1.4}/tools/workflow_runtime.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ai-dev-workflow
3
- Version: 0.3.1.2
3
+ Version: 0.3.1.4
4
4
  Summary: Provider-neutral AI-assisted development workflow and CLI
5
5
  Author: vuthethienlong
6
6
  Project-URL: Homepage, https://github.com/vuthethienlong/ai-dev-workflow
@@ -11,6 +11,7 @@ Classifier: Operating System :: OS Independent
11
11
  Requires-Python: >=3.10
12
12
  Description-Content-Type: text/markdown
13
13
  Requires-Dist: PyYAML>=6.0
14
+ Requires-Dist: azure-identity>=1.25.0
14
15
 
15
16
  # AI Dev Workflow
16
17
 
@@ -4,12 +4,13 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "ai-dev-workflow"
7
- version = "0.3.1.2"
7
+ version = "0.3.1.4"
8
8
  description = "Provider-neutral AI-assisted development workflow and CLI"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
11
11
  dependencies = [
12
12
  "PyYAML>=6.0",
13
+ "azure-identity>=1.25.0",
13
14
  ]
14
15
  authors = [
15
16
  { name = "vuthethienlong" }
@@ -18,7 +18,7 @@ DISCOVERY={
18
18
 
19
19
  class Tests(unittest.TestCase):
20
20
  def test_package_version(self):
21
- self.assertEqual(PACKAGE_VERSION, "0.3.1.1")
21
+ self.assertEqual(PACKAGE_VERSION, "0.3.1.4")
22
22
 
23
23
  def test_guide_command_is_exposed_by_cli(self):
24
24
  output=io.StringIO()
@@ -141,6 +141,86 @@ class Tests(unittest.TestCase):
141
141
  self.assertEqual(credential["auth_scheme"],"basic")
142
142
  self.assertEqual(credential["source"],"worker-env")
143
143
 
144
+ def test_ado_setup_browser_auth_works_without_workers_or_azure_cli(self):
145
+ class Args:
146
+ ado_token_env="AZURE_DEVOPS_TOKEN"
147
+ ado_setup_auth="browser"
148
+ with patch.dict(os.environ,{},clear=True):
149
+ credential=resolve_ado_discovery_credential(
150
+ Args(),
151
+ [],
152
+ azure_cli_token_fn=lambda:None,
153
+ native_token_fn=lambda mode:"entra-token" if mode=="browser" else None,
154
+ )
155
+ self.assertEqual(credential["token"],"entra-token")
156
+ self.assertIsNone(credential["worker_id"])
157
+ self.assertEqual(credential["auth_scheme"],"bearer")
158
+ self.assertEqual(credential["source"],"browser")
159
+
160
+ def test_ado_setup_device_code_auth_works_without_azure_cli(self):
161
+ class Args:
162
+ ado_token_env="AZURE_DEVOPS_TOKEN"
163
+ ado_setup_auth="device-code"
164
+ credential=resolve_ado_discovery_credential(
165
+ Args(),
166
+ [],
167
+ azure_cli_token_fn=lambda:None,
168
+ native_token_fn=lambda mode:"device-token" if mode=="device-code" else None,
169
+ )
170
+ self.assertEqual(credential["token"],"device-token")
171
+ self.assertEqual(credential["source"],"device-code")
172
+
173
+ @patch("setup_wizard.detect_repository_context", side_effect=RuntimeError("not a git repo"))
174
+ def test_ado_wizard_collects_setup_auth_before_workers(self, _detect):
175
+ class Args:
176
+ root="."
177
+ work_items=None
178
+ github_repository=None
179
+ github_auth=None
180
+ github_token_env="GH_TOKEN"
181
+ ado_organization=None
182
+ ado_project=None
183
+ ado_token_env="AZURE_DEVOPS_TOKEN"
184
+ ado_setup_auth=None
185
+ ado_mcp_mode=None
186
+ ado_mcp_auth=None
187
+ ado_team=None
188
+ ai_provider=[]
189
+ role_provider=[]
190
+ worker=[]
191
+ skill=[]
192
+ environment=None
193
+ codespaces_mode=None
194
+ tdd=None
195
+ reviewer_mode=None
196
+ runtime_mode=None
197
+
198
+ answers=iter([
199
+ ".", # root
200
+ "2", # Azure DevOps only
201
+ "contoso", # organization
202
+ "App", # project
203
+ "1", # browser login
204
+ "n", # no multi-repo workspace
205
+ "n", # no CCR
206
+ "n", # no Claude
207
+ "", "", "", "", "", "", "", # manual role defaults
208
+ "n", # no workers
209
+ "n", # optional skills
210
+ "1", # environment none
211
+ "", # TDD yes
212
+ "1", # reviewer manual
213
+ "1", # runtime project-defined
214
+ ])
215
+ args=interactive_setup_options(
216
+ Args(),
217
+ input_fn=lambda _:next(answers),
218
+ output_fn=lambda *_:None,
219
+ )
220
+ self.assertEqual(args.ado_organization,"contoso")
221
+ self.assertEqual(args.ado_project,"App")
222
+ self.assertEqual(args.ado_setup_auth,"browser")
223
+
144
224
  def test_ado_project_defaults_are_selected_from_discovery(self):
145
225
  discovery={
146
226
  "project":"proj",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ai-dev-workflow
3
- Version: 0.3.1.2
3
+ Version: 0.3.1.4
4
4
  Summary: Provider-neutral AI-assisted development workflow and CLI
5
5
  Author: vuthethienlong
6
6
  Project-URL: Homepage, https://github.com/vuthethienlong/ai-dev-workflow
@@ -11,6 +11,7 @@ Classifier: Operating System :: OS Independent
11
11
  Requires-Python: >=3.10
12
12
  Description-Content-Type: text/markdown
13
13
  Requires-Dist: PyYAML>=6.0
14
+ Requires-Dist: azure-identity>=1.25.0
14
15
 
15
16
  # AI Dev Workflow
16
17
 
@@ -0,0 +1,2 @@
1
+ PyYAML>=6.0
2
+ azure-identity>=1.25.0
@@ -67,7 +67,7 @@ DEFAULT_ROLE_PROVIDERS = {
67
67
  "RuntimeVerifierAgent": "manual",
68
68
  }
69
69
 
70
- PACKAGE_VERSION = "0.3.1.2"
70
+ PACKAGE_VERSION = "0.3.1.4"
71
71
 
72
72
  ROLE_KEYS = {
73
73
  "SetupAgent": "setup",
@@ -888,55 +888,73 @@ def setup_cmd(args) -> int:
888
888
  ado_discovery_worker = None
889
889
  if args.work_items in ("azure-devops", "composite"):
890
890
  discovery_credential = resolve_ado_discovery_credential(args, workers)
891
+ setup_auth = getattr(args, "ado_setup_auth", None)
891
892
 
892
- interactive_worker_auth = any(
893
- (((worker.get("identity") or {}).get("azure_devops") or {}).get("auth") or {}).get("type")
894
- in ("interactive", "azure-cli")
895
- or (((worker.get("identity") or {}).get("azure_devops") or {}).get("auth") or {}).get("store")
896
- in ("mcp", "azure-cli")
897
- for worker in workers
898
- )
899
-
900
- if not discovery_credential and interactive_worker_auth and not args.non_interactive:
901
- print(
902
- "ADO worker authentication is interactive. Metadata discovery also needs "
903
- "an authenticated setup session."
904
- )
905
- if _wizard_yes_no(
906
- "Sign in with Azure CLI for metadata discovery (credential is not stored)?",
907
- True,
908
- ):
893
+ if not discovery_credential and not args.non_interactive:
894
+ if setup_auth in ("browser", "device-code"):
895
+ login_cmd = ["az", "login"]
896
+ if setup_auth == "device-code":
897
+ login_cmd.append("--use-device-code")
909
898
  try:
910
- subprocess.run(["az", "login"], check=True)
899
+ if setup_auth == "browser":
900
+ print("Opening Microsoft sign-in for Azure DevOps setup...")
901
+ else:
902
+ print("Starting Microsoft device-code sign-in for Azure DevOps setup...")
903
+ subprocess.run(login_cmd, check=True)
911
904
  except FileNotFoundError:
912
905
  print(
913
- "Azure CLI is not installed. Falling back to a one-time PAT/token prompt."
906
+ "Azure CLI is not installed, so Microsoft interactive login cannot start "
907
+ "from this setup session."
914
908
  )
915
909
  except subprocess.CalledProcessError:
916
- print(
917
- "Azure CLI sign-in did not complete. Falling back to a one-time PAT/token prompt."
918
- )
910
+ print("Microsoft sign-in did not complete successfully.")
919
911
  discovery_credential = resolve_ado_discovery_credential(args, workers)
920
912
 
921
- if not discovery_credential and not args.non_interactive:
922
- ado_token = getpass.getpass(
923
- "ADO setup-only discovery PAT/token (not stored; leave blank to cancel): "
924
- ).strip()
925
- if ado_token:
926
- discovery_credential = {
927
- "token": ado_token,
928
- "worker_id": None,
929
- "auth_scheme": "basic",
930
- "source": "secure-prompt",
931
- }
913
+ elif setup_auth == "azure-cli":
914
+ print(
915
+ "No usable Azure CLI session was found. Run 'az login' first, "
916
+ "or choose browser/device-code authentication."
917
+ )
918
+
919
+ if not discovery_credential and setup_auth == "token-prompt":
920
+ ado_token = getpass.getpass(
921
+ "ADO setup-only PAT/token (not stored; leave blank to cancel): "
922
+ ).strip()
923
+ if ado_token:
924
+ discovery_credential = {
925
+ "token": ado_token,
926
+ "worker_id": None,
927
+ "auth_scheme": "basic",
928
+ "source": "secure-prompt",
929
+ }
930
+
931
+ # Backward-compatible fallback for older invocations that do not
932
+ # carry the new setup-auth choice.
933
+ if not discovery_credential and not setup_auth:
934
+ ado_token = getpass.getpass(
935
+ "ADO setup-only discovery PAT/token (not stored; leave blank to cancel): "
936
+ ).strip()
937
+ if ado_token:
938
+ discovery_credential = {
939
+ "token": ado_token,
940
+ "worker_id": None,
941
+ "auth_scheme": "basic",
942
+ "source": "secure-prompt",
943
+ }
932
944
 
933
945
  if not discovery_credential:
946
+ auth_hint = {
947
+ "browser": "Microsoft browser sign-in failed. Retry, use device-code, or choose PAT/token auth.",
948
+ "device-code": "Microsoft device-code sign-in failed. Retry, use browser login, or choose PAT/token auth.",
949
+ "azure-cli": "Run 'az login' and retry, or choose another ADO setup authentication method.",
950
+ "token-env": f"Export {args.ado_token_env} in this shell and retry.",
951
+ "token-prompt": "Provide a PAT/token at the secure setup prompt.",
952
+ }.get(
953
+ setup_auth,
954
+ "Choose a usable ADO setup authentication method and retry.",
955
+ )
934
956
  raise RuntimeError(
935
- "ADO metadata discovery has no usable setup credential. "
936
- "For env/PAT auth, export the configured worker token variable or --ado-token-env. "
937
- "For Azure CLI or MCP/interactive auth, sign in with 'az login' so setup can obtain "
938
- "a temporary Azure DevOps bearer token. Cloud/Codespaces secret names are runtime "
939
- "references and are not readable from the local setup process."
957
+ "ADO metadata discovery could not authenticate. " + auth_hint
940
958
  )
941
959
 
942
960
  ado_discovery_worker = discovery_credential.get("worker_id")
@@ -156,6 +156,35 @@ def discover_provider(args):
156
156
 
157
157
 
158
158
  ADO_RESOURCE = "499b84ac-1321-427f-aa17-267ca6975798"
159
+ ADO_SCOPE = ADO_RESOURCE + "/.default"
160
+
161
+
162
+ def _native_interactive_access_token(mode: str) -> str | None:
163
+ """Authenticate directly with Microsoft Entra without requiring Azure CLI."""
164
+ try:
165
+ from azure.identity import DeviceCodeCredential, InteractiveBrowserCredential
166
+ from azure.core.exceptions import ClientAuthenticationError, CredentialUnavailableError
167
+ except ImportError as exc:
168
+ raise RuntimeError(
169
+ "Microsoft browser/device-code authentication requires the 'azure-identity' package. "
170
+ "Reinstall or upgrade ai-dev-workflow."
171
+ ) from exc
172
+
173
+ if mode == "browser":
174
+ credential = InteractiveBrowserCredential()
175
+ elif mode == "device-code":
176
+ credential = DeviceCodeCredential()
177
+ else:
178
+ raise ValueError(f"unsupported native ADO auth mode: {mode}")
179
+
180
+ try:
181
+ return credential.get_token(ADO_SCOPE).token
182
+ except (ClientAuthenticationError, CredentialUnavailableError) as exc:
183
+ raise RuntimeError(f"Microsoft {mode} authentication failed: {exc}") from exc
184
+ finally:
185
+ close = getattr(credential, "close", None)
186
+ if callable(close):
187
+ close()
159
188
 
160
189
 
161
190
  def _azure_cli_access_token() -> str | None:
@@ -185,6 +214,7 @@ def resolve_ado_discovery_credential(
185
214
  args,
186
215
  workers: list[dict],
187
216
  azure_cli_token_fn=None,
217
+ native_token_fn=None,
188
218
  ) -> dict | None:
189
219
  """Resolve setup-only ADO discovery auth without persisting the credential.
190
220
 
@@ -221,13 +251,26 @@ def resolve_ado_discovery_credential(
221
251
  cli_worker = worker
222
252
  break
223
253
 
224
- if cli_worker is not None:
254
+ setup_auth = getattr(args, "ado_setup_auth", None)
255
+
256
+ if setup_auth in ("browser", "device-code"):
257
+ token_fn = native_token_fn or _native_interactive_access_token
258
+ token = token_fn(setup_auth)
259
+ if token:
260
+ return {
261
+ "token": token,
262
+ "worker_id": cli_worker.get("worker_id") if cli_worker else None,
263
+ "auth_scheme": "bearer",
264
+ "source": setup_auth,
265
+ }
266
+
267
+ if setup_auth == "azure-cli" or cli_worker is not None:
225
268
  token_fn = azure_cli_token_fn or _azure_cli_access_token
226
269
  token = token_fn()
227
270
  if token:
228
271
  return {
229
272
  "token": token,
230
- "worker_id": cli_worker.get("worker_id"),
273
+ "worker_id": cli_worker.get("worker_id") if cli_worker else None,
231
274
  "auth_scheme": "bearer",
232
275
  "source": "azure-cli",
233
276
  }
@@ -465,9 +508,37 @@ def interactive_setup_options(args, input_fn=input, output_fn=print):
465
508
 
466
509
  if args.work_items in ("azure-devops", "composite"):
467
510
  output_fn("4/11 Azure DevOps project metadata")
468
- args.ado_organization = args.ado_organization or _prompt(input_fn, "ADO organization")
469
- args.ado_project = args.ado_project or _prompt(input_fn, "ADO project")
470
- output_fn("ADO credentials are configured per worker, not as project credentials.")
511
+ args.ado_organization = args.ado_organization or _required_prompt(
512
+ input_fn,
513
+ output_fn,
514
+ "ADO organization",
515
+ )
516
+ args.ado_project = args.ado_project or _required_prompt(
517
+ input_fn,
518
+ output_fn,
519
+ "ADO project",
520
+ )
521
+ output_fn("ADO setup authentication is temporary and is not stored in project config.")
522
+ if not getattr(args, "ado_setup_auth", None):
523
+ args.ado_setup_auth = _choice(
524
+ input_fn,
525
+ output_fn,
526
+ "ADO setup authentication",
527
+ [
528
+ ("browser", "Microsoft sign-in in browser (recommended)"),
529
+ ("device-code", "Microsoft device-code sign-in"),
530
+ ("azure-cli", "Use existing Azure CLI session"),
531
+ ("token-env", "PAT/token environment variable"),
532
+ ("token-prompt", "Paste PAT/token securely for this setup only"),
533
+ ],
534
+ "browser",
535
+ )
536
+ if args.ado_setup_auth == "token-env":
537
+ args.ado_token_env = _prompt(
538
+ input_fn,
539
+ "ADO token environment variable",
540
+ args.ado_token_env or "AZURE_DEVOPS_TOKEN",
541
+ )
471
542
  args.ado_mcp_mode = args.ado_mcp_mode or "none"
472
543
 
473
544
  workspace = interactive_workspace_options(args, input_fn=input_fn, output_fn=output_fn)
@@ -645,6 +716,10 @@ def interactive_setup_options(args, input_fn=input, output_fn=print):
645
716
  parts.extend(["ado_auth=azure-cli", "ado_store=azure-cli"])
646
717
  else:
647
718
  parts.extend(["ado_auth=interactive", "ado_store=mcp"])
719
+ if args.ado_mcp_mode == "none":
720
+ args.ado_mcp_mode = "remote"
721
+ if not getattr(args, "ado_mcp_auth", None):
722
+ args.ado_mcp_auth = "interactive"
648
723
 
649
724
  if environment in ("cloud", "codespaces"):
650
725
  github_env = _prompt(