Skip to content

Commit d94acc3

Browse files
committed
refactor(workflows): improve template validation and update engine configs
- Replace simple boolean validator with detailed validation function that returns error messages - Update workflow templates to use codex/gpt-5 instead of claude/cursor - Enhance error reporting in template loader with detailed validation errors
1 parent d117a75 commit d94acc3

File tree

4 files changed

+130
-111
lines changed

4 files changed

+130
-111
lines changed

src/workflows/templates/loader.ts

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { existsSync } from 'node:fs';
33
import { createRequire } from 'node:module';
44
import { fileURLToPath, pathToFileURL } from 'node:url';
55
import type { WorkflowTemplate } from './types.js';
6-
import { isWorkflowTemplate } from './validator.js';
6+
import { isWorkflowTemplate, validateWorkflowTemplate } from './validator.js';
77
import { ensureTemplateGlobals } from './globals.js';
88

99
// Package root resolution
@@ -51,19 +51,22 @@ export async function loadTemplate(cwd: string, templatePath?: string): Promise<
5151
const codemachineTemplate = path.resolve(templatesDir, 'codemachine.workflow.js');
5252
const candidates = [resolvedTemplateOverride, codemachineTemplate].filter(Boolean) as string[];
5353

54+
const errors: string[] = [];
5455
for (const modPath of candidates) {
5556
try {
5657
const tpl = (await loadWorkflowModule(modPath)) as unknown;
57-
if (isWorkflowTemplate(tpl)) return tpl;
58-
} catch {
59-
// try next candidate
58+
const result = validateWorkflowTemplate(tpl);
59+
if (result.valid) return tpl as WorkflowTemplate;
60+
const rel = path.relative(cwd, modPath);
61+
errors.push(`${rel}: ${result.errors.join('; ')}`);
62+
} catch (e) {
63+
const rel = path.relative(cwd, modPath);
64+
errors.push(`${rel}: ${e instanceof Error ? e.message : String(e)}`);
6065
}
6166
}
62-
throw new Error(
63-
`No workflow template found. Looked for: ${candidates
64-
.map((p) => path.relative(cwd, p))
65-
.join(', ')}`,
66-
);
67+
const looked = candidates.map((p) => path.relative(cwd, p)).join(', ');
68+
const details = errors.length ? `\nValidation errors:\n- ${errors.join('\n- ')}` : '';
69+
throw new Error(`No workflow template found. Looked for: ${looked}${details}`);
6770
}
6871

6972
export async function loadTemplateWithPath(cwd: string, templatePath?: string): Promise<{ template: WorkflowTemplate; resolvedPath: string }> {
@@ -75,17 +78,20 @@ export async function loadTemplateWithPath(cwd: string, templatePath?: string):
7578
const codemachineTemplate = path.resolve(templatesDir, 'codemachine.workflow.js');
7679
const candidates = [resolvedTemplateOverride, codemachineTemplate].filter(Boolean) as string[];
7780

81+
const errors: string[] = [];
7882
for (const modPath of candidates) {
7983
try {
8084
const tpl = (await loadWorkflowModule(modPath)) as unknown;
81-
if (isWorkflowTemplate(tpl)) return { template: tpl, resolvedPath: modPath };
82-
} catch {
83-
// try next candidate
85+
const result = validateWorkflowTemplate(tpl);
86+
if (result.valid) return { template: tpl as WorkflowTemplate, resolvedPath: modPath };
87+
const rel = path.relative(cwd, modPath);
88+
errors.push(`${rel}: ${result.errors.join('; ')}`);
89+
} catch (e) {
90+
const rel = path.relative(cwd, modPath);
91+
errors.push(`${rel}: ${e instanceof Error ? e.message : String(e)}`);
8492
}
8593
}
86-
throw new Error(
87-
`No workflow template found. Looked for: ${candidates
88-
.map((p) => path.relative(cwd, p))
89-
.join(', ')}`,
90-
);
94+
const looked = candidates.map((p) => path.relative(cwd, p)).join(', ');
95+
const details = errors.length ? `\nValidation errors:\n- ${errors.join('\n- ')}` : '';
96+
throw new Error(`No workflow template found. Looked for: ${looked}${details}`);
9197
}
Lines changed: 104 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -1,97 +1,110 @@
11
import type { WorkflowTemplate } from './types.js';
22

