ai-dev-workflow 0.1.0__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.0 → ai_dev_workflow-0.2.0}/PKG-INFO +1 -1
- {ai_dev_workflow-0.1.0 → ai_dev_workflow-0.2.0}/pyproject.toml +2 -1
- {ai_dev_workflow-0.1.0 → ai_dev_workflow-0.2.0}/tests/test_ai_workflow_cli.py +1 -1
- ai_dev_workflow-0.2.0/tests/test_instruction_adapter.py +54 -0
- {ai_dev_workflow-0.1.0 → ai_dev_workflow-0.2.0}/tests/test_setup_wizard.py +74 -1
- {ai_dev_workflow-0.1.0 → ai_dev_workflow-0.2.0}/tools/ai_dev_workflow.egg-info/PKG-INFO +1 -1
- {ai_dev_workflow-0.1.0 → ai_dev_workflow-0.2.0}/tools/ai_dev_workflow.egg-info/SOURCES.txt +2 -0
- {ai_dev_workflow-0.1.0 → ai_dev_workflow-0.2.0}/tools/ai_dev_workflow.egg-info/top_level.txt +1 -0
- {ai_dev_workflow-0.1.0 → ai_dev_workflow-0.2.0}/tools/ai_workflow_cli.py +85 -21
- ai_dev_workflow-0.2.0/tools/instruction_adapter.py +77 -0
- ai_dev_workflow-0.2.0/tools/setup_wizard.py +446 -0
- ai_dev_workflow-0.1.0/tools/setup_wizard.py +0 -221
- {ai_dev_workflow-0.1.0 → ai_dev_workflow-0.2.0}/README.md +0 -0
- {ai_dev_workflow-0.1.0 → ai_dev_workflow-0.2.0}/setup.cfg +0 -0
- {ai_dev_workflow-0.1.0 → ai_dev_workflow-0.2.0}/tests/test_ado_provider.py +0 -0
- {ai_dev_workflow-0.1.0 → ai_dev_workflow-0.2.0}/tests/test_ai_provider.py +0 -0
- {ai_dev_workflow-0.1.0 → ai_dev_workflow-0.2.0}/tests/test_bootstrap_project.py +0 -0
- {ai_dev_workflow-0.1.0 → ai_dev_workflow-0.2.0}/tests/test_codespaces_environment.py +0 -0
- {ai_dev_workflow-0.1.0 → ai_dev_workflow-0.2.0}/tests/test_codex_cloud_executor.py +0 -0
- {ai_dev_workflow-0.1.0 → ai_dev_workflow-0.2.0}/tests/test_composite_work_item.py +0 -0
- {ai_dev_workflow-0.1.0 → ai_dev_workflow-0.2.0}/tests/test_connection_and_mcp.py +0 -0
- {ai_dev_workflow-0.1.0 → ai_dev_workflow-0.2.0}/tests/test_framework_update.py +0 -0
- {ai_dev_workflow-0.1.0 → ai_dev_workflow-0.2.0}/tests/test_general_init.py +0 -0
- {ai_dev_workflow-0.1.0 → ai_dev_workflow-0.2.0}/tests/test_issue_publisher.py +0 -0
- {ai_dev_workflow-0.1.0 → ai_dev_workflow-0.2.0}/tests/test_package_install.py +0 -0
- {ai_dev_workflow-0.1.0 → ai_dev_workflow-0.2.0}/tests/test_planning_context.py +0 -0
- {ai_dev_workflow-0.1.0 → ai_dev_workflow-0.2.0}/tests/test_queue_dispatcher.py +0 -0
- {ai_dev_workflow-0.1.0 → ai_dev_workflow-0.2.0}/tests/test_worker_registry.py +0 -0
- {ai_dev_workflow-0.1.0 → ai_dev_workflow-0.2.0}/tools/ado_mcp.py +0 -0
- {ai_dev_workflow-0.1.0 → ai_dev_workflow-0.2.0}/tools/ado_provider.py +0 -0
- {ai_dev_workflow-0.1.0 → ai_dev_workflow-0.2.0}/tools/ai_dev_workflow.egg-info/dependency_links.txt +0 -0
- {ai_dev_workflow-0.1.0 → ai_dev_workflow-0.2.0}/tools/ai_dev_workflow.egg-info/entry_points.txt +0 -0
- {ai_dev_workflow-0.1.0 → ai_dev_workflow-0.2.0}/tools/ai_dev_workflow.egg-info/requires.txt +0 -0
- {ai_dev_workflow-0.1.0 → ai_dev_workflow-0.2.0}/tools/ai_provider.py +0 -0
- {ai_dev_workflow-0.1.0 → ai_dev_workflow-0.2.0}/tools/composite_work_item.py +0 -0
- {ai_dev_workflow-0.1.0 → ai_dev_workflow-0.2.0}/tools/framework_update.py +0 -0
- {ai_dev_workflow-0.1.0 → ai_dev_workflow-0.2.0}/tools/github_work_item_provider.py +0 -0
- {ai_dev_workflow-0.1.0 → ai_dev_workflow-0.2.0}/tools/issue_publisher.py +0 -0
- {ai_dev_workflow-0.1.0 → ai_dev_workflow-0.2.0}/tools/planning_context.py +0 -0
- {ai_dev_workflow-0.1.0 → ai_dev_workflow-0.2.0}/tools/setup_context.py +0 -0
- {ai_dev_workflow-0.1.0 → ai_dev_workflow-0.2.0}/tools/source_connection.py +0 -0
- {ai_dev_workflow-0.1.0 → ai_dev_workflow-0.2.0}/tools/work_item_provider.py +0 -0
- {ai_dev_workflow-0.1.0 → ai_dev_workflow-0.2.0}/tools/work_item_registry.py +0 -0
- {ai_dev_workflow-0.1.0 → ai_dev_workflow-0.2.0}/tools/worker_registry.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "ai-dev-workflow"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.2.0"
|
|
8
8
|
description = "Provider-neutral AI-assisted development workflow and CLI"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -37,6 +37,7 @@ py-modules = [
|
|
|
37
37
|
"composite_work_item",
|
|
38
38
|
"framework_update",
|
|
39
39
|
"github_work_item_provider",
|
|
40
|
+
"instruction_adapter",
|
|
40
41
|
"issue_publisher",
|
|
41
42
|
"planning_context",
|
|
42
43
|
"setup_context",
|
|
@@ -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)
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import tempfile, unittest
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
from instruction_adapter import (
|
|
5
|
+
START, END, adapter_for_provider, ensure_provider_instruction_file, merge_managed_block
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
class Tests(unittest.TestCase):
|
|
9
|
+
def test_codex_uses_agents_directly(self):
|
|
10
|
+
adapter = adapter_for_provider("chatgpt-codex")
|
|
11
|
+
self.assertEqual(adapter["shared"], "AGENTS.md")
|
|
12
|
+
self.assertIsNone(adapter["provider_file"])
|
|
13
|
+
|
|
14
|
+
def test_claude_adapter_targets_claude_md(self):
|
|
15
|
+
adapter = adapter_for_provider("anthropic")
|
|
16
|
+
self.assertEqual(adapter["provider_file"], "CLAUDE.md")
|
|
17
|
+
self.assertEqual(adapter["merge_policy"], "preserve-existing")
|
|
18
|
+
|
|
19
|
+
def test_existing_user_rules_are_preserved(self):
|
|
20
|
+
original = "# Claude rules\n\nNever modify generated files.\n"
|
|
21
|
+
merged = merge_managed_block(original)
|
|
22
|
+
self.assertTrue(merged.startswith(original))
|
|
23
|
+
self.assertIn(START, merged)
|
|
24
|
+
self.assertIn("AGENTS.md", merged)
|
|
25
|
+
self.assertIn("CURRENT.md", merged)
|
|
26
|
+
|
|
27
|
+
def test_existing_managed_block_is_replaced_not_duplicated(self):
|
|
28
|
+
original = f"before\n{START}\nold\n{END}\nafter\n"
|
|
29
|
+
merged = merge_managed_block(original)
|
|
30
|
+
self.assertEqual(merged.count(START), 1)
|
|
31
|
+
self.assertEqual(merged.count(END), 1)
|
|
32
|
+
self.assertTrue(merged.startswith("before\n"))
|
|
33
|
+
self.assertTrue(merged.endswith("\nafter\n"))
|
|
34
|
+
|
|
35
|
+
def test_idempotent_file_update(self):
|
|
36
|
+
with tempfile.TemporaryDirectory() as td:
|
|
37
|
+
path = Path(td) / "CLAUDE.md"
|
|
38
|
+
path.write_text("# Mine\n", encoding="utf-8")
|
|
39
|
+
first = ensure_provider_instruction_file(td, "anthropic")
|
|
40
|
+
once = path.read_text(encoding="utf-8")
|
|
41
|
+
second = ensure_provider_instruction_file(td, "anthropic")
|
|
42
|
+
twice = path.read_text(encoding="utf-8")
|
|
43
|
+
self.assertTrue(first["changed"])
|
|
44
|
+
self.assertFalse(second["changed"])
|
|
45
|
+
self.assertEqual(once, twice)
|
|
46
|
+
self.assertIn("# Mine", twice)
|
|
47
|
+
|
|
48
|
+
def test_current_is_conditional_context(self):
|
|
49
|
+
merged = merge_managed_block("")
|
|
50
|
+
self.assertIn("active ai-dev-workflow task", merged)
|
|
51
|
+
self.assertIn("not required for unrelated sessions", merged)
|
|
52
|
+
|
|
53
|
+
if __name__ == "__main__":
|
|
54
|
+
unittest.main()
|
|
@@ -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()
|
|
@@ -10,6 +10,7 @@ tests/test_composite_work_item.py
|
|
|
10
10
|
tests/test_connection_and_mcp.py
|
|
11
11
|
tests/test_framework_update.py
|
|
12
12
|
tests/test_general_init.py
|
|
13
|
+
tests/test_instruction_adapter.py
|
|
13
14
|
tests/test_issue_publisher.py
|
|
14
15
|
tests/test_package_install.py
|
|
15
16
|
tests/test_planning_context.py
|
|
@@ -23,6 +24,7 @@ tools/ai_workflow_cli.py
|
|
|
23
24
|
tools/composite_work_item.py
|
|
24
25
|
tools/framework_update.py
|
|
25
26
|
tools/github_work_item_provider.py
|
|
27
|
+
tools/instruction_adapter.py
|
|
26
28
|
tools/issue_publisher.py
|
|
27
29
|
tools/planning_context.py
|
|
28
30
|
tools/setup_context.py
|
|
@@ -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
|
|
@@ -21,6 +21,8 @@ from work_item_registry import validate_work_item_provider
|
|
|
21
21
|
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
|
+
from instruction_adapter import adapter_for_provider, ensure_provider_instruction_file
|
|
25
|
+
from planning_context import normalize_workspace
|
|
24
26
|
|
|
25
27
|
|
|
26
28
|
DEFAULT_ROLE_PROVIDERS = {
|
|
@@ -32,7 +34,7 @@ DEFAULT_ROLE_PROVIDERS = {
|
|
|
32
34
|
"RuntimeVerifierAgent": "project-defined",
|
|
33
35
|
}
|
|
34
36
|
|
|
35
|
-
PACKAGE_VERSION = "0.
|
|
37
|
+
PACKAGE_VERSION = "0.2.0"
|
|
36
38
|
|
|
37
39
|
ROLE_KEYS = {
|
|
38
40
|
"SetupAgent": "setup",
|
|
@@ -385,6 +387,7 @@ def build_environment(environment: str, codespaces_mode: str) -> dict:
|
|
|
385
387
|
|
|
386
388
|
|
|
387
389
|
def perform_init(args, ado_api_factory=AzureDevOps) -> tuple[dict, dict | None]:
|
|
390
|
+
root = Path(getattr(args, "root", "."))
|
|
388
391
|
roles = parse_role_provider(args.role_provider)
|
|
389
392
|
discovery = None
|
|
390
393
|
if args.work_items == "azure-devops":
|
|
@@ -406,6 +409,8 @@ def perform_init(args, ado_api_factory=AzureDevOps) -> tuple[dict, dict | None]:
|
|
|
406
409
|
)
|
|
407
410
|
|
|
408
411
|
output = Path(args.output)
|
|
412
|
+
if not output.is_absolute():
|
|
413
|
+
output = root / output
|
|
409
414
|
existing = _load_yaml(output) if output.exists() else {}
|
|
410
415
|
config = generated if args.force or not existing else merge_owned(existing, generated)
|
|
411
416
|
output.write_text(_dump_yaml(config), encoding="utf-8")
|
|
@@ -418,17 +423,17 @@ def perform_init(args, ado_api_factory=AzureDevOps) -> tuple[dict, dict | None]:
|
|
|
418
423
|
)
|
|
419
424
|
|
|
420
425
|
if not args.no_files:
|
|
421
|
-
ensure_file(
|
|
422
|
-
ensure_file(
|
|
426
|
+
ensure_file(root / "AGENTS.md", generated_agents_md(), args.force)
|
|
427
|
+
ensure_file(root / "CURRENT.md", generated_current_md(), args.force)
|
|
423
428
|
if args.environment == "codespaces":
|
|
424
429
|
ensure_file(
|
|
425
|
-
|
|
430
|
+
root / ".devcontainer/devcontainer.json",
|
|
426
431
|
generated_devcontainer(),
|
|
427
432
|
args.force,
|
|
428
433
|
)
|
|
429
434
|
if args.work_items == "github-issues":
|
|
430
435
|
ensure_file(
|
|
431
|
-
|
|
436
|
+
root / ".github/workflows/ai-workflow.yml",
|
|
432
437
|
generated_github_caller(args.framework_ref),
|
|
433
438
|
args.force,
|
|
434
439
|
)
|
|
@@ -479,6 +484,20 @@ def init_ado(args) -> int:
|
|
|
479
484
|
|
|
480
485
|
|
|
481
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
|
+
|
|
482
501
|
github_auth = None
|
|
483
502
|
if args.github_repository:
|
|
484
503
|
_, github_auth = resolve_github_token(args.github_auth, args.github_token_env)
|
|
@@ -536,12 +555,18 @@ def setup_cmd(args) -> int:
|
|
|
536
555
|
ado_field_mapping=ado_field_mapping,
|
|
537
556
|
)
|
|
538
557
|
plan["work_items"]["mapping_confirmed"] = not args.plan_only
|
|
539
|
-
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")
|
|
540
562
|
print(render_user_steps(plan))
|
|
541
|
-
print(f"\nWrote setup plan: {
|
|
563
|
+
print(f"\nWrote setup plan: {plan_output}")
|
|
542
564
|
|
|
543
565
|
if args.plan_only:
|
|
544
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
|
|
545
570
|
|
|
546
571
|
if args.work_items == "github-issues":
|
|
547
572
|
discovery, mapping, created = apply_github_status_bootstrap(args, mapping)
|
|
@@ -551,6 +576,7 @@ def setup_cmd(args) -> int:
|
|
|
551
576
|
class InitArgs:
|
|
552
577
|
pass
|
|
553
578
|
init = InitArgs()
|
|
579
|
+
init.root = args.root
|
|
554
580
|
init.work_items = "azure-devops" if args.work_items == "composite" else args.work_items
|
|
555
581
|
init.ado_organization = args.ado_organization
|
|
556
582
|
init.ado_project = args.ado_project
|
|
@@ -567,6 +593,20 @@ def setup_cmd(args) -> int:
|
|
|
567
593
|
config["workflow"] = workflow
|
|
568
594
|
config["providers"] = providers
|
|
569
595
|
config["workers"] = workers
|
|
596
|
+
instruction_adapters = {}
|
|
597
|
+
if not args.no_files:
|
|
598
|
+
for provider_id, provider_config in providers.items():
|
|
599
|
+
provider_type = provider_config.get("type")
|
|
600
|
+
adapter = ensure_provider_instruction_file(args.root, provider_type)
|
|
601
|
+
instruction_adapters[provider_id] = {
|
|
602
|
+
key: value for key, value in adapter.items() if key != "changed"
|
|
603
|
+
}
|
|
604
|
+
if adapter.get("changed"):
|
|
605
|
+
print(f"Updated provider instruction adapter: {adapter.get('provider_file')}")
|
|
606
|
+
else:
|
|
607
|
+
for provider_id, provider_config in providers.items():
|
|
608
|
+
instruction_adapters[provider_id] = adapter_for_provider(provider_config.get("type"))
|
|
609
|
+
config["instruction_adapters"] = instruction_adapters
|
|
570
610
|
config["source_connection"] = {"github": github_auth or {}}
|
|
571
611
|
if ado_mcp:
|
|
572
612
|
config["mcp"] = {"azure_devops": ado_mcp}
|
|
@@ -592,7 +632,23 @@ def setup_cmd(args) -> int:
|
|
|
592
632
|
)
|
|
593
633
|
config["framework"]["source"] = framework_source
|
|
594
634
|
config["framework"]["ref"] = framework_version
|
|
595
|
-
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
|
+
|
|
596
652
|
errors = doctor(config, discovery)
|
|
597
653
|
if errors:
|
|
598
654
|
print("Doctor findings:")
|
|
@@ -706,25 +762,25 @@ def main() -> int:
|
|
|
706
762
|
|
|
707
763
|
setup = sub.add_parser("setup")
|
|
708
764
|
setup.add_argument("--root", default=".")
|
|
709
|
-
setup.add_argument("--work-items", choices=["github-issues", "azure-devops", "composite"]
|
|
765
|
+
setup.add_argument("--work-items", choices=["github-issues", "azure-devops", "composite"])
|
|
710
766
|
setup.add_argument("--github-repository")
|
|
711
767
|
setup.add_argument("--github-token-env", default="GH_TOKEN")
|
|
712
|
-
setup.add_argument("--github-auth", choices=["gh-cli","token-env"]
|
|
768
|
+
setup.add_argument("--github-auth", choices=["gh-cli","token-env"])
|
|
713
769
|
setup.add_argument("--ado-organization")
|
|
714
770
|
setup.add_argument("--ado-project")
|
|
715
771
|
setup.add_argument("--ado-token-env", default="AZURE_DEVOPS_TOKEN")
|
|
716
|
-
setup.add_argument("--ado-mcp-mode", choices=["none","remote","local"]
|
|
717
|
-
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"])
|
|
718
774
|
setup.add_argument("--ado-team")
|
|
719
775
|
setup.add_argument("--ado-field", action="append", default=[])
|
|
720
|
-
setup.add_argument("--environment", choices=["none", "codespaces"]
|
|
721
|
-
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"])
|
|
722
778
|
setup.add_argument("--role-provider", action="append", default=[])
|
|
723
779
|
setup.add_argument("--ai-provider", action="append", default=[])
|
|
724
780
|
setup.add_argument("--worker", action="append", default=[])
|
|
725
|
-
setup.add_argument("--tdd", action=argparse.BooleanOptionalAction, default=
|
|
726
|
-
setup.add_argument("--reviewer-mode", choices=["manual", "automatic", "none"]
|
|
727
|
-
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"])
|
|
728
784
|
setup.add_argument("--output", default=".ai-workflow.yml")
|
|
729
785
|
setup.add_argument("--plan-output", default=".ai-setup-plan.yml")
|
|
730
786
|
setup.add_argument("--framework-ref", default="vuthethienlong/ai-dev-workflow/.github/workflows/queue-dispatcher.yml@main")
|
|
@@ -749,11 +805,19 @@ def main() -> int:
|
|
|
749
805
|
doc.set_defaults(func=doctor_cmd)
|
|
750
806
|
|
|
751
807
|
args = parser.parse_args()
|
|
752
|
-
if args.command
|
|
808
|
+
if args.command == "init" and args.work_items == "azure-devops":
|
|
753
809
|
if not args.ado_organization or not args.ado_project:
|
|
754
810
|
parser.error("--ado-organization and --ado-project are required for azure-devops")
|
|
755
|
-
if args.command == "setup" and args.
|
|
756
|
-
|
|
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"
|
|
757
821
|
return args.func(args)
|
|
758
822
|
|
|
759
823
|
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""Provider-specific instruction file adapters.
|
|
2
|
+
|
|
3
|
+
AGENTS.md is the shared provider-neutral source of truth.
|
|
4
|
+
Provider-native files keep user-owned content; ai-dev-workflow only owns the
|
|
5
|
+
bounded managed block below.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
START = "<!-- ai-dev-workflow:start -->"
|
|
12
|
+
END = "<!-- ai-dev-workflow:end -->"
|
|
13
|
+
|
|
14
|
+
ADAPTERS = {
|
|
15
|
+
"chatgpt-codex": {
|
|
16
|
+
"shared": "AGENTS.md",
|
|
17
|
+
"provider_file": None,
|
|
18
|
+
"merge_policy": "direct",
|
|
19
|
+
},
|
|
20
|
+
"anthropic": {
|
|
21
|
+
"shared": "AGENTS.md",
|
|
22
|
+
"provider_file": "CLAUDE.md",
|
|
23
|
+
"merge_policy": "preserve-existing",
|
|
24
|
+
},
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def adapter_for_provider(provider_type: str) -> dict:
|
|
29
|
+
return dict(ADAPTERS.get(provider_type, {
|
|
30
|
+
"shared": "AGENTS.md",
|
|
31
|
+
"provider_file": None,
|
|
32
|
+
"merge_policy": "direct",
|
|
33
|
+
}))
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def managed_block() -> str:
|
|
37
|
+
return (
|
|
38
|
+
f"{START}\n"
|
|
39
|
+
"## Shared workflow instructions\n\n"
|
|
40
|
+
"Read and follow `AGENTS.md` as the shared project instruction source.\n\n"
|
|
41
|
+
"When you are executing an active ai-dev-workflow task and `CURRENT.md` "
|
|
42
|
+
"exists, read it as the short-lived task/context router. `CURRENT.md` is "
|
|
43
|
+
"not a permanent provider rule and is not required for unrelated sessions.\n"
|
|
44
|
+
f"{END}"
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def merge_managed_block(existing: str) -> str:
|
|
49
|
+
block = managed_block()
|
|
50
|
+
if START in existing or END in existing:
|
|
51
|
+
if START not in existing or END not in existing:
|
|
52
|
+
raise ValueError("instruction adapter managed block is malformed")
|
|
53
|
+
before, rest = existing.split(START, 1)
|
|
54
|
+
_, after = rest.split(END, 1)
|
|
55
|
+
return before + block + after
|
|
56
|
+
|
|
57
|
+
if not existing:
|
|
58
|
+
return block + "\n"
|
|
59
|
+
separator = "" if existing.endswith("\n\n") else ("\n" if existing.endswith("\n") else "\n\n")
|
|
60
|
+
return existing + separator + block + "\n"
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def ensure_provider_instruction_file(root: str | Path, provider_type: str) -> dict:
|
|
64
|
+
adapter = adapter_for_provider(provider_type)
|
|
65
|
+
provider_file = adapter.get("provider_file")
|
|
66
|
+
if not provider_file:
|
|
67
|
+
return {**adapter, "changed": False}
|
|
68
|
+
|
|
69
|
+
path = Path(root) / provider_file
|
|
70
|
+
existing = path.read_text(encoding="utf-8") if path.exists() else ""
|
|
71
|
+
merged = merge_managed_block(existing)
|
|
72
|
+
changed = merged != existing
|
|
73
|
+
if changed:
|
|
74
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
75
|
+
path.write_text(merged, encoding="utf-8")
|
|
76
|
+
|
|
77
|
+
return {**adapter, "changed": changed}
|
|
@@ -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
|
{ai_dev_workflow-0.1.0 → ai_dev_workflow-0.2.0}/tools/ai_dev_workflow.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{ai_dev_workflow-0.1.0 → ai_dev_workflow-0.2.0}/tools/ai_dev_workflow.egg-info/entry_points.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
|