-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbuilder.yaml
More file actions
185 lines (155 loc) · 7.53 KB
/
Copy pathbuilder.yaml
File metadata and controls
185 lines (155 loc) · 7.53 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
id: ai_project_builder_gemini
namespace: company.team
description: Generate a simple C++ project folder structure with starter files and README as a zip using Google Gemini (No Makefile)
inputs:
- id: assignment_text
type: STRING
description: "The assignment description from the user."
required: false
tasks:
# 1️⃣ Generate project structure from assignment text using Gemini
- id: generate_project_structure
type: io.kestra.plugin.ai.completion.JSONStructuredExtraction
provider:
type: io.kestra.plugin.ai.provider.GoogleGemini
apiKey: "{{ secret('GEMINI_API_KEY') }}"
modelName: gemini-2.5-flash
prompt: |
# TASK
You are a C++ project structure generator for university programming assignments. Generate a complete, compilable C++17 project with starter code and documentation.
# OUTPUT FORMAT
Return ONLY valid JSON. No markdown, no explanations, no code blocks - just raw JSON.
{
"project_name": "CamelCaseProjectName",
"folders": ["src", "include", "tests"],
"files": [
{
"path": "src/main.cpp",
"starter_code": "#include <iostream>\n\nint main() {\n // TODO: Implement core functionality\n std::cout << \"Program started\" << std::endl;\n return 0;\n}"
},
{
"path": "include/MyClass.h",
"starter_code": "#ifndef MYCLASS_H\n#define MYCLASS_H\n\nclass MyClass {\npublic:\n MyClass();\n void doSomething(); // TODO: Implement\n};\n\n#endif"
}
],
"readme": "# Project Title\n\n## Overview\nBrief description of the assignment.\n\n## Project Structure\n```\nproject_name/\n├── src/\n│ └── main.cpp\n├── include/\n│ └── MyClass.h\n└── README.md\n```\n\n## Files Description\n\n### src/main.cpp\nEntry point. Contains main() function and starter code.\n\n### include/MyClass.h\nHeader for a class used in the project.\n\n## Key Classes/Functions\n\n### MyClass\n- `MyClass()`: Constructor\n- `doSomething()`: TODO implementation\n\n## Compilation Instructions\n```bash\ng++ -std=c++17 -Wall -Iinclude src/*.cpp -o project_name\n./project_name\n```\n\n## Implementation Tasks\n1. Implement core functionality in main.cpp\n2. Implement MyClass methods\n3. Add unit tests if needed\n\n## Notes\n- Ensure code compiles without errors\n- Use only C++17 features\n- Keep starter code simple and documented"
# CRITICAL RULES
- "folders" MUST be a JSON array: ["src", "include"], not a string
- "files" MUST be a JSON array of objects with "path" and "starter_code"
- Use actual \n for line breaks in code
- main.cpp is always required
- Include optional headers in include/
ASSIGNMENT:
{{ trigger.body.assignment_text ?? inputs.assignment_text ?? "Create a simple C++ hello world program" }}
jsonFields:
- project_name
- folders
- files
- readme
schemaName: ProjectStructure
# 2️⃣ Debug AI output
- id: debug_ai_output
type: io.kestra.plugin.core.log.Log
message: |
=== GEMINI OUTPUT ===
{{ outputs.generate_project_structure.extractedJson }}
# 3️⃣ Fix and validate AI output
- id: fix_and_validate_structure
type: io.kestra.plugin.scripts.python.Script
inputFiles:
project_data.json: "{{ outputs.generate_project_structure.extractedJson }}"
outputFiles:
- "fixed_project_data.json"
script: |
import json, sys, os, re
try:
with open("project_data.json", "r") as f:
data = json.load(f)
# Fix folders if string
if isinstance(data.get("folders", []), str):
data["folders"] = json.loads(data["folders"])
# Fix files if string
if isinstance(data.get("files", []), str):
try:
data["files"] = json.loads(data["files"])
except json.JSONDecodeError:
# Replace unescaped quotes in starter_code
fixed_files = re.sub(r'(std::cout\s*<<\s*)"([^"]*)"', r"\1'\2'", data["files"])
data["files"] = json.loads(fixed_files)
# Validation
errors = []
if not data.get("project_name"): errors.append("Missing project_name")
if not isinstance(data["folders"], list) or not data["folders"]: errors.append("Invalid folders")
if not isinstance(data["files"], list) or not data["files"]: errors.append("Invalid files")
file_paths = [f.get("path") for f in data["files"] if isinstance(f, dict)]
if "src/main.cpp" not in file_paths: errors.append("Missing src/main.cpp")
if not data.get("readme"): errors.append("Missing readme")
if errors:
for e in errors: print(f"❌ {e}")
sys.exit(1)
with open("fixed_project_data.json", "w") as f:
json.dump(data, f, indent=2)
except Exception as e:
print(f"❌ FATAL ERROR: {e}")
sys.exit(1)
# 4️⃣ Create files and zip
- id: create_files_and_zip
type: io.kestra.plugin.scripts.python.Script
inputFiles:
project_data.json: "{{ outputs.fix_and_validate_structure.outputFiles['fixed_project_data.json'] }}"
outputFiles:
- "project.zip"
script: |
import os, sys, json, zipfile
try:
with open("project_data.json", "r") as f:
raw = f.read().strip()
try:
data = json.loads(raw)
except json.JSONDecodeError:
# Try to recover from invalid JSON by fixing common issues
import re
# Replace unescaped newlines inside strings
raw = raw.replace("\n", "\\n")
# Replace double quotes in C++ cout with single quotes
raw = re.sub(
r'(std::cout\s*<<\s*)"([^"]*)"',
r"\1'\2'",
raw
)
data = json.loads(raw)
project_name = data["project_name"]
project_path = os.path.join(os.getcwd(), project_name)
os.makedirs(project_path, exist_ok=True)
# Create folders
for folder in data["folders"]:
os.makedirs(os.path.join(project_path, folder), exist_ok=True)
# Create files
for file_info in data["files"]:
full_path = os.path.join(project_path, file_info["path"])
os.makedirs(os.path.dirname(full_path), exist_ok=True)
with open(full_path, "w") as f:
f.write(file_info["starter_code"])
# Create README.md
with open(os.path.join(project_path, "README.md"), "w") as f:
f.write(data["readme"])
# Create zip
zip_path = os.path.join(os.getcwd(), "project.zip")
with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as zipf:
for root, dirs, files_in_dir in os.walk(project_path):
for file in files_in_dir:
zipf.write(os.path.join(root, file), os.path.relpath(os.path.join(root, file), project_path))
except Exception as e:
print(f"❌ FATAL ERROR: {e}")
sys.exit(1)
outputs:
- id: download
type: FILE
value: "{{ outputs.create_files_and_zip.outputFiles['project.zip'] }}"
- id: project_info
type: JSON
value: "{{ read(outputs.fix_and_validate_structure.outputFiles['fixed_project_data.json']) }}"
triggers:
- id: webhook
type: io.kestra.plugin.core.trigger.Webhook
key: umer