3-
export function isWorkflowTemplate(value: unknown): value is WorkflowTemplate {
4-
if (!value || typeof value !== 'object') return false;
5-
const obj = value as { name?: unknown; steps?: unknown };
6-
if (typeof obj.name !== 'string' || obj.name.trim().length === 0) return false;
7-
if (!Array.isArray(obj.steps)) return false;
8-
return obj.steps.every((step) => {
9-
if (!step || typeof step !== 'object') return false;
10-
const candidate = step as {
11-
type?: unknown;
12-
agentId?: unknown;
13-
agentName?: unknown;
14-
promptPath?: unknown;
15-
model?: unknown;
16-
modelReasoningEffort?: unknown;
17-
module?: unknown;
18-
executeOnce?: unknown;
19-
};
20-
if (
21-
candidate.type !== 'module' ||
22-
typeof candidate.agentId !== 'string' ||
23-
typeof candidate.agentName !== 'string' ||
24-
typeof candidate.promptPath !== 'string'
25-
) {
26-
return false;
27-
}
28-
29-
if (candidate.model !== undefined && typeof candidate.model !== 'string') {
30-
return false;
31-
}
32-
33-
if (
34-
candidate.modelReasoningEffort !== undefined &&
35-
candidate.modelReasoningEffort !== 'low' &&
36-
candidate.modelReasoningEffort !== 'medium' &&
37-
candidate.modelReasoningEffort !== 'high'
38-
) {
39-
return false;
40-
}
41-
42-
if (candidate.executeOnce !== undefined && typeof candidate.executeOnce !== 'boolean') {
43-
return false;
44-
}
45-
46-
if (candidate.module === undefined) {
47-
return true;
48-
}
49-
50-
if (!candidate.module || typeof candidate.module !== 'object') {
51-
return false;
52-
}
53-
54-
const moduleMeta = candidate.module as {
55-
id?: unknown;
56-
behavior?: unknown;
57-
};
58-
59-
if (typeof moduleMeta.id !== 'string') {
60-
return false;
61-
}
62-
63-
if (moduleMeta.behavior === undefined) {
64-
return true;
65-
}
66-
67-
if (!moduleMeta.behavior || typeof moduleMeta.behavior !== 'object') {
68-
return false;
69-
}
70-
71-
const behavior = moduleMeta.behavior as {
72-
type?: unknown;
73-
action?: unknown;
74-
steps?: unknown;
75-
trigger?: unknown;
76-
maxIterations?: unknown;
77-
};
78-
79-
if (behavior.type !== 'loop' || behavior.action !== 'stepBack') {
80-
return false;
81-
}
82-
83-
if (typeof behavior.steps !== 'number' || behavior.steps <= 0) {
84-
return false;
85-
}
3+
export interface ValidationResult {
4+
valid: boolean;
5+
errors: string[];
6+
}
867

87-
if (typeof behavior.trigger !== 'string') {
88-
return false;
89-
}
8+
export function validateWorkflowTemplate(value: unknown): ValidationResult {
9+
const errors: string[] = [];
10+
if (!value || typeof value !== 'object') {
11+
return { valid: false, errors: ['Template is not an object'] };
12+
}
9013

91-
if (behavior.maxIterations !== undefined && typeof behavior.maxIterations !== 'number') {
92-
return false;
93-
}
14+
const obj = value as { name?: unknown; steps?: unknown };
15+
if (typeof obj.name !== 'string' || obj.name.trim().length === 0) {
16+
errors.push('Template.name must be a non-empty string');
17+
}
18+
if (!Array.isArray(obj.steps)) {
19+
errors.push('Template.steps must be an array');
20+
} else {
21+
obj.steps.forEach((step, index) => {
22+
if (!step || typeof step !== 'object') {
23+
errors.push(`Step[${index}] must be an object`);
24+
return;
25+
}
26+
const candidate = step as {
27+
type?: unknown;
28+
agentId?: unknown;
29+
agentName?: unknown;
30+
promptPath?: unknown;
31+
model?: unknown;
32+
modelReasoningEffort?: unknown;
33+
module?: unknown;
34+
executeOnce?: unknown;
35+
};
36+
37+
if (candidate.type !== 'module') {
38+
errors.push(`Step[${index}].type must be 'module'`);
39+
}
40+
if (typeof candidate.agentId !== 'string') {
41+
errors.push(`Step[${index}].agentId must be a string`);
42+
}
43+
if (typeof candidate.agentName !== 'string') {
44+
errors.push(`Step[${index}].agentName must be a string`);
45+
}
46+
if (typeof candidate.promptPath !== 'string') {
47+
errors.push(`Step[${index}].promptPath must be a string`);
48+
}
49+
50+
if (candidate.model !== undefined && typeof candidate.model !== 'string') {
51+
errors.push(`Step[${index}].model must be a string`);
52+
}
53+
54+
if (candidate.modelReasoningEffort !== undefined) {
55+
const mre = candidate.modelReasoningEffort;
56+
if (mre !== 'low' && mre !== 'medium' && mre !== 'high') {
57+
errors.push(
58+
`Step[${index}].modelReasoningEffort must be one of 'low'|'medium'|'high' (got '${String(mre)}')`,
59+
);
60+
}
61+
}
62+
63+
if (candidate.executeOnce !== undefined && typeof candidate.executeOnce !== 'boolean') {
64+
errors.push(`Step[${index}].executeOnce must be a boolean`);
65+
}
66+
67+
if (candidate.module !== undefined) {
68+
if (!candidate.module || typeof candidate.module !== 'object') {
69+
errors.push(`Step[${index}].module must be an object`);
70+
} else {
71+
const moduleMeta = candidate.module as { id?: unknown; behavior?: unknown };
72+
if (typeof moduleMeta.id !== 'string') {
73+
errors.push(`Step[${index}].module.id must be a string`);
74+
}
75+
if (moduleMeta.behavior !== undefined) {
76+
if (!moduleMeta.behavior || typeof moduleMeta.behavior !== 'object') {
77+
errors.push(`Step[${index}].module.behavior must be an object`);
78+
} else {
79+
const behavior = moduleMeta.behavior as {
80+
type?: unknown;
81+
action?: unknown;
82+
steps?: unknown;
83+
trigger?: unknown;
84+
maxIterations?: unknown;
85+
};
86+
if (behavior.type !== 'loop' || behavior.action !== 'stepBack') {
87+
errors.push(`Step[${index}].module.behavior must be { type: 'loop', action: 'stepBack', ... }`);
88+
}
89+
if (typeof behavior.steps !== 'number' || behavior.steps <= 0) {
90+
errors.push(`Step[${index}].module.behavior.steps must be a positive number`);
91+
}
92+
if (typeof behavior.trigger !== 'string') {
93+
errors.push(`Step[${index}].module.behavior.trigger must be a string`);
94+
}
95+
if (behavior.maxIterations !== undefined && typeof behavior.maxIterations !== 'number') {
96+
errors.push(`Step[${index}].module.behavior.maxIterations must be a number`);
97+
}
98+
}
99+
}
100+
}
101+
}
102+
});
103+
}
104+
105+
return { valid: errors.length === 0, errors };
106+
}
94107

95-
return true;
96-
});
108+
export function isWorkflowTemplate(value: unknown): value is WorkflowTemplate {
109+
return validateWorkflowTemplate(value).valid;
97110
}

templates/workflows/_example.workflow.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,8 @@ export default {
6666
// COMPLETE OVERRIDE - All options combined
6767
// ============================================
6868
resolveStep('full-featured-agent', {
69-
engine: 'claude', // Use Claude
70-
model: 'sonnet', // With Sonnet model
69+
engine: 'codex', // Use Codex
70+
model: 'gpt-5', // With GPT-5 model
7171
modelReasoningEffort: 'high', // Maximum thinking
7272
agentName: 'AI Architect Pro', // Custom name
7373
promptPath: './prompts/custom/arch.txt', // Custom prompt

templates/workflows/codemachine.workflow.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
export default {
22
name: 'CodeMachine Workflow',
33
steps: [
4-
resolveStep('git-commit', { executeOnce: true, engine: 'cursor' }), // Commit the initial project specification to git
4+
resolveStep('git-commit', { executeOnce: true, engine: 'codex', model: 'gpt-5', modelReasoningEffort: 'low' }), // Commit the initial project specification to git
55
resolveStep('arch-agent', { executeOnce: true }), // Define system architecture and technical design decisions
66
resolveStep('plan-agent', { executeOnce: true }), // Generate comprehensive iterative development plan with architectural artifacts
77
resolveStep('task-breakdown', { executeOnce: true }), // Extract and structure tasks from project plan into JSON format

0 commit comments

Comments
 (0)