guild-agents 0.3.1 → 1.1.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.
Files changed (40) hide show
  1. package/README.md +5 -1
  2. package/bin/guild.js +75 -1
  3. package/package.json +12 -5
  4. package/src/commands/doctor.js +70 -1
  5. package/src/commands/logs.js +63 -0
  6. package/src/commands/reset-learnings.js +44 -0
  7. package/src/commands/run.js +105 -0
  8. package/src/templates/agents/advisor.md +1 -0
  9. package/src/templates/agents/bugfix.md +1 -0
  10. package/src/templates/agents/code-reviewer.md +1 -0
  11. package/src/templates/agents/db-migration.md +1 -0
  12. package/src/templates/agents/developer.md +1 -0
  13. package/src/templates/agents/learnings-extractor.md +49 -0
  14. package/src/templates/agents/platform-expert.md +1 -0
  15. package/src/templates/agents/product-owner.md +1 -0
  16. package/src/templates/agents/qa.md +1 -0
  17. package/src/templates/agents/tech-lead.md +1 -0
  18. package/src/templates/skills/build-feature/SKILL.md +130 -26
  19. package/src/templates/skills/council/SKILL.md +51 -4
  20. package/src/templates/skills/create-pr/SKILL.md +32 -0
  21. package/src/templates/skills/dev-flow/SKILL.md +14 -0
  22. package/src/templates/skills/guild-specialize/SKILL.md +45 -3
  23. package/src/templates/skills/new-feature/SKILL.md +33 -0
  24. package/src/templates/skills/qa-cycle/SKILL.md +48 -5
  25. package/src/templates/skills/review/SKILL.md +22 -1
  26. package/src/templates/skills/session-end/SKILL.md +27 -0
  27. package/src/templates/skills/session-start/SKILL.md +32 -0
  28. package/src/templates/skills/status/SKILL.md +19 -0
  29. package/src/utils/dispatch-protocol.js +74 -0
  30. package/src/utils/dispatch.js +172 -0
  31. package/src/utils/executor.js +183 -0
  32. package/src/utils/learnings-io.js +76 -0
  33. package/src/utils/learnings.js +204 -0
  34. package/src/utils/orchestrator-io.js +356 -0
  35. package/src/utils/orchestrator.js +590 -0
  36. package/src/utils/providers/claude-code.js +43 -0
  37. package/src/utils/skill-loader.js +83 -0
  38. package/src/utils/trace.js +400 -0
  39. package/src/utils/version.js +90 -0
  40. 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
+ }