agent-state-machine 2.0.13 → 2.0.15
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.
- package/README.md +19 -6
- package/bin/cli.js +23 -5
- package/lib/setup.js +82 -388
- package/package.json +2 -1
- package/templates/project-builder/README.md +119 -0
- package/templates/project-builder/agents/assumptions-clarifier.md +66 -0
- package/templates/project-builder/agents/code-reviewer.md +82 -0
- package/templates/project-builder/agents/code-writer.md +75 -0
- package/templates/project-builder/agents/requirements-clarifier.md +56 -0
- package/templates/project-builder/agents/roadmap-generator.md +74 -0
- package/templates/project-builder/agents/scope-clarifier.md +45 -0
- package/templates/project-builder/agents/security-clarifier.md +72 -0
- package/templates/project-builder/agents/security-reviewer.md +72 -0
- package/templates/project-builder/agents/task-planner.md +63 -0
- package/templates/project-builder/agents/test-planner.md +77 -0
- package/templates/project-builder/config.js +13 -0
- package/templates/project-builder/scripts/mac-notification.js +24 -0
- package/templates/project-builder/scripts/text-human.js +92 -0
- package/templates/project-builder/scripts/workflow-helpers.js +167 -0
- package/templates/project-builder/state/current.json +9 -0
- package/templates/project-builder/state/history.jsonl +0 -0
- package/templates/project-builder/steering/config.json +5 -0
- package/templates/project-builder/steering/global.md +19 -0
- package/templates/project-builder/workflow.js +394 -0
- package/templates/starter/README.md +118 -0
- package/templates/starter/agents/example.js +36 -0
- package/templates/starter/agents/yoda-greeter.md +12 -0
- package/templates/starter/agents/yoda-name-collector.md +12 -0
- package/templates/starter/config.js +12 -0
- package/templates/starter/interactions/.gitkeep +0 -0
- package/templates/starter/scripts/mac-notification.js +24 -0
- package/templates/starter/state/current.json +9 -0
- package/templates/starter/state/history.jsonl +0 -0
- package/templates/starter/steering/config.json +5 -0
- package/templates/starter/steering/global.md +19 -0
- package/templates/starter/workflow.js +52 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
---
|
|
2
|
+
model: high
|
|
3
|
+
output: result
|
|
4
|
+
format: json
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Task Planner Agent
|
|
8
|
+
|
|
9
|
+
You are a task breakdown specialist. Generate detailed task lists for a specific phase as structured JSON.
|
|
10
|
+
|
|
11
|
+
## Context
|
|
12
|
+
Project Description: {{projectDescription}}
|
|
13
|
+
Scope: {{scope}}
|
|
14
|
+
Requirements: {{requirements}}
|
|
15
|
+
Phase Number: {{phaseIndex}}
|
|
16
|
+
Phase Details: {{phase}}
|
|
17
|
+
{{#if feedback}}
|
|
18
|
+
User Feedback: {{feedback}}
|
|
19
|
+
{{/if}}
|
|
20
|
+
|
|
21
|
+
## Instructions
|
|
22
|
+
|
|
23
|
+
Break down the phase into specific, actionable tasks. Each task should:
|
|
24
|
+
- Be small enough to complete in a focused work session
|
|
25
|
+
- Have a clear definition of done
|
|
26
|
+
- Include a sanity check the user can verify
|
|
27
|
+
|
|
28
|
+
**Task Principles:**
|
|
29
|
+
- One task = one concern (don't combine unrelated work)
|
|
30
|
+
- Tasks should be independently verifiable
|
|
31
|
+
- Order tasks by dependency (what must come first)
|
|
32
|
+
- Include setup/preparation tasks if needed
|
|
33
|
+
|
|
34
|
+
## Output Format
|
|
35
|
+
|
|
36
|
+
Return a valid JSON object (no markdown code blocks, just raw JSON):
|
|
37
|
+
|
|
38
|
+
{
|
|
39
|
+
"phaseNumber": 1,
|
|
40
|
+
"phaseTitle": "Phase Title",
|
|
41
|
+
"tasks": [
|
|
42
|
+
{
|
|
43
|
+
"id": 1,
|
|
44
|
+
"title": "Task Title",
|
|
45
|
+
"description": "What needs to be done",
|
|
46
|
+
"doneDefinition": "Specific completion criteria that can be verified",
|
|
47
|
+
"sanityCheck": "How the user can verify this is working correctly",
|
|
48
|
+
"stage": "pending"
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"id": 2,
|
|
52
|
+
"title": "Task Title",
|
|
53
|
+
"description": "What needs to be done",
|
|
54
|
+
"doneDefinition": "Specific completion criteria",
|
|
55
|
+
"sanityCheck": "Verification method",
|
|
56
|
+
"stage": "pending"
|
|
57
|
+
}
|
|
58
|
+
]
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
**Stage values:** pending, in_progress, completed, failed
|
|
62
|
+
|
|
63
|
+
Keep tasks focused and achievable. Aim for 3-8 tasks per phase depending on complexity. Every task MUST have a doneDefinition and sanityCheck.
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
---
|
|
2
|
+
model: med
|
|
3
|
+
output: result
|
|
4
|
+
format: json
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Test Planner Agent
|
|
8
|
+
|
|
9
|
+
You are a test planning specialist. Create test plans for tasks before implementation.
|
|
10
|
+
|
|
11
|
+
## Context
|
|
12
|
+
Task: {{task}}
|
|
13
|
+
Phase: {{phase}}
|
|
14
|
+
Requirements: {{requirements}}
|
|
15
|
+
Security Considerations: {{securityConsiderations}}
|
|
16
|
+
{{#if feedback}}
|
|
17
|
+
Previous Feedback: {{feedback}}
|
|
18
|
+
{{/if}}
|
|
19
|
+
|
|
20
|
+
## Instructions
|
|
21
|
+
|
|
22
|
+
Create a comprehensive test plan for the task. Include:
|
|
23
|
+
|
|
24
|
+
**Test Categories:**
|
|
25
|
+
- Unit tests (individual functions/components)
|
|
26
|
+
- Integration tests (component interactions)
|
|
27
|
+
- Security tests (based on security review)
|
|
28
|
+
- Edge case tests (boundary conditions)
|
|
29
|
+
|
|
30
|
+
**Test Principles:**
|
|
31
|
+
- Test behavior, not implementation
|
|
32
|
+
- Cover happy path and error cases
|
|
33
|
+
- Include tests for security concerns flagged in review
|
|
34
|
+
- Prioritize tests by risk and importance
|
|
35
|
+
|
|
36
|
+
## Output Format
|
|
37
|
+
|
|
38
|
+
Return a valid JSON object:
|
|
39
|
+
|
|
40
|
+
{
|
|
41
|
+
"testPlan": {
|
|
42
|
+
"summary": "Brief description of testing approach",
|
|
43
|
+
"unitTests": [
|
|
44
|
+
{
|
|
45
|
+
"name": "should validate user input",
|
|
46
|
+
"description": "Verify input sanitization works correctly",
|
|
47
|
+
"expectedBehavior": "Invalid input should be rejected with error message",
|
|
48
|
+
"priority": "high"
|
|
49
|
+
}
|
|
50
|
+
],
|
|
51
|
+
"integrationTests": [
|
|
52
|
+
{
|
|
53
|
+
"name": "should save and retrieve data",
|
|
54
|
+
"description": "Verify database integration works",
|
|
55
|
+
"components": ["API", "Database"],
|
|
56
|
+
"priority": "high"
|
|
57
|
+
}
|
|
58
|
+
],
|
|
59
|
+
"securityTests": [
|
|
60
|
+
{
|
|
61
|
+
"name": "should prevent SQL injection",
|
|
62
|
+
"threat": "SQL injection via user input",
|
|
63
|
+
"testMethod": "Attempt injection with malicious strings",
|
|
64
|
+
"priority": "high"
|
|
65
|
+
}
|
|
66
|
+
],
|
|
67
|
+
"edgeCases": [
|
|
68
|
+
{
|
|
69
|
+
"scenario": "Empty input handling",
|
|
70
|
+
"expectedBehavior": "Return validation error"
|
|
71
|
+
}
|
|
72
|
+
]
|
|
73
|
+
},
|
|
74
|
+
"testingNotes": "Any special considerations or setup needed"
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
Focus on tests that validate the definition of done. Don't over-test trivial functionality.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export const config = {
|
|
2
|
+
models: {
|
|
3
|
+
low: "gemini",
|
|
4
|
+
med: "gemini",
|
|
5
|
+
high: "gemini",
|
|
6
|
+
},
|
|
7
|
+
apiKeys: {
|
|
8
|
+
gemini: process.env.GEMINI_API_KEY,
|
|
9
|
+
anthropic: process.env.ANTHROPIC_API_KEY,
|
|
10
|
+
openai: process.env.OPENAI_API_KEY,
|
|
11
|
+
},
|
|
12
|
+
remotePath: "TczrLmUecnqZPpPhBTrvU374CGlfzDfINrr0eN0nMgQ",
|
|
13
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import { spawnSync } from "node:child_process";
|
|
4
|
+
import { existsSync } from "node:fs";
|
|
5
|
+
|
|
6
|
+
function escAppleScript(s) {
|
|
7
|
+
return String(s).replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function notify(title = "Notification", message = "Everything finished!") {
|
|
11
|
+
const script = `display notification "${escAppleScript(message)}" with title "${escAppleScript(title)}"`;
|
|
12
|
+
spawnSync("osascript", ["-e", script], { stdio: "ignore" });
|
|
13
|
+
|
|
14
|
+
const soundPath = "/System/Library/Sounds/Glass.aiff";
|
|
15
|
+
const fallbackPath = "/System/Library/Sounds/Ping.aiff";
|
|
16
|
+
|
|
17
|
+
if (existsSync(soundPath)) {
|
|
18
|
+
spawnSync("afplay", [soundPath], { stdio: "ignore" });
|
|
19
|
+
} else if (existsSync(fallbackPath)) {
|
|
20
|
+
spawnSync("afplay", [fallbackPath], { stdio: "ignore" });
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export { notify };
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
4
|
+
import nodemailer from "nodemailer";
|
|
5
|
+
|
|
6
|
+
function loadEnvFile() {
|
|
7
|
+
if (typeof process.loadEnvFile === "function") {
|
|
8
|
+
process.loadEnvFile();
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const envPath = ".env";
|
|
13
|
+
if (!existsSync(envPath)) {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const lines = readFileSync(envPath, "utf8").split(/\r?\n/);
|
|
18
|
+
for (const line of lines) {
|
|
19
|
+
const trimmed = line.trim();
|
|
20
|
+
if (!trimmed || trimmed.startsWith("#")) {
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const match = trimmed.match(/^([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/);
|
|
25
|
+
if (!match) {
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const key = match[1];
|
|
30
|
+
if (process.env[key] !== undefined) {
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
let value = match[2] ?? "";
|
|
35
|
+
if (
|
|
36
|
+
(value.startsWith("\"") && value.endsWith("\"")) ||
|
|
37
|
+
(value.startsWith("'") && value.endsWith("'"))
|
|
38
|
+
) {
|
|
39
|
+
value = value.slice(1, -1);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
process.env[key] = value;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function requireEnv(name) {
|
|
47
|
+
const value = process.env[name];
|
|
48
|
+
if (!value) {
|
|
49
|
+
throw new Error(`Missing required env var: ${name}`);
|
|
50
|
+
}
|
|
51
|
+
return value;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function createTransport() {
|
|
55
|
+
const host = requireEnv("SMTP_HOST");
|
|
56
|
+
const port = Number(requireEnv("SMTP_PORT"));
|
|
57
|
+
const user = requireEnv("SMTP_USER");
|
|
58
|
+
const pass = requireEnv("SMTP_PASS");
|
|
59
|
+
const secure = String(process.env.SMTP_SECURE || "").toLowerCase() === "true";
|
|
60
|
+
|
|
61
|
+
return nodemailer.createTransport({
|
|
62
|
+
host,
|
|
63
|
+
port,
|
|
64
|
+
secure,
|
|
65
|
+
auth: {
|
|
66
|
+
user,
|
|
67
|
+
pass,
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function textHuman(message) {
|
|
73
|
+
loadEnvFile();
|
|
74
|
+
|
|
75
|
+
if (!message || typeof message !== "string") {
|
|
76
|
+
throw new Error("textHuman(message) requires a non-empty string.");
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const from = process.env.SMS_FROM || requireEnv("SMTP_FROM");
|
|
80
|
+
const to = requireEnv("SMS_TO");
|
|
81
|
+
const transporter = createTransport();
|
|
82
|
+
|
|
83
|
+
const info = await transporter.sendMail({
|
|
84
|
+
from,
|
|
85
|
+
to,
|
|
86
|
+
subject: "",
|
|
87
|
+
text: message,
|
|
88
|
+
});
|
|
89
|
+
void info;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export { textHuman };
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { agent, memory } from 'agent-state-machine';
|
|
4
|
+
|
|
5
|
+
// Normalize agent output - always extract the result consistently
|
|
6
|
+
function unwrap(agentResult) {
|
|
7
|
+
if (!agentResult) return null;
|
|
8
|
+
if (typeof agentResult === 'object' && 'result' in agentResult) {
|
|
9
|
+
return agentResult.result;
|
|
10
|
+
}
|
|
11
|
+
return agentResult;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Write markdown file to workflow state directory
|
|
15
|
+
function writeMarkdownFile(stateDir, filename, content) {
|
|
16
|
+
if (!fs.existsSync(stateDir)) fs.mkdirSync(stateDir, { recursive: true });
|
|
17
|
+
const filePath = path.join(stateDir, filename);
|
|
18
|
+
fs.writeFileSync(filePath, content);
|
|
19
|
+
console.log(` [File] Updated: ${filename}`);
|
|
20
|
+
return filePath;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Strict approval parsing - only accepts explicit approval
|
|
24
|
+
function isApproval(response) {
|
|
25
|
+
if (!response || typeof response !== 'string') return false;
|
|
26
|
+
const trimmed = response.trim().toLowerCase();
|
|
27
|
+
// Must start with 'a' or be exactly 'approve/approved/yes/y'
|
|
28
|
+
return /^a\b/.test(trimmed) ||
|
|
29
|
+
/^approve/.test(trimmed) ||
|
|
30
|
+
/^yes\b/.test(trimmed) ||
|
|
31
|
+
/^y\b/.test(trimmed);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Generate markdown from roadmap JSON
|
|
35
|
+
function renderRoadmapMarkdown(roadmap) {
|
|
36
|
+
if (!roadmap || !roadmap.phases) return '# Project Roadmap\n\nNo phases defined.';
|
|
37
|
+
|
|
38
|
+
let md = `# Project Roadmap: ${roadmap.title || 'Untitled Project'}\n\n`;
|
|
39
|
+
|
|
40
|
+
for (const phase of roadmap.phases) {
|
|
41
|
+
const status = phase.completed ? ' [COMPLETED]' : '';
|
|
42
|
+
md += `## Phase ${phase.number}: ${phase.title}${status}\n`;
|
|
43
|
+
md += `**Objective:** ${phase.objective || 'No objective specified'}\n\n`;
|
|
44
|
+
|
|
45
|
+
for (const item of phase.checklist || []) {
|
|
46
|
+
const check = item.completed ? 'x' : ' ';
|
|
47
|
+
md += `- [${check}] ${item.text}\n`;
|
|
48
|
+
}
|
|
49
|
+
md += '\n';
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (roadmap.notes && roadmap.notes.length > 0) {
|
|
53
|
+
md += '---\n\n**Notes:**\n';
|
|
54
|
+
for (const note of roadmap.notes) {
|
|
55
|
+
md += `- ${note}\n`;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return md;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Generate markdown from tasks JSON
|
|
63
|
+
function renderTasksMarkdown(phaseNumber, phaseTitle, tasks) {
|
|
64
|
+
if (!tasks || !Array.isArray(tasks)) return `# Phase ${phaseNumber} Tasks\n\nNo tasks defined.`;
|
|
65
|
+
|
|
66
|
+
let md = `# Phase ${phaseNumber} Tasks: ${phaseTitle}\n\n`;
|
|
67
|
+
|
|
68
|
+
for (const task of tasks) {
|
|
69
|
+
const status = task.stage === 'completed' ? ' [COMPLETED]' :
|
|
70
|
+
task.stage === 'in_progress' ? ' [IN PROGRESS]' : '';
|
|
71
|
+
md += `## Task ${task.id}: ${task.title}${status}\n`;
|
|
72
|
+
md += `**Description:** ${task.description || 'No description'}\n\n`;
|
|
73
|
+
md += `**Definition of Done:**\n- ${task.doneDefinition || 'Task completed successfully'}\n\n`;
|
|
74
|
+
md += `**Sanity Check:**\n- ${task.sanityCheck || 'Review the implementation and confirm it meets requirements.'}\n\n`;
|
|
75
|
+
md += '---\n\n';
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
md += '## Checklist Summary\n';
|
|
79
|
+
for (const task of tasks) {
|
|
80
|
+
const check = task.stage === 'completed' ? 'x' : ' ';
|
|
81
|
+
md += `- [${check}] Task ${task.id}: ${task.title}\n`;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return md;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Run agent with error handling and retry capability
|
|
88
|
+
async function safeAgent(name, params, options = {}) {
|
|
89
|
+
const { maxRetries = 1, onError } = options;
|
|
90
|
+
let lastError;
|
|
91
|
+
|
|
92
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
93
|
+
try {
|
|
94
|
+
const result = await agent(name, params);
|
|
95
|
+
return unwrap(result);
|
|
96
|
+
} catch (error) {
|
|
97
|
+
lastError = error;
|
|
98
|
+
console.error(` [Agent: ${name}] Error (attempt ${attempt}/${maxRetries}): ${error.message}`);
|
|
99
|
+
|
|
100
|
+
if (onError) {
|
|
101
|
+
const shouldRetry = await onError(error, attempt);
|
|
102
|
+
if (!shouldRetry) break;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (attempt < maxRetries) {
|
|
106
|
+
console.log(` [Agent: ${name}] Retrying...`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Store error in memory for debugging
|
|
112
|
+
if (!memory._errors) memory._errors = [];
|
|
113
|
+
memory._errors.push({
|
|
114
|
+
agent: name,
|
|
115
|
+
error: lastError?.message,
|
|
116
|
+
timestamp: new Date().toISOString()
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
throw lastError;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Task stage management
|
|
123
|
+
const TASK_STAGES = {
|
|
124
|
+
PENDING: 'pending',
|
|
125
|
+
SECURITY_PRE: 'security_pre',
|
|
126
|
+
TEST_PLANNING: 'test_planning',
|
|
127
|
+
IMPLEMENTING: 'implementing',
|
|
128
|
+
CODE_REVIEW: 'code_review',
|
|
129
|
+
SECURITY_POST: 'security_post',
|
|
130
|
+
AWAITING_APPROVAL: 'awaiting_approval',
|
|
131
|
+
COMPLETED: 'completed',
|
|
132
|
+
FAILED: 'failed'
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
function getTaskStage(phaseIndex, taskId) {
|
|
136
|
+
const key = `phase_${phaseIndex}_task_${taskId}_stage`;
|
|
137
|
+
return memory[key] || TASK_STAGES.PENDING;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function setTaskStage(phaseIndex, taskId, stage) {
|
|
141
|
+
const key = `phase_${phaseIndex}_task_${taskId}_stage`;
|
|
142
|
+
memory[key] = stage;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function getTaskData(phaseIndex, taskId, dataKey) {
|
|
146
|
+
const key = `phase_${phaseIndex}_task_${taskId}_${dataKey}`;
|
|
147
|
+
return memory[key];
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function setTaskData(phaseIndex, taskId, dataKey, value) {
|
|
151
|
+
const key = `phase_${phaseIndex}_task_${taskId}_${dataKey}`;
|
|
152
|
+
memory[key] = value;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export {
|
|
156
|
+
unwrap,
|
|
157
|
+
writeMarkdownFile,
|
|
158
|
+
isApproval,
|
|
159
|
+
renderRoadmapMarkdown,
|
|
160
|
+
renderTasksMarkdown,
|
|
161
|
+
safeAgent,
|
|
162
|
+
TASK_STAGES,
|
|
163
|
+
getTaskStage,
|
|
164
|
+
setTaskStage,
|
|
165
|
+
getTaskData,
|
|
166
|
+
setTaskData
|
|
167
|
+
};
|
|
File without changes
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Global Steering Prompt
|
|
2
|
+
|
|
3
|
+
This content is included with every agent execution in the project-builder workflow.
|
|
4
|
+
|
|
5
|
+
## Guidelines
|
|
6
|
+
|
|
7
|
+
- Process data carefully and validate inputs
|
|
8
|
+
- Return well-structured JSON when applicable
|
|
9
|
+
- Log meaningful progress messages
|
|
10
|
+
- Handle errors gracefully
|
|
11
|
+
|
|
12
|
+
## Helpful Tips
|
|
13
|
+
- Scripts live in /workflows/project-builder/scripts/
|
|
14
|
+
- .gitignore includes /workflows so include ignored files when searching for a script and use `search_file_content` with the `no_ignore` flag to read the contents of script files.
|
|
15
|
+
- Shell execution via `run_shell_command` is unavailable, so reproduce the script's intended side effect to fulfill the task.
|
|
16
|
+
|
|
17
|
+
## Notes
|
|
18
|
+
|
|
19
|
+
This file is automatically loaded and passed to every agent in the workflow via `context._steering.global`.
|