mirror of
https://github.com/johndoe6345789/metabuilder.git
synced 2026-04-25 06:14:59 +00:00
feat: Add Python plugins from AutoMetabuilder + restructure workflow folder
Restructure workflow/ for multi-language plugin support:
- Rename src/ to core/ (engine code: DAG executor, registry, types)
- Create executor/{cpp,python,ts}/ for language-specific runtimes
- Consolidate plugins to plugins/{ts,python}/ by language then category
Add 80+ Python plugins from AutoMetabuilder in 14 categories:
- control: bot control, switch logic, state management
- convert: type conversions (json, boolean, dict, list, number, string)
- core: AI requests, context management, tool calls
- dict: dictionary operations (get, set, keys, values, merge)
- list: list operations (concat, find, sort, slice, filter)
- logic: boolean logic (and, or, xor, equals, comparisons)
- math: arithmetic operations (add, subtract, multiply, power, etc.)
- string: string manipulation (concat, split, replace, format)
- notifications: Slack, Discord integrations
- test: assertion helpers and test suite runner
- tools: file operations, git, docker, testing utilities
- utils: filtering, mapping, reducing, condition branching
- var: variable store operations (get, set, delete, exists)
- web: Flask server, environment variables, JSON handling
Add language executor runtimes:
- TypeScript: direct import execution (default, fast startup)
- Python: child process with JSON stdin/stdout communication
- C++: placeholder for native FFI bindings (Phase 3)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
1
workflow/plugins/python/core/__init__.py
Normal file
1
workflow/plugins/python/core/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""Core AI/workflow plugins: AI requests, message handling, context loading."""
|
||||
39
workflow/plugins/python/core/core_ai_request.py
Normal file
39
workflow/plugins/python/core/core_ai_request.py
Normal file
@@ -0,0 +1,39 @@
|
||||
"""Workflow plugin: AI request."""
|
||||
from tenacity import retry, stop_after_attempt, wait_exponential
|
||||
|
||||
|
||||
@retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=2, max=10))
|
||||
def _get_completion(client, model, messages, tools):
|
||||
"""Request a chat completion with retries."""
|
||||
return client.chat.completions.create(
|
||||
model=model,
|
||||
messages=messages,
|
||||
tools=tools,
|
||||
tool_choice="auto",
|
||||
temperature=1.0,
|
||||
top_p=1.0,
|
||||
)
|
||||
|
||||
|
||||
def run(runtime, inputs):
|
||||
"""Invoke the model with current messages."""
|
||||
messages = list(inputs.get("messages") or [])
|
||||
response = _get_completion(
|
||||
runtime.context["client"],
|
||||
runtime.context["model_name"],
|
||||
messages,
|
||||
runtime.context["tools"]
|
||||
)
|
||||
resp_msg = response.choices[0].message
|
||||
runtime.logger.info(
|
||||
resp_msg.content
|
||||
if resp_msg.content
|
||||
else runtime.context["msgs"]["info_tool_call_requested"]
|
||||
)
|
||||
messages.append(resp_msg)
|
||||
tool_calls = getattr(resp_msg, "tool_calls", None) or []
|
||||
return {
|
||||
"response": resp_msg,
|
||||
"has_tool_calls": bool(tool_calls),
|
||||
"tool_calls_count": len(tool_calls)
|
||||
}
|
||||
13
workflow/plugins/python/core/core_append_context_message.py
Normal file
13
workflow/plugins/python/core/core_append_context_message.py
Normal file
@@ -0,0 +1,13 @@
|
||||
"""Workflow plugin: append context message."""
|
||||
|
||||
|
||||
def run(runtime, inputs):
|
||||
"""Append context to the message list."""
|
||||
messages = list(inputs.get("messages") or [])
|
||||
context_val = inputs.get("context")
|
||||
if context_val:
|
||||
messages.append({
|
||||
"role": "system",
|
||||
"content": f"{runtime.context['msgs']['sdlc_context_label']}{context_val}",
|
||||
})
|
||||
return {"messages": messages}
|
||||
44
workflow/plugins/python/core/core_append_tool_results.py
Normal file
44
workflow/plugins/python/core/core_append_tool_results.py
Normal file
@@ -0,0 +1,44 @@
|
||||
"""Workflow plugin: append tool results."""
|
||||
import os
|
||||
import re
|
||||
|
||||
|
||||
def _is_mvp_reached() -> bool:
|
||||
"""Check if the MVP section in ROADMAP.md is completed."""
|
||||
if not os.path.exists("ROADMAP.md"):
|
||||
return False
|
||||
|
||||
with open("ROADMAP.md", "r", encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
|
||||
# Find the header line containing (MVP)
|
||||
header_match = re.search(r"^## .*?\(MVP\).*?$", content, re.MULTILINE | re.IGNORECASE)
|
||||
if not header_match:
|
||||
return False
|
||||
|
||||
start_pos = header_match.end()
|
||||
next_header_match = re.search(r"^## ", content[start_pos:], re.MULTILINE)
|
||||
if next_header_match:
|
||||
mvp_section = content[start_pos : start_pos + next_header_match.start()]
|
||||
else:
|
||||
mvp_section = content[start_pos:]
|
||||
|
||||
if "[ ]" in mvp_section:
|
||||
return False
|
||||
if "[x]" in mvp_section:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def run(runtime, inputs):
|
||||
"""Append tool results to the message list."""
|
||||
messages = list(inputs.get("messages") or [])
|
||||
tool_results = inputs.get("tool_results") or []
|
||||
if tool_results:
|
||||
messages.extend(tool_results)
|
||||
|
||||
if runtime.context.get("args", {}).get("yolo") and _is_mvp_reached():
|
||||
runtime.logger.info("MVP reached. Stopping YOLO loop.")
|
||||
|
||||
return {"messages": messages}
|
||||
@@ -0,0 +1,8 @@
|
||||
"""Workflow plugin: append user instruction."""
|
||||
|
||||
|
||||
def run(runtime, inputs):
|
||||
"""Append the next user instruction."""
|
||||
messages = list(inputs.get("messages") or [])
|
||||
messages.append({"role": "user", "content": runtime.context["msgs"]["user_next_step"]})
|
||||
return {"messages": messages}
|
||||
43
workflow/plugins/python/core/core_load_context.py
Normal file
43
workflow/plugins/python/core/core_load_context.py
Normal file
@@ -0,0 +1,43 @@
|
||||
"""Workflow plugin: load SDLC context."""
|
||||
import os
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger("metabuilder")
|
||||
|
||||
|
||||
def run(runtime, _inputs):
|
||||
"""Load SDLC context into the workflow store."""
|
||||
gh = runtime.context.get("gh")
|
||||
msgs = runtime.context.get("msgs", {})
|
||||
|
||||
sdlc_context = ""
|
||||
|
||||
# Load ROADMAP.md if it exists
|
||||
if os.path.exists("ROADMAP.md"):
|
||||
with open("ROADMAP.md", "r", encoding="utf-8") as f:
|
||||
roadmap_content = f.read()
|
||||
label = msgs.get("roadmap_label", "ROADMAP.md Content:")
|
||||
sdlc_context += f"\n{label}\n{roadmap_content}\n"
|
||||
else:
|
||||
msg = msgs.get(
|
||||
"missing_roadmap_msg",
|
||||
"ROADMAP.md is missing. Please analyze the repository and create it."
|
||||
)
|
||||
sdlc_context += f"\n{msg}\n"
|
||||
|
||||
# Load GitHub issues and PRs if integration is available
|
||||
if gh:
|
||||
try:
|
||||
issues = gh.get_open_issues()
|
||||
issue_list = "\n".join([f"- #{i.number}: {i.title}" for i in issues[:5]])
|
||||
if issue_list:
|
||||
sdlc_context += f"\n{msgs['open_issues_label']}\n{issue_list}"
|
||||
|
||||
prs = gh.get_pull_requests()
|
||||
pr_list = "\n".join([f"- #{p.number}: {p.title}" for p in prs[:5]])
|
||||
if pr_list:
|
||||
sdlc_context += f"\n{msgs['open_prs_label']}\n{pr_list}"
|
||||
except Exception as error:
|
||||
logger.error(msgs.get("error_sdlc_context", "Error: {error}").format(error=error))
|
||||
|
||||
return {"context": sdlc_context}
|
||||
37
workflow/plugins/python/core/core_run_tool_calls.py
Normal file
37
workflow/plugins/python/core/core_run_tool_calls.py
Normal file
@@ -0,0 +1,37 @@
|
||||
"""Workflow plugin: run tool calls."""
|
||||
|
||||
|
||||
def run(runtime, inputs):
|
||||
"""Execute tool calls from an AI response."""
|
||||
resp_msg = inputs.get("response")
|
||||
tool_calls = getattr(resp_msg, "tool_calls", None) or []
|
||||
if not resp_msg:
|
||||
return {"tool_results": [], "no_tool_calls": True}
|
||||
|
||||
# Handle tool calls using tool map from context
|
||||
tool_results = []
|
||||
tool_map = runtime.context.get("tool_map", {})
|
||||
|
||||
for tool_call in tool_calls:
|
||||
func_name = tool_call.function.name
|
||||
if func_name in tool_map:
|
||||
try:
|
||||
import json
|
||||
args = json.loads(tool_call.function.arguments)
|
||||
result = tool_map[func_name](**args)
|
||||
tool_results.append({
|
||||
"role": "tool",
|
||||
"tool_call_id": tool_call.id,
|
||||
"content": str(result)
|
||||
})
|
||||
except Exception as e:
|
||||
tool_results.append({
|
||||
"role": "tool",
|
||||
"tool_call_id": tool_call.id,
|
||||
"content": f"Error: {str(e)}"
|
||||
})
|
||||
|
||||
return {
|
||||
"tool_results": tool_results,
|
||||
"no_tool_calls": not bool(tool_calls)
|
||||
}
|
||||
7
workflow/plugins/python/core/core_seed_messages.py
Normal file
7
workflow/plugins/python/core/core_seed_messages.py
Normal file
@@ -0,0 +1,7 @@
|
||||
"""Workflow plugin: seed messages."""
|
||||
|
||||
|
||||
def run(runtime, _inputs):
|
||||
"""Seed messages from the prompt."""
|
||||
prompt = runtime.context["prompt"]
|
||||
return {"messages": list(prompt["messages"])}
|
||||
Reference in New Issue
Block a user