speccrew 0.6.69 → 0.7.1
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/.speccrew/agents/speccrew-task-worker.md +1 -1
- package/.speccrew/agents/speccrew-team-leader.md +336 -189
- package/.speccrew/skills/speccrew-agentflow-manager/SKILL.md +161 -0
- package/.speccrew/skills/speccrew-agentflow-manager/workflow.agentflow.xml +347 -0
- package/.speccrew/skills/speccrew-deploy-build/SKILL.md +3 -56
- package/.speccrew/skills/speccrew-deploy-build/workflow.agentflow.xml +125 -0
- package/.speccrew/skills/speccrew-deploy-migrate/SKILL.md +3 -64
- package/.speccrew/skills/speccrew-deploy-migrate/workflow.agentflow.xml +135 -0
- package/.speccrew/skills/speccrew-deploy-smoke-test/SKILL.md +4 -156
- package/.speccrew/skills/speccrew-deploy-smoke-test/workflow.agentflow.xml +178 -0
- package/.speccrew/skills/speccrew-deploy-startup/SKILL.md +3 -135
- package/.speccrew/skills/speccrew-deploy-startup/workflow.agentflow.xml +223 -0
- package/.speccrew/skills/speccrew-dev-backend/SKILL.md +10 -2
- package/.speccrew/skills/speccrew-dev-backend/workflow.agentflow.xml +254 -0
- package/.speccrew/skills/speccrew-dev-desktop-electron/SKILL.md +10 -2
- package/.speccrew/skills/speccrew-dev-desktop-electron/workflow.agentflow.xml +259 -0
- package/.speccrew/skills/speccrew-dev-desktop-tauri/SKILL.md +10 -2
- package/.speccrew/skills/speccrew-dev-desktop-tauri/workflow.agentflow.xml +245 -0
- package/.speccrew/skills/speccrew-dev-frontend/SKILL.md +10 -2
- package/.speccrew/skills/speccrew-dev-frontend/workflow.agentflow.xml +262 -0
- package/.speccrew/skills/speccrew-dev-mobile/SKILL.md +10 -2
- package/.speccrew/skills/speccrew-dev-mobile/workflow.agentflow.xml +244 -0
- package/.speccrew/skills/speccrew-dev-review-backend/SKILL.md +10 -2
- package/.speccrew/skills/speccrew-dev-review-backend/workflow.agentflow.xml +251 -0
- package/.speccrew/skills/speccrew-dev-review-desktop/SKILL.md +10 -2
- package/.speccrew/skills/speccrew-dev-review-desktop/workflow.agentflow.xml +214 -0
- package/.speccrew/skills/speccrew-dev-review-frontend/SKILL.md +10 -2
- package/.speccrew/skills/speccrew-dev-review-frontend/workflow.agentflow.xml +213 -0
- package/.speccrew/skills/speccrew-dev-review-mobile/SKILL.md +10 -2
- package/.speccrew/skills/speccrew-dev-review-mobile/workflow.agentflow.xml +214 -0
- package/.speccrew/skills/speccrew-fd-api-contract/SKILL.md +7 -1
- package/.speccrew/skills/speccrew-fd-api-contract/workflow.agentflow.xml +222 -0
- package/.speccrew/skills/speccrew-fd-feature-analyze/SKILL.md +7 -1
- package/.speccrew/skills/speccrew-fd-feature-analyze/workflow.agentflow.xml +223 -0
- package/.speccrew/skills/speccrew-fd-feature-design/SKILL.md +7 -1
- package/.speccrew/skills/speccrew-fd-feature-design/workflow.agentflow.xml +322 -0
- package/.speccrew/skills/speccrew-get-timestamp/SKILL.md +3 -39
- package/.speccrew/skills/speccrew-get-timestamp/workflow.agentflow.xml +43 -0
- package/.speccrew/skills/speccrew-knowledge-bizs-api-analyze/SKILL.md +57 -508
- package/.speccrew/skills/{speccrew-knowledge-bizs-api-analyze-xml/SKILL.md → speccrew-knowledge-bizs-api-analyze/workflow.agentflow.xml} +1 -154
- package/.speccrew/skills/speccrew-knowledge-bizs-api-graph/SKILL.md +73 -283
- package/.speccrew/skills/{speccrew-knowledge-bizs-api-graph-xml/SKILL.md → speccrew-knowledge-bizs-api-graph/workflow.agentflow.xml} +0 -298
- package/.speccrew/skills/speccrew-knowledge-bizs-dispatch/SKILL.md +931 -801
- package/.speccrew/skills/{speccrew-knowledge-bizs-dispatch-xml/SKILL.md → speccrew-knowledge-bizs-dispatch/workflow.agentflow.xml} +42 -272
- package/.speccrew/skills/speccrew-knowledge-bizs-identify-entries/SKILL.md +263 -71
- package/.speccrew/skills/{speccrew-knowledge-bizs-identify-entries-xml/SKILL.md → speccrew-knowledge-bizs-identify-entries/workflow.agentflow.xml} +8 -184
- package/.speccrew/skills/speccrew-knowledge-bizs-init-features/SKILL.md +200 -181
- package/.speccrew/skills/{speccrew-knowledge-bizs-init-features-xml/SKILL.md → speccrew-knowledge-bizs-init-features/workflow.agentflow.xml} +7 -134
- package/.speccrew/skills/speccrew-knowledge-bizs-module-classify/SKILL.md +5 -89
- package/.speccrew/skills/speccrew-knowledge-bizs-module-classify/workflow.agentflow.xml +129 -0
- package/.speccrew/skills/speccrew-knowledge-bizs-ui-analyze/SKILL.md +454 -326
- package/.speccrew/skills/{speccrew-knowledge-bizs-ui-analyze-xml/SKILL.md → speccrew-knowledge-bizs-ui-analyze/workflow.agentflow.xml} +8 -128
- package/.speccrew/skills/speccrew-knowledge-bizs-ui-graph/SKILL.md +302 -247
- package/.speccrew/skills/{speccrew-knowledge-bizs-ui-graph-xml/SKILL.md → speccrew-knowledge-bizs-ui-graph/workflow.agentflow.xml} +7 -199
- package/.speccrew/skills/speccrew-knowledge-bizs-ui-style-extract/SKILL.md +267 -156
- package/.speccrew/skills/{speccrew-knowledge-bizs-ui-style-extract-xml/SKILL.md → speccrew-knowledge-bizs-ui-style-extract/workflow.agentflow.xml} +7 -151
- package/.speccrew/skills/speccrew-knowledge-graph-query/SKILL.md +3 -122
- package/.speccrew/skills/speccrew-knowledge-graph-query/workflow.agentflow.xml +106 -0
- package/.speccrew/skills/speccrew-knowledge-graph-write/SKILL.md +3 -80
- package/.speccrew/skills/speccrew-knowledge-graph-write/workflow.agentflow.xml +152 -0
- package/.speccrew/skills/speccrew-knowledge-module-summarize/SKILL.md +371 -265
- package/.speccrew/skills/{speccrew-knowledge-module-summarize-xml/SKILL.md → speccrew-knowledge-module-summarize/workflow.agentflow.xml} +7 -197
- package/.speccrew/skills/speccrew-knowledge-system-summarize/SKILL.md +45 -333
- package/.speccrew/skills/{speccrew-knowledge-system-summarize-xml/SKILL.md → speccrew-knowledge-system-summarize/workflow.agentflow.xml} +0 -177
- package/.speccrew/skills/speccrew-knowledge-techs-dispatch/SKILL.md +174 -727
- package/.speccrew/skills/{speccrew-knowledge-techs-dispatch-xml/SKILL.md → speccrew-knowledge-techs-dispatch/workflow.agentflow.xml} +10 -351
- package/.speccrew/skills/speccrew-knowledge-techs-generate/SKILL.md +20 -150
- package/.speccrew/skills/{speccrew-knowledge-techs-generate-xml/SKILL.md → speccrew-knowledge-techs-generate/workflow.agentflow.xml} +0 -169
- package/.speccrew/skills/speccrew-knowledge-techs-generate-conventions/SKILL.md +75 -587
- package/.speccrew/skills/{speccrew-knowledge-techs-generate-conventions-xml/SKILL.md → speccrew-knowledge-techs-generate-conventions/workflow.agentflow.xml} +0 -153
- package/.speccrew/skills/speccrew-knowledge-techs-generate-quality/SKILL.md +463 -297
- package/.speccrew/skills/{speccrew-knowledge-techs-generate-quality-xml/SKILL.md → speccrew-knowledge-techs-generate-quality/workflow.agentflow.xml} +0 -164
- package/.speccrew/skills/speccrew-knowledge-techs-generate-ui-style/SKILL.md +57 -292
- package/.speccrew/skills/{speccrew-knowledge-techs-generate-ui-style-xml/SKILL.md → speccrew-knowledge-techs-generate-ui-style/workflow.agentflow.xml} +2 -193
- package/.speccrew/skills/speccrew-knowledge-techs-index/SKILL.md +49 -335
- package/.speccrew/skills/{speccrew-knowledge-techs-index-xml/SKILL.md → speccrew-knowledge-techs-index/workflow.agentflow.xml} +0 -167
- package/.speccrew/skills/speccrew-knowledge-techs-init/SKILL.md +28 -109
- package/.speccrew/skills/{speccrew-knowledge-techs-init-xml/SKILL.md → speccrew-knowledge-techs-init/workflow.agentflow.xml} +0 -189
- package/.speccrew/skills/speccrew-knowledge-techs-ui-analyze/SKILL.md +3 -487
- package/.speccrew/skills/speccrew-knowledge-techs-ui-analyze/workflow.agentflow.xml +278 -0
- package/.speccrew/skills/speccrew-pm-knowledge-detector/SKILL.md +3 -71
- package/.speccrew/skills/speccrew-pm-knowledge-detector/workflow.agentflow.xml +108 -0
- package/.speccrew/skills/speccrew-pm-module-initializer/SKILL.md +3 -107
- package/.speccrew/skills/speccrew-pm-module-initializer/workflow.agentflow.xml +139 -0
- package/.speccrew/skills/speccrew-pm-module-matcher/SKILL.md +3 -115
- package/.speccrew/skills/speccrew-pm-module-matcher/workflow.agentflow.xml +146 -0
- package/.speccrew/skills/speccrew-pm-requirement-analysis/SKILL.md +3 -343
- package/.speccrew/skills/speccrew-pm-requirement-analysis/workflow.agentflow.xml +174 -0
- package/.speccrew/skills/speccrew-pm-requirement-assess/SKILL.md +3 -91
- package/.speccrew/skills/speccrew-pm-requirement-assess/workflow.agentflow.xml +173 -0
- package/.speccrew/skills/speccrew-pm-requirement-clarify/SKILL.md +3 -224
- package/.speccrew/skills/speccrew-pm-requirement-clarify/workflow.agentflow.xml +159 -0
- package/.speccrew/skills/speccrew-pm-requirement-model/SKILL.md +3 -275
- package/.speccrew/skills/speccrew-pm-requirement-model/workflow.agentflow.xml +210 -0
- package/.speccrew/skills/speccrew-pm-requirement-simple/SKILL.md +3 -76
- package/.speccrew/skills/speccrew-pm-requirement-simple/workflow.agentflow.xml +120 -0
- package/.speccrew/skills/speccrew-pm-sub-prd-generate/SKILL.md +7 -1
- package/.speccrew/skills/speccrew-pm-sub-prd-generate/workflow.agentflow.xml +218 -0
- package/.speccrew/skills/speccrew-sd-backend/SKILL.md +7 -1
- package/.speccrew/skills/speccrew-sd-backend/workflow.agentflow.xml +264 -0
- package/.speccrew/skills/speccrew-sd-desktop/SKILL.md +7 -1
- package/.speccrew/skills/speccrew-sd-desktop/workflow.agentflow.xml +288 -0
- package/.speccrew/skills/speccrew-sd-framework-evaluate/SKILL.md +7 -1
- package/.speccrew/skills/speccrew-sd-framework-evaluate/workflow.agentflow.xml +235 -0
- package/.speccrew/skills/speccrew-sd-frontend/SKILL.md +7 -1
- package/.speccrew/skills/speccrew-sd-frontend/workflow.agentflow.xml +299 -0
- package/.speccrew/skills/speccrew-sd-mobile/SKILL.md +7 -1
- package/.speccrew/skills/speccrew-sd-mobile/workflow.agentflow.xml +301 -0
- package/.speccrew/skills/speccrew-test-case-design/SKILL.md +165 -284
- package/.speccrew/skills/speccrew-test-case-design/workflow.agentflow.xml +210 -0
- package/.speccrew/skills/speccrew-test-code-gen/SKILL.md +204 -324
- package/.speccrew/skills/speccrew-test-code-gen/workflow.agentflow.xml +265 -0
- package/.speccrew/skills/speccrew-test-reporter/SKILL.md +205 -184
- package/.speccrew/skills/speccrew-test-reporter/workflow.agentflow.xml +284 -0
- package/.speccrew/skills/speccrew-test-runner/SKILL.md +242 -241
- package/.speccrew/skills/speccrew-test-runner/workflow.agentflow.xml +314 -0
- package/bin/cli.js +8 -1
- package/lib/commands/init.js +11 -3
- package/lib/commands/update.js +11 -3
- package/lib/commands/validate.js +565 -0
- package/lib/utils.js +43 -0
- package/package.json +1 -1
- package/workspace-template/docs/rules/{xml-workflow-spec.md → agentflow-spec.md} +5 -5
- package/workspace-template/scripts/validate-agentflow.js +637 -0
- package/.speccrew/agents/speccrew-team-leader-xml.md +0 -480
- package/.speccrew/skills/speccrew-knowledge-bizs-dispatch/STATUS-FORMATS.md +0 -99
- package/.speccrew/skills/speccrew-knowledge-bizs-dispatch/scripts/batch-orchestrator.js +0 -176
- package/.speccrew/skills/speccrew-knowledge-bizs-dispatch/scripts/get-next-batch.js +0 -150
- package/.speccrew/skills/speccrew-knowledge-bizs-dispatch/scripts/get-pending-features.js +0 -106
- package/.speccrew/skills/speccrew-knowledge-bizs-dispatch/scripts/mark-stale.js +0 -249
- package/.speccrew/skills/speccrew-knowledge-bizs-dispatch/scripts/merge-features.js +0 -300
- package/.speccrew/skills/speccrew-knowledge-bizs-dispatch/scripts/process-batch-results.js +0 -915
- package/.speccrew/skills/speccrew-knowledge-bizs-dispatch/scripts/update-feature-status.js +0 -226
- package/.speccrew/skills/speccrew-knowledge-bizs-init-features/examples/features.json +0 -34
- package/.speccrew/skills/speccrew-knowledge-bizs-init-features/scripts/generate-inventory.js +0 -1087
- package/.speccrew/skills/speccrew-knowledge-bizs-init-features/scripts/test-inventory.js +0 -26
- package/.speccrew/skills/speccrew-knowledge-techs-dispatch/STATUS-FORMATS.md +0 -550
|
@@ -0,0 +1,565 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { getWorkspaceTemplatePath } = require('../utils');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Validate command - Validates AgentFlow XML files
|
|
7
|
+
* Usage: speccrew validate [path] [--format json|text] [--strict]
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// Valid block types per specification
|
|
11
|
+
const VALID_BLOCK_TYPES = [
|
|
12
|
+
'input',
|
|
13
|
+
'output',
|
|
14
|
+
'task',
|
|
15
|
+
'gateway',
|
|
16
|
+
'loop',
|
|
17
|
+
'event',
|
|
18
|
+
'error-handler',
|
|
19
|
+
'checkpoint',
|
|
20
|
+
'rule'
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
// Built-in variables that don't need prior definition
|
|
24
|
+
const BUILTIN_VARIABLES = [
|
|
25
|
+
'workspace',
|
|
26
|
+
'platform',
|
|
27
|
+
'timestamp',
|
|
28
|
+
'workflow.id',
|
|
29
|
+
'workflow.status'
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Get line number for a position in content
|
|
34
|
+
* @param {string} content - File content
|
|
35
|
+
* @param {number} position - Character position
|
|
36
|
+
* @returns {number} Line number (1-based)
|
|
37
|
+
*/
|
|
38
|
+
function getLineNumber(content, position) {
|
|
39
|
+
const lines = content.substring(0, position).split('\n');
|
|
40
|
+
return lines.length;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Extract all block elements from XML content
|
|
45
|
+
* @param {string} content - XML content
|
|
46
|
+
* @returns {Array} Array of block objects with metadata
|
|
47
|
+
*/
|
|
48
|
+
function extractBlocks(content) {
|
|
49
|
+
const blocks = [];
|
|
50
|
+
const blockRegex = /<block\s+([^>]+)>/g;
|
|
51
|
+
let match;
|
|
52
|
+
|
|
53
|
+
while ((match = blockRegex.exec(content)) !== null) {
|
|
54
|
+
const attrsString = match[1];
|
|
55
|
+
const startPos = match.index;
|
|
56
|
+
const line = getLineNumber(content, startPos);
|
|
57
|
+
|
|
58
|
+
// Parse attributes
|
|
59
|
+
const attrs = {};
|
|
60
|
+
const attrRegex = /(\w+)=["']([^"']*)["']/g;
|
|
61
|
+
let attrMatch;
|
|
62
|
+
while ((attrMatch = attrRegex.exec(attrsString)) !== null) {
|
|
63
|
+
attrs[attrMatch[1]] = attrMatch[2];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
blocks.push({
|
|
67
|
+
line,
|
|
68
|
+
position: startPos,
|
|
69
|
+
attributes: attrs,
|
|
70
|
+
raw: match[0]
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return blocks;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Extract branch elements from XML content
|
|
79
|
+
* @param {string} content - XML content
|
|
80
|
+
* @returns {Array} Array of branch objects
|
|
81
|
+
*/
|
|
82
|
+
function extractBranches(content) {
|
|
83
|
+
const branches = [];
|
|
84
|
+
const branchRegex = /<branch\s+([^>]*)>/g;
|
|
85
|
+
let match;
|
|
86
|
+
|
|
87
|
+
while ((match = branchRegex.exec(content)) !== null) {
|
|
88
|
+
const attrsString = match[1];
|
|
89
|
+
const line = getLineNumber(content, match.index);
|
|
90
|
+
|
|
91
|
+
const attrs = {};
|
|
92
|
+
const attrRegex = /(\w+)=["']([^"']*)["']/g;
|
|
93
|
+
let attrMatch;
|
|
94
|
+
while ((attrMatch = attrRegex.exec(attrsString)) !== null) {
|
|
95
|
+
attrs[attrMatch[1]] = attrMatch[2];
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
branches.push({
|
|
99
|
+
line,
|
|
100
|
+
attributes: attrs,
|
|
101
|
+
raw: match[0]
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return branches;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Check if XML has proper structure
|
|
110
|
+
* @param {string} content - XML content
|
|
111
|
+
* @returns {Array} Array of error objects
|
|
112
|
+
*/
|
|
113
|
+
function validateXmlStructure(content) {
|
|
114
|
+
const errors = [];
|
|
115
|
+
|
|
116
|
+
// Check for workflow root element
|
|
117
|
+
const workflowMatch = content.match(/<workflow\s+[^>]*>/);
|
|
118
|
+
if (!workflowMatch) {
|
|
119
|
+
errors.push({
|
|
120
|
+
line: 1,
|
|
121
|
+
rule: 'root-element',
|
|
122
|
+
message: 'Missing root element <workflow>'
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Check for unclosed tags (basic check)
|
|
127
|
+
const openTags = content.match(/<block\s+[^>]*>/g) || [];
|
|
128
|
+
const closeTags = content.match(/<\/block>/g) || [];
|
|
129
|
+
const selfClosingBlocks = content.match(/<block\s+[^>]*\/>/g) || [];
|
|
130
|
+
const nonSelfClosingBlocks = openTags.length - selfClosingBlocks.length;
|
|
131
|
+
|
|
132
|
+
if (nonSelfClosingBlocks !== closeTags.length) {
|
|
133
|
+
errors.push({
|
|
134
|
+
line: 1,
|
|
135
|
+
rule: 'unclosed-tags',
|
|
136
|
+
message: `Block tag mismatch: ${openTags.length} opening, ${closeTags.length} closing tags`
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Check for unquoted attributes
|
|
141
|
+
const unquotedAttrRegex = /<block\s+[^>]*\w+=[^"'][^>]*>/g;
|
|
142
|
+
let match;
|
|
143
|
+
while ((match = unquotedAttrRegex.exec(content)) !== null) {
|
|
144
|
+
const line = getLineNumber(content, match.index);
|
|
145
|
+
errors.push({
|
|
146
|
+
line,
|
|
147
|
+
rule: 'unquoted-attribute',
|
|
148
|
+
message: 'Attribute values must be quoted'
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return errors;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Validate block types
|
|
157
|
+
* @param {Array} blocks - Extracted blocks
|
|
158
|
+
* @returns {Array} Array of error objects
|
|
159
|
+
*/
|
|
160
|
+
function validateBlockTypes(blocks) {
|
|
161
|
+
const errors = [];
|
|
162
|
+
|
|
163
|
+
for (const block of blocks) {
|
|
164
|
+
const type = block.attributes.type;
|
|
165
|
+
if (!type) {
|
|
166
|
+
errors.push({
|
|
167
|
+
line: block.line,
|
|
168
|
+
rule: 'missing-type',
|
|
169
|
+
message: 'Block is missing required "type" attribute'
|
|
170
|
+
});
|
|
171
|
+
} else if (!VALID_BLOCK_TYPES.includes(type)) {
|
|
172
|
+
errors.push({
|
|
173
|
+
line: block.line,
|
|
174
|
+
rule: 'invalid-type',
|
|
175
|
+
message: `Invalid block type "${type}". Valid types: ${VALID_BLOCK_TYPES.join(', ')}`
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return errors;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Validate block ID uniqueness
|
|
185
|
+
* @param {Array} blocks - Extracted blocks
|
|
186
|
+
* @returns {Array} Array of error objects
|
|
187
|
+
*/
|
|
188
|
+
function validateUniqueIds(blocks) {
|
|
189
|
+
const errors = [];
|
|
190
|
+
const idMap = new Map();
|
|
191
|
+
|
|
192
|
+
for (const block of blocks) {
|
|
193
|
+
const id = block.attributes.id;
|
|
194
|
+
if (id) {
|
|
195
|
+
if (idMap.has(id)) {
|
|
196
|
+
errors.push({
|
|
197
|
+
line: block.line,
|
|
198
|
+
rule: 'unique-id',
|
|
199
|
+
message: `Duplicate block id "${id}" (first defined at line ${idMap.get(id)})`
|
|
200
|
+
});
|
|
201
|
+
} else {
|
|
202
|
+
idMap.set(id, block.line);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return errors;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Validate required attributes for each block type
|
|
212
|
+
* @param {Array} blocks - Extracted blocks
|
|
213
|
+
* @returns {Array} Array of error objects
|
|
214
|
+
*/
|
|
215
|
+
function validateRequiredAttributes(blocks) {
|
|
216
|
+
const errors = [];
|
|
217
|
+
|
|
218
|
+
for (const block of blocks) {
|
|
219
|
+
const type = block.attributes.type;
|
|
220
|
+
const id = block.attributes.id;
|
|
221
|
+
|
|
222
|
+
// All blocks must have id
|
|
223
|
+
if (!id) {
|
|
224
|
+
errors.push({
|
|
225
|
+
line: block.line,
|
|
226
|
+
rule: 'missing-id',
|
|
227
|
+
message: 'Block is missing required "id" attribute'
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Type-specific required attributes
|
|
232
|
+
if (type === 'task') {
|
|
233
|
+
if (!block.attributes.action) {
|
|
234
|
+
errors.push({
|
|
235
|
+
line: block.line,
|
|
236
|
+
rule: 'missing-action',
|
|
237
|
+
message: 'Task block is missing required "action" attribute'
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
if (!block.attributes.desc) {
|
|
241
|
+
errors.push({
|
|
242
|
+
line: block.line,
|
|
243
|
+
rule: 'missing-desc',
|
|
244
|
+
message: 'Task block is missing required "desc" attribute'
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (type === 'input' || type === 'output') {
|
|
250
|
+
if (!block.attributes.desc) {
|
|
251
|
+
errors.push({
|
|
252
|
+
line: block.line,
|
|
253
|
+
rule: 'missing-desc',
|
|
254
|
+
message: `${type} block is missing required "desc" attribute`
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return errors;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Validate next references
|
|
265
|
+
* @param {string} content - XML content
|
|
266
|
+
* @param {Array} blocks - Extracted blocks
|
|
267
|
+
* @returns {Array} Array of error objects
|
|
268
|
+
*/
|
|
269
|
+
function validateNextReferences(content, blocks) {
|
|
270
|
+
const errors = [];
|
|
271
|
+
const validIds = new Set(blocks.map(b => b.attributes.id).filter(Boolean));
|
|
272
|
+
|
|
273
|
+
// Check field name="next" references
|
|
274
|
+
const fieldRegex = /<field\s+name=["']next["'][^>]*>([^<]*)<\/field>/g;
|
|
275
|
+
let match;
|
|
276
|
+
|
|
277
|
+
while ((match = fieldRegex.exec(content)) !== null) {
|
|
278
|
+
const nextId = match[1].trim();
|
|
279
|
+
const line = getLineNumber(content, match.index);
|
|
280
|
+
|
|
281
|
+
if (nextId && !validIds.has(nextId)) {
|
|
282
|
+
errors.push({
|
|
283
|
+
line,
|
|
284
|
+
rule: 'invalid-next-ref',
|
|
285
|
+
message: `Reference to undefined block id "${nextId}"`
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return errors;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Validate variable references
|
|
295
|
+
* @param {string} content - XML content
|
|
296
|
+
* @param {Array} blocks - Extracted blocks
|
|
297
|
+
* @returns {Array} Array of warning objects
|
|
298
|
+
*/
|
|
299
|
+
function validateVariableRefs(content, blocks) {
|
|
300
|
+
const warnings = [];
|
|
301
|
+
const definedVars = new Set(BUILTIN_VARIABLES);
|
|
302
|
+
|
|
303
|
+
// Collect output variables from blocks
|
|
304
|
+
for (const block of blocks) {
|
|
305
|
+
const blockEndPos = content.indexOf('</block>', block.position);
|
|
306
|
+
const blockContent = content.substring(block.position, blockEndPos > 0 ? blockEndPos : block.position + 500);
|
|
307
|
+
|
|
308
|
+
// Check for output var attribute
|
|
309
|
+
const outputMatch = blockContent.match(/<field[^>]*\s+var=["']([^"']+)["']/);
|
|
310
|
+
if (outputMatch) {
|
|
311
|
+
definedVars.add(outputMatch[1]);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Check for output field in task blocks
|
|
315
|
+
const outputFieldMatch = blockContent.match(/<field\s+name=["']output["'][^>]*\s+var=["']([^"']+)["']/);
|
|
316
|
+
if (outputFieldMatch) {
|
|
317
|
+
definedVars.add(outputFieldMatch[1]);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Loop variables
|
|
321
|
+
if (block.attributes.type === 'loop') {
|
|
322
|
+
const asAttr = block.attributes.as;
|
|
323
|
+
if (asAttr) {
|
|
324
|
+
definedVars.add(asAttr);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Check all variable references
|
|
330
|
+
const varRegex = /\$\{([^}]+)\}/g;
|
|
331
|
+
let match;
|
|
332
|
+
|
|
333
|
+
while ((match = varRegex.exec(content)) !== null) {
|
|
334
|
+
const fullVar = match[1];
|
|
335
|
+
const baseVar = fullVar.split('.')[0].split('[')[0];
|
|
336
|
+
const line = getLineNumber(content, match.index);
|
|
337
|
+
|
|
338
|
+
if (!definedVars.has(baseVar) && !BUILTIN_VARIABLES.includes(baseVar)) {
|
|
339
|
+
const parentVar = fullVar.split('.')[0];
|
|
340
|
+
if (!definedVars.has(parentVar)) {
|
|
341
|
+
warnings.push({
|
|
342
|
+
line,
|
|
343
|
+
rule: 'var-ref',
|
|
344
|
+
message: `Variable "${baseVar}" may not be defined before use`
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
return warnings;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Validate a single AgentFlow XML file
|
|
355
|
+
* @param {string} filePath - Path to the XML file
|
|
356
|
+
* @returns {Object} Validation result
|
|
357
|
+
*/
|
|
358
|
+
function validateFile(filePath) {
|
|
359
|
+
const result = {
|
|
360
|
+
file: filePath,
|
|
361
|
+
errors: [],
|
|
362
|
+
warnings: [],
|
|
363
|
+
summary: {
|
|
364
|
+
blocks: 0,
|
|
365
|
+
errors: 0,
|
|
366
|
+
warnings: 0
|
|
367
|
+
}
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
try {
|
|
371
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
372
|
+
|
|
373
|
+
// Extract all blocks
|
|
374
|
+
const blocks = extractBlocks(content);
|
|
375
|
+
result.summary.blocks = blocks.length;
|
|
376
|
+
|
|
377
|
+
// Run all validations
|
|
378
|
+
result.errors.push(...validateXmlStructure(content));
|
|
379
|
+
result.errors.push(...validateBlockTypes(blocks));
|
|
380
|
+
result.errors.push(...validateUniqueIds(blocks));
|
|
381
|
+
result.errors.push(...validateRequiredAttributes(blocks));
|
|
382
|
+
result.errors.push(...validateNextReferences(content, blocks));
|
|
383
|
+
result.warnings.push(...validateVariableRefs(content, blocks));
|
|
384
|
+
|
|
385
|
+
// Update summary
|
|
386
|
+
result.summary.errors = result.errors.length;
|
|
387
|
+
result.summary.warnings = result.warnings.length;
|
|
388
|
+
|
|
389
|
+
} catch (error) {
|
|
390
|
+
result.errors.push({
|
|
391
|
+
line: 0,
|
|
392
|
+
rule: 'file-error',
|
|
393
|
+
message: `Failed to read file: ${error.message}`
|
|
394
|
+
});
|
|
395
|
+
result.summary.errors = 1;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
return result;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Find all .agentflow.xml files in a directory recursively
|
|
403
|
+
* @param {string} dir - Directory to search
|
|
404
|
+
* @returns {Array} Array of file paths
|
|
405
|
+
*/
|
|
406
|
+
function findAgentFlowFiles(dir) {
|
|
407
|
+
const files = [];
|
|
408
|
+
|
|
409
|
+
function scan(directory) {
|
|
410
|
+
if (!fs.existsSync(directory)) return;
|
|
411
|
+
|
|
412
|
+
const entries = fs.readdirSync(directory, { withFileTypes: true });
|
|
413
|
+
for (const entry of entries) {
|
|
414
|
+
const fullPath = path.join(directory, entry.name);
|
|
415
|
+
if (entry.isDirectory()) {
|
|
416
|
+
scan(fullPath);
|
|
417
|
+
} else if (entry.name.endsWith('.agentflow.xml')) {
|
|
418
|
+
files.push(fullPath);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
scan(dir);
|
|
424
|
+
return files;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Parse command arguments
|
|
429
|
+
* @param {Array} args - Command arguments
|
|
430
|
+
* @returns {Object} Parsed options
|
|
431
|
+
*/
|
|
432
|
+
function parseOptions(args) {
|
|
433
|
+
const options = {
|
|
434
|
+
format: 'text',
|
|
435
|
+
strict: false
|
|
436
|
+
};
|
|
437
|
+
|
|
438
|
+
for (let i = 0; i < args.length; i++) {
|
|
439
|
+
const arg = args[i];
|
|
440
|
+
if (arg === '--format' && i + 1 < args.length) {
|
|
441
|
+
options.format = args[i + 1];
|
|
442
|
+
i++;
|
|
443
|
+
} else if (arg === '--strict') {
|
|
444
|
+
options.strict = true;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
return options;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Format output as text
|
|
453
|
+
* @param {Array} results - Validation results
|
|
454
|
+
* @param {boolean} strict - Treat warnings as errors
|
|
455
|
+
* @returns {string} Formatted text output
|
|
456
|
+
*/
|
|
457
|
+
function formatTextOutput(results, strict) {
|
|
458
|
+
const lines = [];
|
|
459
|
+
let totalErrors = 0;
|
|
460
|
+
let totalWarnings = 0;
|
|
461
|
+
let totalBlocks = 0;
|
|
462
|
+
|
|
463
|
+
for (const result of results) {
|
|
464
|
+
lines.push('');
|
|
465
|
+
lines.push(result.file);
|
|
466
|
+
|
|
467
|
+
if (result.errors.length === 0 && result.warnings.length === 0) {
|
|
468
|
+
lines.push(' PASS No issues found');
|
|
469
|
+
} else {
|
|
470
|
+
for (const error of result.errors) {
|
|
471
|
+
lines.push(` ERROR Line ${error.line}: [${error.rule}] ${error.message}`);
|
|
472
|
+
}
|
|
473
|
+
for (const warning of result.warnings) {
|
|
474
|
+
const label = strict ? 'ERROR' : 'WARN';
|
|
475
|
+
lines.push(` ${label} Line ${warning.line}: [${warning.rule}] ${warning.message}`);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
totalErrors += result.summary.errors;
|
|
480
|
+
totalWarnings += result.summary.warnings;
|
|
481
|
+
totalBlocks += result.summary.blocks;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
lines.push('');
|
|
485
|
+
lines.push('─'.repeat(50));
|
|
486
|
+
lines.push(`Summary: ${results.length} file(s), ${totalBlocks} block(s), ${totalErrors} error(s), ${totalWarnings} warning(s)`);
|
|
487
|
+
|
|
488
|
+
if (strict) {
|
|
489
|
+
const totalIssues = totalErrors + totalWarnings;
|
|
490
|
+
lines.push(`Strict mode: ${totalIssues} total issue(s)`);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
return lines.join('\n');
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* Main run function
|
|
498
|
+
* @param {string} projectRoot - Project root directory
|
|
499
|
+
* @param {Array} args - Command arguments
|
|
500
|
+
*/
|
|
501
|
+
function run(projectRoot, args) {
|
|
502
|
+
console.log('SpecCrew AgentFlow Validator\n');
|
|
503
|
+
|
|
504
|
+
const options = parseOptions(args);
|
|
505
|
+
|
|
506
|
+
// Determine target path
|
|
507
|
+
let targetPath = null;
|
|
508
|
+
for (const arg of args) {
|
|
509
|
+
if (!arg.startsWith('--')) {
|
|
510
|
+
targetPath = arg;
|
|
511
|
+
break;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// Default to speccrew-workspace
|
|
516
|
+
if (!targetPath) {
|
|
517
|
+
targetPath = path.join(projectRoot, 'speccrew-workspace');
|
|
518
|
+
} else {
|
|
519
|
+
targetPath = path.resolve(projectRoot, targetPath);
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// Check if target exists
|
|
523
|
+
if (!fs.existsSync(targetPath)) {
|
|
524
|
+
console.error(`Error: Path not found: ${targetPath}`);
|
|
525
|
+
process.exit(1);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// Collect files to validate
|
|
529
|
+
let files = [];
|
|
530
|
+
const stats = fs.statSync(targetPath);
|
|
531
|
+
if (stats.isDirectory()) {
|
|
532
|
+
files = findAgentFlowFiles(targetPath);
|
|
533
|
+
} else if (targetPath.endsWith('.agentflow.xml')) {
|
|
534
|
+
files = [targetPath];
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
if (files.length === 0) {
|
|
538
|
+
console.log(`No .agentflow.xml files found in ${targetPath}`);
|
|
539
|
+
return true;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// Validate all files
|
|
543
|
+
const results = files.map(file => validateFile(file));
|
|
544
|
+
|
|
545
|
+
// Calculate totals
|
|
546
|
+
let totalErrors = 0;
|
|
547
|
+
let totalWarnings = 0;
|
|
548
|
+
for (const result of results) {
|
|
549
|
+
totalErrors += result.summary.errors;
|
|
550
|
+
totalWarnings += result.summary.warnings;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
// Output results
|
|
554
|
+
if (options.format === 'json') {
|
|
555
|
+
console.log(JSON.stringify(results, null, 2));
|
|
556
|
+
} else {
|
|
557
|
+
console.log(formatTextOutput(results, options.strict));
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
// Return success/failure
|
|
561
|
+
const hasErrors = totalErrors > 0 || (options.strict && totalWarnings > 0);
|
|
562
|
+
return !hasErrors;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
module.exports = { run };
|
package/lib/utils.js
CHANGED
|
@@ -113,6 +113,29 @@ const DEPRECATED_SKILLS = [
|
|
|
113
113
|
'speccrew-dev-review', // replaced by speccrew-dev-review-backend/frontend/mobile/desktop
|
|
114
114
|
'speccrew-test-execute', // replaced by speccrew-test-runner + speccrew-test-reporter
|
|
115
115
|
'speccrew-fd-feature-registry-init', // replaced by speccrew-fd-feature-registry
|
|
116
|
+
// Legacy -xml skills (replaced by non-xml versions)
|
|
117
|
+
'speccrew-knowledge-bizs-api-analyze-xml',
|
|
118
|
+
'speccrew-knowledge-bizs-api-graph-xml',
|
|
119
|
+
'speccrew-knowledge-bizs-dispatch-xml',
|
|
120
|
+
'speccrew-knowledge-bizs-identify-entries-xml',
|
|
121
|
+
'speccrew-knowledge-bizs-init-features-xml',
|
|
122
|
+
'speccrew-knowledge-bizs-ui-analyze-xml',
|
|
123
|
+
'speccrew-knowledge-bizs-ui-graph-xml',
|
|
124
|
+
'speccrew-knowledge-bizs-ui-style-extract-xml',
|
|
125
|
+
'speccrew-knowledge-module-summarize-xml',
|
|
126
|
+
'speccrew-knowledge-system-summarize-xml',
|
|
127
|
+
'speccrew-knowledge-techs-dispatch-xml',
|
|
128
|
+
'speccrew-knowledge-techs-generate-conventions-xml',
|
|
129
|
+
'speccrew-knowledge-techs-generate-quality-xml',
|
|
130
|
+
'speccrew-knowledge-techs-generate-ui-style-xml',
|
|
131
|
+
'speccrew-knowledge-techs-generate-xml',
|
|
132
|
+
'speccrew-knowledge-techs-index-xml',
|
|
133
|
+
'speccrew-knowledge-techs-init-xml',
|
|
134
|
+
];
|
|
135
|
+
|
|
136
|
+
// Deprecated agents that should be auto-cleaned on init/update
|
|
137
|
+
const DEPRECATED_AGENTS = [
|
|
138
|
+
'speccrew-team-leader-xml.md', // replaced by speccrew-team-leader.md
|
|
116
139
|
];
|
|
117
140
|
|
|
118
141
|
function cleanDeprecatedSkills(destDir, deprecatedList) {
|
|
@@ -133,6 +156,24 @@ function cleanDeprecatedSkills(destDir, deprecatedList) {
|
|
|
133
156
|
return cleaned;
|
|
134
157
|
}
|
|
135
158
|
|
|
159
|
+
// Clean deprecated agent files from agents directory
|
|
160
|
+
function cleanDeprecatedAgents(destDir, deprecatedList) {
|
|
161
|
+
if (!fs.existsSync(destDir)) return 0;
|
|
162
|
+
let cleaned = 0;
|
|
163
|
+
try {
|
|
164
|
+
for (const fileName of deprecatedList) {
|
|
165
|
+
const fullPath = path.join(destDir, fileName);
|
|
166
|
+
if (fs.existsSync(fullPath)) {
|
|
167
|
+
fs.unlinkSync(fullPath);
|
|
168
|
+
cleaned++;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
} catch (error) {
|
|
172
|
+
// Silently ignore cleanup errors to not block main flow
|
|
173
|
+
}
|
|
174
|
+
return cleaned;
|
|
175
|
+
}
|
|
176
|
+
|
|
136
177
|
module.exports = {
|
|
137
178
|
copyDirRecursive,
|
|
138
179
|
isSpeccrewFile,
|
|
@@ -144,5 +185,7 @@ module.exports = {
|
|
|
144
185
|
ensureDirectories,
|
|
145
186
|
removeDirRecursive,
|
|
146
187
|
DEPRECATED_SKILLS,
|
|
188
|
+
DEPRECATED_AGENTS,
|
|
147
189
|
cleanDeprecatedSkills,
|
|
190
|
+
cleanDeprecatedAgents,
|
|
148
191
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
#
|
|
1
|
+
# AgentFlow Specification
|
|
2
2
|
|
|
3
|
-
> This document defines the XML Block workflow format used in SpecCrew skill and agent definitions.
|
|
3
|
+
> This document defines the AgentFlow XML Block workflow format used in SpecCrew skill and agent definitions.
|
|
4
4
|
> **MANDATORY**: Any agent or worker that encounters XML `<workflow>` blocks MUST read and understand this specification before execution.
|
|
5
5
|
|
|
6
6
|
## Execution Model
|
|
@@ -346,7 +346,7 @@ Dispatch skills (naming pattern: `*-dispatch-xml`) are **orchestration playbooks
|
|
|
346
346
|
When Team Leader loads a dispatch skill via `action="run-skill"`:
|
|
347
347
|
|
|
348
348
|
1. **Load**: Team Leader invokes the Skill tool with the dispatch skill name (e.g., `speccrew-knowledge-bizs-dispatch-xml`)
|
|
349
|
-
2. **Parse**: Read and understand the complete XML workflow in the loaded SKILL.md
|
|
349
|
+
2. **Parse**: Read and understand the complete AgentFlow XML workflow in the loaded SKILL.md
|
|
350
350
|
3. **Execute block-by-block**: Walk through each Stage's blocks in document order, mapping each `action` attribute to the correct IDE tool:
|
|
351
351
|
|
|
352
352
|
| Block Action | What Team Leader MUST Do |
|
|
@@ -484,7 +484,7 @@ Team Leader executes:
|
|
|
484
484
|
|
|
485
485
|
### Strict Block Adherence
|
|
486
486
|
|
|
487
|
-
When executing an XML workflow, the Agent MUST strictly follow the block sequence defined in the workflow:
|
|
487
|
+
When executing an AgentFlow XML workflow, the Agent MUST strictly follow the block sequence defined in the workflow:
|
|
488
488
|
|
|
489
489
|
1. **Only perform actions defined by blocks** — Every action the Agent takes must correspond to a specific block in the workflow. The Agent MUST NOT perform any actions not defined by the blocks.
|
|
490
490
|
2. **Prohibited extra actions** include but are not limited to:
|
|
@@ -509,5 +509,5 @@ Before executing each `<block>`, the agent MUST announce the block being execute
|
|
|
509
509
|
3. For `<block type="loop">` blocks, announce the loop entry with iteration context (e.g., "Loop [S2-L2] — Iterating over ${batch}, 5 items, parallel=true").
|
|
510
510
|
4. For `<block type="checkpoint">` blocks, announce the checkpoint and its validation result.
|
|
511
511
|
5. For `<block type="rule">` blocks, announce the rule being applied.
|
|
512
|
-
6. This protocol applies to ALL agents executing XML workflows — both orchestrating agents (Team Leader) and worker agents.
|
|
512
|
+
6. This protocol applies to ALL agents executing AgentFlow XML workflows — both orchestrating agents (Team Leader) and worker agents.
|
|
513
513
|
7. Nested blocks inside loops should also be announced for each iteration.
|