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:
2026-01-21 16:08:08 +00:00
parent c760bd7cd0
commit 5ac579a2ed
171 changed files with 2188 additions and 1565 deletions

View File

@@ -0,0 +1 @@
"""Core AI/workflow plugins: AI requests, message handling, context loading."""

View 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)
}

View 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}

View 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}

View File

@@ -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}

View 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}

View 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)
}

View 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"])}