ai-dev-workflow 0.1.1__tar.gz → 0.2.0__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.
- {ai_dev_workflow-0.1.1 → ai_dev_workflow-0.2.0}/PKG-INFO +1 -1
- {ai_dev_workflow-0.1.1 → ai_dev_workflow-0.2.0}/pyproject.toml +1 -1
- {ai_dev_workflow-0.1.1 → ai_dev_workflow-0.2.0}/tests/test_ai_workflow_cli.py +1 -1
- {ai_dev_workflow-0.1.1 → ai_dev_workflow-0.2.0}/tests/test_setup_wizard.py +74 -1
- {ai_dev_workflow-0.1.1 → ai_dev_workflow-0.2.0}/tools/ai_dev_workflow.egg-info/PKG-INFO +1 -1
- {ai_dev_workflow-0.1.1 → ai_dev_workflow-0.2.0}/tools/ai_workflow_cli.py +70 -21
- ai_dev_workflow-0.2.0/tools/setup_wizard.py +446 -0
- ai_dev_workflow-0.1.1/tools/setup_wizard.py +0 -221
- {ai_dev_workflow-0.1.1 → ai_dev_workflow-0.2.0}/README.md +0 -0
- {ai_dev_workflow-0.1.1 → ai_dev_workflow-0.2.0}/setup.cfg +0 -0
- {ai_dev_workflow-0.1.1 → ai_dev_workflow-0.2.0}/tests/test_ado_provider.py +0 -0
- {ai_dev_workflow-0.1.1 → ai_dev_workflow-0.2.0}/tests/test_ai_provider.py +0 -0
- {ai_dev_workflow-0.1.1 → ai_dev_workflow-0.2.0}/tests/test_bootstrap_project.py +0 -0
- {ai_dev_workflow-0.1.1 → ai_dev_workflow-0.2.0}/tests/test_codespaces_environment.py +0 -0
- {ai_dev_workflow-0.1.1 → ai_dev_workflow-0.2.0}/tests/test_codex_cloud_executor.py +0 -0
- {ai_dev_workflow-0.1.1 → ai_dev_workflow-0.2.0}/tests/test_composite_work_item.py +0 -0
- {ai_dev_workflow-0.1.1 → ai_dev_workflow-0.2.0}/tests/test_connection_and_mcp.py +0 -0
- {ai_dev_workflow-0.1.1 → ai_dev_workflow-0.2.0}/tests/test_framework_update.py +0 -0
- {ai_dev_workflow-0.1.1 → ai_dev_workflow-0.2.0}/tests/test_general_init.py +0 -0
- {ai_dev_workflow-0.1.1 → ai_dev_workflow-0.2.0}/tests/test_instruction_adapter.py +0 -0
- {ai_dev_workflow-0.1.1 → ai_dev_workflow-0.2.0}/tests/test_issue_publisher.py +0 -0
- {ai_dev_workflow-0.1.1 → ai_dev_workflow-0.2.0}/tests/test_package_install.py +0 -0
- {ai_dev_workflow-0.1.1 → ai_dev_workflow-0.2.0}/tests/test_planning_context.py +0 -0
- {ai_dev_workflow-0.1.1 → ai_dev_workflow-0.2.0}/tests/test_queue_dispatcher.py +0 -0
- {ai_dev_workflow-0.1.1 → ai_dev_workflow-0.2.0}/tests/test_worker_registry.py +0 -0
- {ai_dev_workflow-0.1.1 → ai_dev_workflow-0.2.0}/tools/ado_mcp.py +0 -0
- {ai_dev_workflow-0.1.1 → ai_dev_workflow-0.2.0}/tools/ado_provider.py +0 -0
- {ai_dev_workflow-0.1.1 → ai_dev_workflow-0.2.0}/tools/ai_dev_workflow.egg-info/SOURCES.txt +0 -0
- {ai_dev_workflow-0.1.1 → ai_dev_workflow-0.2.0}/tools/ai_dev_workflow.egg-info/dependency_links.txt +0 -0
- {ai_dev_workflow-0.1.1 → ai_dev_workflow-0.2.0}/tools/ai_dev_workflow.egg-info/entry_points.txt +0 -0
- {ai_dev_workflow-0.1.1 → ai_dev_workflow-0.2.0}/tools/ai_dev_workflow.egg-info/requires.txt +0 -0
- {ai_dev_workflow-0.1.1 → ai_dev_workflow-0.2.0}/tools/ai_dev_workflow.egg-info/top_level.txt +0 -0
- {ai_dev_workflow-0.1.1 → ai_dev_workflow-0.2.0}/tools/ai_provider.py +0 -0
- {ai_dev_workflow-0.1.1 → ai_dev_workflow-0.2.0}/tools/composite_work_item.py +0 -0
- {ai_dev_workflow-0.1.1 → ai_dev_workflow-0.2.0}/tools/framework_update.py +0 -0
- {ai_dev_workflow-0.1.1 → ai_dev_workflow-0.2.0}/tools/github_work_item_provider.py +0 -0
- {ai_dev_workflow-0.1.1 → ai_dev_workflow-0.2.0}/tools/instruction_adapter.py +0 -0
- {ai_dev_workflow-0.1.1 → ai_dev_workflow-0.2.0}/tools/issue_publisher.py +0 -0
- {ai_dev_workflow-0.1.1 → ai_dev_workflow-0.2.0}/tools/planning_context.py +0 -0
- {ai_dev_workflow-0.1.1 → ai_dev_workflow-0.2.0}/tools/setup_context.py +0 -0
- {ai_dev_workflow-0.1.1 → ai_dev_workflow-0.2.0}/tools/source_connection.py +0 -0
- {ai_dev_workflow-0.1.1 → ai_dev_workflow-0.2.0}/tools/work_item_provider.py +0 -0
- {ai_dev_workflow-0.1.1 → ai_dev_workflow-0.2.0}/tools/work_item_registry.py +0 -0
- {ai_dev_workflow-0.1.1 → ai_dev_workflow-0.2.0}/tools/worker_registry.py +0 -0
|
@@ -16,7 +16,7 @@ DISCOVERY={
|
|
|
16
16
|
|
|
17
17
|
class Tests(unittest.TestCase):
|
|
18
18
|
def test_package_version(self):
|
|
19
|
-
self.assertEqual(PACKAGE_VERSION, "0.
|
|
19
|
+
self.assertEqual(PACKAGE_VERSION, "0.2.0")
|
|
20
20
|
|
|
21
21
|
def test_init_generates_explicit_mapping_and_agents(self):
|
|
22
22
|
cfg=build_ado_config(DISCOVERY)
|
|
@@ -4,7 +4,7 @@ sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "tools"))
|
|
|
4
4
|
|
|
5
5
|
from github_work_item_provider import build_github_mapping, suggest_github_state, validate_github_mapping
|
|
6
6
|
from setup_context import analyze_repository, setup_agent_context
|
|
7
|
-
from setup_wizard import build_setup_plan, credential_requirements
|
|
7
|
+
from setup_wizard import build_setup_plan, credential_requirements, interactive_setup_options, interactive_workspace_options, confirm_setup_plan
|
|
8
8
|
|
|
9
9
|
class Tests(unittest.TestCase):
|
|
10
10
|
def test_context_analysis_detects_project_signals(self):
|
|
@@ -61,4 +61,77 @@ class Tests(unittest.TestCase):
|
|
|
61
61
|
self.assertEqual(suggest_github_state("Awaiting runtime verification"),"runtime-pending")
|
|
62
62
|
self.assertIsNone(suggest_github_state("customer-impact"))
|
|
63
63
|
|
|
64
|
+
|
|
65
|
+
def test_full_wizard_collects_github_and_multi_repo(self):
|
|
66
|
+
class Args:
|
|
67
|
+
root="."
|
|
68
|
+
work_items=None
|
|
69
|
+
github_repository=None
|
|
70
|
+
github_auth=None
|
|
71
|
+
github_token_env="GH_TOKEN"
|
|
72
|
+
ado_organization=None
|
|
73
|
+
ado_project=None
|
|
74
|
+
ado_token_env="AZURE_DEVOPS_TOKEN"
|
|
75
|
+
ado_mcp_mode=None
|
|
76
|
+
ado_mcp_auth=None
|
|
77
|
+
ado_team=None
|
|
78
|
+
ai_provider=[]
|
|
79
|
+
role_provider=[]
|
|
80
|
+
worker=[]
|
|
81
|
+
environment=None
|
|
82
|
+
codespaces_mode=None
|
|
83
|
+
tdd=None
|
|
84
|
+
reviewer_mode=None
|
|
85
|
+
runtime_mode=None
|
|
86
|
+
|
|
87
|
+
answers=iter([
|
|
88
|
+
".", # root
|
|
89
|
+
"1", # GitHub Issues
|
|
90
|
+
"owner/main", # repository
|
|
91
|
+
"1", # gh-cli
|
|
92
|
+
"y", # multi-repo
|
|
93
|
+
"dragon-workspace", # workspace name
|
|
94
|
+
"main", # primary ref
|
|
95
|
+
"primary-game", # primary purpose
|
|
96
|
+
"y", # add repo
|
|
97
|
+
"owner/shared", # second repo
|
|
98
|
+
"shared-lib", # purpose
|
|
99
|
+
"integration", # ref
|
|
100
|
+
"1", # read
|
|
101
|
+
"AGENTS.md|docs/**", # context
|
|
102
|
+
"n", # no more repos
|
|
103
|
+
"owner/main", # default repo
|
|
104
|
+
"n", # no Claude
|
|
105
|
+
"", "", "", "", "", "", # role defaults
|
|
106
|
+
"n", # no workers
|
|
107
|
+
"1", # env none
|
|
108
|
+
"", # TDD default yes
|
|
109
|
+
"1", # reviewer manual
|
|
110
|
+
"1", # runtime project-defined
|
|
111
|
+
])
|
|
112
|
+
args=interactive_setup_options(Args(), input_fn=lambda _: next(answers), output_fn=lambda *_: None)
|
|
113
|
+
self.assertEqual(args.work_items,"github-issues")
|
|
114
|
+
self.assertEqual(args.github_repository,"owner/main")
|
|
115
|
+
self.assertEqual(args.workspace_config["workspace"]["name"],"dragon-workspace")
|
|
116
|
+
self.assertEqual(len(args.workspace_config["repositories"]),2)
|
|
117
|
+
self.assertEqual(args.workspace_config["repositories"][1]["repository"],"owner/shared")
|
|
118
|
+
self.assertEqual(args.workspace_config["planning"]["default_repository"],"owner/main")
|
|
119
|
+
self.assertTrue(args.tdd)
|
|
120
|
+
|
|
121
|
+
def test_workspace_can_be_skipped(self):
|
|
122
|
+
class Args:
|
|
123
|
+
github_repository="owner/repo"
|
|
124
|
+
answers=iter(["n"])
|
|
125
|
+
self.assertIsNone(interactive_workspace_options(Args(), input_fn=lambda _: next(answers), output_fn=lambda *_: None))
|
|
126
|
+
|
|
127
|
+
def test_review_can_cancel_apply(self):
|
|
128
|
+
plan={
|
|
129
|
+
"work_items":{"provider":"github-issues"},
|
|
130
|
+
"providers":{"codex":{}},
|
|
131
|
+
"workers":[],
|
|
132
|
+
"execution_environment":{"type":"none"},
|
|
133
|
+
"workflow":{"tdd":True,"reviewer":{"mode":"manual"},"runtime_verification":{"mode":"project-defined"}},
|
|
134
|
+
}
|
|
135
|
+
self.assertFalse(confirm_setup_plan(plan,input_fn=lambda _:"n",output_fn=lambda *_:None))
|
|
136
|
+
|
|
64
137
|
if __name__=="__main__": unittest.main()
|
|
@@ -13,7 +13,7 @@ except ImportError:
|
|
|
13
13
|
|
|
14
14
|
from ado_provider import AzureDevOps, build_state_mapping, validate_mapping, validate_field_mapping
|
|
15
15
|
from work_item_provider import REQUIRED_AGENT_ROLES, REQUIRED_CANONICAL_STATES
|
|
16
|
-
from setup_wizard import build_setup_plan, discover_provider, interactive_mapping, interactive_ado_field_mapping, render_user_steps, apply_github_status_bootstrap
|
|
16
|
+
from setup_wizard import build_setup_plan, discover_provider, interactive_mapping, interactive_ado_field_mapping, render_user_steps, apply_github_status_bootstrap, interactive_setup_options, confirm_setup_plan
|
|
17
17
|
from github_work_item_provider import validate_github_mapping
|
|
18
18
|
from ai_provider import provider_template, validate_provider_registry
|
|
19
19
|
from source_connection import resolve_github_token
|
|
@@ -22,6 +22,7 @@ from ado_mcp import build_ado_mcp
|
|
|
22
22
|
from worker_registry import build_worker, validate_worker_registry
|
|
23
23
|
from framework_update import DEFAULT_FRAMEWORK_SOURCE, ensure_framework_metadata, build_update_plan, apply_update
|
|
24
24
|
from instruction_adapter import adapter_for_provider, ensure_provider_instruction_file
|
|
25
|
+
from planning_context import normalize_workspace
|
|
25
26
|
|
|
26
27
|
|
|
27
28
|
DEFAULT_ROLE_PROVIDERS = {
|
|
@@ -33,7 +34,7 @@ DEFAULT_ROLE_PROVIDERS = {
|
|
|
33
34
|
"RuntimeVerifierAgent": "project-defined",
|
|
34
35
|
}
|
|
35
36
|
|
|
36
|
-
PACKAGE_VERSION = "0.
|
|
37
|
+
PACKAGE_VERSION = "0.2.0"
|
|
37
38
|
|
|
38
39
|
ROLE_KEYS = {
|
|
39
40
|
"SetupAgent": "setup",
|
|
@@ -386,6 +387,7 @@ def build_environment(environment: str, codespaces_mode: str) -> dict:
|
|
|
386
387
|
|
|
387
388
|
|
|
388
389
|
def perform_init(args, ado_api_factory=AzureDevOps) -> tuple[dict, dict | None]:
|
|
390
|
+
root = Path(getattr(args, "root", "."))
|
|
389
391
|
roles = parse_role_provider(args.role_provider)
|
|
390
392
|
discovery = None
|
|
391
393
|
if args.work_items == "azure-devops":
|
|
@@ -407,6 +409,8 @@ def perform_init(args, ado_api_factory=AzureDevOps) -> tuple[dict, dict | None]:
|
|
|
407
409
|
)
|
|
408
410
|
|
|
409
411
|
output = Path(args.output)
|
|
412
|
+
if not output.is_absolute():
|
|
413
|
+
output = root / output
|
|
410
414
|
existing = _load_yaml(output) if output.exists() else {}
|
|
411
415
|
config = generated if args.force or not existing else merge_owned(existing, generated)
|
|
412
416
|
output.write_text(_dump_yaml(config), encoding="utf-8")
|
|
@@ -419,17 +423,17 @@ def perform_init(args, ado_api_factory=AzureDevOps) -> tuple[dict, dict | None]:
|
|
|
419
423
|
)
|
|
420
424
|
|
|
421
425
|
if not args.no_files:
|
|
422
|
-
ensure_file(
|
|
423
|
-
ensure_file(
|
|
426
|
+
ensure_file(root / "AGENTS.md", generated_agents_md(), args.force)
|
|
427
|
+
ensure_file(root / "CURRENT.md", generated_current_md(), args.force)
|
|
424
428
|
if args.environment == "codespaces":
|
|
425
429
|
ensure_file(
|
|
426
|
-
|
|
430
|
+
root / ".devcontainer/devcontainer.json",
|
|
427
431
|
generated_devcontainer(),
|
|
428
432
|
args.force,
|
|
429
433
|
)
|
|
430
434
|
if args.work_items == "github-issues":
|
|
431
435
|
ensure_file(
|
|
432
|
-
|
|
436
|
+
root / ".github/workflows/ai-workflow.yml",
|
|
433
437
|
generated_github_caller(args.framework_ref),
|
|
434
438
|
args.force,
|
|
435
439
|
)
|
|
@@ -480,6 +484,20 @@ def init_ado(args) -> int:
|
|
|
480
484
|
|
|
481
485
|
|
|
482
486
|
def setup_cmd(args) -> int:
|
|
487
|
+
if not args.non_interactive:
|
|
488
|
+
args = interactive_setup_options(args)
|
|
489
|
+
|
|
490
|
+
required = []
|
|
491
|
+
if args.work_items in ("github-issues", "composite") and not args.github_repository:
|
|
492
|
+
required.append("GitHub repository")
|
|
493
|
+
if args.work_items in ("azure-devops", "composite"):
|
|
494
|
+
if not args.ado_organization:
|
|
495
|
+
required.append("ADO organization")
|
|
496
|
+
if not args.ado_project:
|
|
497
|
+
required.append("ADO project")
|
|
498
|
+
if required:
|
|
499
|
+
raise RuntimeError("missing setup values: " + ", ".join(required))
|
|
500
|
+
|
|
483
501
|
github_auth = None
|
|
484
502
|
if args.github_repository:
|
|
485
503
|
_, github_auth = resolve_github_token(args.github_auth, args.github_token_env)
|
|
@@ -537,12 +555,18 @@ def setup_cmd(args) -> int:
|
|
|
537
555
|
ado_field_mapping=ado_field_mapping,
|
|
538
556
|
)
|
|
539
557
|
plan["work_items"]["mapping_confirmed"] = not args.plan_only
|
|
540
|
-
Path(args.plan_output)
|
|
558
|
+
plan_output = Path(args.plan_output)
|
|
559
|
+
if not plan_output.is_absolute():
|
|
560
|
+
plan_output = Path(args.root) / plan_output
|
|
561
|
+
plan_output.write_text(_dump_yaml(plan), encoding="utf-8")
|
|
541
562
|
print(render_user_steps(plan))
|
|
542
|
-
print(f"\nWrote setup plan: {
|
|
563
|
+
print(f"\nWrote setup plan: {plan_output}")
|
|
543
564
|
|
|
544
565
|
if args.plan_only:
|
|
545
566
|
return 0
|
|
567
|
+
if not args.non_interactive and not confirm_setup_plan(plan):
|
|
568
|
+
print("Setup cancelled; no project configuration was applied.")
|
|
569
|
+
return 0
|
|
546
570
|
|
|
547
571
|
if args.work_items == "github-issues":
|
|
548
572
|
discovery, mapping, created = apply_github_status_bootstrap(args, mapping)
|
|
@@ -552,6 +576,7 @@ def setup_cmd(args) -> int:
|
|
|
552
576
|
class InitArgs:
|
|
553
577
|
pass
|
|
554
578
|
init = InitArgs()
|
|
579
|
+
init.root = args.root
|
|
555
580
|
init.work_items = "azure-devops" if args.work_items == "composite" else args.work_items
|
|
556
581
|
init.ado_organization = args.ado_organization
|
|
557
582
|
init.ado_project = args.ado_project
|
|
@@ -607,7 +632,23 @@ def setup_cmd(args) -> int:
|
|
|
607
632
|
)
|
|
608
633
|
config["framework"]["source"] = framework_source
|
|
609
634
|
config["framework"]["ref"] = framework_version
|
|
610
|
-
Path(args.output)
|
|
635
|
+
config_output = Path(args.output)
|
|
636
|
+
if not config_output.is_absolute():
|
|
637
|
+
config_output = Path(args.root) / config_output
|
|
638
|
+
config_output.write_text(_dump_yaml(config), encoding="utf-8")
|
|
639
|
+
|
|
640
|
+
workspace_config = getattr(args, "workspace_config", None)
|
|
641
|
+
if workspace_config:
|
|
642
|
+
normalized_workspace = normalize_workspace(workspace_config)
|
|
643
|
+
workspace_path = Path(args.root) / ".ai-workspace.yml"
|
|
644
|
+
workspace_path.write_text(_dump_yaml(workspace_config), encoding="utf-8")
|
|
645
|
+
config["planning_context"] = {
|
|
646
|
+
"workspace_file": ".ai-workspace.yml",
|
|
647
|
+
**normalized_workspace["planning_context"],
|
|
648
|
+
}
|
|
649
|
+
config_output.write_text(_dump_yaml(config), encoding="utf-8")
|
|
650
|
+
print(f"Wrote workspace registry: {workspace_path}")
|
|
651
|
+
|
|
611
652
|
errors = doctor(config, discovery)
|
|
612
653
|
if errors:
|
|
613
654
|
print("Doctor findings:")
|
|
@@ -721,25 +762,25 @@ def main() -> int:
|
|
|
721
762
|
|
|
722
763
|
setup = sub.add_parser("setup")
|
|
723
764
|
setup.add_argument("--root", default=".")
|
|
724
|
-
setup.add_argument("--work-items", choices=["github-issues", "azure-devops", "composite"]
|
|
765
|
+
setup.add_argument("--work-items", choices=["github-issues", "azure-devops", "composite"])
|
|
725
766
|
setup.add_argument("--github-repository")
|
|
726
767
|
setup.add_argument("--github-token-env", default="GH_TOKEN")
|
|
727
|
-
setup.add_argument("--github-auth", choices=["gh-cli","token-env"]
|
|
768
|
+
setup.add_argument("--github-auth", choices=["gh-cli","token-env"])
|
|
728
769
|
setup.add_argument("--ado-organization")
|
|
729
770
|
setup.add_argument("--ado-project")
|
|
730
771
|
setup.add_argument("--ado-token-env", default="AZURE_DEVOPS_TOKEN")
|
|
731
|
-
setup.add_argument("--ado-mcp-mode", choices=["none","remote","local"]
|
|
732
|
-
setup.add_argument("--ado-mcp-auth", choices=["interactive","azcli","env","envvar","pat"]
|
|
772
|
+
setup.add_argument("--ado-mcp-mode", choices=["none","remote","local"])
|
|
773
|
+
setup.add_argument("--ado-mcp-auth", choices=["interactive","azcli","env","envvar","pat"])
|
|
733
774
|
setup.add_argument("--ado-team")
|
|
734
775
|
setup.add_argument("--ado-field", action="append", default=[])
|
|
735
|
-
setup.add_argument("--environment", choices=["none", "codespaces"]
|
|
736
|
-
setup.add_argument("--codespaces-mode", choices=["off", "on-demand", "always-on"]
|
|
776
|
+
setup.add_argument("--environment", choices=["none", "codespaces"])
|
|
777
|
+
setup.add_argument("--codespaces-mode", choices=["off", "on-demand", "always-on"])
|
|
737
778
|
setup.add_argument("--role-provider", action="append", default=[])
|
|
738
779
|
setup.add_argument("--ai-provider", action="append", default=[])
|
|
739
780
|
setup.add_argument("--worker", action="append", default=[])
|
|
740
|
-
setup.add_argument("--tdd", action=argparse.BooleanOptionalAction, default=
|
|
741
|
-
setup.add_argument("--reviewer-mode", choices=["manual", "automatic", "none"]
|
|
742
|
-
setup.add_argument("--runtime-mode", choices=["project-defined", "batched", "none"]
|
|
781
|
+
setup.add_argument("--tdd", action=argparse.BooleanOptionalAction, default=None)
|
|
782
|
+
setup.add_argument("--reviewer-mode", choices=["manual", "automatic", "none"])
|
|
783
|
+
setup.add_argument("--runtime-mode", choices=["project-defined", "batched", "none"])
|
|
743
784
|
setup.add_argument("--output", default=".ai-workflow.yml")
|
|
744
785
|
setup.add_argument("--plan-output", default=".ai-setup-plan.yml")
|
|
745
786
|
setup.add_argument("--framework-ref", default="vuthethienlong/ai-dev-workflow/.github/workflows/queue-dispatcher.yml@main")
|
|
@@ -764,11 +805,19 @@ def main() -> int:
|
|
|
764
805
|
doc.set_defaults(func=doctor_cmd)
|
|
765
806
|
|
|
766
807
|
args = parser.parse_args()
|
|
767
|
-
if args.command
|
|
808
|
+
if args.command == "init" and args.work_items == "azure-devops":
|
|
768
809
|
if not args.ado_organization or not args.ado_project:
|
|
769
810
|
parser.error("--ado-organization and --ado-project are required for azure-devops")
|
|
770
|
-
if args.command == "setup" and args.
|
|
771
|
-
|
|
811
|
+
if args.command == "setup" and args.non_interactive:
|
|
812
|
+
args.work_items = args.work_items or "github-issues"
|
|
813
|
+
args.github_auth = args.github_auth or "gh-cli"
|
|
814
|
+
args.ado_mcp_mode = args.ado_mcp_mode or "remote"
|
|
815
|
+
args.ado_mcp_auth = args.ado_mcp_auth or "interactive"
|
|
816
|
+
args.environment = args.environment or "none"
|
|
817
|
+
args.codespaces_mode = args.codespaces_mode or "on-demand"
|
|
818
|
+
args.tdd = True if args.tdd is None else args.tdd
|
|
819
|
+
args.reviewer_mode = args.reviewer_mode or "manual"
|
|
820
|
+
args.runtime_mode = args.runtime_mode or "project-defined"
|
|
772
821
|
return args.func(args)
|
|
773
822
|
|
|
774
823
|
|
|
@@ -0,0 +1,446 @@
|
|
|
1
|
+
"""Step-by-step repository setup for ai-dev-workflow."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import os
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from ado_provider import AzureDevOps, build_state_mapping, available_fields
|
|
8
|
+
from github_work_item_provider import GitHubIssues, build_github_mapping, ensure_managed_status_labels
|
|
9
|
+
from setup_context import analyze_repository, setup_agent_context
|
|
10
|
+
from ai_provider import credential_requirements_for_providers, provider_template
|
|
11
|
+
from source_connection import resolve_github_token
|
|
12
|
+
from ado_mcp import build_ado_mcp, ado_mcp_credentials
|
|
13
|
+
from worker_registry import worker_credential_requirements
|
|
14
|
+
|
|
15
|
+
CANONICAL_HELP = (
|
|
16
|
+
"draft, ready, in-progress, in-review, runtime-pending, "
|
|
17
|
+
"release-pending, blocked, completed"
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
DEFAULT_WORKFLOW = {
|
|
21
|
+
"tdd": True,
|
|
22
|
+
"reviewer": {"mode": "manual"},
|
|
23
|
+
"runtime_verification": {"mode": "project-defined"},
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def credential_requirements(work_items: str, environment: str, agents: dict, providers: dict | None = None, github_auth=None, ado_mcp=None, workers: list[dict] | None = None) -> list[dict]:
|
|
28
|
+
requirements = []
|
|
29
|
+
if work_items in ("github-issues", "composite"):
|
|
30
|
+
if (github_auth or {}).get("type") == "gh-cli-oauth":
|
|
31
|
+
requirements.append({
|
|
32
|
+
"name": "GitHub CLI OAuth session",
|
|
33
|
+
"purpose": "Authenticate GitHub API through existing gh auth login session.",
|
|
34
|
+
"required_for_setup": True,
|
|
35
|
+
"store": "GitHub CLI credential store",
|
|
36
|
+
})
|
|
37
|
+
else:
|
|
38
|
+
requirements.append({
|
|
39
|
+
"name": (github_auth or {}).get("env", "GH_TOKEN"),
|
|
40
|
+
"purpose": "Authenticate GitHub API using token environment fallback.",
|
|
41
|
+
"required_for_setup": True,
|
|
42
|
+
"store": "environment or GitHub Actions secret; never commit",
|
|
43
|
+
})
|
|
44
|
+
if work_items in ("azure-devops", "composite"):
|
|
45
|
+
requirements.append({
|
|
46
|
+
"name": "AZURE_DEVOPS_TOKEN",
|
|
47
|
+
"purpose": "Discover ADO process/WIT states and manage work items.",
|
|
48
|
+
"required_for_setup": True,
|
|
49
|
+
"store": "environment/secret store; never commit",
|
|
50
|
+
})
|
|
51
|
+
if environment == "codespaces":
|
|
52
|
+
requirements.append({
|
|
53
|
+
"name": "CODESPACES_TOKEN",
|
|
54
|
+
"purpose": "Provision/start/stop GitHub Codespaces worker environments.",
|
|
55
|
+
"required_for_setup": False,
|
|
56
|
+
"store": "GitHub Actions repository secret",
|
|
57
|
+
})
|
|
58
|
+
requirements.extend(credential_requirements_for_providers(providers or {}))
|
|
59
|
+
requirements.extend(ado_mcp_credentials(ado_mcp or {}))
|
|
60
|
+
requirements.extend(worker_credential_requirements(workers or [], work_items))
|
|
61
|
+
return requirements
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def build_setup_plan(
|
|
65
|
+
root=".",
|
|
66
|
+
work_items="github-issues",
|
|
67
|
+
discovery=None,
|
|
68
|
+
mapping=None,
|
|
69
|
+
workflow=None,
|
|
70
|
+
agents=None,
|
|
71
|
+
environment=None,
|
|
72
|
+
providers=None,
|
|
73
|
+
github_auth=None,
|
|
74
|
+
ado_mcp=None,
|
|
75
|
+
workers=None,
|
|
76
|
+
ado_field_mapping=None,
|
|
77
|
+
) -> dict:
|
|
78
|
+
analysis = analyze_repository(root)
|
|
79
|
+
if providers is None:
|
|
80
|
+
providers = {}
|
|
81
|
+
referenced = {item.get("provider") for item in (agents or {}).values() if isinstance(item, dict)}
|
|
82
|
+
if "codex" in referenced:
|
|
83
|
+
providers["codex"] = provider_template("chatgpt-codex")
|
|
84
|
+
if "manual" in referenced:
|
|
85
|
+
providers["manual"] = provider_template("interactive")
|
|
86
|
+
return {
|
|
87
|
+
"version": 1,
|
|
88
|
+
"status": "review-required",
|
|
89
|
+
"phases": [
|
|
90
|
+
{"id": 1, "name": "connect-source-host", "status": "complete"},
|
|
91
|
+
{"id": 2, "name": "analyze-context", "status": "complete"},
|
|
92
|
+
{"id": 3, "name": "select-work-item-provider", "status": "complete"},
|
|
93
|
+
{"id": 4, "name": "connect-work-item-provider", "status": "complete"},
|
|
94
|
+
{"id": 5, "name": "discover-existing-statuses", "status": "complete" if discovery is not None else "pending"},
|
|
95
|
+
{"id": 6, "name": "confirm-status-mapping", "status": "review-required"},
|
|
96
|
+
{"id": 7, "name": "configure-workflow-and-agents", "status": "review-required"},
|
|
97
|
+
{"id": 8, "name": "register-workers", "status": "review-required"},
|
|
98
|
+
{"id": 9, "name": "configure-environment-and-credentials", "status": "review-required"},
|
|
99
|
+
{"id": 10, "name": "doctor-and-apply", "status": "pending"},
|
|
100
|
+
],
|
|
101
|
+
"context": analysis,
|
|
102
|
+
"setup_agent": setup_agent_context(analysis),
|
|
103
|
+
"source_connection": {"github": github_auth or {}},
|
|
104
|
+
"work_items": {
|
|
105
|
+
"provider": work_items,
|
|
106
|
+
"discovery": discovery,
|
|
107
|
+
"suggested_mapping": mapping or {},
|
|
108
|
+
"mapping_confirmed": False,
|
|
109
|
+
"ado_field_mapping": ado_field_mapping or {},
|
|
110
|
+
},
|
|
111
|
+
"workflow": workflow or DEFAULT_WORKFLOW,
|
|
112
|
+
"agents": agents or {},
|
|
113
|
+
"providers": providers or {},
|
|
114
|
+
"ado_mcp": ado_mcp or {},
|
|
115
|
+
"workers": workers or [],
|
|
116
|
+
"execution_environment": environment or {"type": "none"},
|
|
117
|
+
"credential_requirements": credential_requirements(
|
|
118
|
+
work_items,
|
|
119
|
+
(environment or {"type": "none"}).get("type", "none"),
|
|
120
|
+
agents or {},
|
|
121
|
+
providers or {},
|
|
122
|
+
github_auth,
|
|
123
|
+
ado_mcp,
|
|
124
|
+
workers or [],
|
|
125
|
+
),
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def discover_provider(args):
|
|
130
|
+
if args.work_items in ("azure-devops", "composite"):
|
|
131
|
+
token = os.environ.get(args.ado_token_env)
|
|
132
|
+
if not token:
|
|
133
|
+
raise RuntimeError(f"{args.ado_token_env} is required for ADO discovery")
|
|
134
|
+
discovery = AzureDevOps(args.ado_organization, token).discover(args.ado_project)
|
|
135
|
+
return discovery, build_state_mapping(discovery)
|
|
136
|
+
|
|
137
|
+
token, _ = resolve_github_token(args.github_auth, args.github_token_env)
|
|
138
|
+
discovery = GitHubIssues(args.github_repository, token).discover()
|
|
139
|
+
return discovery, build_github_mapping(discovery)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def interactive_ado_field_mapping(discovery: dict, input_fn=input, output_fn=print) -> dict:
|
|
143
|
+
result = {}
|
|
144
|
+
output_fn("\nADO existing-field mapping (existing fields only; setup will not create fields)")
|
|
145
|
+
for wit in discovery.get("work_item_types", []):
|
|
146
|
+
ref = wit["reference_name"]
|
|
147
|
+
fields = available_fields(discovery, ref)
|
|
148
|
+
if not fields:
|
|
149
|
+
continue
|
|
150
|
+
output_fn(f"\nWork Item Type: {wit['name']} ({ref})")
|
|
151
|
+
by_ref = {f["reference_name"]: f for f in fields}
|
|
152
|
+
configured = {}
|
|
153
|
+
for key in ("workflow_phase","github_issue","worker","agent_role","pull_request","test_status","runtime_status","workflow_profile"):
|
|
154
|
+
field_ref = input_fn(f" Map {key} to existing field reference (blank=skip): ").strip()
|
|
155
|
+
if not field_ref:
|
|
156
|
+
continue
|
|
157
|
+
if field_ref not in by_ref:
|
|
158
|
+
raise ValueError(f"ADO field does not exist for {ref}: {field_ref}")
|
|
159
|
+
default = input_fn(f" Default value for {key} (blank=no default): ")
|
|
160
|
+
item = {"field": field_ref}
|
|
161
|
+
if default != "":
|
|
162
|
+
item["default"] = default
|
|
163
|
+
configured[key] = item
|
|
164
|
+
if configured:
|
|
165
|
+
result[ref] = configured
|
|
166
|
+
return result
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def interactive_mapping(provider: str, discovery: dict, suggestions: dict, input_fn=input, output_fn=print):
|
|
170
|
+
output_fn(f"Canonical states: {CANONICAL_HELP}")
|
|
171
|
+
if provider == "azure-devops":
|
|
172
|
+
result = {}
|
|
173
|
+
for wit in discovery.get("work_item_types", []):
|
|
174
|
+
ref = wit["reference_name"]
|
|
175
|
+
result[ref] = {}
|
|
176
|
+
output_fn(f"\nWork Item Type: {wit['name']} ({ref})")
|
|
177
|
+
for state in wit.get("states", []):
|
|
178
|
+
if state.get("hidden"):
|
|
179
|
+
continue
|
|
180
|
+
name = state["name"]
|
|
181
|
+
default = suggestions.get(ref, {}).get(name, "draft")
|
|
182
|
+
value = input_fn(f" {name} [{default}]: ").strip() or default
|
|
183
|
+
result[ref][name] = value
|
|
184
|
+
return result
|
|
185
|
+
|
|
186
|
+
result = {}
|
|
187
|
+
labels = discovery.get("labels", [])
|
|
188
|
+
for item in labels:
|
|
189
|
+
name = item["name"]
|
|
190
|
+
default = suggestions.get(name)
|
|
191
|
+
prompt_default = default or "skip"
|
|
192
|
+
value = input_fn(f" {name} [{prompt_default}]: ").strip()
|
|
193
|
+
if not value:
|
|
194
|
+
value = default
|
|
195
|
+
if value and value != "skip":
|
|
196
|
+
result[name] = value
|
|
197
|
+
return result
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def apply_github_status_bootstrap(args, mapping: dict) -> tuple[dict, dict, list[str]]:
|
|
201
|
+
token, _ = resolve_github_token(args.github_auth, args.github_token_env)
|
|
202
|
+
api = GitHubIssues(args.github_repository, token)
|
|
203
|
+
before = api.discover()
|
|
204
|
+
created = ensure_managed_status_labels(api, before)
|
|
205
|
+
after = api.discover() if created else before
|
|
206
|
+
refreshed = build_github_mapping(after)
|
|
207
|
+
refreshed.update(mapping)
|
|
208
|
+
return after, refreshed, created
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def render_user_steps(plan: dict) -> str:
|
|
212
|
+
lines = ["Setup phases:"]
|
|
213
|
+
for phase in plan["phases"]:
|
|
214
|
+
lines.append(f"{phase['id']}. {phase['name']} — {phase['status']}")
|
|
215
|
+
lines.append("")
|
|
216
|
+
lines.append("Manual credential/settings checklist:")
|
|
217
|
+
if not plan["credential_requirements"]:
|
|
218
|
+
lines.append("- none")
|
|
219
|
+
for item in plan["credential_requirements"]:
|
|
220
|
+
lines.append(f"- {item['name']}: {item['purpose']} Store: {item['store']}.")
|
|
221
|
+
return "\n".join(lines)
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def _prompt(input_fn, text: str, default: str | None = None) -> str:
|
|
225
|
+
suffix = f" [{default}]" if default not in (None, "") else ""
|
|
226
|
+
value = input_fn(f"{text}{suffix}: ").strip()
|
|
227
|
+
return value if value else (default or "")
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def _choice(input_fn, output_fn, title: str, options: list[tuple[str, str]], default: str) -> str:
|
|
231
|
+
output_fn(title)
|
|
232
|
+
by_number = {}
|
|
233
|
+
allowed = set()
|
|
234
|
+
for index, (value, label) in enumerate(options, 1):
|
|
235
|
+
by_number[str(index)] = value
|
|
236
|
+
allowed.add(value)
|
|
237
|
+
marker = " (default)" if value == default else ""
|
|
238
|
+
output_fn(f" {index}. {label}{marker}")
|
|
239
|
+
while True:
|
|
240
|
+
raw = _prompt(input_fn, "Choose", default)
|
|
241
|
+
value = by_number.get(raw, raw)
|
|
242
|
+
if value in allowed:
|
|
243
|
+
return value
|
|
244
|
+
output_fn("Invalid choice. Try again.")
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def _yes_no(input_fn, text: str, default: bool = False) -> bool:
|
|
248
|
+
hint = "Y/n" if default else "y/N"
|
|
249
|
+
raw = input_fn(f"{text} [{hint}]: ").strip().lower()
|
|
250
|
+
if not raw:
|
|
251
|
+
return default
|
|
252
|
+
return raw in ("y", "yes", "1", "true", "on")
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def interactive_setup_options(args, input_fn=input, output_fn=print):
|
|
256
|
+
"""Collect a complete setup configuration while preserving explicit CLI flags."""
|
|
257
|
+
output_fn("ai-dev-workflow setup wizard")
|
|
258
|
+
output_fn("Press Enter to accept the value shown in brackets.\n")
|
|
259
|
+
|
|
260
|
+
args.root = _prompt(input_fn, "1/9 Project root", args.root or ".")
|
|
261
|
+
args.work_items = args.work_items or _choice(
|
|
262
|
+
input_fn, output_fn, "2/9 Work Item Provider",
|
|
263
|
+
[
|
|
264
|
+
("github-issues", "GitHub Issues only"),
|
|
265
|
+
("azure-devops", "Azure DevOps only"),
|
|
266
|
+
("composite", "Azure DevOps business items + GitHub execution issues"),
|
|
267
|
+
],
|
|
268
|
+
"github-issues",
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
if args.work_items in ("github-issues", "composite"):
|
|
272
|
+
args.github_repository = args.github_repository or _prompt(
|
|
273
|
+
input_fn, "3/9 GitHub repository (owner/repo)"
|
|
274
|
+
)
|
|
275
|
+
args.github_auth = args.github_auth or _choice(
|
|
276
|
+
input_fn, output_fn, "GitHub authentication",
|
|
277
|
+
[("gh-cli", "GitHub CLI login (recommended)"), ("token-env", "Token environment variable")],
|
|
278
|
+
"gh-cli",
|
|
279
|
+
)
|
|
280
|
+
if args.github_auth == "token-env":
|
|
281
|
+
args.github_token_env = _prompt(input_fn, "GitHub token env", args.github_token_env or "GH_TOKEN")
|
|
282
|
+
|
|
283
|
+
if args.work_items in ("azure-devops", "composite"):
|
|
284
|
+
output_fn("4/9 Azure DevOps settings")
|
|
285
|
+
args.ado_organization = args.ado_organization or _prompt(input_fn, "ADO organization")
|
|
286
|
+
args.ado_project = args.ado_project or _prompt(input_fn, "ADO project")
|
|
287
|
+
args.ado_token_env = _prompt(input_fn, "ADO token env", args.ado_token_env or "AZURE_DEVOPS_TOKEN")
|
|
288
|
+
args.ado_mcp_mode = args.ado_mcp_mode or _choice(
|
|
289
|
+
input_fn, output_fn, "ADO MCP mode",
|
|
290
|
+
[("none", "Disabled"), ("remote", "Remote MCP"), ("local", "Local MCP")],
|
|
291
|
+
"remote",
|
|
292
|
+
)
|
|
293
|
+
if args.ado_mcp_mode != "none":
|
|
294
|
+
args.ado_mcp_auth = args.ado_mcp_auth or _choice(
|
|
295
|
+
input_fn, output_fn, "ADO MCP authentication",
|
|
296
|
+
[
|
|
297
|
+
("interactive", "Interactive"),
|
|
298
|
+
("azcli", "Azure CLI"),
|
|
299
|
+
("env", "Environment"),
|
|
300
|
+
("envvar", "Environment variable"),
|
|
301
|
+
("pat", "PAT"),
|
|
302
|
+
],
|
|
303
|
+
"interactive",
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
workspace = interactive_workspace_options(args, input_fn=input_fn, output_fn=output_fn)
|
|
307
|
+
args.workspace_config = workspace
|
|
308
|
+
|
|
309
|
+
output_fn("5/10 AI providers")
|
|
310
|
+
if not args.ai_provider and _yes_no(input_fn, "Add Claude Code / Anthropic provider?", False):
|
|
311
|
+
provider_id = _prompt(input_fn, "Provider id", "claude")
|
|
312
|
+
env = _prompt(input_fn, "Anthropic API key env", "ANTHROPIC_API_KEY")
|
|
313
|
+
args.ai_provider.append(f"{provider_id}=anthropic,auth=api-key-env,env={env}")
|
|
314
|
+
|
|
315
|
+
output_fn("6/10 Agent role assignment")
|
|
316
|
+
role_defaults = {
|
|
317
|
+
"SetupAgent": "manual",
|
|
318
|
+
"ArchitectAgent": "manual",
|
|
319
|
+
"TesterAgent": "codex",
|
|
320
|
+
"ImplementerAgent": "codex",
|
|
321
|
+
"ReviewerAgent": "manual",
|
|
322
|
+
"RuntimeVerifierAgent": "project-defined",
|
|
323
|
+
}
|
|
324
|
+
if not args.role_provider:
|
|
325
|
+
for role, default in role_defaults.items():
|
|
326
|
+
provider = _prompt(input_fn, f"{role} provider", default)
|
|
327
|
+
args.role_provider.append(f"{role}={provider}")
|
|
328
|
+
|
|
329
|
+
output_fn("7/10 Workers")
|
|
330
|
+
if not args.worker:
|
|
331
|
+
while _yes_no(input_fn, "Register a worker?", False):
|
|
332
|
+
worker_id = _prompt(input_fn, "Worker id")
|
|
333
|
+
mode = _choice(
|
|
334
|
+
input_fn, output_fn, "Worker mode",
|
|
335
|
+
[("planner", "Planner"), ("executor", "Executor"), ("hybrid", "Hybrid")],
|
|
336
|
+
"hybrid",
|
|
337
|
+
)
|
|
338
|
+
provider = _prompt(input_fn, "Provider id", "codex")
|
|
339
|
+
environment = _choice(
|
|
340
|
+
input_fn, output_fn, "Worker environment",
|
|
341
|
+
[("local", "Local"), ("codespaces", "GitHub Codespaces"), ("cloud", "Cloud")],
|
|
342
|
+
"local",
|
|
343
|
+
)
|
|
344
|
+
if mode == "planner":
|
|
345
|
+
default_roles = "SetupAgent|ArchitectAgent|ReviewerAgent"
|
|
346
|
+
elif mode == "executor":
|
|
347
|
+
default_roles = "TesterAgent|ImplementerAgent|RuntimeVerifierAgent"
|
|
348
|
+
else:
|
|
349
|
+
default_roles = "ArchitectAgent|TesterAgent|ImplementerAgent|ReviewerAgent"
|
|
350
|
+
roles = _prompt(input_fn, "Roles separated by |", default_roles)
|
|
351
|
+
capacity = _prompt(input_fn, "Capacity", "1")
|
|
352
|
+
parts = [mode, f"provider={provider}", f"environment={environment}", f"roles={roles}", f"capacity={capacity}"]
|
|
353
|
+
if environment == "local" and mode in ("executor", "hybrid"):
|
|
354
|
+
assignee = _prompt(input_fn, "GitHub/Work Item assignee for local claim ownership")
|
|
355
|
+
if assignee:
|
|
356
|
+
parts.append(f"assignee={assignee}")
|
|
357
|
+
parts.append("managed=false")
|
|
358
|
+
args.worker.append(f"{worker_id}=" + ",".join(parts))
|
|
359
|
+
|
|
360
|
+
args.environment = args.environment or _choice(
|
|
361
|
+
input_fn, output_fn, "8/10 Framework execution environment",
|
|
362
|
+
[("none", "No managed environment"), ("codespaces", "GitHub Codespaces")],
|
|
363
|
+
"none",
|
|
364
|
+
)
|
|
365
|
+
if args.environment == "codespaces":
|
|
366
|
+
args.codespaces_mode = args.codespaces_mode or _choice(
|
|
367
|
+
input_fn, output_fn, "Codespaces mode",
|
|
368
|
+
[("off", "Off"), ("on-demand", "On demand"), ("always-on", "Always on")],
|
|
369
|
+
"on-demand",
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
output_fn("9/10 Workflow policy")
|
|
373
|
+
if args.tdd is None:
|
|
374
|
+
args.tdd = _yes_no(input_fn, "Require TDD/test design", True)
|
|
375
|
+
args.reviewer_mode = args.reviewer_mode or _choice(
|
|
376
|
+
input_fn, output_fn, "Reviewer mode",
|
|
377
|
+
[("manual", "Manual"), ("automatic", "Automatic"), ("none", "Disabled")],
|
|
378
|
+
"manual",
|
|
379
|
+
)
|
|
380
|
+
args.runtime_mode = args.runtime_mode or _choice(
|
|
381
|
+
input_fn, output_fn, "Runtime verification",
|
|
382
|
+
[("project-defined", "Project-defined"), ("batched", "Batched runtime verification"), ("none", "Disabled")],
|
|
383
|
+
"project-defined",
|
|
384
|
+
)
|
|
385
|
+
output_fn("10/10 Review will be shown after provider discovery and status mapping.")
|
|
386
|
+
return args
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
def confirm_setup_plan(plan: dict, input_fn=input, output_fn=print) -> bool:
|
|
390
|
+
output_fn("\nReview")
|
|
391
|
+
output_fn(f"- Work Item Provider: {plan['work_items']['provider']}")
|
|
392
|
+
output_fn(f"- AI providers: {', '.join(plan.get('providers', {}).keys()) or 'none'}")
|
|
393
|
+
output_fn(f"- Workers: {len(plan.get('workers', []))}")
|
|
394
|
+
output_fn(f"- Environment: {plan.get('execution_environment', {}).get('type', 'none')}")
|
|
395
|
+
workflow = plan.get("workflow", {})
|
|
396
|
+
output_fn(f"- TDD: {workflow.get('tdd')}")
|
|
397
|
+
output_fn(f"- Reviewer: {(workflow.get('reviewer') or {}).get('mode')}")
|
|
398
|
+
output_fn(f"- Runtime verification: {(workflow.get('runtime_verification') or {}).get('mode')}")
|
|
399
|
+
return _yes_no(input_fn, "Apply this configuration?", True)
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
def interactive_workspace_options(args, input_fn=input, output_fn=print) -> dict | None:
|
|
403
|
+
"""Collect optional multi-repository workspace configuration."""
|
|
404
|
+
if not _yes_no(input_fn, "Use a multi-repository workspace?", False):
|
|
405
|
+
return None
|
|
406
|
+
|
|
407
|
+
output_fn("\nMulti-repository workspace")
|
|
408
|
+
name = _prompt(input_fn, "Workspace name", "ai-workspace")
|
|
409
|
+
repositories = []
|
|
410
|
+
|
|
411
|
+
primary_repo = args.github_repository or _prompt(input_fn, "Primary repository (owner/repo)")
|
|
412
|
+
primary_ref = _prompt(input_fn, "Primary repository ref", "main")
|
|
413
|
+
repositories.append({
|
|
414
|
+
"repository": primary_repo,
|
|
415
|
+
"purpose": _prompt(input_fn, "Primary repository purpose", "primary-application"),
|
|
416
|
+
"ref": primary_ref,
|
|
417
|
+
"access": "read-write",
|
|
418
|
+
"context": ["AGENTS.md", "CURRENT.md", "docs/**"],
|
|
419
|
+
})
|
|
420
|
+
|
|
421
|
+
while _yes_no(input_fn, "Add another repository?", False):
|
|
422
|
+
repository = _prompt(input_fn, "Repository (owner/repo)")
|
|
423
|
+
purpose = _prompt(input_fn, "Purpose", "shared-dependency")
|
|
424
|
+
ref = _prompt(input_fn, "Ref", "main")
|
|
425
|
+
access = _choice(
|
|
426
|
+
input_fn, output_fn, "Access",
|
|
427
|
+
[("read", "Read only"), ("read-write", "Read/write")],
|
|
428
|
+
"read",
|
|
429
|
+
)
|
|
430
|
+
context_raw = _prompt(input_fn, "Context paths separated by |", "AGENTS.md|docs/**")
|
|
431
|
+
repositories.append({
|
|
432
|
+
"repository": repository,
|
|
433
|
+
"purpose": purpose,
|
|
434
|
+
"ref": ref,
|
|
435
|
+
"access": access,
|
|
436
|
+
"context": [item for item in context_raw.split("|") if item],
|
|
437
|
+
})
|
|
438
|
+
|
|
439
|
+
default_repository = _prompt(input_fn, "Default planning repository", primary_repo)
|
|
440
|
+
return {
|
|
441
|
+
"version": 1,
|
|
442
|
+
"workspace": {"name": name},
|
|
443
|
+
"repositories": repositories,
|
|
444
|
+
"planning": {"default_repository": default_repository},
|
|
445
|
+
"initiatives": {},
|
|
446
|
+
}
|
|
@@ -1,221 +0,0 @@
|
|
|
1
|
-
"""Step-by-step repository setup for ai-dev-workflow."""
|
|
2
|
-
from __future__ import annotations
|
|
3
|
-
|
|
4
|
-
import os
|
|
5
|
-
from pathlib import Path
|
|
6
|
-
|
|
7
|
-
from ado_provider import AzureDevOps, build_state_mapping, available_fields
|
|
8
|
-
from github_work_item_provider import GitHubIssues, build_github_mapping, ensure_managed_status_labels
|
|
9
|
-
from setup_context import analyze_repository, setup_agent_context
|
|
10
|
-
from ai_provider import credential_requirements_for_providers, provider_template
|
|
11
|
-
from source_connection import resolve_github_token
|
|
12
|
-
from ado_mcp import build_ado_mcp, ado_mcp_credentials
|
|
13
|
-
from worker_registry import worker_credential_requirements
|
|
14
|
-
|
|
15
|
-
CANONICAL_HELP = (
|
|
16
|
-
"draft, ready, in-progress, in-review, runtime-pending, "
|
|
17
|
-
"release-pending, blocked, completed"
|
|
18
|
-
)
|
|
19
|
-
|
|
20
|
-
DEFAULT_WORKFLOW = {
|
|
21
|
-
"tdd": True,
|
|
22
|
-
"reviewer": {"mode": "manual"},
|
|
23
|
-
"runtime_verification": {"mode": "project-defined"},
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
def credential_requirements(work_items: str, environment: str, agents: dict, providers: dict | None = None, github_auth=None, ado_mcp=None, workers: list[dict] | None = None) -> list[dict]:
|
|
28
|
-
requirements = []
|
|
29
|
-
if work_items in ("github-issues", "composite"):
|
|
30
|
-
if (github_auth or {}).get("type") == "gh-cli-oauth":
|
|
31
|
-
requirements.append({
|
|
32
|
-
"name": "GitHub CLI OAuth session",
|
|
33
|
-
"purpose": "Authenticate GitHub API through existing gh auth login session.",
|
|
34
|
-
"required_for_setup": True,
|
|
35
|
-
"store": "GitHub CLI credential store",
|
|
36
|
-
})
|
|
37
|
-
else:
|
|
38
|
-
requirements.append({
|
|
39
|
-
"name": (github_auth or {}).get("env", "GH_TOKEN"),
|
|
40
|
-
"purpose": "Authenticate GitHub API using token environment fallback.",
|
|
41
|
-
"required_for_setup": True,
|
|
42
|
-
"store": "environment or GitHub Actions secret; never commit",
|
|
43
|
-
})
|
|
44
|
-
if work_items in ("azure-devops", "composite"):
|
|
45
|
-
requirements.append({
|
|
46
|
-
"name": "AZURE_DEVOPS_TOKEN",
|
|
47
|
-
"purpose": "Discover ADO process/WIT states and manage work items.",
|
|
48
|
-
"required_for_setup": True,
|
|
49
|
-
"store": "environment/secret store; never commit",
|
|
50
|
-
})
|
|
51
|
-
if environment == "codespaces":
|
|
52
|
-
requirements.append({
|
|
53
|
-
"name": "CODESPACES_TOKEN",
|
|
54
|
-
"purpose": "Provision/start/stop GitHub Codespaces worker environments.",
|
|
55
|
-
"required_for_setup": False,
|
|
56
|
-
"store": "GitHub Actions repository secret",
|
|
57
|
-
})
|
|
58
|
-
requirements.extend(credential_requirements_for_providers(providers or {}))
|
|
59
|
-
requirements.extend(ado_mcp_credentials(ado_mcp or {}))
|
|
60
|
-
requirements.extend(worker_credential_requirements(workers or [], work_items))
|
|
61
|
-
return requirements
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
def build_setup_plan(
|
|
65
|
-
root=".",
|
|
66
|
-
work_items="github-issues",
|
|
67
|
-
discovery=None,
|
|
68
|
-
mapping=None,
|
|
69
|
-
workflow=None,
|
|
70
|
-
agents=None,
|
|
71
|
-
environment=None,
|
|
72
|
-
providers=None,
|
|
73
|
-
github_auth=None,
|
|
74
|
-
ado_mcp=None,
|
|
75
|
-
workers=None,
|
|
76
|
-
ado_field_mapping=None,
|
|
77
|
-
) -> dict:
|
|
78
|
-
analysis = analyze_repository(root)
|
|
79
|
-
if providers is None:
|
|
80
|
-
providers = {}
|
|
81
|
-
referenced = {item.get("provider") for item in (agents or {}).values() if isinstance(item, dict)}
|
|
82
|
-
if "codex" in referenced:
|
|
83
|
-
providers["codex"] = provider_template("chatgpt-codex")
|
|
84
|
-
if "manual" in referenced:
|
|
85
|
-
providers["manual"] = provider_template("interactive")
|
|
86
|
-
return {
|
|
87
|
-
"version": 1,
|
|
88
|
-
"status": "review-required",
|
|
89
|
-
"phases": [
|
|
90
|
-
{"id": 1, "name": "connect-source-host", "status": "complete"},
|
|
91
|
-
{"id": 2, "name": "analyze-context", "status": "complete"},
|
|
92
|
-
{"id": 3, "name": "select-work-item-provider", "status": "complete"},
|
|
93
|
-
{"id": 4, "name": "connect-work-item-provider", "status": "complete"},
|
|
94
|
-
{"id": 5, "name": "discover-existing-statuses", "status": "complete" if discovery is not None else "pending"},
|
|
95
|
-
{"id": 6, "name": "confirm-status-mapping", "status": "review-required"},
|
|
96
|
-
{"id": 7, "name": "configure-workflow-and-agents", "status": "review-required"},
|
|
97
|
-
{"id": 8, "name": "register-workers", "status": "review-required"},
|
|
98
|
-
{"id": 9, "name": "configure-environment-and-credentials", "status": "review-required"},
|
|
99
|
-
{"id": 10, "name": "doctor-and-apply", "status": "pending"},
|
|
100
|
-
],
|
|
101
|
-
"context": analysis,
|
|
102
|
-
"setup_agent": setup_agent_context(analysis),
|
|
103
|
-
"source_connection": {"github": github_auth or {}},
|
|
104
|
-
"work_items": {
|
|
105
|
-
"provider": work_items,
|
|
106
|
-
"discovery": discovery,
|
|
107
|
-
"suggested_mapping": mapping or {},
|
|
108
|
-
"mapping_confirmed": False,
|
|
109
|
-
"ado_field_mapping": ado_field_mapping or {},
|
|
110
|
-
},
|
|
111
|
-
"workflow": workflow or DEFAULT_WORKFLOW,
|
|
112
|
-
"agents": agents or {},
|
|
113
|
-
"providers": providers or {},
|
|
114
|
-
"ado_mcp": ado_mcp or {},
|
|
115
|
-
"workers": workers or [],
|
|
116
|
-
"execution_environment": environment or {"type": "none"},
|
|
117
|
-
"credential_requirements": credential_requirements(
|
|
118
|
-
work_items,
|
|
119
|
-
(environment or {"type": "none"}).get("type", "none"),
|
|
120
|
-
agents or {},
|
|
121
|
-
providers or {},
|
|
122
|
-
github_auth,
|
|
123
|
-
ado_mcp,
|
|
124
|
-
workers or [],
|
|
125
|
-
),
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
def discover_provider(args):
|
|
130
|
-
if args.work_items in ("azure-devops", "composite"):
|
|
131
|
-
token = os.environ.get(args.ado_token_env)
|
|
132
|
-
if not token:
|
|
133
|
-
raise RuntimeError(f"{args.ado_token_env} is required for ADO discovery")
|
|
134
|
-
discovery = AzureDevOps(args.ado_organization, token).discover(args.ado_project)
|
|
135
|
-
return discovery, build_state_mapping(discovery)
|
|
136
|
-
|
|
137
|
-
token, _ = resolve_github_token(args.github_auth, args.github_token_env)
|
|
138
|
-
discovery = GitHubIssues(args.github_repository, token).discover()
|
|
139
|
-
return discovery, build_github_mapping(discovery)
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
def interactive_ado_field_mapping(discovery: dict, input_fn=input, output_fn=print) -> dict:
|
|
143
|
-
result = {}
|
|
144
|
-
output_fn("\nADO existing-field mapping (existing fields only; setup will not create fields)")
|
|
145
|
-
for wit in discovery.get("work_item_types", []):
|
|
146
|
-
ref = wit["reference_name"]
|
|
147
|
-
fields = available_fields(discovery, ref)
|
|
148
|
-
if not fields:
|
|
149
|
-
continue
|
|
150
|
-
output_fn(f"\nWork Item Type: {wit['name']} ({ref})")
|
|
151
|
-
by_ref = {f["reference_name"]: f for f in fields}
|
|
152
|
-
configured = {}
|
|
153
|
-
for key in ("workflow_phase","github_issue","worker","agent_role","pull_request","test_status","runtime_status","workflow_profile"):
|
|
154
|
-
field_ref = input_fn(f" Map {key} to existing field reference (blank=skip): ").strip()
|
|
155
|
-
if not field_ref:
|
|
156
|
-
continue
|
|
157
|
-
if field_ref not in by_ref:
|
|
158
|
-
raise ValueError(f"ADO field does not exist for {ref}: {field_ref}")
|
|
159
|
-
default = input_fn(f" Default value for {key} (blank=no default): ")
|
|
160
|
-
item = {"field": field_ref}
|
|
161
|
-
if default != "":
|
|
162
|
-
item["default"] = default
|
|
163
|
-
configured[key] = item
|
|
164
|
-
if configured:
|
|
165
|
-
result[ref] = configured
|
|
166
|
-
return result
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
def interactive_mapping(provider: str, discovery: dict, suggestions: dict, input_fn=input, output_fn=print):
|
|
170
|
-
output_fn(f"Canonical states: {CANONICAL_HELP}")
|
|
171
|
-
if provider == "azure-devops":
|
|
172
|
-
result = {}
|
|
173
|
-
for wit in discovery.get("work_item_types", []):
|
|
174
|
-
ref = wit["reference_name"]
|
|
175
|
-
result[ref] = {}
|
|
176
|
-
output_fn(f"\nWork Item Type: {wit['name']} ({ref})")
|
|
177
|
-
for state in wit.get("states", []):
|
|
178
|
-
if state.get("hidden"):
|
|
179
|
-
continue
|
|
180
|
-
name = state["name"]
|
|
181
|
-
default = suggestions.get(ref, {}).get(name, "draft")
|
|
182
|
-
value = input_fn(f" {name} [{default}]: ").strip() or default
|
|
183
|
-
result[ref][name] = value
|
|
184
|
-
return result
|
|
185
|
-
|
|
186
|
-
result = {}
|
|
187
|
-
labels = discovery.get("labels", [])
|
|
188
|
-
for item in labels:
|
|
189
|
-
name = item["name"]
|
|
190
|
-
default = suggestions.get(name)
|
|
191
|
-
prompt_default = default or "skip"
|
|
192
|
-
value = input_fn(f" {name} [{prompt_default}]: ").strip()
|
|
193
|
-
if not value:
|
|
194
|
-
value = default
|
|
195
|
-
if value and value != "skip":
|
|
196
|
-
result[name] = value
|
|
197
|
-
return result
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
def apply_github_status_bootstrap(args, mapping: dict) -> tuple[dict, dict, list[str]]:
|
|
201
|
-
token, _ = resolve_github_token(args.github_auth, args.github_token_env)
|
|
202
|
-
api = GitHubIssues(args.github_repository, token)
|
|
203
|
-
before = api.discover()
|
|
204
|
-
created = ensure_managed_status_labels(api, before)
|
|
205
|
-
after = api.discover() if created else before
|
|
206
|
-
refreshed = build_github_mapping(after)
|
|
207
|
-
refreshed.update(mapping)
|
|
208
|
-
return after, refreshed, created
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
def render_user_steps(plan: dict) -> str:
|
|
212
|
-
lines = ["Setup phases:"]
|
|
213
|
-
for phase in plan["phases"]:
|
|
214
|
-
lines.append(f"{phase['id']}. {phase['name']} — {phase['status']}")
|
|
215
|
-
lines.append("")
|
|
216
|
-
lines.append("Manual credential/settings checklist:")
|
|
217
|
-
if not plan["credential_requirements"]:
|
|
218
|
-
lines.append("- none")
|
|
219
|
-
for item in plan["credential_requirements"]:
|
|
220
|
-
lines.append(f"- {item['name']}: {item['purpose']} Store: {item['store']}.")
|
|
221
|
-
return "\n".join(lines)
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ai_dev_workflow-0.1.1 → ai_dev_workflow-0.2.0}/tools/ai_dev_workflow.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{ai_dev_workflow-0.1.1 → ai_dev_workflow-0.2.0}/tools/ai_dev_workflow.egg-info/entry_points.txt
RENAMED
|
File without changes
|
|
File without changes
|
{ai_dev_workflow-0.1.1 → ai_dev_workflow-0.2.0}/tools/ai_dev_workflow.egg-info/top_level.txt
RENAMED
|
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
|