guild-agents 0.3.1 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -1
- package/bin/guild.js +74 -1
- package/package.json +12 -5
- package/src/commands/doctor.js +70 -1
- package/src/commands/logs.js +63 -0
- package/src/commands/reset-learnings.js +44 -0
- package/src/commands/run.js +62 -0
- package/src/templates/agents/advisor.md +1 -0
- package/src/templates/agents/bugfix.md +1 -0
- package/src/templates/agents/code-reviewer.md +1 -0
- package/src/templates/agents/db-migration.md +1 -0
- package/src/templates/agents/developer.md +1 -0
- package/src/templates/agents/learnings-extractor.md +49 -0
- package/src/templates/agents/platform-expert.md +1 -0
- package/src/templates/agents/product-owner.md +1 -0
- package/src/templates/agents/qa.md +1 -0
- package/src/templates/agents/tech-lead.md +1 -0
- package/src/templates/skills/build-feature/SKILL.md +130 -26
- package/src/templates/skills/council/SKILL.md +51 -4
- package/src/templates/skills/create-pr/SKILL.md +32 -0
- package/src/templates/skills/dev-flow/SKILL.md +14 -0
- package/src/templates/skills/guild-specialize/SKILL.md +45 -3
- package/src/templates/skills/new-feature/SKILL.md +33 -0
- package/src/templates/skills/qa-cycle/SKILL.md +48 -5
- package/src/templates/skills/review/SKILL.md +22 -1
- package/src/templates/skills/session-end/SKILL.md +27 -0
- package/src/templates/skills/session-start/SKILL.md +32 -0
- package/src/templates/skills/status/SKILL.md +19 -0
- package/src/utils/dispatch-protocol.js +74 -0
- package/src/utils/dispatch.js +172 -0
- package/src/utils/learnings-io.js +76 -0
- package/src/utils/learnings.js +204 -0
- package/src/utils/orchestrator-io.js +356 -0
- package/src/utils/orchestrator.js +590 -0
- package/src/utils/skill-loader.js +83 -0
- package/src/utils/trace.js +400 -0
- package/src/utils/version.js +90 -0
- package/src/utils/workflow-parser.js +225 -0
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* workflow-parser.js — Parser and validator for Guild declarative workflows.
|
|
3
|
+
*
|
|
4
|
+
* Parses SKILL.md files with nested YAML frontmatter (workflow definitions)
|
|
5
|
+
* using a full YAML parser. Validates workflow structure against the schema.
|
|
6
|
+
* Backward compatible: skills without a `workflow` key return workflow: null.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import YAML from 'yaml';
|
|
10
|
+
import { MODEL_TIERS, FAILURE_STRATEGIES, DEFAULT_FAILURE_STRATEGY } from './dispatch-protocol.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Extracts the raw YAML frontmatter string and body from markdown content.
|
|
14
|
+
* @param {string} content - Raw markdown content
|
|
15
|
+
* @returns {{ yaml: string, body: string } | null} Null if no frontmatter found
|
|
16
|
+
*/
|
|
17
|
+
export function extractFrontmatterBlock(content) {
|
|
18
|
+
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
19
|
+
if (!match) return null;
|
|
20
|
+
return {
|
|
21
|
+
yaml: match[1],
|
|
22
|
+
body: content.slice(match[0].length).trim(),
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Parses YAML frontmatter with full nested structure support.
|
|
28
|
+
* Uses the `yaml` npm package for spec-compliant parsing.
|
|
29
|
+
* @param {string} yamlString - Raw YAML frontmatter
|
|
30
|
+
* @returns {object} Parsed frontmatter object
|
|
31
|
+
*/
|
|
32
|
+
export function parseYamlFrontmatter(yamlString) {
|
|
33
|
+
return YAML.parse(yamlString) || {};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Normalizes a raw workflow step from YAML into a consistent object shape.
|
|
38
|
+
* Converts kebab-case keys to camelCase where appropriate.
|
|
39
|
+
* @param {object} raw - Raw step object from YAML
|
|
40
|
+
* @returns {object} Normalized step
|
|
41
|
+
*/
|
|
42
|
+
function normalizeStep(raw) {
|
|
43
|
+
return {
|
|
44
|
+
id: raw.id,
|
|
45
|
+
role: raw.role,
|
|
46
|
+
intent: raw.intent,
|
|
47
|
+
requires: raw.requires || [],
|
|
48
|
+
produces: raw.produces || [],
|
|
49
|
+
modelTier: raw['model-tier'] || undefined,
|
|
50
|
+
blocking: raw.blocking !== undefined ? raw.blocking : true,
|
|
51
|
+
onFailure: raw['on-failure'] || DEFAULT_FAILURE_STRATEGY,
|
|
52
|
+
gate: raw.gate || false,
|
|
53
|
+
retry: raw.retry || undefined,
|
|
54
|
+
condition: raw.condition || undefined,
|
|
55
|
+
parallel: raw.parallel || undefined,
|
|
56
|
+
commands: raw.commands || undefined,
|
|
57
|
+
delegatesTo: raw['delegates-to'] || undefined,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Parses a SKILL.md file and extracts the workflow definition.
|
|
63
|
+
* If no `workflow` key exists in frontmatter, returns { workflow: null }
|
|
64
|
+
* to indicate the Skill uses prose-based execution (backward compatible).
|
|
65
|
+
*
|
|
66
|
+
* @param {string} content - Raw content of SKILL.md
|
|
67
|
+
* @returns {{ name: string, description: string, userInvocable: boolean, workflow: object|null, body: string }}
|
|
68
|
+
* @throws {Error} If YAML frontmatter is malformed
|
|
69
|
+
*/
|
|
70
|
+
export function parseSkill(content) {
|
|
71
|
+
const block = extractFrontmatterBlock(content);
|
|
72
|
+
if (!block) {
|
|
73
|
+
return {
|
|
74
|
+
name: '',
|
|
75
|
+
description: '',
|
|
76
|
+
userInvocable: false,
|
|
77
|
+
workflow: null,
|
|
78
|
+
body: content,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const frontmatter = parseYamlFrontmatter(block.yaml);
|
|
83
|
+
|
|
84
|
+
const skill = {
|
|
85
|
+
name: frontmatter.name || '',
|
|
86
|
+
description: frontmatter.description || '',
|
|
87
|
+
userInvocable: frontmatter['user-invocable'] === true,
|
|
88
|
+
workflow: null,
|
|
89
|
+
body: block.body,
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
if (frontmatter.workflow) {
|
|
93
|
+
const raw = frontmatter.workflow;
|
|
94
|
+
skill.workflow = {
|
|
95
|
+
version: raw.version,
|
|
96
|
+
steps: Array.isArray(raw.steps)
|
|
97
|
+
? raw.steps.map(normalizeStep)
|
|
98
|
+
: [],
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return skill;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Validates a workflow definition against the schema.
|
|
107
|
+
* Returns an array of validation errors (empty if valid).
|
|
108
|
+
*
|
|
109
|
+
* @param {object} workflow - Parsed workflow object with version and steps
|
|
110
|
+
* @returns {string[]} Array of error messages
|
|
111
|
+
*/
|
|
112
|
+
export function validateWorkflow(workflow) {
|
|
113
|
+
const errors = [];
|
|
114
|
+
|
|
115
|
+
if (workflow.version !== 1) {
|
|
116
|
+
errors.push(`Unsupported workflow version: ${workflow.version}. This Guild version supports workflow version 1.`);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (!Array.isArray(workflow.steps) || workflow.steps.length === 0) {
|
|
120
|
+
errors.push('Workflow must have at least one step.');
|
|
121
|
+
return errors;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const ids = new Set();
|
|
125
|
+
|
|
126
|
+
for (const step of workflow.steps) {
|
|
127
|
+
// Required fields
|
|
128
|
+
if (!step.id) {
|
|
129
|
+
errors.push('Step missing required field: id');
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (ids.has(step.id)) {
|
|
134
|
+
errors.push(`Duplicate step id: "${step.id}"`);
|
|
135
|
+
}
|
|
136
|
+
ids.add(step.id);
|
|
137
|
+
|
|
138
|
+
if (!step.role) {
|
|
139
|
+
errors.push(`Step "${step.id}" missing required field: role`);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (!step.intent) {
|
|
143
|
+
errors.push(`Step "${step.id}" missing required field: intent`);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Valid model-tier
|
|
147
|
+
if (step.modelTier && !MODEL_TIERS.includes(step.modelTier)) {
|
|
148
|
+
errors.push(`Step "${step.id}" has invalid model-tier: "${step.modelTier}"`);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Valid on-failure
|
|
152
|
+
if (step.onFailure) {
|
|
153
|
+
const isGoto = step.onFailure.startsWith('goto:');
|
|
154
|
+
if (!FAILURE_STRATEGIES.includes(step.onFailure) && !isGoto) {
|
|
155
|
+
errors.push(`Step "${step.id}" has invalid on-failure: "${step.onFailure}"`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Retry limits
|
|
160
|
+
if (step.retry) {
|
|
161
|
+
if (step.retry.max !== undefined && step.retry.max > 10) {
|
|
162
|
+
errors.push(`Step "${step.id}" retry.max exceeds limit of 10`);
|
|
163
|
+
}
|
|
164
|
+
if (step.retry.max !== undefined && step.retry.max < 1) {
|
|
165
|
+
errors.push(`Step "${step.id}" retry.max must be at least 1`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// System step validation
|
|
170
|
+
if (step.role === 'system') {
|
|
171
|
+
if (!step.commands && !step.delegatesTo && !step.gate) {
|
|
172
|
+
errors.push(`System step "${step.id}" must have commands, delegates-to, or gate`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Validate goto targets exist
|
|
178
|
+
for (const step of workflow.steps) {
|
|
179
|
+
if (step.onFailure && step.onFailure.startsWith('goto:')) {
|
|
180
|
+
const target = step.onFailure.split(':')[1];
|
|
181
|
+
if (!ids.has(target)) {
|
|
182
|
+
errors.push(`Step "${step.id}" on-failure goto target "${target}" does not exist`);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return errors;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Resolves the execution order of steps considering conditions,
|
|
192
|
+
* dependencies (requires/produces), and parallel groups.
|
|
193
|
+
* Returns a flat execution plan with groupings.
|
|
194
|
+
*
|
|
195
|
+
* @param {object} workflow - Validated workflow
|
|
196
|
+
* @returns {{ groups: Array<{ steps: object[], parallel: boolean }> }} Ordered execution plan
|
|
197
|
+
*/
|
|
198
|
+
export function resolveExecutionPlan(workflow) {
|
|
199
|
+
const groups = [];
|
|
200
|
+
let i = 0;
|
|
201
|
+
const steps = workflow.steps;
|
|
202
|
+
|
|
203
|
+
while (i < steps.length) {
|
|
204
|
+
const step = steps[i];
|
|
205
|
+
|
|
206
|
+
// Check if this step has parallel peers
|
|
207
|
+
if (step.parallel && step.parallel.length > 0) {
|
|
208
|
+
const parallelIds = new Set([step.id, ...step.parallel]);
|
|
209
|
+
const parallelSteps = [];
|
|
210
|
+
|
|
211
|
+
// Collect all steps in this parallel group
|
|
212
|
+
while (i < steps.length && parallelIds.has(steps[i].id)) {
|
|
213
|
+
parallelSteps.push(steps[i]);
|
|
214
|
+
i++;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
groups.push({ steps: parallelSteps, parallel: true });
|
|
218
|
+
} else {
|
|
219
|
+
groups.push({ steps: [step], parallel: false });
|
|
220
|
+
i++;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return { groups };
|
|
225
|
+
}
|