goiabaseeds 1.0.0
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 +173 -0
- package/bin/goiabaseeds.js +98 -0
- package/eslint.config.js +14 -0
- package/package.json +61 -0
- package/skills/README.md +60 -0
- package/skills/apify/SKILL.md +55 -0
- package/skills/blotato/SKILL.md +63 -0
- package/skills/canva/SKILL.md +60 -0
- package/skills/goiabaseeds-agent-creator/SKILL.md +192 -0
- package/skills/goiabaseeds-skill-creator/SKILL.md +407 -0
- package/skills/goiabaseeds-skill-creator/agents/analyzer.md +274 -0
- package/skills/goiabaseeds-skill-creator/agents/comparator.md +202 -0
- package/skills/goiabaseeds-skill-creator/agents/grader.md +223 -0
- package/skills/goiabaseeds-skill-creator/assets/eval_review.html +146 -0
- package/skills/goiabaseeds-skill-creator/eval-viewer/generate_review.py +471 -0
- package/skills/goiabaseeds-skill-creator/eval-viewer/viewer.html +1325 -0
- package/skills/goiabaseeds-skill-creator/references/schemas.md +430 -0
- package/skills/goiabaseeds-skill-creator/references/skill-format.md +235 -0
- package/skills/goiabaseeds-skill-creator/scripts/__init__.py +0 -0
- package/skills/goiabaseeds-skill-creator/scripts/aggregate_benchmark.py +401 -0
- package/skills/goiabaseeds-skill-creator/scripts/quick_validate.py +103 -0
- package/skills/goiabaseeds-skill-creator/scripts/run_eval.py +310 -0
- package/skills/goiabaseeds-skill-creator/scripts/utils.py +47 -0
- package/skills/image-creator/SKILL.md +155 -0
- package/skills/image-fetcher/SKILL.md +91 -0
- package/skills/image-generator/SKILL.md +124 -0
- package/skills/image-generator/scripts/generate.py +175 -0
- package/skills/instagram-publisher/SKILL.md +118 -0
- package/skills/instagram-publisher/scripts/publish.js +164 -0
- package/src/agent-session.js +110 -0
- package/src/agents-cli.js +158 -0
- package/src/agents.js +134 -0
- package/src/bundle-detector.js +75 -0
- package/src/bundle.js +286 -0
- package/src/context.js +142 -0
- package/src/export.js +52 -0
- package/src/i18n.js +48 -0
- package/src/init.js +367 -0
- package/src/locales/en.json +72 -0
- package/src/locales/es.json +71 -0
- package/src/locales/pt-BR.json +71 -0
- package/src/logger.js +38 -0
- package/src/models-cli.js +165 -0
- package/src/pipeline-runner.js +478 -0
- package/src/prompt.js +46 -0
- package/src/provider.js +156 -0
- package/src/readme/README.md +181 -0
- package/src/run.js +100 -0
- package/src/runs.js +90 -0
- package/src/skills-cli.js +157 -0
- package/src/skills.js +146 -0
- package/src/state-manager.js +280 -0
- package/src/tools.js +158 -0
- package/src/update.js +140 -0
- package/templates/_goiabaseeds/.goiabaseeds-version +1 -0
- package/templates/_goiabaseeds/_investigations/.gitkeep +0 -0
- package/templates/_goiabaseeds/config/playwright.config.json +11 -0
- package/templates/_goiabaseeds/core/architect.agent.yaml +1141 -0
- package/templates/_goiabaseeds/core/best-practices/_catalog.yaml +116 -0
- package/templates/_goiabaseeds/core/best-practices/blog-post.md +132 -0
- package/templates/_goiabaseeds/core/best-practices/blog-seo.md +127 -0
- package/templates/_goiabaseeds/core/best-practices/copywriting.md +428 -0
- package/templates/_goiabaseeds/core/best-practices/data-analysis.md +401 -0
- package/templates/_goiabaseeds/core/best-practices/email-newsletter.md +118 -0
- package/templates/_goiabaseeds/core/best-practices/email-sales.md +110 -0
- package/templates/_goiabaseeds/core/best-practices/image-design.md +349 -0
- package/templates/_goiabaseeds/core/best-practices/instagram-feed.md +235 -0
- package/templates/_goiabaseeds/core/best-practices/instagram-reels.md +112 -0
- package/templates/_goiabaseeds/core/best-practices/instagram-stories.md +107 -0
- package/templates/_goiabaseeds/core/best-practices/linkedin-article.md +116 -0
- package/templates/_goiabaseeds/core/best-practices/linkedin-post.md +121 -0
- package/templates/_goiabaseeds/core/best-practices/researching.md +347 -0
- package/templates/_goiabaseeds/core/best-practices/review.md +269 -0
- package/templates/_goiabaseeds/core/best-practices/social-networks-publishing.md +294 -0
- package/templates/_goiabaseeds/core/best-practices/strategist.md +344 -0
- package/templates/_goiabaseeds/core/best-practices/technical-writing.md +363 -0
- package/templates/_goiabaseeds/core/best-practices/twitter-post.md +105 -0
- package/templates/_goiabaseeds/core/best-practices/twitter-thread.md +122 -0
- package/templates/_goiabaseeds/core/best-practices/whatsapp-broadcast.md +107 -0
- package/templates/_goiabaseeds/core/best-practices/youtube-script.md +122 -0
- package/templates/_goiabaseeds/core/best-practices/youtube-shorts.md +112 -0
- package/templates/_goiabaseeds/core/prompts/auguste.dupin.prompt.md +1008 -0
- package/templates/_goiabaseeds/core/runner.pipeline.md +467 -0
- package/templates/_goiabaseeds/core/skills.engine.md +381 -0
- package/templates/dashboard/index.html +12 -0
- package/templates/dashboard/package-lock.json +2082 -0
- package/templates/dashboard/package.json +28 -0
- package/templates/dashboard/src/App.tsx +46 -0
- package/templates/dashboard/src/components/DepartmentCard.tsx +47 -0
- package/templates/dashboard/src/components/DepartmentSelector.tsx +61 -0
- package/templates/dashboard/src/components/StatusBadge.tsx +32 -0
- package/templates/dashboard/src/components/StatusBar.tsx +97 -0
- package/templates/dashboard/src/hooks/useDepartmentSocket.ts +84 -0
- package/templates/dashboard/src/lib/formatTime.ts +16 -0
- package/templates/dashboard/src/lib/normalizeState.ts +25 -0
- package/templates/dashboard/src/main.tsx +10 -0
- package/templates/dashboard/src/office/AgentDesk.tsx +151 -0
- package/templates/dashboard/src/office/HandoffEnvelope.tsx +108 -0
- package/templates/dashboard/src/office/OfficeScene.tsx +147 -0
- package/templates/dashboard/src/office/drawDesk.ts +263 -0
- package/templates/dashboard/src/office/drawFurniture.ts +129 -0
- package/templates/dashboard/src/office/drawRoom.ts +51 -0
- package/templates/dashboard/src/office/palette.ts +181 -0
- package/templates/dashboard/src/office/textures.ts +254 -0
- package/templates/dashboard/src/plugin/departmentWatcher.ts +210 -0
- package/templates/dashboard/src/store/useDepartmentStore.ts +56 -0
- package/templates/dashboard/src/styles/globals.css +36 -0
- package/templates/dashboard/src/types/state.ts +64 -0
- package/templates/dashboard/src/vite-env.d.ts +1 -0
- package/templates/dashboard/tsconfig.json +24 -0
- package/templates/dashboard/vite.config.ts +13 -0
- package/templates/departments/.gitkeep +0 -0
- package/templates/ide-templates/antigravity/.agent/rules/goiabaseeds.md +55 -0
- package/templates/ide-templates/antigravity/.agent/workflows/goiabaseeds.md +102 -0
- package/templates/ide-templates/claude-code/.claude/skills/goiabaseeds/SKILL.md +182 -0
- package/templates/ide-templates/claude-code/.mcp.json +8 -0
- package/templates/ide-templates/claude-code/CLAUDE.md +43 -0
- package/templates/ide-templates/codex/.agents/skills/goiabaseeds/SKILL.md +6 -0
- package/templates/ide-templates/codex/AGENTS.md +105 -0
- package/templates/ide-templates/cursor/.cursor/commands/goiabaseeds.md +9 -0
- package/templates/ide-templates/cursor/.cursor/mcp.json +8 -0
- package/templates/ide-templates/cursor/.cursor/rules/goiabaseeds.mdc +48 -0
- package/templates/ide-templates/cursor/.cursorignore +3 -0
- package/templates/ide-templates/opencode/.opencode/commands/goiabaseeds.md +9 -0
- package/templates/ide-templates/opencode/AGENTS.md +105 -0
- package/templates/ide-templates/vscode-copilot/.github/prompts/goiabaseeds.prompt.md +201 -0
- package/templates/ide-templates/vscode-copilot/.vscode/mcp.json +8 -0
- package/templates/ide-templates/vscode-copilot/.vscode/settings.json +3 -0
- package/templates/package.json +8 -0
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
import { readFile, readdir, mkdir, stat } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { StateManager } from './state-manager.js';
|
|
4
|
+
import { AgentSession } from './agent-session.js';
|
|
5
|
+
import { composeAgentContext, parseFrontmatter } from './context.js';
|
|
6
|
+
import { buildTools } from './tools.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* PipelineRunner — programmatic pipeline execution engine.
|
|
10
|
+
*
|
|
11
|
+
* Translates the 400-line runner.pipeline.md LLM instructions
|
|
12
|
+
* into deterministic Node.js code.
|
|
13
|
+
*/
|
|
14
|
+
export class PipelineRunner {
|
|
15
|
+
/**
|
|
16
|
+
* @param {object} options
|
|
17
|
+
* @param {string} options.targetDir - Project root directory
|
|
18
|
+
* @param {string} options.departmentName - Department directory name
|
|
19
|
+
* @param {object} options.provider - Provider instance from createProvider()
|
|
20
|
+
* @param {(msg: string) => void} [options.onLog] - Logging callback
|
|
21
|
+
* @param {() => Promise<string>} [options.onCheckpoint] - Checkpoint approval callback
|
|
22
|
+
*/
|
|
23
|
+
constructor({ targetDir, departmentName, provider, onLog, onCheckpoint }) {
|
|
24
|
+
this.targetDir = targetDir;
|
|
25
|
+
this.departmentName = departmentName;
|
|
26
|
+
this.departmentDir = join(targetDir, 'departments', departmentName);
|
|
27
|
+
this.provider = provider;
|
|
28
|
+
this.onLog = onLog || ((msg) => console.log(` ${msg}`));
|
|
29
|
+
this.onCheckpoint = onCheckpoint;
|
|
30
|
+
this.stateManager = null;
|
|
31
|
+
this.runId = null;
|
|
32
|
+
this.completedAgentIds = [];
|
|
33
|
+
this.mainSession = null;
|
|
34
|
+
this.versionCache = new Map();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Execute the full pipeline.
|
|
39
|
+
* @returns {Promise<{success: boolean, runId: string}>}
|
|
40
|
+
*/
|
|
41
|
+
async run() {
|
|
42
|
+
// --- INITIALIZATION ---
|
|
43
|
+
const { department, agents, pipeline, skills } = await this._loadDepartment();
|
|
44
|
+
|
|
45
|
+
this.onLog(`🏢 ${department.name || this.departmentName}`);
|
|
46
|
+
this.onLog(`📋 ${pipeline.steps.length} steps | ${agents.length} agents`);
|
|
47
|
+
|
|
48
|
+
// Generate run ID
|
|
49
|
+
this.runId = await this._generateRunId();
|
|
50
|
+
await mkdir(join(this.departmentDir, 'output', this.runId), { recursive: true });
|
|
51
|
+
this.onLog(`📁 Run: ${this.runId}`);
|
|
52
|
+
|
|
53
|
+
// Initialize state manager
|
|
54
|
+
this.stateManager = new StateManager({
|
|
55
|
+
departmentDir: this.departmentDir,
|
|
56
|
+
departmentCode: department.code || this.departmentName,
|
|
57
|
+
agents,
|
|
58
|
+
totalSteps: pipeline.steps.length,
|
|
59
|
+
});
|
|
60
|
+
await this.stateManager.initialize();
|
|
61
|
+
|
|
62
|
+
// Build tools
|
|
63
|
+
const tools = await buildTools(skills, this.targetDir);
|
|
64
|
+
|
|
65
|
+
// --- STEP EXECUTION LOOP ---
|
|
66
|
+
try {
|
|
67
|
+
for (let i = 0; i < pipeline.steps.length; i++) {
|
|
68
|
+
const stepFile = pipeline.steps[i];
|
|
69
|
+
const stepPath = join(this.departmentDir, 'pipeline', 'steps', stepFile);
|
|
70
|
+
const stepContent = await readFile(stepPath, 'utf-8');
|
|
71
|
+
const { meta: stepMeta, body: stepBody } = parseFrontmatter(stepContent);
|
|
72
|
+
|
|
73
|
+
const stepId = stepMeta.step_id || stepFile.replace(/\.md$/, '');
|
|
74
|
+
const agentId = stepMeta.agent;
|
|
75
|
+
const execution = stepMeta.execution || 'subagent';
|
|
76
|
+
const modelTier = stepMeta.model_tier || 'powerful';
|
|
77
|
+
|
|
78
|
+
this.onLog('');
|
|
79
|
+
this.onLog(`━━━ Step ${i + 1}/${pipeline.steps.length}: ${stepId} ━━━`);
|
|
80
|
+
|
|
81
|
+
// --- DASHBOARD HANDOFF (between steps) ---
|
|
82
|
+
if (i > 0 && agentId) {
|
|
83
|
+
const prevStep = pipeline.steps[i - 1];
|
|
84
|
+
const prevContent = await readFile(
|
|
85
|
+
join(this.departmentDir, 'pipeline', 'steps', prevStep), 'utf-8'
|
|
86
|
+
);
|
|
87
|
+
const { meta: prevMeta } = parseFrontmatter(prevContent);
|
|
88
|
+
const prevAgentId = prevMeta.agent;
|
|
89
|
+
|
|
90
|
+
if (prevAgentId && prevAgentId !== agentId) {
|
|
91
|
+
await this.stateManager.setDelivering(prevAgentId, agentId);
|
|
92
|
+
await sleep(3000);
|
|
93
|
+
await this.stateManager.setWorking(prevAgentId, agentId);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// --- CHECKPOINT ---
|
|
98
|
+
if (stepMeta.type === 'checkpoint') {
|
|
99
|
+
await this._executeCheckpoint(stepMeta, stepBody, i, agentId);
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// --- UPDATE STATE ---
|
|
104
|
+
await this.stateManager.updateStep(i, stepId, agentId, this.completedAgentIds);
|
|
105
|
+
|
|
106
|
+
// --- RESOLVE AGENT ---
|
|
107
|
+
const agentPath = this._resolveAgentPath(agentId, agents);
|
|
108
|
+
|
|
109
|
+
// --- COMPOSE CONTEXT ---
|
|
110
|
+
const systemPrompt = await composeAgentContext({
|
|
111
|
+
agentPath,
|
|
112
|
+
stepMeta,
|
|
113
|
+
departmentDir: this.departmentDir,
|
|
114
|
+
targetDir: this.targetDir,
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// --- BUILD USER PROMPT ---
|
|
118
|
+
const userPrompt = await this._buildUserPrompt(stepMeta, stepBody);
|
|
119
|
+
|
|
120
|
+
// --- EXECUTE ---
|
|
121
|
+
if (execution === 'subagent') {
|
|
122
|
+
await this._executeSubagent({
|
|
123
|
+
stepMeta, systemPrompt, userPrompt, modelTier, tools, agentId, stepId,
|
|
124
|
+
});
|
|
125
|
+
} else {
|
|
126
|
+
// inline
|
|
127
|
+
await this._executeInline({
|
|
128
|
+
stepMeta, systemPrompt, userPrompt, modelTier, tools, agentId, stepId,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Track completed agent
|
|
133
|
+
if (agentId && !this.completedAgentIds.includes(agentId)) {
|
|
134
|
+
this.completedAgentIds.push(agentId);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// --- POST-COMPLETION ---
|
|
139
|
+
await this.stateManager.complete();
|
|
140
|
+
await this.stateManager.archive(this.runId);
|
|
141
|
+
|
|
142
|
+
this.onLog('');
|
|
143
|
+
this.onLog('✅ Pipeline completed!');
|
|
144
|
+
this.onLog(`📁 Output: departments/${this.departmentName}/output/${this.runId}/`);
|
|
145
|
+
|
|
146
|
+
// Wait 10s for dashboard to show completed state, then clean up
|
|
147
|
+
await sleep(10_000);
|
|
148
|
+
await this.stateManager.cleanup();
|
|
149
|
+
|
|
150
|
+
return { success: true, runId: this.runId };
|
|
151
|
+
|
|
152
|
+
} catch (err) {
|
|
153
|
+
await this.stateManager.fail(err.message);
|
|
154
|
+
this.onLog(`\n❌ Pipeline failed: ${err.message}`);
|
|
155
|
+
return { success: false, runId: this.runId };
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// ── Execution modes ──────────────────────────────────────────
|
|
160
|
+
|
|
161
|
+
async _executeSubagent({ stepMeta, systemPrompt, userPrompt, modelTier, tools, agentId, stepId }) {
|
|
162
|
+
const agentInfo = this._findAgent(agentId);
|
|
163
|
+
this.onLog(`🔍 ${agentInfo?.icon || '🤖'} ${agentInfo?.name || agentId} is working in the background...`);
|
|
164
|
+
|
|
165
|
+
const model = this.provider.getModel(modelTier);
|
|
166
|
+
const session = new AgentSession({ model, systemPrompt, tools });
|
|
167
|
+
const result = await session.run(userPrompt);
|
|
168
|
+
|
|
169
|
+
// Verify output file if specified
|
|
170
|
+
if (stepMeta.outputFile) {
|
|
171
|
+
const outputPath = this._transformOutputPath(stepMeta.outputFile);
|
|
172
|
+
await this._verifyOrWriteOutput(outputPath, result);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Veto condition check
|
|
176
|
+
if (stepMeta.veto) {
|
|
177
|
+
await this._checkVeto(result, stepMeta, systemPrompt, tools, modelTier);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
this.onLog(`✓ ${agentInfo?.name || agentId} completed`);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async _executeInline({ stepMeta, systemPrompt, userPrompt, modelTier, tools, agentId, stepId }) {
|
|
184
|
+
const agentInfo = this._findAgent(agentId);
|
|
185
|
+
this.onLog(`${agentInfo?.icon || '🤖'} ${agentInfo?.name || agentId} is working...`);
|
|
186
|
+
|
|
187
|
+
const model = this.provider.getModel(modelTier);
|
|
188
|
+
|
|
189
|
+
// Inline steps share the main session
|
|
190
|
+
if (!this.mainSession) {
|
|
191
|
+
this.mainSession = new AgentSession({ model, systemPrompt, tools });
|
|
192
|
+
} else {
|
|
193
|
+
// Update system prompt for new agent
|
|
194
|
+
this.mainSession.systemPrompt = systemPrompt;
|
|
195
|
+
this.mainSession.model = model;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const result = await this.mainSession.runStreaming(userPrompt, (chunk) => {
|
|
199
|
+
process.stdout.write(chunk);
|
|
200
|
+
});
|
|
201
|
+
console.log(''); // newline after streaming
|
|
202
|
+
|
|
203
|
+
if (stepMeta.outputFile) {
|
|
204
|
+
const outputPath = this._transformOutputPath(stepMeta.outputFile);
|
|
205
|
+
await this._verifyOrWriteOutput(outputPath, result);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
this.onLog(`✓ ${agentInfo?.name || agentId} completed`);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
async _executeCheckpoint(stepMeta, stepBody, stepIndex, agentId) {
|
|
212
|
+
const question = stepMeta.approval_question || stepBody.trim() || 'Approve and continue?';
|
|
213
|
+
|
|
214
|
+
this.onLog(`⏸️ Checkpoint: ${stepMeta.step_id || 'approval'}`);
|
|
215
|
+
|
|
216
|
+
// Set checkpoint state for dashboard
|
|
217
|
+
if (agentId) {
|
|
218
|
+
await this.stateManager.setCheckpoint(agentId, {
|
|
219
|
+
step: stepMeta.step_id || `step-${stepIndex}`,
|
|
220
|
+
question,
|
|
221
|
+
context: stepBody,
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Get approval — CLI or dashboard
|
|
226
|
+
let response;
|
|
227
|
+
if (this.onCheckpoint) {
|
|
228
|
+
response = await this.onCheckpoint(question, stepMeta);
|
|
229
|
+
} else {
|
|
230
|
+
response = await this._cliCheckpoint(question);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
this.onLog(`✓ Checkpoint resolved: ${response.action}`);
|
|
234
|
+
|
|
235
|
+
// Write response to outputFile if specified (run_id injection only, no version folder)
|
|
236
|
+
if (stepMeta.outputFile) {
|
|
237
|
+
const outputPath = this._transformOutputPathStep1Only(stepMeta.outputFile);
|
|
238
|
+
const { writeFile: fsWriteFile } = await import('node:fs/promises');
|
|
239
|
+
const { dirname: pathDirname } = await import('node:path');
|
|
240
|
+
const { mkdir: fsMkdir } = await import('node:fs/promises');
|
|
241
|
+
await fsMkdir(pathDirname(outputPath), { recursive: true });
|
|
242
|
+
await fsWriteFile(outputPath, response.instruction || response.action, 'utf-8');
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Update state back to running
|
|
246
|
+
await this.stateManager.updateStep(stepIndex, stepMeta.step_id || '', agentId, this.completedAgentIds);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
async _cliCheckpoint(question) {
|
|
250
|
+
const { default: selectFn } = await import('@inquirer/select');
|
|
251
|
+
const action = await selectFn({
|
|
252
|
+
message: question,
|
|
253
|
+
choices: [
|
|
254
|
+
{ name: 'Approve', value: 'approve' },
|
|
255
|
+
{ name: 'Revise', value: 'revise' },
|
|
256
|
+
],
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
let instruction = '';
|
|
260
|
+
if (action === 'revise') {
|
|
261
|
+
const { default: inputFn } = await import('@inquirer/input');
|
|
262
|
+
instruction = await inputFn({ message: 'Revision instructions:' });
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return { action, instruction };
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// ── Veto conditions ──────────────────────────────────────────
|
|
269
|
+
|
|
270
|
+
async _checkVeto(output, stepMeta, systemPrompt, tools, modelTier) {
|
|
271
|
+
// Simple veto check using fast model
|
|
272
|
+
const model = this.provider.getModel('fast');
|
|
273
|
+
const vetoSession = new AgentSession({
|
|
274
|
+
model,
|
|
275
|
+
systemPrompt: 'You are a quality checker. Evaluate if the output violates any veto conditions. Reply with PASS if all conditions are met, or FAIL: <reason> if any are violated.',
|
|
276
|
+
tools: {},
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
const vetoPrompt = `## Veto Conditions\n${stepMeta.veto}\n\n## Output to Check\n${output}`;
|
|
280
|
+
const result = await vetoSession.run(vetoPrompt);
|
|
281
|
+
|
|
282
|
+
if (result.toUpperCase().startsWith('FAIL')) {
|
|
283
|
+
this.onLog(`⚠️ Veto triggered: ${result}`);
|
|
284
|
+
// Could implement retry logic here (max 2 attempts per runner.pipeline.md)
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// ── Output path transformation ───────────────────────────────
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Apply full output path transformation (Step 1 + Step 2).
|
|
292
|
+
* Step 1: Insert run_id after departments/{name}/output/
|
|
293
|
+
* Step 2: Insert version folder (v1, v2, etc.) before filename
|
|
294
|
+
*/
|
|
295
|
+
_transformOutputPath(rawPath) {
|
|
296
|
+
let path = this._transformOutputPathStep1Only(rawPath);
|
|
297
|
+
|
|
298
|
+
// Step 2: Version folder
|
|
299
|
+
const parts = path.replace(/\\/g, '/').split('/');
|
|
300
|
+
const filename = parts.pop();
|
|
301
|
+
const groupDir = parts.join('/');
|
|
302
|
+
|
|
303
|
+
// Check cache for this group
|
|
304
|
+
if (this.versionCache.has(groupDir)) {
|
|
305
|
+
return join(groupDir, this.versionCache.get(groupDir), filename);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Detect existing versions
|
|
309
|
+
let version = 'v1';
|
|
310
|
+
try {
|
|
311
|
+
const { readdirSync } = require('node:fs');
|
|
312
|
+
const entries = readdirSync(groupDir);
|
|
313
|
+
const versions = entries
|
|
314
|
+
.filter(e => /^v\d+$/.test(e))
|
|
315
|
+
.map(e => parseInt(e.slice(1)))
|
|
316
|
+
.sort((a, b) => a - b);
|
|
317
|
+
if (versions.length > 0) {
|
|
318
|
+
version = `v${versions[versions.length - 1] + 1}`;
|
|
319
|
+
}
|
|
320
|
+
} catch {
|
|
321
|
+
// Directory doesn't exist yet — v1
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
this.versionCache.set(groupDir, version);
|
|
325
|
+
return join(groupDir, version, filename);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Step 1 only: Insert run_id after departments/{name}/output/
|
|
330
|
+
* Used for checkpoint files (no version folder).
|
|
331
|
+
*/
|
|
332
|
+
_transformOutputPathStep1Only(rawPath) {
|
|
333
|
+
const normalized = rawPath.replace(/\\/g, '/');
|
|
334
|
+
const outputPrefix = `departments/${this.departmentName}/output/`;
|
|
335
|
+
|
|
336
|
+
if (normalized.startsWith(outputPrefix)) {
|
|
337
|
+
const rest = normalized.slice(outputPrefix.length);
|
|
338
|
+
return join(this.targetDir, outputPrefix, this.runId, rest);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// If path doesn't match pattern, resolve relative to targetDir
|
|
342
|
+
return join(this.targetDir, normalized);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// ── Helper methods ───────────────────────────────────────────
|
|
346
|
+
|
|
347
|
+
async _loadDepartment() {
|
|
348
|
+
const { parse } = await import('yaml');
|
|
349
|
+
|
|
350
|
+
// Read department.yaml
|
|
351
|
+
const deptYaml = await readFile(join(this.departmentDir, 'department.yaml'), 'utf-8');
|
|
352
|
+
const department = parse(deptYaml);
|
|
353
|
+
|
|
354
|
+
// Read department-party.csv for agent info
|
|
355
|
+
const agents = await this._parsePartyCSV();
|
|
356
|
+
|
|
357
|
+
// Read pipeline.yaml
|
|
358
|
+
const pipelineYaml = await readFile(
|
|
359
|
+
join(this.departmentDir, 'pipeline', 'pipeline.yaml'), 'utf-8'
|
|
360
|
+
);
|
|
361
|
+
const pipelineRaw = parse(pipelineYaml);
|
|
362
|
+
|
|
363
|
+
// Extract step file list from pipeline
|
|
364
|
+
const steps = [];
|
|
365
|
+
if (Array.isArray(pipelineRaw?.steps)) {
|
|
366
|
+
for (const step of pipelineRaw.steps) {
|
|
367
|
+
if (typeof step === 'string') {
|
|
368
|
+
steps.push(step);
|
|
369
|
+
} else if (step?.file) {
|
|
370
|
+
steps.push(step.file);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Extract skills list
|
|
376
|
+
const skills = department.skills || [];
|
|
377
|
+
|
|
378
|
+
return { department, agents, pipeline: { steps }, skills };
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
async _parsePartyCSV() {
|
|
382
|
+
const partyPath = join(this.departmentDir, 'department-party.csv');
|
|
383
|
+
try {
|
|
384
|
+
const content = await readFile(partyPath, 'utf-8');
|
|
385
|
+
const lines = content.split('\n').filter(l => l.trim());
|
|
386
|
+
|
|
387
|
+
// Skip header if present
|
|
388
|
+
const startIdx = lines[0]?.includes('id,') || lines[0]?.includes('id\t') ? 1 : 0;
|
|
389
|
+
|
|
390
|
+
return lines.slice(startIdx).map(line => {
|
|
391
|
+
const [id, name, icon] = line.split(',').map(s => s?.trim());
|
|
392
|
+
return { id: id || '', name: name || id || '', icon: icon || '🤖' };
|
|
393
|
+
});
|
|
394
|
+
} catch {
|
|
395
|
+
// No party CSV — fallback to department.yaml agents
|
|
396
|
+
const deptYaml = await readFile(join(this.departmentDir, 'department.yaml'), 'utf-8');
|
|
397
|
+
const { parse } = await import('yaml');
|
|
398
|
+
const dept = parse(deptYaml);
|
|
399
|
+
return (dept.agents || []).map((a, i) => {
|
|
400
|
+
const id = typeof a === 'string' ? a : Object.keys(a)[0];
|
|
401
|
+
return { id, name: id, icon: '🤖' };
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
_resolveAgentPath(agentId, agents) {
|
|
407
|
+
// Try standard location
|
|
408
|
+
return join(this.departmentDir, 'agents', `${agentId}.agent.md`);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
_findAgent(agentId) {
|
|
412
|
+
if (!this.stateManager) return null;
|
|
413
|
+
return this.stateManager.agents.find(a => a.id === agentId);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
async _buildUserPrompt(stepMeta, stepBody) {
|
|
417
|
+
const parts = [stepBody];
|
|
418
|
+
|
|
419
|
+
// Include input file content if specified
|
|
420
|
+
if (stepMeta.inputFile) {
|
|
421
|
+
try {
|
|
422
|
+
const inputPath = join(this.targetDir, stepMeta.inputFile);
|
|
423
|
+
const inputContent = await readFile(inputPath, 'utf-8');
|
|
424
|
+
parts.push(`\n--- INPUT ---\n${inputContent}`);
|
|
425
|
+
} catch {
|
|
426
|
+
parts.push(`\n--- INPUT ---\n(Input file not found: ${stepMeta.inputFile})`);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// Include transformed output path
|
|
431
|
+
if (stepMeta.outputFile) {
|
|
432
|
+
const outputPath = this._transformOutputPath(stepMeta.outputFile);
|
|
433
|
+
parts.push(`\n--- OUTPUT PATH ---\nWrite your output to: ${outputPath}`);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
return parts.join('\n');
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
async _verifyOrWriteOutput(outputPath, content) {
|
|
440
|
+
const { writeFile: fsWrite } = await import('node:fs/promises');
|
|
441
|
+
const { dirname: pathDirname } = await import('node:path');
|
|
442
|
+
const dir = pathDirname(outputPath);
|
|
443
|
+
await mkdir(dir, { recursive: true });
|
|
444
|
+
|
|
445
|
+
// Check if the agent already wrote the file via tools
|
|
446
|
+
try {
|
|
447
|
+
await stat(outputPath);
|
|
448
|
+
return; // File exists — agent wrote it
|
|
449
|
+
} catch {
|
|
450
|
+
// Agent didn't write it — write the response text as output
|
|
451
|
+
await fsWrite(outputPath, content, 'utf-8');
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
async _generateRunId() {
|
|
456
|
+
const now = new Date();
|
|
457
|
+
const pad = (n) => String(n).padStart(2, '0');
|
|
458
|
+
let baseId = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}-${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`;
|
|
459
|
+
|
|
460
|
+
const outputDir = join(this.departmentDir, 'output');
|
|
461
|
+
try {
|
|
462
|
+
const entries = await readdir(outputDir);
|
|
463
|
+
let id = baseId;
|
|
464
|
+
let suffix = 2;
|
|
465
|
+
while (entries.includes(id)) {
|
|
466
|
+
id = `${baseId}-${suffix}`;
|
|
467
|
+
suffix++;
|
|
468
|
+
}
|
|
469
|
+
return id;
|
|
470
|
+
} catch {
|
|
471
|
+
return baseId;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
function sleep(ms) {
|
|
477
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
478
|
+
}
|
package/src/prompt.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { t } from './i18n.js';
|
|
2
|
+
|
|
3
|
+
export function createPrompt() {
|
|
4
|
+
return {
|
|
5
|
+
async ask(question) {
|
|
6
|
+
const { default: input } = await import('@inquirer/input');
|
|
7
|
+
return input({ message: question });
|
|
8
|
+
},
|
|
9
|
+
async choose(question, options) {
|
|
10
|
+
const { default: select } = await import('@inquirer/select');
|
|
11
|
+
|
|
12
|
+
const choices = options.map(opt => ({
|
|
13
|
+
name: opt.label,
|
|
14
|
+
value: opt,
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
return select({
|
|
18
|
+
message: ` ${question}`,
|
|
19
|
+
choices,
|
|
20
|
+
loop: false,
|
|
21
|
+
});
|
|
22
|
+
},
|
|
23
|
+
async multiChoose(question, options, { validate } = {}) {
|
|
24
|
+
const { default: checkbox, Separator } = await import('@inquirer/checkbox');
|
|
25
|
+
|
|
26
|
+
const choices = options.map(opt => {
|
|
27
|
+
if (opt.separator) return new Separator(opt.label);
|
|
28
|
+
return {
|
|
29
|
+
name: opt.label,
|
|
30
|
+
value: opt.value,
|
|
31
|
+
checked: opt.checked ?? false,
|
|
32
|
+
disabled: opt.disabled ? t('comingSoon') : false,
|
|
33
|
+
};
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
return checkbox({
|
|
37
|
+
message: ` ${question}`,
|
|
38
|
+
choices,
|
|
39
|
+
loop: false,
|
|
40
|
+
validate: validate ?? ((selected) =>
|
|
41
|
+
selected.length > 0 || t('atLeastOneIde')),
|
|
42
|
+
});
|
|
43
|
+
},
|
|
44
|
+
close() {},
|
|
45
|
+
};
|
|
46
|
+
}
|
package/src/provider.js
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
|
|
4
|
+
const CONFIG_PATH = '_goiabaseeds/config.yaml';
|
|
5
|
+
|
|
6
|
+
const PROVIDER_PACKAGES = {
|
|
7
|
+
anthropic: '@ai-sdk/anthropic',
|
|
8
|
+
openai: '@ai-sdk/openai',
|
|
9
|
+
google: '@ai-sdk/google',
|
|
10
|
+
'github-copilot': '@ai-sdk/openai',
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const ENV_KEYS = {
|
|
14
|
+
anthropic: 'ANTHROPIC_API_KEY',
|
|
15
|
+
openai: 'OPENAI_API_KEY',
|
|
16
|
+
google: 'GOOGLE_GENERATIVE_AI_API_KEY',
|
|
17
|
+
'github-copilot': 'GITHUB_TOKEN',
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const DEFAULT_CONFIG = {
|
|
21
|
+
runtime: 'standalone',
|
|
22
|
+
default_provider: 'anthropic',
|
|
23
|
+
providers: {
|
|
24
|
+
anthropic: {
|
|
25
|
+
models: {
|
|
26
|
+
powerful: 'claude-sonnet-4-20250514',
|
|
27
|
+
fast: 'claude-haiku-4-5-20251001',
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
openai: {
|
|
31
|
+
models: {
|
|
32
|
+
powerful: 'gpt-4o',
|
|
33
|
+
fast: 'gpt-4o-mini',
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
google: {
|
|
37
|
+
models: {
|
|
38
|
+
powerful: 'gemini-2.5-pro',
|
|
39
|
+
fast: 'gemini-2.0-flash',
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
'github-copilot': {
|
|
43
|
+
models: {
|
|
44
|
+
powerful: 'gpt-4o',
|
|
45
|
+
fast: 'gpt-4o-mini',
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Parse a simple YAML config file.
|
|
53
|
+
* Uses the `yaml` package for nested structures.
|
|
54
|
+
*/
|
|
55
|
+
export async function loadProviderConfig(targetDir) {
|
|
56
|
+
const configPath = join(targetDir, CONFIG_PATH);
|
|
57
|
+
try {
|
|
58
|
+
const raw = await readFile(configPath, 'utf-8');
|
|
59
|
+
const { parse } = await import('yaml');
|
|
60
|
+
const parsed = parse(raw);
|
|
61
|
+
return {
|
|
62
|
+
runtime: parsed?.runtime || DEFAULT_CONFIG.runtime,
|
|
63
|
+
default_provider: parsed?.default_provider || DEFAULT_CONFIG.default_provider,
|
|
64
|
+
providers: { ...DEFAULT_CONFIG.providers, ...parsed?.providers },
|
|
65
|
+
};
|
|
66
|
+
} catch (err) {
|
|
67
|
+
if (err.code === 'ENOENT') return { ...DEFAULT_CONFIG };
|
|
68
|
+
throw err;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Create a provider instance with model resolution.
|
|
74
|
+
*
|
|
75
|
+
* @param {object} config - The provider config from loadProviderConfig()
|
|
76
|
+
* @returns {{ getModel(tier: string): object, getProviderName(): string, listModels(): object, getEnvKey(): string }}
|
|
77
|
+
*/
|
|
78
|
+
export async function createProvider(config) {
|
|
79
|
+
const providerName = config.default_provider;
|
|
80
|
+
const providerConfig = config.providers[providerName];
|
|
81
|
+
|
|
82
|
+
if (!providerConfig) {
|
|
83
|
+
throw new Error(`Provider "${providerName}" not found in config. Available: ${Object.keys(config.providers).join(', ')}`);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const envKey = ENV_KEYS[providerName];
|
|
87
|
+
const apiKey = process.env[envKey];
|
|
88
|
+
|
|
89
|
+
if (!apiKey) {
|
|
90
|
+
throw new Error(
|
|
91
|
+
`Missing API key for ${providerName}. Set the ${envKey} environment variable.\n` +
|
|
92
|
+
` Example: export ${envKey}=your-key-here`
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Dynamic import — only load the SDK package for the configured provider
|
|
97
|
+
const pkgName = PROVIDER_PACKAGES[providerName];
|
|
98
|
+
let sdkModule;
|
|
99
|
+
try {
|
|
100
|
+
sdkModule = await import(pkgName);
|
|
101
|
+
} catch {
|
|
102
|
+
throw new Error(
|
|
103
|
+
`Provider package "${pkgName}" not installed.\n` +
|
|
104
|
+
` Run: npm install ${pkgName}`
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Build the provider factory
|
|
109
|
+
let providerInstance;
|
|
110
|
+
|
|
111
|
+
if (providerName === 'github-copilot') {
|
|
112
|
+
// Copilot uses OpenAI SDK with custom endpoint
|
|
113
|
+
const { createOpenAI } = sdkModule;
|
|
114
|
+
providerInstance = createOpenAI({
|
|
115
|
+
apiKey,
|
|
116
|
+
baseURL: 'https://api.githubcopilot.com',
|
|
117
|
+
});
|
|
118
|
+
} else if (providerName === 'anthropic') {
|
|
119
|
+
const { createAnthropic } = sdkModule;
|
|
120
|
+
providerInstance = createAnthropic({ apiKey });
|
|
121
|
+
} else if (providerName === 'openai') {
|
|
122
|
+
const { createOpenAI } = sdkModule;
|
|
123
|
+
providerInstance = createOpenAI({ apiKey });
|
|
124
|
+
} else if (providerName === 'google') {
|
|
125
|
+
const { createGoogleGenerativeAI } = sdkModule;
|
|
126
|
+
providerInstance = createGoogleGenerativeAI({ apiKey });
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
/**
|
|
131
|
+
* Get an AI SDK model instance for the given tier.
|
|
132
|
+
* @param {'fast'|'powerful'} tier
|
|
133
|
+
* @returns {object} AI SDK model instance
|
|
134
|
+
*/
|
|
135
|
+
getModel(tier) {
|
|
136
|
+
const modelId = tier === 'fast'
|
|
137
|
+
? providerConfig.models.fast
|
|
138
|
+
: providerConfig.models.powerful;
|
|
139
|
+
return providerInstance(modelId);
|
|
140
|
+
},
|
|
141
|
+
|
|
142
|
+
getProviderName() {
|
|
143
|
+
return providerName;
|
|
144
|
+
},
|
|
145
|
+
|
|
146
|
+
getEnvKey() {
|
|
147
|
+
return envKey;
|
|
148
|
+
},
|
|
149
|
+
|
|
150
|
+
listModels() {
|
|
151
|
+
return { ...providerConfig.models };
|
|
152
|
+
},
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export { DEFAULT_CONFIG, ENV_KEYS, PROVIDER_PACKAGES };
|