ai-dev-workflow 0.1.0__py3-none-any.whl
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.
- ado_mcp.py +49 -0
- ado_provider.py +245 -0
- ai_dev_workflow-0.1.0.dist-info/METADATA +348 -0
- ai_dev_workflow-0.1.0.dist-info/RECORD +20 -0
- ai_dev_workflow-0.1.0.dist-info/WHEEL +5 -0
- ai_dev_workflow-0.1.0.dist-info/entry_points.txt +2 -0
- ai_dev_workflow-0.1.0.dist-info/top_level.txt +15 -0
- ai_provider.py +149 -0
- ai_workflow_cli.py +761 -0
- composite_work_item.py +62 -0
- framework_update.py +145 -0
- github_work_item_provider.py +134 -0
- issue_publisher.py +243 -0
- planning_context.py +151 -0
- setup_context.py +99 -0
- setup_wizard.py +221 -0
- source_connection.py +21 -0
- work_item_provider.py +20 -0
- work_item_registry.py +25 -0
- worker_registry.py +170 -0
worker_registry.py
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"""Provider-neutral worker registration for planner/executor agents."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import re
|
|
5
|
+
|
|
6
|
+
WORKER_MODES = {"planner", "executor", "hybrid"}
|
|
7
|
+
WORKER_ENVIRONMENTS = {"local", "codespaces", "cloud"}
|
|
8
|
+
PLANNING_ROLES = {"SetupAgent", "ArchitectAgent", "ReviewerAgent"}
|
|
9
|
+
EXECUTION_ROLES = {"TesterAgent", "ImplementerAgent", "RuntimeVerifierAgent"}
|
|
10
|
+
ALL_ROLES = PLANNING_ROLES | EXECUTION_ROLES
|
|
11
|
+
|
|
12
|
+
ROLE_CAPABILITIES = {
|
|
13
|
+
"SetupAgent": {"planning"},
|
|
14
|
+
"ArchitectAgent": {"planning"},
|
|
15
|
+
"TesterAgent": {"test"},
|
|
16
|
+
"ImplementerAgent": {"implementation"},
|
|
17
|
+
"ReviewerAgent": {"review"},
|
|
18
|
+
"RuntimeVerifierAgent": {"runtime-verification"},
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def build_worker(
|
|
23
|
+
worker_id: str,
|
|
24
|
+
*,
|
|
25
|
+
mode: str,
|
|
26
|
+
provider: str,
|
|
27
|
+
environment: str,
|
|
28
|
+
roles: list[str],
|
|
29
|
+
capabilities: list[str] | None = None,
|
|
30
|
+
capacity: int = 1,
|
|
31
|
+
managed: bool | None = None,
|
|
32
|
+
scope: dict | None = None,
|
|
33
|
+
planning_updates: str = "approval-required",
|
|
34
|
+
assignee: str | None = None,
|
|
35
|
+
) -> dict:
|
|
36
|
+
caps = set(capabilities or [])
|
|
37
|
+
for role in roles:
|
|
38
|
+
caps |= ROLE_CAPABILITIES.get(role, set())
|
|
39
|
+
if managed is None:
|
|
40
|
+
managed = environment in ("codespaces", "cloud")
|
|
41
|
+
return {
|
|
42
|
+
"worker_id": worker_id,
|
|
43
|
+
"mode": mode,
|
|
44
|
+
"provider": provider,
|
|
45
|
+
"environment": environment,
|
|
46
|
+
"roles": list(roles),
|
|
47
|
+
"capabilities": sorted(caps),
|
|
48
|
+
"capacity": capacity,
|
|
49
|
+
"managed": managed,
|
|
50
|
+
"assignee": assignee,
|
|
51
|
+
"scope": scope or {"repositories": ["current"]},
|
|
52
|
+
"planning": {
|
|
53
|
+
"work_item_access": "read-write" if mode in ("planner", "hybrid") else "read",
|
|
54
|
+
"update_policy": planning_updates,
|
|
55
|
+
"frozen_contract_mutation": "forbidden",
|
|
56
|
+
},
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def validate_worker(worker: dict, providers: dict) -> list[str]:
|
|
61
|
+
errors = []
|
|
62
|
+
worker_id = worker.get("worker_id")
|
|
63
|
+
if not isinstance(worker_id, str) or not re.fullmatch(r"[A-Za-z0-9_.-]+", worker_id):
|
|
64
|
+
errors.append("worker_id must use letters, numbers, dot, underscore or dash")
|
|
65
|
+
|
|
66
|
+
mode = worker.get("mode")
|
|
67
|
+
if mode not in WORKER_MODES:
|
|
68
|
+
errors.append(f"worker {worker_id}: invalid mode {mode!r}")
|
|
69
|
+
|
|
70
|
+
environment = worker.get("environment")
|
|
71
|
+
if environment not in WORKER_ENVIRONMENTS:
|
|
72
|
+
errors.append(f"worker {worker_id}: invalid environment {environment!r}")
|
|
73
|
+
|
|
74
|
+
provider = worker.get("provider")
|
|
75
|
+
if provider not in providers:
|
|
76
|
+
errors.append(f"worker {worker_id}: unknown provider {provider!r}")
|
|
77
|
+
|
|
78
|
+
roles = worker.get("roles")
|
|
79
|
+
if not isinstance(roles, list) or not roles:
|
|
80
|
+
errors.append(f"worker {worker_id}: at least one role is required")
|
|
81
|
+
roles = []
|
|
82
|
+
for role in roles:
|
|
83
|
+
if role not in ALL_ROLES:
|
|
84
|
+
errors.append(f"worker {worker_id}: unsupported role {role!r}")
|
|
85
|
+
|
|
86
|
+
if mode == "planner" and any(role in EXECUTION_ROLES for role in roles):
|
|
87
|
+
errors.append(f"worker {worker_id}: planner mode cannot own execution roles")
|
|
88
|
+
if mode == "executor" and any(role in PLANNING_ROLES for role in roles):
|
|
89
|
+
errors.append(f"worker {worker_id}: executor mode cannot own planning roles")
|
|
90
|
+
|
|
91
|
+
caps = worker.get("capabilities")
|
|
92
|
+
if not isinstance(caps, list):
|
|
93
|
+
errors.append(f"worker {worker_id}: capabilities must be a list")
|
|
94
|
+
caps = []
|
|
95
|
+
for role in roles:
|
|
96
|
+
missing = ROLE_CAPABILITIES.get(role, set()) - set(caps)
|
|
97
|
+
if missing:
|
|
98
|
+
errors.append(
|
|
99
|
+
f"worker {worker_id}: role {role} missing capabilities {sorted(missing)}"
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
if mode in ("executor", "hybrid") and environment == "local" and not worker.get("managed") and not worker.get("assignee"):
|
|
103
|
+
errors.append(f"worker {worker_id}: local executor requires assignee for claim ownership")
|
|
104
|
+
|
|
105
|
+
capacity = worker.get("capacity")
|
|
106
|
+
if type(capacity) is not int or capacity < 1:
|
|
107
|
+
errors.append(f"worker {worker_id}: capacity must be >= 1")
|
|
108
|
+
|
|
109
|
+
planning = worker.get("planning") or {}
|
|
110
|
+
if planning.get("update_policy") not in (
|
|
111
|
+
"approval-required",
|
|
112
|
+
"draft-auto",
|
|
113
|
+
"read-only",
|
|
114
|
+
):
|
|
115
|
+
errors.append(f"worker {worker_id}: invalid planning update policy")
|
|
116
|
+
if planning.get("frozen_contract_mutation") != "forbidden":
|
|
117
|
+
errors.append(f"worker {worker_id}: frozen contract mutation must be forbidden")
|
|
118
|
+
|
|
119
|
+
return errors
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def validate_worker_registry(workers: list[dict], providers: dict) -> list[str]:
|
|
123
|
+
errors = []
|
|
124
|
+
seen = set()
|
|
125
|
+
for worker in workers:
|
|
126
|
+
worker_id = worker.get("worker_id")
|
|
127
|
+
if worker_id in seen:
|
|
128
|
+
errors.append(f"duplicate worker_id: {worker_id}")
|
|
129
|
+
seen.add(worker_id)
|
|
130
|
+
errors.extend(validate_worker(worker, providers))
|
|
131
|
+
return errors
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def worker_credential_requirements(workers: list[dict], work_item_provider: str) -> list[dict]:
|
|
135
|
+
result = []
|
|
136
|
+
if any(w.get("environment") == "codespaces" for w in workers):
|
|
137
|
+
result.append({
|
|
138
|
+
"name": "CODESPACES_TOKEN",
|
|
139
|
+
"purpose": "Provision/start/stop registered Codespaces workers.",
|
|
140
|
+
"required_for_setup": False,
|
|
141
|
+
"store": "GitHub Actions repository secret",
|
|
142
|
+
})
|
|
143
|
+
if any(w.get("mode") in ("planner", "hybrid") for w in workers):
|
|
144
|
+
result.append({
|
|
145
|
+
"name": f"{work_item_provider} connector access",
|
|
146
|
+
"purpose": "Allow planner/hybrid workers to read project context and propose work-item updates.",
|
|
147
|
+
"required_for_setup": False,
|
|
148
|
+
"store": "connector/MCP authentication configured during setup",
|
|
149
|
+
})
|
|
150
|
+
return result
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def dispatcher_registry(workers: list[dict]) -> dict:
|
|
154
|
+
"""Project first-class workers into the existing queue-dispatcher registry contract."""
|
|
155
|
+
projected = []
|
|
156
|
+
for worker in workers:
|
|
157
|
+
if worker.get("mode") not in ("executor", "hybrid"):
|
|
158
|
+
continue
|
|
159
|
+
item = {
|
|
160
|
+
"worker_id": worker["worker_id"],
|
|
161
|
+
"execution_location": "local" if worker["environment"] == "local" else "cloud",
|
|
162
|
+
"provider": worker["provider"],
|
|
163
|
+
"capabilities": list(worker.get("capabilities", [])),
|
|
164
|
+
"capacity": worker.get("capacity", 1),
|
|
165
|
+
"managed": bool(worker.get("managed", False)),
|
|
166
|
+
}
|
|
167
|
+
if worker.get("assignee"):
|
|
168
|
+
item["assignee"] = worker["assignee"]
|
|
169
|
+
projected.append(item)
|
|
170
|
+
return {"version": 1, "workers": projected}
|