@mastra/agent-builder 0.0.1-alpha.1 → 0.0.1-alpha.2
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/CHANGELOG.md +15 -0
- package/dist/agent/index.d.ts +5885 -0
- package/dist/agent/index.d.ts.map +1 -0
- package/dist/defaults.d.ts +6529 -0
- package/dist/defaults.d.ts.map +1 -0
- package/dist/index.d.ts +4 -4
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1810 -36
- package/dist/index.js.map +1 -0
- package/dist/processors/tool-summary.d.ts +29 -0
- package/dist/processors/tool-summary.d.ts.map +1 -0
- package/dist/processors/write-file.d.ts +10 -0
- package/dist/processors/write-file.d.ts.map +1 -0
- package/dist/types.d.ts +1121 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/utils.d.ts +63 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/workflows/index.d.ts +5 -0
- package/dist/workflows/index.d.ts.map +1 -0
- package/dist/workflows/shared/schema.d.ts +139 -0
- package/dist/workflows/shared/schema.d.ts.map +1 -0
- package/dist/workflows/task-planning/prompts.d.ts +37 -0
- package/dist/workflows/task-planning/prompts.d.ts.map +1 -0
- package/dist/workflows/task-planning/schema.d.ts +548 -0
- package/dist/workflows/task-planning/schema.d.ts.map +1 -0
- package/dist/workflows/task-planning/task-planning.d.ts +992 -0
- package/dist/workflows/task-planning/task-planning.d.ts.map +1 -0
- package/dist/workflows/template-builder/template-builder.d.ts +1910 -0
- package/dist/workflows/template-builder/template-builder.d.ts.map +1 -0
- package/dist/workflows/workflow-builder/prompts.d.ts +44 -0
- package/dist/workflows/workflow-builder/prompts.d.ts.map +1 -0
- package/dist/workflows/workflow-builder/schema.d.ts +1170 -0
- package/dist/workflows/workflow-builder/schema.d.ts.map +1 -0
- package/dist/workflows/workflow-builder/tools.d.ts +309 -0
- package/dist/workflows/workflow-builder/tools.d.ts.map +1 -0
- package/dist/workflows/workflow-builder/workflow-builder.d.ts +2714 -0
- package/dist/workflows/workflow-builder/workflow-builder.d.ts.map +1 -0
- package/dist/workflows/workflow-map.d.ts +3735 -0
- package/dist/workflows/workflow-map.d.ts.map +1 -0
- package/package.json +20 -9
- package/.turbo/turbo-build.log +0 -12
- package/dist/_tsup-dts-rollup.d.cts +0 -14933
- package/dist/_tsup-dts-rollup.d.ts +0 -14933
- package/dist/index.cjs +0 -4357
- package/dist/index.d.cts +0 -4
- package/eslint.config.js +0 -11
- package/integration-tests/CHANGELOG.md +0 -9
- package/integration-tests/README.md +0 -154
- package/integration-tests/docker-compose.yml +0 -39
- package/integration-tests/package.json +0 -38
- package/integration-tests/src/agent-template-behavior.test.ts +0 -103
- package/integration-tests/src/fixtures/minimal-mastra-project/env.example +0 -6
- package/integration-tests/src/fixtures/minimal-mastra-project/package.json +0 -17
- package/integration-tests/src/fixtures/minimal-mastra-project/src/mastra/agents/weather.ts +0 -34
- package/integration-tests/src/fixtures/minimal-mastra-project/src/mastra/index.ts +0 -15
- package/integration-tests/src/fixtures/minimal-mastra-project/src/mastra/mcp/index.ts +0 -46
- package/integration-tests/src/fixtures/minimal-mastra-project/src/mastra/tools/weather.ts +0 -14
- package/integration-tests/src/fixtures/minimal-mastra-project/tsconfig.json +0 -17
- package/integration-tests/src/template-integration.test.ts +0 -312
- package/integration-tests/tsconfig.json +0 -9
- package/integration-tests/vitest.config.ts +0 -18
- package/src/agent/index.ts +0 -187
- package/src/agent-builder.test.ts +0 -313
- package/src/defaults.ts +0 -2876
- package/src/index.ts +0 -3
- package/src/processors/tool-summary.ts +0 -145
- package/src/processors/write-file.ts +0 -17
- package/src/types.ts +0 -305
- package/src/utils.ts +0 -409
- package/src/workflows/index.ts +0 -1
- package/src/workflows/template-builder.ts +0 -1682
- package/tsconfig.json +0 -5
- package/vitest.config.ts +0 -11
|
@@ -1,1682 +0,0 @@
|
|
|
1
|
-
import { existsSync } from 'fs';
|
|
2
|
-
import { mkdtemp, copyFile, readFile, mkdir, readdir, rm, writeFile } from 'fs/promises';
|
|
3
|
-
import { tmpdir } from 'os';
|
|
4
|
-
import { join, dirname, resolve, extname, basename } from 'path';
|
|
5
|
-
import { openai } from '@ai-sdk/openai';
|
|
6
|
-
import { Agent } from '@mastra/core/agent';
|
|
7
|
-
import type { MastraLanguageModel } from '@mastra/core/agent';
|
|
8
|
-
import { createTool } from '@mastra/core/tools';
|
|
9
|
-
import { createWorkflow, createStep } from '@mastra/core/workflows';
|
|
10
|
-
import { z } from 'zod';
|
|
11
|
-
import { AgentBuilder } from '..';
|
|
12
|
-
import { AgentBuilderDefaults } from '../defaults';
|
|
13
|
-
import type { TemplateUnit, UnitKind } from '../types';
|
|
14
|
-
import {
|
|
15
|
-
ApplyResultSchema,
|
|
16
|
-
AgentBuilderInputSchema,
|
|
17
|
-
CloneTemplateResultSchema,
|
|
18
|
-
PackageAnalysisSchema,
|
|
19
|
-
DiscoveryResultSchema,
|
|
20
|
-
OrderedUnitsSchema,
|
|
21
|
-
PackageMergeInputSchema,
|
|
22
|
-
PackageMergeResultSchema,
|
|
23
|
-
InstallInputSchema,
|
|
24
|
-
InstallResultSchema,
|
|
25
|
-
FileCopyInputSchema,
|
|
26
|
-
FileCopyResultSchema,
|
|
27
|
-
IntelligentMergeInputSchema,
|
|
28
|
-
IntelligentMergeResultSchema,
|
|
29
|
-
ValidationFixInputSchema,
|
|
30
|
-
ValidationFixResultSchema,
|
|
31
|
-
PrepareBranchInputSchema,
|
|
32
|
-
PrepareBranchResultSchema,
|
|
33
|
-
} from '../types';
|
|
34
|
-
import {
|
|
35
|
-
getMastraTemplate,
|
|
36
|
-
kindWeight,
|
|
37
|
-
spawnSWPM,
|
|
38
|
-
logGitState,
|
|
39
|
-
backupAndReplaceFile,
|
|
40
|
-
renameAndCopyFile,
|
|
41
|
-
gitCheckoutBranch,
|
|
42
|
-
gitClone,
|
|
43
|
-
gitCheckoutRef,
|
|
44
|
-
gitRevParse,
|
|
45
|
-
gitAddAndCommit,
|
|
46
|
-
} from '../utils';
|
|
47
|
-
|
|
48
|
-
// Helper function to resolve the model to use
|
|
49
|
-
const resolveModel = (runtimeContext: any): MastraLanguageModel => {
|
|
50
|
-
const modelFromContext = runtimeContext.get('model');
|
|
51
|
-
if (modelFromContext) {
|
|
52
|
-
// Type check to ensure it's a MastraLanguageModel
|
|
53
|
-
if (isValidMastraLanguageModel(modelFromContext)) {
|
|
54
|
-
return modelFromContext;
|
|
55
|
-
}
|
|
56
|
-
throw new Error(
|
|
57
|
-
'Invalid model provided. Model must be a MastraLanguageModel instance (e.g., openai("gpt-4"), anthropic("claude-3-5-sonnet"), etc.)',
|
|
58
|
-
);
|
|
59
|
-
}
|
|
60
|
-
return openai('gpt-4.1'); // Default model
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
// Type guard to check if object is a valid MastraLanguageModel
|
|
64
|
-
const isValidMastraLanguageModel = (model: any): model is MastraLanguageModel => {
|
|
65
|
-
return (
|
|
66
|
-
model && typeof model === 'object' && typeof model.modelId === 'string' && typeof model.generate === 'function'
|
|
67
|
-
);
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
// Step 1: Clone template to temp directory
|
|
71
|
-
const cloneTemplateStep = createStep({
|
|
72
|
-
id: 'clone-template',
|
|
73
|
-
description: 'Clone the template repository to a temporary directory at the specified ref',
|
|
74
|
-
inputSchema: AgentBuilderInputSchema,
|
|
75
|
-
outputSchema: CloneTemplateResultSchema,
|
|
76
|
-
execute: async ({ inputData }) => {
|
|
77
|
-
const { repo, ref = 'main', slug } = inputData;
|
|
78
|
-
|
|
79
|
-
if (!repo) {
|
|
80
|
-
throw new Error('Repository URL or path is required');
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// Extract slug from repo URL if not provided
|
|
84
|
-
const inferredSlug =
|
|
85
|
-
slug ||
|
|
86
|
-
repo
|
|
87
|
-
.split('/')
|
|
88
|
-
.pop()
|
|
89
|
-
?.replace(/\.git$/, '') ||
|
|
90
|
-
'template';
|
|
91
|
-
|
|
92
|
-
// Create temporary directory
|
|
93
|
-
const tempDir = await mkdtemp(join(tmpdir(), 'mastra-template-'));
|
|
94
|
-
|
|
95
|
-
try {
|
|
96
|
-
// Clone repository
|
|
97
|
-
await gitClone(repo, tempDir);
|
|
98
|
-
|
|
99
|
-
// Checkout specific ref if provided
|
|
100
|
-
if (ref !== 'main' && ref !== 'master') {
|
|
101
|
-
await gitCheckoutRef(tempDir, ref);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// Get commit SHA
|
|
105
|
-
const commitSha = await gitRevParse(tempDir, 'HEAD');
|
|
106
|
-
|
|
107
|
-
return {
|
|
108
|
-
templateDir: tempDir,
|
|
109
|
-
commitSha: commitSha.trim(),
|
|
110
|
-
slug: inferredSlug,
|
|
111
|
-
success: true,
|
|
112
|
-
};
|
|
113
|
-
} catch (error) {
|
|
114
|
-
// Cleanup on error
|
|
115
|
-
try {
|
|
116
|
-
await rm(tempDir, { recursive: true, force: true });
|
|
117
|
-
} catch {}
|
|
118
|
-
|
|
119
|
-
return {
|
|
120
|
-
templateDir: '',
|
|
121
|
-
commitSha: '',
|
|
122
|
-
slug: slug || 'unknown',
|
|
123
|
-
success: false,
|
|
124
|
-
error: `Failed to clone template: ${error instanceof Error ? error.message : String(error)}`,
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
},
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
// Step 2: Analyze template package.json for dependencies
|
|
131
|
-
const analyzePackageStep = createStep({
|
|
132
|
-
id: 'analyze-package',
|
|
133
|
-
description: 'Analyze the template package.json to extract dependency information',
|
|
134
|
-
inputSchema: CloneTemplateResultSchema,
|
|
135
|
-
outputSchema: PackageAnalysisSchema,
|
|
136
|
-
execute: async ({ inputData }) => {
|
|
137
|
-
console.log('Analyzing template package.json...');
|
|
138
|
-
const { templateDir } = inputData;
|
|
139
|
-
const packageJsonPath = join(templateDir, 'package.json');
|
|
140
|
-
|
|
141
|
-
try {
|
|
142
|
-
const packageJsonContent = await readFile(packageJsonPath, 'utf-8');
|
|
143
|
-
const packageJson = JSON.parse(packageJsonContent);
|
|
144
|
-
|
|
145
|
-
console.log('Template package.json:', JSON.stringify(packageJson, null, 2));
|
|
146
|
-
|
|
147
|
-
return {
|
|
148
|
-
dependencies: packageJson.dependencies || {},
|
|
149
|
-
devDependencies: packageJson.devDependencies || {},
|
|
150
|
-
peerDependencies: packageJson.peerDependencies || {},
|
|
151
|
-
scripts: packageJson.scripts || {},
|
|
152
|
-
name: packageJson.name || '',
|
|
153
|
-
version: packageJson.version || '',
|
|
154
|
-
description: packageJson.description || '',
|
|
155
|
-
success: true,
|
|
156
|
-
};
|
|
157
|
-
} catch (error) {
|
|
158
|
-
console.warn(`Failed to read template package.json: ${error instanceof Error ? error.message : String(error)}`);
|
|
159
|
-
return {
|
|
160
|
-
dependencies: {},
|
|
161
|
-
devDependencies: {},
|
|
162
|
-
peerDependencies: {},
|
|
163
|
-
scripts: {},
|
|
164
|
-
name: '',
|
|
165
|
-
version: '',
|
|
166
|
-
description: '',
|
|
167
|
-
success: true, // This is a graceful fallback, not a failure
|
|
168
|
-
};
|
|
169
|
-
}
|
|
170
|
-
},
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
// Step 3: Discover template units by scanning the templates directory
|
|
174
|
-
const discoverUnitsStep = createStep({
|
|
175
|
-
id: 'discover-units',
|
|
176
|
-
description: 'Discover template units by analyzing the templates directory structure',
|
|
177
|
-
inputSchema: CloneTemplateResultSchema,
|
|
178
|
-
outputSchema: DiscoveryResultSchema,
|
|
179
|
-
execute: async ({ inputData, runtimeContext }) => {
|
|
180
|
-
const { templateDir } = inputData;
|
|
181
|
-
|
|
182
|
-
const tools = await AgentBuilderDefaults.DEFAULT_TOOLS(templateDir);
|
|
183
|
-
|
|
184
|
-
try {
|
|
185
|
-
const agent = new Agent({
|
|
186
|
-
model: resolveModel(runtimeContext),
|
|
187
|
-
instructions: `You are an expert at analyzing Mastra projects.
|
|
188
|
-
|
|
189
|
-
Your task is to scan the provided directory and identify all available units (agents, workflows, tools, MCP servers, networks).
|
|
190
|
-
|
|
191
|
-
Mastra Project Structure Analysis:
|
|
192
|
-
- Each Mastra project has a structure like: ${AgentBuilderDefaults.DEFAULT_FOLDER_STRUCTURE.agent}, ${AgentBuilderDefaults.DEFAULT_FOLDER_STRUCTURE.workflow}, ${AgentBuilderDefaults.DEFAULT_FOLDER_STRUCTURE.tool}, ${AgentBuilderDefaults.DEFAULT_FOLDER_STRUCTURE['mcp-server']}, ${AgentBuilderDefaults.DEFAULT_FOLDER_STRUCTURE.network}
|
|
193
|
-
- Analyze TypeScript files in each category directory to identify exported units
|
|
194
|
-
|
|
195
|
-
CRITICAL: YOU MUST USE YOUR TOOLS (readFile, listDirectory) TO DISCOVER THE UNITS IN THE TEMPLATE DIRECTORY.
|
|
196
|
-
|
|
197
|
-
IMPORTANT - Agent Discovery Rules:
|
|
198
|
-
1. **Multiple Agent Files**: Some templates have separate files for each agent (e.g., evaluationAgent.ts, researchAgent.ts)
|
|
199
|
-
2. **Single File Multiple Agents**: Some files may export multiple agents (look for multiple 'export const' or 'export default' statements)
|
|
200
|
-
3. **Agent Identification**: Look for exported variables that are instances of 'new Agent()' or similar patterns
|
|
201
|
-
4. **Naming Convention**: Agent names should be extracted from the export name (e.g., 'weatherAgent', 'evaluationAgent')
|
|
202
|
-
|
|
203
|
-
For each Mastra project directory you analyze:
|
|
204
|
-
1. Scan all TypeScript files in ${AgentBuilderDefaults.DEFAULT_FOLDER_STRUCTURE.agent} and identify ALL exported agents
|
|
205
|
-
2. Scan all TypeScript files in ${AgentBuilderDefaults.DEFAULT_FOLDER_STRUCTURE.workflow} and identify ALL exported workflows
|
|
206
|
-
3. Scan all TypeScript files in ${AgentBuilderDefaults.DEFAULT_FOLDER_STRUCTURE.tool} and identify ALL exported tools
|
|
207
|
-
4. Scan all TypeScript files in ${AgentBuilderDefaults.DEFAULT_FOLDER_STRUCTURE['mcp-server']} and identify ALL exported MCP servers
|
|
208
|
-
5. Scan all TypeScript files in ${AgentBuilderDefaults.DEFAULT_FOLDER_STRUCTURE.network} and identify ALL exported networks
|
|
209
|
-
6. Scan for any OTHER files in src/mastra that are NOT in the above default folders (e.g., lib/, utils/, types/, etc.) and identify them as 'other' files
|
|
210
|
-
|
|
211
|
-
IMPORTANT - Naming Consistency Rules:
|
|
212
|
-
- For ALL unit types (including 'other'), the 'name' field should be the filename WITHOUT extension
|
|
213
|
-
- For structured units (agents, workflows, tools, etc.), prefer the actual export name if clearly identifiable
|
|
214
|
-
- use the base filename without extension for the id (e.g., 'util.ts' → name: 'util')
|
|
215
|
-
- use the relative path from the template root for the file (e.g., 'src/mastra/lib/util.ts' → file: 'src/mastra/lib/util.ts')
|
|
216
|
-
|
|
217
|
-
Return the actual exported names of the units, as well as the file names.`,
|
|
218
|
-
name: 'Mastra Project Discoverer',
|
|
219
|
-
tools: {
|
|
220
|
-
readFile: tools.readFile,
|
|
221
|
-
listDirectory: tools.listDirectory,
|
|
222
|
-
},
|
|
223
|
-
});
|
|
224
|
-
|
|
225
|
-
const result = await agent.generate(
|
|
226
|
-
`Analyze the Mastra project directory structure at "${templateDir}".
|
|
227
|
-
|
|
228
|
-
List directory contents using listDirectory tool, and then analyze each file with readFile tool.
|
|
229
|
-
IMPORTANT:
|
|
230
|
-
- Look inside the actual file content to find export statements like 'export const agentName = new Agent(...)'
|
|
231
|
-
- A single file may contain multiple exports
|
|
232
|
-
- Return the actual exported variable names, as well as the file names
|
|
233
|
-
- If a directory doesn't exist or has no files, return an empty array
|
|
234
|
-
|
|
235
|
-
Return the analysis in the exact format specified in the output schema.`,
|
|
236
|
-
{
|
|
237
|
-
experimental_output: z.object({
|
|
238
|
-
agents: z.array(z.object({ name: z.string(), file: z.string() })).optional(),
|
|
239
|
-
workflows: z.array(z.object({ name: z.string(), file: z.string() })).optional(),
|
|
240
|
-
tools: z.array(z.object({ name: z.string(), file: z.string() })).optional(),
|
|
241
|
-
mcp: z.array(z.object({ name: z.string(), file: z.string() })).optional(),
|
|
242
|
-
networks: z.array(z.object({ name: z.string(), file: z.string() })).optional(),
|
|
243
|
-
other: z.array(z.object({ name: z.string(), file: z.string() })).optional(),
|
|
244
|
-
}),
|
|
245
|
-
maxSteps: 100,
|
|
246
|
-
},
|
|
247
|
-
);
|
|
248
|
-
|
|
249
|
-
const template = result.object ?? {};
|
|
250
|
-
|
|
251
|
-
const units: TemplateUnit[] = [];
|
|
252
|
-
|
|
253
|
-
// Add agents
|
|
254
|
-
template.agents?.forEach((agentId: { name: string; file: string }) => {
|
|
255
|
-
units.push({ kind: 'agent', id: agentId.name, file: agentId.file });
|
|
256
|
-
});
|
|
257
|
-
|
|
258
|
-
// Add workflows
|
|
259
|
-
template.workflows?.forEach((workflowId: { name: string; file: string }) => {
|
|
260
|
-
units.push({ kind: 'workflow', id: workflowId.name, file: workflowId.file });
|
|
261
|
-
});
|
|
262
|
-
|
|
263
|
-
// Add tools
|
|
264
|
-
template.tools?.forEach((toolId: { name: string; file: string }) => {
|
|
265
|
-
units.push({ kind: 'tool', id: toolId.name, file: toolId.file });
|
|
266
|
-
});
|
|
267
|
-
|
|
268
|
-
// Add MCP servers
|
|
269
|
-
template.mcp?.forEach((mcpId: { name: string; file: string }) => {
|
|
270
|
-
units.push({ kind: 'mcp-server', id: mcpId.name, file: mcpId.file });
|
|
271
|
-
});
|
|
272
|
-
|
|
273
|
-
// Add networks
|
|
274
|
-
template.networks?.forEach((networkId: { name: string; file: string }) => {
|
|
275
|
-
units.push({ kind: 'network', id: networkId.name, file: networkId.file });
|
|
276
|
-
});
|
|
277
|
-
|
|
278
|
-
// Add other files
|
|
279
|
-
template.other?.forEach((otherId: { name: string; file: string }) => {
|
|
280
|
-
units.push({ kind: 'other', id: otherId.name, file: otherId.file });
|
|
281
|
-
});
|
|
282
|
-
|
|
283
|
-
console.log('Discovered units:', JSON.stringify(units, null, 2));
|
|
284
|
-
|
|
285
|
-
if (units.length === 0) {
|
|
286
|
-
throw new Error(`No Mastra units (agents, workflows, tools) found in template.
|
|
287
|
-
Possible causes:
|
|
288
|
-
- Template may not follow standard Mastra structure
|
|
289
|
-
- AI agent couldn't analyze template files (model/token limits)
|
|
290
|
-
- Template is empty or in wrong branch
|
|
291
|
-
|
|
292
|
-
Debug steps:
|
|
293
|
-
- Check template has files in src/mastra/ directories
|
|
294
|
-
- Try a different branch
|
|
295
|
-
- Check template repository structure manually`);
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
return {
|
|
299
|
-
units,
|
|
300
|
-
success: true,
|
|
301
|
-
};
|
|
302
|
-
} catch (error) {
|
|
303
|
-
console.error('Failed to discover units:', error);
|
|
304
|
-
return {
|
|
305
|
-
units: [],
|
|
306
|
-
success: false,
|
|
307
|
-
error: `Failed to discover units: ${error instanceof Error ? error.message : String(error)}`,
|
|
308
|
-
};
|
|
309
|
-
}
|
|
310
|
-
},
|
|
311
|
-
});
|
|
312
|
-
|
|
313
|
-
// Step 4: Topological ordering (simplified)
|
|
314
|
-
const orderUnitsStep = createStep({
|
|
315
|
-
id: 'order-units',
|
|
316
|
-
description: 'Sort units in topological order based on kind weights',
|
|
317
|
-
inputSchema: DiscoveryResultSchema,
|
|
318
|
-
outputSchema: OrderedUnitsSchema,
|
|
319
|
-
execute: async ({ inputData }) => {
|
|
320
|
-
const { units } = inputData;
|
|
321
|
-
|
|
322
|
-
// Simple sort by kind weight (mcp-servers first, then tools, agents, workflows, integration last)
|
|
323
|
-
const orderedUnits = [...units].sort((a, b) => {
|
|
324
|
-
const aWeight = kindWeight(a.kind);
|
|
325
|
-
const bWeight = kindWeight(b.kind);
|
|
326
|
-
return aWeight - bWeight;
|
|
327
|
-
});
|
|
328
|
-
|
|
329
|
-
return {
|
|
330
|
-
orderedUnits,
|
|
331
|
-
success: true,
|
|
332
|
-
};
|
|
333
|
-
},
|
|
334
|
-
});
|
|
335
|
-
|
|
336
|
-
// Step 5: Prepare branch
|
|
337
|
-
const prepareBranchStep = createStep({
|
|
338
|
-
id: 'prepare-branch',
|
|
339
|
-
description: 'Create or switch to integration branch before modifications',
|
|
340
|
-
inputSchema: PrepareBranchInputSchema,
|
|
341
|
-
outputSchema: PrepareBranchResultSchema,
|
|
342
|
-
execute: async ({ inputData, runtimeContext }) => {
|
|
343
|
-
const targetPath = inputData.targetPath || runtimeContext.get('targetPath') || process.cwd();
|
|
344
|
-
|
|
345
|
-
try {
|
|
346
|
-
const branchName = `feat/install-template-${inputData.slug}`;
|
|
347
|
-
await gitCheckoutBranch(branchName, targetPath);
|
|
348
|
-
|
|
349
|
-
return {
|
|
350
|
-
branchName,
|
|
351
|
-
success: true,
|
|
352
|
-
};
|
|
353
|
-
} catch (error) {
|
|
354
|
-
console.error('Failed to prepare branch:', error);
|
|
355
|
-
return {
|
|
356
|
-
branchName: `feat/install-template-${inputData.slug}`, // Return the intended name anyway
|
|
357
|
-
success: false,
|
|
358
|
-
error: `Failed to prepare branch: ${error instanceof Error ? error.message : String(error)}`,
|
|
359
|
-
};
|
|
360
|
-
}
|
|
361
|
-
},
|
|
362
|
-
});
|
|
363
|
-
|
|
364
|
-
// Step 6: Package merge
|
|
365
|
-
const packageMergeStep = createStep({
|
|
366
|
-
id: 'package-merge',
|
|
367
|
-
description: 'Merge template package.json dependencies into target project',
|
|
368
|
-
inputSchema: PackageMergeInputSchema,
|
|
369
|
-
outputSchema: PackageMergeResultSchema,
|
|
370
|
-
execute: async ({ inputData, runtimeContext }) => {
|
|
371
|
-
console.log('Package merge step starting...');
|
|
372
|
-
const { slug, packageInfo } = inputData;
|
|
373
|
-
const targetPath = inputData.targetPath || runtimeContext.get('targetPath') || process.cwd();
|
|
374
|
-
|
|
375
|
-
try {
|
|
376
|
-
const targetPkgPath = join(targetPath, 'package.json');
|
|
377
|
-
|
|
378
|
-
let targetPkgRaw = '{}';
|
|
379
|
-
try {
|
|
380
|
-
targetPkgRaw = await readFile(targetPkgPath, 'utf-8');
|
|
381
|
-
} catch {
|
|
382
|
-
console.warn(`No existing package.json at ${targetPkgPath}, creating a new one`);
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
let targetPkg: any;
|
|
386
|
-
try {
|
|
387
|
-
targetPkg = JSON.parse(targetPkgRaw || '{}');
|
|
388
|
-
} catch (e) {
|
|
389
|
-
throw new Error(
|
|
390
|
-
`Failed to parse existing package.json at ${targetPkgPath}: ${e instanceof Error ? e.message : String(e)}`,
|
|
391
|
-
);
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
const ensureObj = (o: any) => (o && typeof o === 'object' ? o : {});
|
|
395
|
-
|
|
396
|
-
targetPkg.dependencies = ensureObj(targetPkg.dependencies);
|
|
397
|
-
targetPkg.devDependencies = ensureObj(targetPkg.devDependencies);
|
|
398
|
-
targetPkg.peerDependencies = ensureObj(targetPkg.peerDependencies);
|
|
399
|
-
targetPkg.scripts = ensureObj(targetPkg.scripts);
|
|
400
|
-
|
|
401
|
-
const tplDeps = ensureObj(packageInfo.dependencies);
|
|
402
|
-
const tplDevDeps = ensureObj(packageInfo.devDependencies);
|
|
403
|
-
const tplPeerDeps = ensureObj(packageInfo.peerDependencies);
|
|
404
|
-
const tplScripts = ensureObj(packageInfo.scripts);
|
|
405
|
-
|
|
406
|
-
const existsAnywhere = (name: string) =>
|
|
407
|
-
name in targetPkg.dependencies || name in targetPkg.devDependencies || name in targetPkg.peerDependencies;
|
|
408
|
-
|
|
409
|
-
// Merge dependencies: add only if missing everywhere
|
|
410
|
-
for (const [name, ver] of Object.entries(tplDeps)) {
|
|
411
|
-
if (!existsAnywhere(name)) {
|
|
412
|
-
(targetPkg.dependencies as Record<string, string>)[name] = String(ver);
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
// Merge devDependencies
|
|
417
|
-
for (const [name, ver] of Object.entries(tplDevDeps)) {
|
|
418
|
-
if (!existsAnywhere(name)) {
|
|
419
|
-
(targetPkg.devDependencies as Record<string, string>)[name] = String(ver);
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
// Merge peerDependencies
|
|
424
|
-
for (const [name, ver] of Object.entries(tplPeerDeps)) {
|
|
425
|
-
if (!(name in targetPkg.peerDependencies)) {
|
|
426
|
-
(targetPkg.peerDependencies as Record<string, string>)[name] = String(ver);
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
// Merge scripts with prefixed keys to avoid collisions
|
|
431
|
-
const prefix = `template:${slug}:`;
|
|
432
|
-
for (const [name, cmd] of Object.entries(tplScripts)) {
|
|
433
|
-
const newKey = `${prefix}${name}`;
|
|
434
|
-
if (!(newKey in targetPkg.scripts)) {
|
|
435
|
-
(targetPkg.scripts as Record<string, string>)[newKey] = String(cmd);
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
await writeFile(targetPkgPath, JSON.stringify(targetPkg, null, 2), 'utf-8');
|
|
440
|
-
|
|
441
|
-
await gitAddAndCommit(targetPath, `feat(template): merge deps for ${slug}`, [targetPkgPath], {
|
|
442
|
-
skipIfNoStaged: true,
|
|
443
|
-
});
|
|
444
|
-
|
|
445
|
-
return {
|
|
446
|
-
success: true,
|
|
447
|
-
applied: true,
|
|
448
|
-
message: `Successfully merged template dependencies for ${slug}`,
|
|
449
|
-
};
|
|
450
|
-
} catch (error) {
|
|
451
|
-
console.error('Package merge failed:', error);
|
|
452
|
-
return {
|
|
453
|
-
success: false,
|
|
454
|
-
applied: false,
|
|
455
|
-
message: `Package merge failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
456
|
-
error: error instanceof Error ? error.message : String(error),
|
|
457
|
-
};
|
|
458
|
-
}
|
|
459
|
-
},
|
|
460
|
-
});
|
|
461
|
-
|
|
462
|
-
// Step 7: Install
|
|
463
|
-
const installStep = createStep({
|
|
464
|
-
id: 'install',
|
|
465
|
-
description: 'Install packages based on merged package.json',
|
|
466
|
-
inputSchema: InstallInputSchema,
|
|
467
|
-
outputSchema: InstallResultSchema,
|
|
468
|
-
execute: async ({ inputData, runtimeContext }) => {
|
|
469
|
-
console.log('Running install step...');
|
|
470
|
-
const targetPath = inputData.targetPath || runtimeContext.get('targetPath') || process.cwd();
|
|
471
|
-
|
|
472
|
-
try {
|
|
473
|
-
// Run install using swpm (no specific packages)
|
|
474
|
-
await spawnSWPM(targetPath, 'install', []);
|
|
475
|
-
|
|
476
|
-
const lock = ['pnpm-lock.yaml', 'package-lock.json', 'yarn.lock']
|
|
477
|
-
.map(f => join(targetPath, f))
|
|
478
|
-
.find(f => existsSync(f));
|
|
479
|
-
|
|
480
|
-
if (lock) {
|
|
481
|
-
await gitAddAndCommit(targetPath, `chore(template): commit lockfile after install`, [lock], {
|
|
482
|
-
skipIfNoStaged: true,
|
|
483
|
-
});
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
return {
|
|
487
|
-
success: true,
|
|
488
|
-
};
|
|
489
|
-
} catch (error) {
|
|
490
|
-
console.error('Install failed:', error);
|
|
491
|
-
return {
|
|
492
|
-
success: false,
|
|
493
|
-
error: error instanceof Error ? error.message : String(error),
|
|
494
|
-
};
|
|
495
|
-
}
|
|
496
|
-
},
|
|
497
|
-
});
|
|
498
|
-
|
|
499
|
-
// Step 7: Programmatic File Copy Step - copies template files to target project
|
|
500
|
-
const programmaticFileCopyStep = createStep({
|
|
501
|
-
id: 'programmatic-file-copy',
|
|
502
|
-
description: 'Programmatically copy template files to target project based on ordered units',
|
|
503
|
-
inputSchema: FileCopyInputSchema,
|
|
504
|
-
outputSchema: FileCopyResultSchema,
|
|
505
|
-
execute: async ({ inputData, runtimeContext }) => {
|
|
506
|
-
console.log('Programmatic file copy step starting...');
|
|
507
|
-
const { orderedUnits, templateDir, commitSha, slug } = inputData;
|
|
508
|
-
const targetPath = inputData.targetPath || runtimeContext.get('targetPath') || process.cwd();
|
|
509
|
-
|
|
510
|
-
try {
|
|
511
|
-
const copiedFiles: Array<{
|
|
512
|
-
source: string;
|
|
513
|
-
destination: string;
|
|
514
|
-
unit: { kind: UnitKind; id: string };
|
|
515
|
-
}> = [];
|
|
516
|
-
|
|
517
|
-
const conflicts: Array<{
|
|
518
|
-
unit: { kind: UnitKind; id: string };
|
|
519
|
-
issue: string;
|
|
520
|
-
sourceFile: string;
|
|
521
|
-
targetFile: string;
|
|
522
|
-
}> = [];
|
|
523
|
-
|
|
524
|
-
// Analyze target project naming convention first
|
|
525
|
-
const analyzeNamingConvention = async (
|
|
526
|
-
directory: string,
|
|
527
|
-
): Promise<'camelCase' | 'snake_case' | 'kebab-case' | 'PascalCase' | 'unknown'> => {
|
|
528
|
-
try {
|
|
529
|
-
const files = await readdir(resolve(targetPath, directory), { withFileTypes: true });
|
|
530
|
-
const tsFiles = files.filter(f => f.isFile() && f.name.endsWith('.ts')).map(f => f.name);
|
|
531
|
-
|
|
532
|
-
if (tsFiles.length === 0) return 'unknown';
|
|
533
|
-
|
|
534
|
-
// Check for patterns
|
|
535
|
-
const camelCaseCount = tsFiles.filter(f => /^[a-z][a-zA-Z0-9]*\.ts$/.test(f)).length;
|
|
536
|
-
const snakeCaseCount = tsFiles.filter(f => /^[a-z][a-z0-9_]*\.ts$/.test(f) && f.includes('_')).length;
|
|
537
|
-
const kebabCaseCount = tsFiles.filter(f => /^[a-z][a-z0-9-]*\.ts$/.test(f) && f.includes('-')).length;
|
|
538
|
-
const pascalCaseCount = tsFiles.filter(f => /^[A-Z][a-zA-Z0-9]*\.ts$/.test(f)).length;
|
|
539
|
-
|
|
540
|
-
const max = Math.max(camelCaseCount, snakeCaseCount, kebabCaseCount, pascalCaseCount);
|
|
541
|
-
if (max === 0) return 'unknown';
|
|
542
|
-
|
|
543
|
-
if (camelCaseCount === max) return 'camelCase';
|
|
544
|
-
if (snakeCaseCount === max) return 'snake_case';
|
|
545
|
-
if (kebabCaseCount === max) return 'kebab-case';
|
|
546
|
-
if (pascalCaseCount === max) return 'PascalCase';
|
|
547
|
-
|
|
548
|
-
return 'unknown';
|
|
549
|
-
} catch {
|
|
550
|
-
return 'unknown';
|
|
551
|
-
}
|
|
552
|
-
};
|
|
553
|
-
|
|
554
|
-
// Convert naming based on convention
|
|
555
|
-
const convertNaming = (name: string, convention: string): string => {
|
|
556
|
-
const baseName = basename(name, extname(name));
|
|
557
|
-
const ext = extname(name);
|
|
558
|
-
|
|
559
|
-
switch (convention) {
|
|
560
|
-
case 'camelCase':
|
|
561
|
-
return (
|
|
562
|
-
baseName
|
|
563
|
-
.replace(/[-_]/g, '')
|
|
564
|
-
.replace(/([A-Z])/g, (match, p1, offset) => (offset === 0 ? p1.toLowerCase() : p1)) + ext
|
|
565
|
-
);
|
|
566
|
-
case 'snake_case':
|
|
567
|
-
return (
|
|
568
|
-
baseName
|
|
569
|
-
.replace(/[-]/g, '_')
|
|
570
|
-
.replace(/([A-Z])/g, (match, p1, offset) => (offset === 0 ? '' : '_') + p1.toLowerCase()) + ext
|
|
571
|
-
);
|
|
572
|
-
case 'kebab-case':
|
|
573
|
-
return (
|
|
574
|
-
baseName
|
|
575
|
-
.replace(/[_]/g, '-')
|
|
576
|
-
.replace(/([A-Z])/g, (match, p1, offset) => (offset === 0 ? '' : '-') + p1.toLowerCase()) + ext
|
|
577
|
-
);
|
|
578
|
-
case 'PascalCase':
|
|
579
|
-
return baseName.replace(/[-_]/g, '').replace(/^[a-z]/, match => match.toUpperCase()) + ext;
|
|
580
|
-
default:
|
|
581
|
-
return name;
|
|
582
|
-
}
|
|
583
|
-
};
|
|
584
|
-
|
|
585
|
-
// Process each unit
|
|
586
|
-
for (const unit of orderedUnits) {
|
|
587
|
-
console.log(`Processing ${unit.kind} unit "${unit.id}" from file "${unit.file}"`);
|
|
588
|
-
|
|
589
|
-
// Resolve source file path with fallback logic
|
|
590
|
-
let sourceFile: string;
|
|
591
|
-
let resolvedUnitFile: string;
|
|
592
|
-
|
|
593
|
-
// Check if unit.file already contains directory structure
|
|
594
|
-
if (unit.file.includes('/')) {
|
|
595
|
-
// unit.file has path structure (e.g., "src/mastra/agents/weatherAgent.ts")
|
|
596
|
-
sourceFile = resolve(templateDir, unit.file);
|
|
597
|
-
resolvedUnitFile = unit.file;
|
|
598
|
-
} else {
|
|
599
|
-
// unit.file is just filename (e.g., "weatherAgent.ts") - use fallback
|
|
600
|
-
const folderPath =
|
|
601
|
-
AgentBuilderDefaults.DEFAULT_FOLDER_STRUCTURE[
|
|
602
|
-
unit.kind as keyof typeof AgentBuilderDefaults.DEFAULT_FOLDER_STRUCTURE
|
|
603
|
-
];
|
|
604
|
-
if (!folderPath) {
|
|
605
|
-
conflicts.push({
|
|
606
|
-
unit: { kind: unit.kind, id: unit.id },
|
|
607
|
-
issue: `Unknown unit kind: ${unit.kind}`,
|
|
608
|
-
sourceFile: unit.file,
|
|
609
|
-
targetFile: 'N/A',
|
|
610
|
-
});
|
|
611
|
-
continue;
|
|
612
|
-
}
|
|
613
|
-
resolvedUnitFile = `${folderPath}/${unit.file}`;
|
|
614
|
-
sourceFile = resolve(templateDir, resolvedUnitFile);
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
// Check if source file exists
|
|
618
|
-
if (!existsSync(sourceFile)) {
|
|
619
|
-
conflicts.push({
|
|
620
|
-
unit: { kind: unit.kind, id: unit.id },
|
|
621
|
-
issue: `Source file not found: ${sourceFile}`,
|
|
622
|
-
sourceFile: resolvedUnitFile,
|
|
623
|
-
targetFile: 'N/A',
|
|
624
|
-
});
|
|
625
|
-
continue;
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
// Extract target directory from resolved unit file path
|
|
629
|
-
const targetDir = dirname(resolvedUnitFile);
|
|
630
|
-
|
|
631
|
-
// Analyze target naming convention
|
|
632
|
-
const namingConvention = await analyzeNamingConvention(targetDir);
|
|
633
|
-
console.log(`Detected naming convention in ${targetDir}: ${namingConvention}`);
|
|
634
|
-
|
|
635
|
-
// Convert unit.id to target filename with proper extension
|
|
636
|
-
// Note: Check if unit.id already includes extension to avoid double extensions
|
|
637
|
-
const hasExtension = extname(unit.id) !== '';
|
|
638
|
-
const baseId = hasExtension ? basename(unit.id, extname(unit.id)) : unit.id;
|
|
639
|
-
const fileExtension = extname(unit.file);
|
|
640
|
-
const convertedFileName =
|
|
641
|
-
namingConvention !== 'unknown'
|
|
642
|
-
? convertNaming(baseId + fileExtension, namingConvention)
|
|
643
|
-
: baseId + fileExtension;
|
|
644
|
-
|
|
645
|
-
const targetFile = resolve(targetPath, targetDir, convertedFileName);
|
|
646
|
-
|
|
647
|
-
// Handle file conflicts with strategy-based resolution
|
|
648
|
-
if (existsSync(targetFile)) {
|
|
649
|
-
const strategy = determineConflictStrategy(unit, targetFile);
|
|
650
|
-
console.log(`File exists: ${convertedFileName}, using strategy: ${strategy}`);
|
|
651
|
-
|
|
652
|
-
switch (strategy) {
|
|
653
|
-
case 'skip':
|
|
654
|
-
conflicts.push({
|
|
655
|
-
unit: { kind: unit.kind, id: unit.id },
|
|
656
|
-
issue: `File exists - skipped: ${convertedFileName}`,
|
|
657
|
-
sourceFile: unit.file,
|
|
658
|
-
targetFile: `${targetDir}/${convertedFileName}`,
|
|
659
|
-
});
|
|
660
|
-
console.log(`⏭️ Skipped ${unit.kind} "${unit.id}": file already exists`);
|
|
661
|
-
continue;
|
|
662
|
-
|
|
663
|
-
case 'backup-and-replace':
|
|
664
|
-
try {
|
|
665
|
-
await backupAndReplaceFile(sourceFile, targetFile);
|
|
666
|
-
copiedFiles.push({
|
|
667
|
-
source: sourceFile,
|
|
668
|
-
destination: targetFile,
|
|
669
|
-
unit: { kind: unit.kind, id: unit.id },
|
|
670
|
-
});
|
|
671
|
-
console.log(
|
|
672
|
-
`🔄 Replaced ${unit.kind} "${unit.id}": ${unit.file} → ${convertedFileName} (backup created)`,
|
|
673
|
-
);
|
|
674
|
-
continue;
|
|
675
|
-
} catch (backupError) {
|
|
676
|
-
conflicts.push({
|
|
677
|
-
unit: { kind: unit.kind, id: unit.id },
|
|
678
|
-
issue: `Failed to backup and replace: ${backupError instanceof Error ? backupError.message : String(backupError)}`,
|
|
679
|
-
sourceFile: unit.file,
|
|
680
|
-
targetFile: `${targetDir}/${convertedFileName}`,
|
|
681
|
-
});
|
|
682
|
-
continue;
|
|
683
|
-
}
|
|
684
|
-
|
|
685
|
-
case 'rename':
|
|
686
|
-
try {
|
|
687
|
-
const uniqueTargetFile = await renameAndCopyFile(sourceFile, targetFile);
|
|
688
|
-
copiedFiles.push({
|
|
689
|
-
source: sourceFile,
|
|
690
|
-
destination: uniqueTargetFile,
|
|
691
|
-
unit: { kind: unit.kind, id: unit.id },
|
|
692
|
-
});
|
|
693
|
-
console.log(`📝 Renamed ${unit.kind} "${unit.id}": ${unit.file} → ${basename(uniqueTargetFile)}`);
|
|
694
|
-
continue;
|
|
695
|
-
} catch (renameError) {
|
|
696
|
-
conflicts.push({
|
|
697
|
-
unit: { kind: unit.kind, id: unit.id },
|
|
698
|
-
issue: `Failed to rename and copy: ${renameError instanceof Error ? renameError.message : String(renameError)}`,
|
|
699
|
-
sourceFile: unit.file,
|
|
700
|
-
targetFile: `${targetDir}/${convertedFileName}`,
|
|
701
|
-
});
|
|
702
|
-
continue;
|
|
703
|
-
}
|
|
704
|
-
|
|
705
|
-
default:
|
|
706
|
-
conflicts.push({
|
|
707
|
-
unit: { kind: unit.kind, id: unit.id },
|
|
708
|
-
issue: `Unknown conflict strategy: ${strategy}`,
|
|
709
|
-
sourceFile: unit.file,
|
|
710
|
-
targetFile: `${targetDir}/${convertedFileName}`,
|
|
711
|
-
});
|
|
712
|
-
continue;
|
|
713
|
-
}
|
|
714
|
-
}
|
|
715
|
-
|
|
716
|
-
// Ensure target directory exists
|
|
717
|
-
await mkdir(dirname(targetFile), { recursive: true });
|
|
718
|
-
|
|
719
|
-
// Copy the file
|
|
720
|
-
try {
|
|
721
|
-
await copyFile(sourceFile, targetFile);
|
|
722
|
-
copiedFiles.push({
|
|
723
|
-
source: sourceFile,
|
|
724
|
-
destination: targetFile,
|
|
725
|
-
unit: { kind: unit.kind, id: unit.id },
|
|
726
|
-
});
|
|
727
|
-
console.log(`✓ Copied ${unit.kind} "${unit.id}": ${unit.file} → ${convertedFileName}`);
|
|
728
|
-
} catch (copyError) {
|
|
729
|
-
conflicts.push({
|
|
730
|
-
unit: { kind: unit.kind, id: unit.id },
|
|
731
|
-
issue: `Failed to copy file: ${copyError instanceof Error ? copyError.message : String(copyError)}`,
|
|
732
|
-
sourceFile: unit.file,
|
|
733
|
-
targetFile: `${targetDir}/${convertedFileName}`,
|
|
734
|
-
});
|
|
735
|
-
}
|
|
736
|
-
}
|
|
737
|
-
|
|
738
|
-
// Ensure tsconfig.json exists in target by copying from template if available, else generate a minimal one
|
|
739
|
-
try {
|
|
740
|
-
const targetTsconfig = resolve(targetPath, 'tsconfig.json');
|
|
741
|
-
if (!existsSync(targetTsconfig)) {
|
|
742
|
-
const templateTsconfig = resolve(templateDir, 'tsconfig.json');
|
|
743
|
-
if (existsSync(templateTsconfig)) {
|
|
744
|
-
await copyFile(templateTsconfig, targetTsconfig);
|
|
745
|
-
copiedFiles.push({
|
|
746
|
-
source: templateTsconfig,
|
|
747
|
-
destination: targetTsconfig,
|
|
748
|
-
unit: { kind: 'other', id: 'tsconfig.json' },
|
|
749
|
-
});
|
|
750
|
-
console.log('✓ Copied tsconfig.json from template to target');
|
|
751
|
-
} else {
|
|
752
|
-
// Generate a minimal tsconfig.json as a fallback
|
|
753
|
-
const minimalTsconfig = {
|
|
754
|
-
compilerOptions: {
|
|
755
|
-
target: 'ES2020',
|
|
756
|
-
module: 'NodeNext',
|
|
757
|
-
moduleResolution: 'NodeNext',
|
|
758
|
-
strict: false,
|
|
759
|
-
esModuleInterop: true,
|
|
760
|
-
skipLibCheck: true,
|
|
761
|
-
resolveJsonModule: true,
|
|
762
|
-
outDir: 'dist',
|
|
763
|
-
},
|
|
764
|
-
include: ['**/*.ts', '**/*.tsx', '**/*.mts', '**/*.cts'],
|
|
765
|
-
exclude: ['node_modules', 'dist', 'build', '.next', '.output', '.turbo'],
|
|
766
|
-
} as const;
|
|
767
|
-
|
|
768
|
-
await writeFile(targetTsconfig, JSON.stringify(minimalTsconfig, null, 2), 'utf-8');
|
|
769
|
-
copiedFiles.push({
|
|
770
|
-
source: '[generated tsconfig.json]',
|
|
771
|
-
destination: targetTsconfig,
|
|
772
|
-
unit: { kind: 'other', id: 'tsconfig.json' },
|
|
773
|
-
});
|
|
774
|
-
console.log('✓ Generated minimal tsconfig.json in target');
|
|
775
|
-
}
|
|
776
|
-
}
|
|
777
|
-
} catch (e) {
|
|
778
|
-
conflicts.push({
|
|
779
|
-
unit: { kind: 'other', id: 'tsconfig.json' },
|
|
780
|
-
issue: `Failed to ensure tsconfig.json: ${e instanceof Error ? e.message : String(e)}`,
|
|
781
|
-
sourceFile: 'tsconfig.json',
|
|
782
|
-
targetFile: 'tsconfig.json',
|
|
783
|
-
});
|
|
784
|
-
}
|
|
785
|
-
|
|
786
|
-
// If the target project has no Mastra index file, copy from template
|
|
787
|
-
try {
|
|
788
|
-
const targetMastraIndex = resolve(targetPath, 'src/mastra/index.ts');
|
|
789
|
-
if (!existsSync(targetMastraIndex)) {
|
|
790
|
-
const templateMastraIndex = resolve(templateDir, 'src/mastra/index.ts');
|
|
791
|
-
if (existsSync(templateMastraIndex)) {
|
|
792
|
-
if (!existsSync(dirname(targetMastraIndex))) {
|
|
793
|
-
await mkdir(dirname(targetMastraIndex), { recursive: true });
|
|
794
|
-
}
|
|
795
|
-
await copyFile(templateMastraIndex, targetMastraIndex);
|
|
796
|
-
copiedFiles.push({
|
|
797
|
-
source: templateMastraIndex,
|
|
798
|
-
destination: targetMastraIndex,
|
|
799
|
-
unit: { kind: 'other', id: 'mastra-index' },
|
|
800
|
-
});
|
|
801
|
-
console.log('✓ Copied src/mastra/index.ts from template to target');
|
|
802
|
-
}
|
|
803
|
-
}
|
|
804
|
-
} catch (e) {
|
|
805
|
-
conflicts.push({
|
|
806
|
-
unit: { kind: 'other', id: 'mastra-index' },
|
|
807
|
-
issue: `Failed to ensure Mastra index file: ${e instanceof Error ? e.message : String(e)}`,
|
|
808
|
-
sourceFile: 'src/mastra/index.ts',
|
|
809
|
-
targetFile: 'src/mastra/index.ts',
|
|
810
|
-
});
|
|
811
|
-
}
|
|
812
|
-
|
|
813
|
-
// Commit the copied files
|
|
814
|
-
if (copiedFiles.length > 0) {
|
|
815
|
-
try {
|
|
816
|
-
const fileList = copiedFiles.map(f => f.destination);
|
|
817
|
-
await gitAddAndCommit(
|
|
818
|
-
targetPath,
|
|
819
|
-
`feat(template): copy ${copiedFiles.length} files from ${slug}@${commitSha.substring(0, 7)}`,
|
|
820
|
-
fileList,
|
|
821
|
-
{ skipIfNoStaged: true },
|
|
822
|
-
);
|
|
823
|
-
console.log(`✓ Committed ${copiedFiles.length} copied files`);
|
|
824
|
-
} catch (commitError) {
|
|
825
|
-
console.warn('Failed to commit copied files:', commitError);
|
|
826
|
-
}
|
|
827
|
-
}
|
|
828
|
-
|
|
829
|
-
const message = `Programmatic file copy completed. Copied ${copiedFiles.length} files, ${conflicts.length} conflicts detected.`;
|
|
830
|
-
console.log(message);
|
|
831
|
-
|
|
832
|
-
return {
|
|
833
|
-
success: true,
|
|
834
|
-
copiedFiles,
|
|
835
|
-
conflicts,
|
|
836
|
-
message,
|
|
837
|
-
};
|
|
838
|
-
} catch (error) {
|
|
839
|
-
console.error('Programmatic file copy failed:', error);
|
|
840
|
-
|
|
841
|
-
return {
|
|
842
|
-
success: false,
|
|
843
|
-
copiedFiles: [],
|
|
844
|
-
conflicts: [],
|
|
845
|
-
message: `Programmatic file copy failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
846
|
-
error: error instanceof Error ? error.message : String(error),
|
|
847
|
-
};
|
|
848
|
-
}
|
|
849
|
-
},
|
|
850
|
-
});
|
|
851
|
-
|
|
852
|
-
// Step 9: Intelligent merging with AgentBuilder
|
|
853
|
-
const intelligentMergeStep = createStep({
|
|
854
|
-
id: 'intelligent-merge',
|
|
855
|
-
description: 'Use AgentBuilder to intelligently merge template files',
|
|
856
|
-
inputSchema: IntelligentMergeInputSchema,
|
|
857
|
-
outputSchema: IntelligentMergeResultSchema,
|
|
858
|
-
execute: async ({ inputData, runtimeContext }) => {
|
|
859
|
-
console.log('Intelligent merge step starting...');
|
|
860
|
-
const { conflicts, copiedFiles, commitSha, slug, templateDir, branchName } = inputData;
|
|
861
|
-
const targetPath = inputData.targetPath || runtimeContext.get('targetPath') || process.cwd();
|
|
862
|
-
try {
|
|
863
|
-
// Create copyFile tool for edge cases
|
|
864
|
-
const copyFileTool = createTool({
|
|
865
|
-
id: 'copy-file',
|
|
866
|
-
description:
|
|
867
|
-
'Copy a file from template to target project (use only for edge cases - most files are already copied programmatically).',
|
|
868
|
-
inputSchema: z.object({
|
|
869
|
-
sourcePath: z.string().describe('Path to the source file relative to template directory'),
|
|
870
|
-
destinationPath: z.string().describe('Path to the destination file relative to target project'),
|
|
871
|
-
}),
|
|
872
|
-
outputSchema: z.object({
|
|
873
|
-
success: z.boolean(),
|
|
874
|
-
message: z.string(),
|
|
875
|
-
error: z.string().optional(),
|
|
876
|
-
}),
|
|
877
|
-
execute: async ({ context }) => {
|
|
878
|
-
try {
|
|
879
|
-
const { sourcePath, destinationPath } = context;
|
|
880
|
-
|
|
881
|
-
// Use templateDir directly from input
|
|
882
|
-
const resolvedSourcePath = resolve(templateDir, sourcePath);
|
|
883
|
-
const resolvedDestinationPath = resolve(targetPath, destinationPath);
|
|
884
|
-
|
|
885
|
-
if (existsSync(resolvedSourcePath) && !existsSync(dirname(resolvedDestinationPath))) {
|
|
886
|
-
await mkdir(dirname(resolvedDestinationPath), { recursive: true });
|
|
887
|
-
}
|
|
888
|
-
|
|
889
|
-
await copyFile(resolvedSourcePath, resolvedDestinationPath);
|
|
890
|
-
return {
|
|
891
|
-
success: true,
|
|
892
|
-
message: `Successfully copied file from ${sourcePath} to ${destinationPath}`,
|
|
893
|
-
};
|
|
894
|
-
} catch (error) {
|
|
895
|
-
return {
|
|
896
|
-
success: false,
|
|
897
|
-
message: `Failed to copy file: ${error instanceof Error ? error.message : String(error)}`,
|
|
898
|
-
error: error instanceof Error ? error.message : String(error),
|
|
899
|
-
};
|
|
900
|
-
}
|
|
901
|
-
},
|
|
902
|
-
});
|
|
903
|
-
|
|
904
|
-
// Initialize AgentBuilder for merge and registration
|
|
905
|
-
const agentBuilder = new AgentBuilder({
|
|
906
|
-
projectPath: targetPath,
|
|
907
|
-
mode: 'template',
|
|
908
|
-
model: resolveModel(runtimeContext),
|
|
909
|
-
instructions: `
|
|
910
|
-
You are an expert at integrating Mastra template components into existing projects.
|
|
911
|
-
|
|
912
|
-
CRITICAL CONTEXT:
|
|
913
|
-
- Files have been programmatically copied from template to target project
|
|
914
|
-
- Your job is to handle integration issues, registration, and validation
|
|
915
|
-
|
|
916
|
-
FILES SUCCESSFULLY COPIED:
|
|
917
|
-
${JSON.stringify(copiedFiles, null, 2)}
|
|
918
|
-
|
|
919
|
-
CONFLICTS TO RESOLVE:
|
|
920
|
-
${JSON.stringify(conflicts, null, 2)}
|
|
921
|
-
|
|
922
|
-
CRITICAL INSTRUCTIONS:
|
|
923
|
-
1. **Package management**: NO need to install packages (already handled by package merge step)
|
|
924
|
-
2. **File copying**: Most files are already copied programmatically. Only use copyFile tool for edge cases where additional files are needed for conflict resolution
|
|
925
|
-
|
|
926
|
-
KEY RESPONSIBILITIES:
|
|
927
|
-
1. Resolve any conflicts from the programmatic copy step
|
|
928
|
-
2. Register components in existing Mastra index file (agents, workflows, networks, mcp-servers)
|
|
929
|
-
3. DO NOT register tools in existing Mastra index file - tools should remain standalone
|
|
930
|
-
4. Copy additional files ONLY if needed for conflict resolution
|
|
931
|
-
|
|
932
|
-
MASTRA INDEX FILE HANDLING (src/mastra/index.ts):
|
|
933
|
-
1. **Verify the file exists**
|
|
934
|
-
- Call readFile
|
|
935
|
-
- If it fails with ENOENT (or listDirectory shows it missing) -> copyFile the template version to src/mastra/index.ts, then confirm it now exists
|
|
936
|
-
- Always verify after copying that the file exists and is accessible
|
|
937
|
-
|
|
938
|
-
2. **Edit the file**
|
|
939
|
-
- Always work with the full file content
|
|
940
|
-
- Generate the complete, correct source (imports, anchors, registrations, formatting)
|
|
941
|
-
- Keep existing registrations intact and maintain file structure
|
|
942
|
-
- Ensure proper spacing and organization of new additions
|
|
943
|
-
|
|
944
|
-
3. **Handle anchors and structure**
|
|
945
|
-
- When generating new content, ensure you do not duplicate existing imports or object entries
|
|
946
|
-
- If required anchors (e.g., agents: {}) are missing, add them while generating the new content
|
|
947
|
-
- Add missing anchors just before the closing brace of the Mastra config
|
|
948
|
-
- Do not restructure or reorder existing anchors and registrations
|
|
949
|
-
|
|
950
|
-
CRITICAL: ALWAYS use writeFile to update the mastra/index.ts file when needed to register new components.
|
|
951
|
-
|
|
952
|
-
MASTRA-SPECIFIC REGISTRATION:
|
|
953
|
-
- Agents: Register in existing Mastra index file
|
|
954
|
-
- Workflows: Register in existing Mastra index file
|
|
955
|
-
- Networks: Register in existing Mastra index file
|
|
956
|
-
- MCP servers: Register in existing Mastra index file
|
|
957
|
-
- Tools: Copy to ${AgentBuilderDefaults.DEFAULT_FOLDER_STRUCTURE.tool} but DO NOT register in existing Mastra index file
|
|
958
|
-
- If an anchor (e.g., "agents: {") is not found, avoid complex restructuring; instead, insert the missing anchor on a new line (e.g., add "agents: {" just before the closing brace of the Mastra config) and then proceed with the other registrations.
|
|
959
|
-
|
|
960
|
-
CONFLICT RESOLUTION AND FILE COPYING:
|
|
961
|
-
- Only copy files if needed to resolve specific conflicts
|
|
962
|
-
- When copying files from template:
|
|
963
|
-
- Ensure you get the right file name and path
|
|
964
|
-
- Verify the destination directory exists
|
|
965
|
-
- Maintain the same relative path structure
|
|
966
|
-
- Only copy files that are actually needed
|
|
967
|
-
- Preserve existing functionality when resolving conflicts
|
|
968
|
-
- Focus on registration and conflict resolution, validation will happen in a later step
|
|
969
|
-
|
|
970
|
-
Template information:
|
|
971
|
-
- Slug: ${slug}
|
|
972
|
-
- Commit: ${commitSha.substring(0, 7)}
|
|
973
|
-
- Branch: ${branchName}
|
|
974
|
-
`,
|
|
975
|
-
tools: {
|
|
976
|
-
copyFile: copyFileTool,
|
|
977
|
-
},
|
|
978
|
-
});
|
|
979
|
-
|
|
980
|
-
// Create task list for systematic processing
|
|
981
|
-
const tasks = [];
|
|
982
|
-
|
|
983
|
-
// Add conflict resolution tasks
|
|
984
|
-
conflicts.forEach(conflict => {
|
|
985
|
-
tasks.push({
|
|
986
|
-
id: `conflict-${conflict.unit.kind}-${conflict.unit.id}`,
|
|
987
|
-
content: `Resolve conflict: ${conflict.issue}`,
|
|
988
|
-
status: 'pending' as const,
|
|
989
|
-
priority: 'high' as const,
|
|
990
|
-
notes: `Unit: ${conflict.unit.kind}:${conflict.unit.id}, Issue: ${conflict.issue}, Source: ${conflict.sourceFile}, Target: ${conflict.targetFile}`,
|
|
991
|
-
});
|
|
992
|
-
});
|
|
993
|
-
|
|
994
|
-
// Add registration tasks for successfully copied files
|
|
995
|
-
const registrableKinds = new Set(['agent', 'workflow', 'network', 'mcp-server']);
|
|
996
|
-
const registrableFiles = copiedFiles.filter(f => registrableKinds.has(f.unit.kind as any));
|
|
997
|
-
const targetMastraIndex = resolve(targetPath, 'src/mastra/index.ts');
|
|
998
|
-
const mastraIndexExists = existsSync(targetMastraIndex);
|
|
999
|
-
console.log(`Mastra index exists: ${mastraIndexExists} at ${targetMastraIndex}`);
|
|
1000
|
-
console.log(
|
|
1001
|
-
'Registrable components:',
|
|
1002
|
-
registrableFiles.map(f => `${f.unit.kind}:${f.unit.id}`),
|
|
1003
|
-
);
|
|
1004
|
-
if (registrableFiles.length > 0) {
|
|
1005
|
-
tasks.push({
|
|
1006
|
-
id: 'register-components',
|
|
1007
|
-
content: `Register ${registrableFiles.length} components in existing Mastra index file (src/mastra/index.ts)`,
|
|
1008
|
-
status: 'pending' as const,
|
|
1009
|
-
priority: 'medium' as const,
|
|
1010
|
-
dependencies: conflicts.length > 0 ? conflicts.map(c => `conflict-${c.unit.kind}-${c.unit.id}`) : undefined,
|
|
1011
|
-
notes: `Components to register: ${registrableFiles.map(f => `${f.unit.kind}:${f.unit.id}`).join(', ')}`,
|
|
1012
|
-
});
|
|
1013
|
-
}
|
|
1014
|
-
|
|
1015
|
-
// Note: Validation is handled by the dedicated validation step, not here
|
|
1016
|
-
|
|
1017
|
-
console.log(`Creating task list with ${tasks.length} tasks...`);
|
|
1018
|
-
await AgentBuilderDefaults.manageTaskList({ action: 'create', tasks });
|
|
1019
|
-
|
|
1020
|
-
// Log git state before merge operations
|
|
1021
|
-
await logGitState(targetPath, 'before intelligent merge');
|
|
1022
|
-
|
|
1023
|
-
// Process tasks systematically
|
|
1024
|
-
const result = await agentBuilder.stream(`
|
|
1025
|
-
You need to work through a task list to complete the template integration.
|
|
1026
|
-
|
|
1027
|
-
CRITICAL INSTRUCTIONS:
|
|
1028
|
-
|
|
1029
|
-
**STEP 1: GET YOUR TASK LIST**
|
|
1030
|
-
1. Use manageTaskList tool with action "list" to see all pending tasks
|
|
1031
|
-
2. Work through tasks in dependency order (complete dependencies first)
|
|
1032
|
-
|
|
1033
|
-
**STEP 2: PROCESS EACH TASK SYSTEMATICALLY**
|
|
1034
|
-
For each task:
|
|
1035
|
-
1. Use manageTaskList to mark the current task as 'in_progress'
|
|
1036
|
-
2. Complete the task according to its requirements
|
|
1037
|
-
3. Use manageTaskList to mark the task as 'completed' when done
|
|
1038
|
-
4. Continue until all tasks are completed
|
|
1039
|
-
|
|
1040
|
-
**TASK TYPES AND REQUIREMENTS:**
|
|
1041
|
-
|
|
1042
|
-
**Conflict Resolution Tasks:**
|
|
1043
|
-
- Analyze the specific conflict and determine best resolution strategy
|
|
1044
|
-
- For file name conflicts: merge content or rename appropriately
|
|
1045
|
-
- For missing files: investigate and copy if needed
|
|
1046
|
-
- For other issues: apply appropriate fixes
|
|
1047
|
-
|
|
1048
|
-
**Component Registration Task:**
|
|
1049
|
-
- Update main Mastra instance file to register new components
|
|
1050
|
-
- Only register: agents, workflows, networks, mcp-servers
|
|
1051
|
-
- DO NOT register tools in main config
|
|
1052
|
-
- Ensure proper import paths and naming conventions
|
|
1053
|
-
|
|
1054
|
-
**COMMIT STRATEGY:**
|
|
1055
|
-
- After resolving conflicts: "feat(template): resolve conflicts for ${slug}@${commitSha.substring(0, 7)}"
|
|
1056
|
-
- After registration: "feat(template): register components from ${slug}@${commitSha.substring(0, 7)}"
|
|
1057
|
-
|
|
1058
|
-
**CRITICAL NOTES:**
|
|
1059
|
-
- Template source: ${templateDir}
|
|
1060
|
-
- Target project: ${targetPath}
|
|
1061
|
-
- Focus ONLY on conflict resolution and component registration
|
|
1062
|
-
- Use executeCommand for git commits after each task
|
|
1063
|
-
- DO NOT perform validation - that's handled by the dedicated validation step
|
|
1064
|
-
|
|
1065
|
-
Start by listing your tasks and work through them systematically!
|
|
1066
|
-
`);
|
|
1067
|
-
|
|
1068
|
-
// Extract actual conflict resolution details from agent execution
|
|
1069
|
-
const actualResolutions: Array<{
|
|
1070
|
-
taskId: string;
|
|
1071
|
-
action: string;
|
|
1072
|
-
status: string;
|
|
1073
|
-
content: string;
|
|
1074
|
-
notes?: string;
|
|
1075
|
-
}> = [];
|
|
1076
|
-
|
|
1077
|
-
for await (const chunk of result.fullStream) {
|
|
1078
|
-
if (chunk.type === 'step-finish' || chunk.type === 'step-start') {
|
|
1079
|
-
console.log({
|
|
1080
|
-
type: chunk.type,
|
|
1081
|
-
msgId: chunk.messageId,
|
|
1082
|
-
});
|
|
1083
|
-
} else {
|
|
1084
|
-
console.log(JSON.stringify(chunk, null, 2));
|
|
1085
|
-
|
|
1086
|
-
// Extract task management tool results
|
|
1087
|
-
if (chunk.type === 'tool-result' && chunk.toolName === 'manageTaskList') {
|
|
1088
|
-
try {
|
|
1089
|
-
const toolResult = chunk.result;
|
|
1090
|
-
if (toolResult.action === 'update' && toolResult.status === 'completed') {
|
|
1091
|
-
actualResolutions.push({
|
|
1092
|
-
taskId: toolResult.taskId || '',
|
|
1093
|
-
action: toolResult.action,
|
|
1094
|
-
status: toolResult.status,
|
|
1095
|
-
content: toolResult.content || '',
|
|
1096
|
-
notes: toolResult.notes,
|
|
1097
|
-
});
|
|
1098
|
-
console.log(`📋 Task completed: ${toolResult.taskId} - ${toolResult.content}`);
|
|
1099
|
-
}
|
|
1100
|
-
} catch (parseError) {
|
|
1101
|
-
console.warn('Failed to parse task management result:', parseError);
|
|
1102
|
-
}
|
|
1103
|
-
}
|
|
1104
|
-
}
|
|
1105
|
-
}
|
|
1106
|
-
|
|
1107
|
-
// Log git state after merge operations
|
|
1108
|
-
await logGitState(targetPath, 'after intelligent merge');
|
|
1109
|
-
|
|
1110
|
-
// Map actual resolutions back to conflicts
|
|
1111
|
-
const conflictResolutions = conflicts.map(conflict => {
|
|
1112
|
-
const taskId = `conflict-${conflict.unit.kind}-${conflict.unit.id}`;
|
|
1113
|
-
const actualResolution = actualResolutions.find(r => r.taskId === taskId);
|
|
1114
|
-
|
|
1115
|
-
if (actualResolution) {
|
|
1116
|
-
return {
|
|
1117
|
-
unit: conflict.unit,
|
|
1118
|
-
issue: conflict.issue,
|
|
1119
|
-
resolution:
|
|
1120
|
-
actualResolution.notes ||
|
|
1121
|
-
actualResolution.content ||
|
|
1122
|
-
`Completed: ${conflict.unit.kind} ${conflict.unit.id}`,
|
|
1123
|
-
actualWork: true,
|
|
1124
|
-
};
|
|
1125
|
-
} else {
|
|
1126
|
-
return {
|
|
1127
|
-
unit: conflict.unit,
|
|
1128
|
-
issue: conflict.issue,
|
|
1129
|
-
resolution: `No specific resolution found for ${conflict.unit.kind} ${conflict.unit.id}`,
|
|
1130
|
-
actualWork: false,
|
|
1131
|
-
};
|
|
1132
|
-
}
|
|
1133
|
-
});
|
|
1134
|
-
|
|
1135
|
-
await gitAddAndCommit(targetPath, `feat(template): apply intelligent merge for ${slug}`, undefined, {
|
|
1136
|
-
skipIfNoStaged: true,
|
|
1137
|
-
});
|
|
1138
|
-
|
|
1139
|
-
return {
|
|
1140
|
-
success: true,
|
|
1141
|
-
applied: true,
|
|
1142
|
-
message: `Successfully resolved ${conflicts.length} conflicts from template ${slug}`,
|
|
1143
|
-
conflictsResolved: conflictResolutions,
|
|
1144
|
-
};
|
|
1145
|
-
} catch (error) {
|
|
1146
|
-
return {
|
|
1147
|
-
success: false,
|
|
1148
|
-
applied: false,
|
|
1149
|
-
message: `Failed to resolve conflicts: ${error instanceof Error ? error.message : String(error)}`,
|
|
1150
|
-
conflictsResolved: [],
|
|
1151
|
-
error: error instanceof Error ? error.message : String(error),
|
|
1152
|
-
};
|
|
1153
|
-
}
|
|
1154
|
-
},
|
|
1155
|
-
});
|
|
1156
|
-
|
|
1157
|
-
// Step 10: Validation and Fix Step - validates merged code and fixes any issues
|
|
1158
|
-
const validationAndFixStep = createStep({
|
|
1159
|
-
id: 'validation-and-fix',
|
|
1160
|
-
description: 'Validate the merged template code and fix any issues using a specialized agent',
|
|
1161
|
-
inputSchema: ValidationFixInputSchema,
|
|
1162
|
-
outputSchema: ValidationFixResultSchema,
|
|
1163
|
-
execute: async ({ inputData, runtimeContext }) => {
|
|
1164
|
-
console.log('Validation and fix step starting...');
|
|
1165
|
-
const { commitSha, slug, orderedUnits, templateDir, copiedFiles, conflictsResolved, maxIterations = 5 } = inputData;
|
|
1166
|
-
const targetPath = inputData.targetPath || runtimeContext.get('targetPath') || process.cwd();
|
|
1167
|
-
|
|
1168
|
-
// Skip validation if no changes were made
|
|
1169
|
-
const hasChanges = copiedFiles.length > 0 || (conflictsResolved && conflictsResolved.length > 0);
|
|
1170
|
-
if (!hasChanges) {
|
|
1171
|
-
console.log('⏭️ Skipping validation - no files copied or conflicts resolved');
|
|
1172
|
-
return {
|
|
1173
|
-
success: true,
|
|
1174
|
-
applied: false,
|
|
1175
|
-
message: 'No changes to validate - template already integrated or no conflicts resolved',
|
|
1176
|
-
validationResults: {
|
|
1177
|
-
valid: true,
|
|
1178
|
-
errorsFixed: 0,
|
|
1179
|
-
remainingErrors: 0,
|
|
1180
|
-
},
|
|
1181
|
-
};
|
|
1182
|
-
}
|
|
1183
|
-
|
|
1184
|
-
console.log(
|
|
1185
|
-
`📋 Changes detected: ${copiedFiles.length} files copied, ${conflictsResolved?.length || 0} conflicts resolved`,
|
|
1186
|
-
);
|
|
1187
|
-
|
|
1188
|
-
let currentIteration = 1; // Declare at function scope for error handling
|
|
1189
|
-
|
|
1190
|
-
try {
|
|
1191
|
-
const allTools = await AgentBuilderDefaults.DEFAULT_TOOLS(targetPath, 'template');
|
|
1192
|
-
|
|
1193
|
-
const validationAgent = new Agent({
|
|
1194
|
-
name: 'code-validator-fixer',
|
|
1195
|
-
description: 'Specialized agent for validating and fixing template integration issues',
|
|
1196
|
-
instructions: `You are a code validation and fixing specialist. Your job is to:
|
|
1197
|
-
|
|
1198
|
-
1. **Run comprehensive validation** using the validateCode tool to check for:
|
|
1199
|
-
- TypeScript compilation errors
|
|
1200
|
-
- ESLint issues
|
|
1201
|
-
- Import/export problems
|
|
1202
|
-
- Missing dependencies
|
|
1203
|
-
- Index file structure and exports
|
|
1204
|
-
- Component registration correctness
|
|
1205
|
-
- Naming convention compliance
|
|
1206
|
-
|
|
1207
|
-
2. **Fix validation errors systematically**:
|
|
1208
|
-
- Use readFile to examine files with errors
|
|
1209
|
-
- Use multiEdit for simple search-replace fixes (single line changes)
|
|
1210
|
-
- Use replaceLines for complex multiline fixes (imports, function signatures, etc.)
|
|
1211
|
-
- Use listDirectory to understand project structure when fixing import paths
|
|
1212
|
-
- Update file contents to resolve TypeScript and linting issues
|
|
1213
|
-
|
|
1214
|
-
3. **Choose the right tool for the job**:
|
|
1215
|
-
- multiEdit: Simple replacements, single line changes, small fixes
|
|
1216
|
-
- replaceLines: Multiline imports, function signatures, complex code blocks
|
|
1217
|
-
- writeFile: ONLY for creating new files (never overwrite existing)
|
|
1218
|
-
|
|
1219
|
-
4. **Create missing files ONLY when necessary**:
|
|
1220
|
-
- Use writeFile ONLY for creating NEW files that don't exist
|
|
1221
|
-
- NEVER overwrite existing files - use multiEdit or replaceLines instead
|
|
1222
|
-
- Common cases: missing barrel files (index.ts), missing config files, missing type definitions
|
|
1223
|
-
- Always check with readFile first to ensure file doesn't exist
|
|
1224
|
-
|
|
1225
|
-
5. **Fix ALL template integration issues**:
|
|
1226
|
-
- Fix import path issues in copied files
|
|
1227
|
-
- Ensure TypeScript imports and exports are correct
|
|
1228
|
-
- Validate integration works properly
|
|
1229
|
-
- Fix files copied with new names based on unit IDs
|
|
1230
|
-
- Update original template imports that reference old filenames
|
|
1231
|
-
- Fix missing imports in index files
|
|
1232
|
-
- Fix incorrect file paths in imports
|
|
1233
|
-
- Fix type mismatches after integration
|
|
1234
|
-
- Fix missing exports in barrel files
|
|
1235
|
-
- Use the COPIED FILES mapping below to fix import paths
|
|
1236
|
-
- Fix any missing dependencies or module resolution issues
|
|
1237
|
-
|
|
1238
|
-
6. **Validate index file structure**:
|
|
1239
|
-
- Correct imports for all components
|
|
1240
|
-
- Proper anchor structure (agents: {}, etc.)
|
|
1241
|
-
- No duplicate registrations
|
|
1242
|
-
- Correct export names and paths
|
|
1243
|
-
- Proper formatting and organization
|
|
1244
|
-
|
|
1245
|
-
7. **Follow naming conventions**:
|
|
1246
|
-
Import paths:
|
|
1247
|
-
- camelCase: import { myAgent } from './myAgent'
|
|
1248
|
-
- snake_case: import { myAgent } from './my_agent'
|
|
1249
|
-
- kebab-case: import { myAgent } from './my-agent'
|
|
1250
|
-
- PascalCase: import { MyAgent } from './MyAgent'
|
|
1251
|
-
|
|
1252
|
-
File names:
|
|
1253
|
-
- camelCase: weatherAgent.ts, chatAgent.ts
|
|
1254
|
-
- snake_case: weather_agent.ts, chat_agent.ts
|
|
1255
|
-
- kebab-case: weather-agent.ts, chat-agent.ts
|
|
1256
|
-
- PascalCase: WeatherAgent.ts, ChatAgent.ts
|
|
1257
|
-
|
|
1258
|
-
Key Rule: Keep variable/export names unchanged, only adapt file names and import paths
|
|
1259
|
-
|
|
1260
|
-
8. **Re-validate after fixes** to ensure all issues are resolved
|
|
1261
|
-
|
|
1262
|
-
CRITICAL: Always validate the entire project first to get a complete picture of issues, then fix them systematically, and re-validate to confirm fixes worked.
|
|
1263
|
-
|
|
1264
|
-
CRITICAL TOOL SELECTION GUIDE:
|
|
1265
|
-
- **multiEdit**: Use for simple string replacements, single-line changes
|
|
1266
|
-
Example: changing './oldPath' to './newPath'
|
|
1267
|
-
|
|
1268
|
-
- **replaceLines**: Use for multiline fixes, complex code structures
|
|
1269
|
-
Example: fixing multiline imports, function signatures, or code blocks
|
|
1270
|
-
Usage: replaceLines({ filePath: 'file.ts', startLine: 5, endLine: 8, newContent: 'new multiline content' })
|
|
1271
|
-
|
|
1272
|
-
- **writeFile**: ONLY for creating new files that don't exist
|
|
1273
|
-
Example: creating missing index.ts barrel files
|
|
1274
|
-
|
|
1275
|
-
CRITICAL WRITEFILЕ SAFETY RULES:
|
|
1276
|
-
- ONLY use writeFile for creating NEW files that don't exist
|
|
1277
|
-
- ALWAYS check with readFile first to verify file doesn't exist
|
|
1278
|
-
- NEVER use writeFile to overwrite existing files - use multiEdit or replaceLines instead
|
|
1279
|
-
- Common valid uses: missing index.ts barrel files, missing type definitions, missing config files
|
|
1280
|
-
|
|
1281
|
-
CRITICAL IMPORT PATH RESOLUTION:
|
|
1282
|
-
The following files were copied from template with new names:
|
|
1283
|
-
${JSON.stringify(copiedFiles, null, 2)}
|
|
1284
|
-
|
|
1285
|
-
When fixing import errors:
|
|
1286
|
-
1. Check if the missing module corresponds to a copied file
|
|
1287
|
-
2. Use listDirectory to verify actual filenames in target directories
|
|
1288
|
-
3. Update import paths to match the actual copied filenames
|
|
1289
|
-
4. Ensure exported variable names match what's being imported
|
|
1290
|
-
|
|
1291
|
-
EXAMPLE: If error shows "Cannot find module './tools/download-csv-tool'" but a file was copied as "csv-fetcher-tool.ts", update the import to "./tools/csv-fetcher-tool"
|
|
1292
|
-
|
|
1293
|
-
${conflictsResolved ? `CONFLICTS RESOLVED BY INTELLIGENT MERGE:\n${JSON.stringify(conflictsResolved, null, 2)}\n` : ''}
|
|
1294
|
-
|
|
1295
|
-
INTEGRATED UNITS:
|
|
1296
|
-
${JSON.stringify(orderedUnits, null, 2)}
|
|
1297
|
-
|
|
1298
|
-
Be thorough and methodical. Always use listDirectory to verify actual file existence before fixing imports.`,
|
|
1299
|
-
model: resolveModel(runtimeContext),
|
|
1300
|
-
tools: {
|
|
1301
|
-
validateCode: allTools.validateCode,
|
|
1302
|
-
readFile: allTools.readFile,
|
|
1303
|
-
writeFile: allTools.writeFile,
|
|
1304
|
-
multiEdit: allTools.multiEdit,
|
|
1305
|
-
replaceLines: allTools.replaceLines,
|
|
1306
|
-
listDirectory: allTools.listDirectory,
|
|
1307
|
-
executeCommand: allTools.executeCommand,
|
|
1308
|
-
},
|
|
1309
|
-
});
|
|
1310
|
-
|
|
1311
|
-
console.log('Starting validation and fix agent with internal loop...');
|
|
1312
|
-
|
|
1313
|
-
let validationResults = {
|
|
1314
|
-
valid: false,
|
|
1315
|
-
errorsFixed: 0,
|
|
1316
|
-
remainingErrors: 1, // Start with 1 to enter the loop
|
|
1317
|
-
iteration: currentIteration,
|
|
1318
|
-
};
|
|
1319
|
-
|
|
1320
|
-
// Loop up to maxIterations times or until all errors are fixed
|
|
1321
|
-
while (validationResults.remainingErrors > 0 && currentIteration <= maxIterations) {
|
|
1322
|
-
console.log(`\n=== Validation Iteration ${currentIteration} ===`);
|
|
1323
|
-
|
|
1324
|
-
const iterationPrompt =
|
|
1325
|
-
currentIteration === 1
|
|
1326
|
-
? `Please validate the template integration and fix any errors found in the project at ${targetPath}. The template "${slug}" (${commitSha.substring(0, 7)}) was just integrated and may have validation issues that need fixing.
|
|
1327
|
-
|
|
1328
|
-
Start by running validateCode with all validation types to get a complete picture of any issues, then systematically fix them.`
|
|
1329
|
-
: `Continue validation and fixing for the template integration at ${targetPath}. This is iteration ${currentIteration} of validation.
|
|
1330
|
-
|
|
1331
|
-
Previous iterations may have fixed some issues, so start by re-running validateCode to see the current state, then fix any remaining issues.`;
|
|
1332
|
-
|
|
1333
|
-
const result = await validationAgent.stream(iterationPrompt, {
|
|
1334
|
-
experimental_output: z.object({ success: z.boolean() }),
|
|
1335
|
-
});
|
|
1336
|
-
|
|
1337
|
-
let iterationErrors = 0;
|
|
1338
|
-
let previousErrors = validationResults.remainingErrors;
|
|
1339
|
-
|
|
1340
|
-
for await (const chunk of result.fullStream) {
|
|
1341
|
-
if (chunk.type === 'step-finish' || chunk.type === 'step-start') {
|
|
1342
|
-
console.log({
|
|
1343
|
-
type: chunk.type,
|
|
1344
|
-
msgId: chunk.messageId,
|
|
1345
|
-
iteration: currentIteration,
|
|
1346
|
-
});
|
|
1347
|
-
} else {
|
|
1348
|
-
console.log(JSON.stringify(chunk, null, 2));
|
|
1349
|
-
}
|
|
1350
|
-
if (chunk.type === 'tool-result') {
|
|
1351
|
-
// Track validation results
|
|
1352
|
-
if (chunk.toolName === 'validateCode') {
|
|
1353
|
-
const toolResult = chunk.result as any;
|
|
1354
|
-
if (toolResult?.summary) {
|
|
1355
|
-
iterationErrors = toolResult.summary.totalErrors || 0;
|
|
1356
|
-
console.log(`Iteration ${currentIteration}: Found ${iterationErrors} errors`);
|
|
1357
|
-
}
|
|
1358
|
-
}
|
|
1359
|
-
}
|
|
1360
|
-
}
|
|
1361
|
-
|
|
1362
|
-
// Update results for this iteration
|
|
1363
|
-
validationResults.remainingErrors = iterationErrors;
|
|
1364
|
-
validationResults.errorsFixed += Math.max(0, previousErrors - iterationErrors);
|
|
1365
|
-
validationResults.valid = iterationErrors === 0;
|
|
1366
|
-
validationResults.iteration = currentIteration;
|
|
1367
|
-
|
|
1368
|
-
console.log(`Iteration ${currentIteration} complete: ${iterationErrors} errors remaining`);
|
|
1369
|
-
|
|
1370
|
-
// Break if no errors or max iterations reached
|
|
1371
|
-
if (iterationErrors === 0) {
|
|
1372
|
-
console.log(`✅ All validation issues resolved in ${currentIteration} iterations!`);
|
|
1373
|
-
break;
|
|
1374
|
-
} else if (currentIteration >= maxIterations) {
|
|
1375
|
-
console.log(`⚠️ Max iterations (${maxIterations}) reached. ${iterationErrors} errors still remaining.`);
|
|
1376
|
-
break;
|
|
1377
|
-
}
|
|
1378
|
-
|
|
1379
|
-
currentIteration++;
|
|
1380
|
-
}
|
|
1381
|
-
|
|
1382
|
-
// Commit the validation fixes
|
|
1383
|
-
try {
|
|
1384
|
-
await gitAddAndCommit(
|
|
1385
|
-
targetPath,
|
|
1386
|
-
`fix(template): resolve validation errors for ${slug}@${commitSha.substring(0, 7)}`,
|
|
1387
|
-
undefined,
|
|
1388
|
-
{
|
|
1389
|
-
skipIfNoStaged: true,
|
|
1390
|
-
},
|
|
1391
|
-
);
|
|
1392
|
-
} catch (commitError) {
|
|
1393
|
-
console.warn('Failed to commit validation fixes:', commitError);
|
|
1394
|
-
}
|
|
1395
|
-
|
|
1396
|
-
return {
|
|
1397
|
-
success: true,
|
|
1398
|
-
applied: true,
|
|
1399
|
-
message: `Validation completed in ${currentIteration} iteration${currentIteration > 1 ? 's' : ''}. ${validationResults.valid ? 'All issues resolved!' : `${validationResults.remainingErrors} issues remaining`}`,
|
|
1400
|
-
validationResults: {
|
|
1401
|
-
valid: validationResults.valid,
|
|
1402
|
-
errorsFixed: validationResults.errorsFixed,
|
|
1403
|
-
remainingErrors: validationResults.remainingErrors,
|
|
1404
|
-
},
|
|
1405
|
-
};
|
|
1406
|
-
} catch (error) {
|
|
1407
|
-
console.error('Validation and fix failed:', error);
|
|
1408
|
-
return {
|
|
1409
|
-
success: false,
|
|
1410
|
-
applied: false,
|
|
1411
|
-
message: `Validation and fix failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
1412
|
-
validationResults: {
|
|
1413
|
-
valid: false,
|
|
1414
|
-
errorsFixed: 0,
|
|
1415
|
-
remainingErrors: -1,
|
|
1416
|
-
},
|
|
1417
|
-
error: error instanceof Error ? error.message : String(error),
|
|
1418
|
-
};
|
|
1419
|
-
} finally {
|
|
1420
|
-
// Cleanup template directory
|
|
1421
|
-
try {
|
|
1422
|
-
await rm(templateDir, { recursive: true, force: true });
|
|
1423
|
-
console.log(`✓ Cleaned up template directory: ${templateDir}`);
|
|
1424
|
-
} catch (cleanupError) {
|
|
1425
|
-
console.warn('Failed to cleanup template directory:', cleanupError);
|
|
1426
|
-
}
|
|
1427
|
-
}
|
|
1428
|
-
},
|
|
1429
|
-
});
|
|
1430
|
-
|
|
1431
|
-
// Create the complete workflow
|
|
1432
|
-
export const agentBuilderTemplateWorkflow = createWorkflow({
|
|
1433
|
-
id: 'agent-builder-template',
|
|
1434
|
-
description:
|
|
1435
|
-
'Merges a Mastra template repository into the current project using intelligent AgentBuilder-powered merging',
|
|
1436
|
-
inputSchema: AgentBuilderInputSchema,
|
|
1437
|
-
outputSchema: ApplyResultSchema,
|
|
1438
|
-
steps: [
|
|
1439
|
-
cloneTemplateStep,
|
|
1440
|
-
analyzePackageStep,
|
|
1441
|
-
discoverUnitsStep,
|
|
1442
|
-
orderUnitsStep,
|
|
1443
|
-
packageMergeStep,
|
|
1444
|
-
installStep,
|
|
1445
|
-
programmaticFileCopyStep,
|
|
1446
|
-
intelligentMergeStep,
|
|
1447
|
-
validationAndFixStep,
|
|
1448
|
-
],
|
|
1449
|
-
})
|
|
1450
|
-
.then(cloneTemplateStep)
|
|
1451
|
-
.map(async ({ getStepResult }) => {
|
|
1452
|
-
const cloneResult = getStepResult(cloneTemplateStep);
|
|
1453
|
-
|
|
1454
|
-
// Check for failure in clone step
|
|
1455
|
-
if (shouldAbortWorkflow(cloneResult)) {
|
|
1456
|
-
throw new Error(`Critical failure in clone step: ${cloneResult.error}`);
|
|
1457
|
-
}
|
|
1458
|
-
|
|
1459
|
-
return cloneResult;
|
|
1460
|
-
})
|
|
1461
|
-
.parallel([analyzePackageStep, discoverUnitsStep])
|
|
1462
|
-
.map(async ({ getStepResult }) => {
|
|
1463
|
-
const analyzeResult = getStepResult(analyzePackageStep);
|
|
1464
|
-
const discoverResult = getStepResult(discoverUnitsStep);
|
|
1465
|
-
|
|
1466
|
-
// Check for failures in parallel steps
|
|
1467
|
-
if (shouldAbortWorkflow(analyzeResult)) {
|
|
1468
|
-
throw new Error(`Failure in analyze package step: ${analyzeResult.error || 'Package analysis failed'}`);
|
|
1469
|
-
}
|
|
1470
|
-
|
|
1471
|
-
if (shouldAbortWorkflow(discoverResult)) {
|
|
1472
|
-
throw new Error(`Failure in discover units step: ${discoverResult.error || 'Unit discovery failed'}`);
|
|
1473
|
-
}
|
|
1474
|
-
|
|
1475
|
-
return discoverResult;
|
|
1476
|
-
})
|
|
1477
|
-
.then(orderUnitsStep)
|
|
1478
|
-
.map(async ({ getStepResult, getInitData }) => {
|
|
1479
|
-
const cloneResult = getStepResult(cloneTemplateStep);
|
|
1480
|
-
const initData = getInitData();
|
|
1481
|
-
return {
|
|
1482
|
-
commitSha: cloneResult.commitSha,
|
|
1483
|
-
slug: cloneResult.slug,
|
|
1484
|
-
targetPath: initData.targetPath,
|
|
1485
|
-
};
|
|
1486
|
-
})
|
|
1487
|
-
.then(prepareBranchStep)
|
|
1488
|
-
.map(async ({ getStepResult, getInitData }) => {
|
|
1489
|
-
const cloneResult = getStepResult(cloneTemplateStep);
|
|
1490
|
-
const packageResult = getStepResult(analyzePackageStep);
|
|
1491
|
-
const initData = getInitData();
|
|
1492
|
-
return {
|
|
1493
|
-
commitSha: cloneResult.commitSha,
|
|
1494
|
-
slug: cloneResult.slug,
|
|
1495
|
-
targetPath: initData.targetPath,
|
|
1496
|
-
packageInfo: packageResult,
|
|
1497
|
-
};
|
|
1498
|
-
})
|
|
1499
|
-
.then(packageMergeStep)
|
|
1500
|
-
.map(async ({ getInitData }) => {
|
|
1501
|
-
const initData = getInitData();
|
|
1502
|
-
return {
|
|
1503
|
-
targetPath: initData.targetPath,
|
|
1504
|
-
};
|
|
1505
|
-
})
|
|
1506
|
-
.then(installStep)
|
|
1507
|
-
.map(async ({ getStepResult, getInitData }) => {
|
|
1508
|
-
const cloneResult = getStepResult(cloneTemplateStep);
|
|
1509
|
-
const orderResult = getStepResult(orderUnitsStep);
|
|
1510
|
-
const installResult = getStepResult(installStep);
|
|
1511
|
-
const initData = getInitData();
|
|
1512
|
-
|
|
1513
|
-
if (shouldAbortWorkflow(installResult)) {
|
|
1514
|
-
throw new Error(`Failure in install step: ${installResult.error || 'Install failed'}`);
|
|
1515
|
-
}
|
|
1516
|
-
return {
|
|
1517
|
-
orderedUnits: orderResult.orderedUnits,
|
|
1518
|
-
templateDir: cloneResult.templateDir,
|
|
1519
|
-
commitSha: cloneResult.commitSha,
|
|
1520
|
-
slug: cloneResult.slug,
|
|
1521
|
-
targetPath: initData.targetPath,
|
|
1522
|
-
};
|
|
1523
|
-
})
|
|
1524
|
-
.then(programmaticFileCopyStep)
|
|
1525
|
-
.map(async ({ getStepResult, getInitData }) => {
|
|
1526
|
-
const copyResult = getStepResult(programmaticFileCopyStep);
|
|
1527
|
-
const cloneResult = getStepResult(cloneTemplateStep);
|
|
1528
|
-
const initData = getInitData();
|
|
1529
|
-
|
|
1530
|
-
return {
|
|
1531
|
-
conflicts: copyResult.conflicts,
|
|
1532
|
-
copiedFiles: copyResult.copiedFiles,
|
|
1533
|
-
commitSha: cloneResult.commitSha,
|
|
1534
|
-
slug: cloneResult.slug,
|
|
1535
|
-
targetPath: initData.targetPath,
|
|
1536
|
-
templateDir: cloneResult.templateDir,
|
|
1537
|
-
};
|
|
1538
|
-
})
|
|
1539
|
-
.then(intelligentMergeStep)
|
|
1540
|
-
.map(async ({ getStepResult, getInitData }) => {
|
|
1541
|
-
const cloneResult = getStepResult(cloneTemplateStep);
|
|
1542
|
-
const orderResult = getStepResult(orderUnitsStep);
|
|
1543
|
-
const copyResult = getStepResult(programmaticFileCopyStep);
|
|
1544
|
-
const mergeResult = getStepResult(intelligentMergeStep);
|
|
1545
|
-
const initData = getInitData();
|
|
1546
|
-
|
|
1547
|
-
return {
|
|
1548
|
-
commitSha: cloneResult.commitSha,
|
|
1549
|
-
slug: cloneResult.slug,
|
|
1550
|
-
targetPath: initData.targetPath,
|
|
1551
|
-
templateDir: cloneResult.templateDir,
|
|
1552
|
-
orderedUnits: orderResult.orderedUnits,
|
|
1553
|
-
copiedFiles: copyResult.copiedFiles,
|
|
1554
|
-
conflictsResolved: mergeResult.conflictsResolved,
|
|
1555
|
-
};
|
|
1556
|
-
})
|
|
1557
|
-
.then(validationAndFixStep)
|
|
1558
|
-
.map(async ({ getStepResult }) => {
|
|
1559
|
-
const cloneResult = getStepResult(cloneTemplateStep);
|
|
1560
|
-
const analyzeResult = getStepResult(analyzePackageStep);
|
|
1561
|
-
const discoverResult = getStepResult(discoverUnitsStep);
|
|
1562
|
-
const orderResult = getStepResult(orderUnitsStep);
|
|
1563
|
-
const prepareBranchResult = getStepResult(prepareBranchStep);
|
|
1564
|
-
const packageMergeResult = getStepResult(packageMergeStep);
|
|
1565
|
-
const installResult = getStepResult(installStep);
|
|
1566
|
-
const copyResult = getStepResult(programmaticFileCopyStep);
|
|
1567
|
-
const intelligentMergeResult = getStepResult(intelligentMergeStep);
|
|
1568
|
-
const validationResult = getStepResult(validationAndFixStep);
|
|
1569
|
-
|
|
1570
|
-
const branchName = prepareBranchResult.branchName;
|
|
1571
|
-
|
|
1572
|
-
// Aggregate errors from all steps
|
|
1573
|
-
const allErrors = [
|
|
1574
|
-
cloneResult.error,
|
|
1575
|
-
analyzeResult.error,
|
|
1576
|
-
discoverResult.error,
|
|
1577
|
-
orderResult.error,
|
|
1578
|
-
prepareBranchResult.error,
|
|
1579
|
-
packageMergeResult.error,
|
|
1580
|
-
installResult.error,
|
|
1581
|
-
copyResult.error,
|
|
1582
|
-
intelligentMergeResult.error,
|
|
1583
|
-
validationResult.error,
|
|
1584
|
-
].filter(Boolean);
|
|
1585
|
-
|
|
1586
|
-
// Determine overall success based on all step results
|
|
1587
|
-
const overallSuccess =
|
|
1588
|
-
cloneResult.success !== false &&
|
|
1589
|
-
analyzeResult.success !== false &&
|
|
1590
|
-
discoverResult.success !== false &&
|
|
1591
|
-
orderResult.success !== false &&
|
|
1592
|
-
prepareBranchResult.success !== false &&
|
|
1593
|
-
packageMergeResult.success !== false &&
|
|
1594
|
-
installResult.success !== false &&
|
|
1595
|
-
copyResult.success !== false &&
|
|
1596
|
-
intelligentMergeResult.success !== false &&
|
|
1597
|
-
validationResult.success !== false;
|
|
1598
|
-
|
|
1599
|
-
// Create comprehensive message
|
|
1600
|
-
const messages = [];
|
|
1601
|
-
if (copyResult.copiedFiles?.length > 0) {
|
|
1602
|
-
messages.push(`${copyResult.copiedFiles.length} files copied`);
|
|
1603
|
-
}
|
|
1604
|
-
if (copyResult.conflicts?.length > 0) {
|
|
1605
|
-
messages.push(`${copyResult.conflicts.length} conflicts skipped`);
|
|
1606
|
-
}
|
|
1607
|
-
if (intelligentMergeResult.conflictsResolved?.length > 0) {
|
|
1608
|
-
messages.push(`${intelligentMergeResult.conflictsResolved.length} conflicts resolved`);
|
|
1609
|
-
}
|
|
1610
|
-
if (validationResult.validationResults?.errorsFixed > 0) {
|
|
1611
|
-
messages.push(`${validationResult.validationResults.errorsFixed} validation errors fixed`);
|
|
1612
|
-
}
|
|
1613
|
-
|
|
1614
|
-
const comprehensiveMessage =
|
|
1615
|
-
messages.length > 0
|
|
1616
|
-
? `Template merge completed: ${messages.join(', ')}`
|
|
1617
|
-
: validationResult.message || 'Template merge completed';
|
|
1618
|
-
|
|
1619
|
-
return {
|
|
1620
|
-
success: overallSuccess,
|
|
1621
|
-
applied: validationResult.applied || copyResult.copiedFiles?.length > 0 || false,
|
|
1622
|
-
message: comprehensiveMessage,
|
|
1623
|
-
validationResults: validationResult.validationResults,
|
|
1624
|
-
error: allErrors.length > 0 ? allErrors.join('; ') : undefined,
|
|
1625
|
-
errors: allErrors.length > 0 ? allErrors : undefined,
|
|
1626
|
-
branchName,
|
|
1627
|
-
// Additional debugging info
|
|
1628
|
-
stepResults: {
|
|
1629
|
-
cloneSuccess: cloneResult.success,
|
|
1630
|
-
analyzeSuccess: analyzeResult.success,
|
|
1631
|
-
discoverSuccess: discoverResult.success,
|
|
1632
|
-
orderSuccess: orderResult.success,
|
|
1633
|
-
prepareBranchSuccess: prepareBranchResult.success,
|
|
1634
|
-
packageMergeSuccess: packageMergeResult.success,
|
|
1635
|
-
installSuccess: installResult.success,
|
|
1636
|
-
copySuccess: copyResult.success,
|
|
1637
|
-
mergeSuccess: intelligentMergeResult.success,
|
|
1638
|
-
validationSuccess: validationResult.success,
|
|
1639
|
-
filesCopied: copyResult.copiedFiles?.length || 0,
|
|
1640
|
-
conflictsSkipped: copyResult.conflicts?.length || 0,
|
|
1641
|
-
conflictsResolved: intelligentMergeResult.conflictsResolved?.length || 0,
|
|
1642
|
-
},
|
|
1643
|
-
};
|
|
1644
|
-
})
|
|
1645
|
-
.commit();
|
|
1646
|
-
|
|
1647
|
-
// Helper to merge a template by slug
|
|
1648
|
-
export async function mergeTemplateBySlug(slug: string, targetPath?: string) {
|
|
1649
|
-
const template = await getMastraTemplate(slug);
|
|
1650
|
-
const run = await agentBuilderTemplateWorkflow.createRunAsync();
|
|
1651
|
-
return await run.start({
|
|
1652
|
-
inputData: {
|
|
1653
|
-
repo: template.githubUrl,
|
|
1654
|
-
slug: template.slug,
|
|
1655
|
-
targetPath,
|
|
1656
|
-
},
|
|
1657
|
-
});
|
|
1658
|
-
}
|
|
1659
|
-
|
|
1660
|
-
// Helper function to determine conflict resolution strategy
|
|
1661
|
-
const determineConflictStrategy = (
|
|
1662
|
-
_unit: { kind: string; id: string },
|
|
1663
|
-
_targetFile: string,
|
|
1664
|
-
): 'skip' | 'backup-and-replace' | 'rename' => {
|
|
1665
|
-
// For now, always skip conflicts to avoid disrupting existing files
|
|
1666
|
-
// TODO: Enable advanced strategies based on user feedback
|
|
1667
|
-
return 'skip';
|
|
1668
|
-
|
|
1669
|
-
// Future logic (currently disabled):
|
|
1670
|
-
// if (['agent', 'workflow', 'network'].includes(unit.kind)) {
|
|
1671
|
-
// return 'backup-and-replace';
|
|
1672
|
-
// }
|
|
1673
|
-
// if (unit.kind === 'tool') {
|
|
1674
|
-
// return 'rename';
|
|
1675
|
-
// }
|
|
1676
|
-
// return 'backup-and-replace';
|
|
1677
|
-
};
|
|
1678
|
-
|
|
1679
|
-
// Helper function to check if a step result indicates a failure
|
|
1680
|
-
const shouldAbortWorkflow = (stepResult: any): boolean => {
|
|
1681
|
-
return stepResult?.success === false || stepResult?.error;
|
|
1682
|
-
};
|