@wrongstack/core 0.1.9 → 0.2.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/dist/agent-bridge-DmBiCipY.d.ts +33 -0
- package/dist/compactor-DSl2FK7a.d.ts +17 -0
- package/dist/config-DXrqb41m.d.ts +193 -0
- package/dist/{provider-txgB0Oq9.d.ts → context-u0bryklF.d.ts} +540 -472
- package/dist/coordination/index.d.ts +892 -0
- package/dist/coordination/index.js +2869 -0
- package/dist/coordination/index.js.map +1 -0
- package/dist/defaults/index.d.ts +34 -2309
- package/dist/defaults/index.js +5610 -4608
- package/dist/defaults/index.js.map +1 -1
- package/dist/events-B6Q03pTu.d.ts +290 -0
- package/dist/execution/index.d.ts +260 -0
- package/dist/execution/index.js +1625 -0
- package/dist/execution/index.js.map +1 -0
- package/dist/index.d.ts +81 -11
- package/dist/index.js +7727 -6174
- package/dist/index.js.map +1 -1
- package/dist/infrastructure/index.d.ts +10 -0
- package/dist/infrastructure/index.js +575 -0
- package/dist/infrastructure/index.js.map +1 -0
- package/dist/input-reader-E-ffP2ee.d.ts +12 -0
- package/dist/kernel/index.d.ts +15 -4
- package/dist/kernel/index.js.map +1 -1
- package/dist/logger-BH6AE0W9.d.ts +24 -0
- package/dist/logger-BMQgxvdy.d.ts +12 -0
- package/dist/mcp-servers-BA1Ofmfj.d.ts +100 -0
- package/dist/memory-CEXuo7sz.d.ts +16 -0
- package/dist/mode-CV077NjV.d.ts +27 -0
- package/dist/models/index.d.ts +60 -0
- package/dist/models/index.js +621 -0
- package/dist/models/index.js.map +1 -0
- package/dist/models-registry-DqzwpBQy.d.ts +46 -0
- package/dist/models-registry-Y2xbog0E.d.ts +95 -0
- package/dist/multi-agent-BDfkxL5C.d.ts +351 -0
- package/dist/observability/index.d.ts +353 -0
- package/dist/observability/index.js +691 -0
- package/dist/observability/index.js.map +1 -0
- package/dist/observability-BhnVLBLS.d.ts +67 -0
- package/dist/path-resolver-CPRj4bFY.d.ts +10 -0
- package/dist/path-resolver-Crkt8wTQ.d.ts +54 -0
- package/dist/plugin-CoYYZKdn.d.ts +447 -0
- package/dist/renderer-0A2ZEtca.d.ts +158 -0
- package/dist/sdd/index.d.ts +206 -0
- package/dist/sdd/index.js +864 -0
- package/dist/sdd/index.js.map +1 -0
- package/dist/secret-scrubber-3TLUkiCV.d.ts +31 -0
- package/dist/secret-scrubber-CwYliRWd.d.ts +54 -0
- package/dist/secret-vault-DoISxaKO.d.ts +19 -0
- package/dist/security/index.d.ts +46 -0
- package/dist/security/index.js +536 -0
- package/dist/security/index.js.map +1 -0
- package/dist/selector-BRqzvugb.d.ts +51 -0
- package/dist/session-reader-C3x96CDR.d.ts +150 -0
- package/dist/skill-Bx8jxznf.d.ts +72 -0
- package/dist/storage/index.d.ts +540 -0
- package/dist/storage/index.js +1802 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/{system-prompt-vAB0F54-.d.ts → system-prompt-CG9jU5-5.d.ts} +9 -1
- package/dist/task-graph-BITvWt4t.d.ts +160 -0
- package/dist/tool-executor-CYdZdtno.d.ts +97 -0
- package/dist/types/index.d.ts +26 -4
- package/dist/types/index.js +1787 -4
- package/dist/types/index.js.map +1 -1
- package/dist/utils/index.d.ts +49 -2
- package/dist/utils/index.js +100 -2
- package/dist/utils/index.js.map +1 -1
- package/package.json +34 -2
- package/dist/mode-Pjt5vMS6.d.ts +0 -815
- package/dist/session-reader-9sOTgmeC.d.ts +0 -1087
|
@@ -0,0 +1,864 @@
|
|
|
1
|
+
// src/sdd/spec-parser.ts
|
|
2
|
+
var SpecParser = class {
|
|
3
|
+
parse(content) {
|
|
4
|
+
const lines = content.split("\n");
|
|
5
|
+
const sections = this.extractSections(lines);
|
|
6
|
+
const requirements = this.extractRequirements(lines);
|
|
7
|
+
const now = Date.now();
|
|
8
|
+
return {
|
|
9
|
+
id: crypto.randomUUID(),
|
|
10
|
+
title: this.extractTitle(lines),
|
|
11
|
+
version: this.extractVersion(lines),
|
|
12
|
+
status: "draft",
|
|
13
|
+
overview: this.extractOverview(lines),
|
|
14
|
+
sections,
|
|
15
|
+
requirements,
|
|
16
|
+
createdAt: now,
|
|
17
|
+
updatedAt: now
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
extractTitle(lines) {
|
|
21
|
+
for (const line of lines) {
|
|
22
|
+
const m = /^#\s+(.+)/.exec(line.trim());
|
|
23
|
+
if (m?.[1]) return m[1];
|
|
24
|
+
}
|
|
25
|
+
return "Untitled Specification";
|
|
26
|
+
}
|
|
27
|
+
extractVersion(lines) {
|
|
28
|
+
for (const line of lines) {
|
|
29
|
+
const m = /version[:\s]+(\d+\.\d+\.\d+)/i.exec(line.trim());
|
|
30
|
+
if (m?.[1]) return m[1];
|
|
31
|
+
}
|
|
32
|
+
return "0.0.1";
|
|
33
|
+
}
|
|
34
|
+
extractOverview(lines) {
|
|
35
|
+
const overviewLines = [];
|
|
36
|
+
let inOverview = false;
|
|
37
|
+
let foundHeading = false;
|
|
38
|
+
for (const line of lines) {
|
|
39
|
+
if (/^##\s+Overview/i.test(line.trim())) {
|
|
40
|
+
inOverview = true;
|
|
41
|
+
foundHeading = true;
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
if (foundHeading && /^##\s+/.test(line.trim())) break;
|
|
45
|
+
if (inOverview) overviewLines.push(line);
|
|
46
|
+
}
|
|
47
|
+
return overviewLines.join("\n").trim() || "No overview provided";
|
|
48
|
+
}
|
|
49
|
+
extractSections(lines) {
|
|
50
|
+
const sections = [];
|
|
51
|
+
let currentSection = null;
|
|
52
|
+
let currentLines = [];
|
|
53
|
+
let depth = 1;
|
|
54
|
+
for (const line of lines) {
|
|
55
|
+
const h2 = /^##\s+(.+)/.exec(line.trim());
|
|
56
|
+
const h3 = /^###\s+(.+)/.exec(line.trim());
|
|
57
|
+
if (h2) {
|
|
58
|
+
if (currentSection && currentLines.length > 0) {
|
|
59
|
+
sections.push({
|
|
60
|
+
type: this.mapSectionType(currentSection.title ?? "unknown"),
|
|
61
|
+
title: currentSection.title ?? "Unknown",
|
|
62
|
+
level: depth,
|
|
63
|
+
content: currentLines.join("\n").trim()
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
currentSection = { title: h2[1] ?? "Unknown" };
|
|
67
|
+
currentLines = [];
|
|
68
|
+
depth = 2;
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
if (h3) {
|
|
72
|
+
currentLines.push(line);
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
if (currentSection) {
|
|
76
|
+
currentLines.push(line);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if (currentSection && currentLines.length > 0) {
|
|
80
|
+
sections.push({
|
|
81
|
+
type: this.mapSectionType(currentSection.title ?? "unknown"),
|
|
82
|
+
title: currentSection.title ?? "Unknown",
|
|
83
|
+
level: depth,
|
|
84
|
+
content: currentLines.join("\n").trim()
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
return sections;
|
|
88
|
+
}
|
|
89
|
+
extractRequirements(lines) {
|
|
90
|
+
const requirements = [];
|
|
91
|
+
let inRequirements = false;
|
|
92
|
+
let idCounter = 0;
|
|
93
|
+
for (const line of lines) {
|
|
94
|
+
if (/^##\s+Requirements/i.test(line.trim())) {
|
|
95
|
+
inRequirements = true;
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
if (inRequirements && /^##\s+/.test(line.trim())) break;
|
|
99
|
+
if (inRequirements) {
|
|
100
|
+
const req = this.parseRequirementLine(line, `REQ-${++idCounter}`);
|
|
101
|
+
if (req) requirements.push(req);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return requirements;
|
|
105
|
+
}
|
|
106
|
+
parseRequirementLine(line, id) {
|
|
107
|
+
const trimmed = line.trim();
|
|
108
|
+
if (!trimmed || trimmed.startsWith("#")) return null;
|
|
109
|
+
const lower = trimmed.toLowerCase();
|
|
110
|
+
const types = [
|
|
111
|
+
"functional",
|
|
112
|
+
"non-functional",
|
|
113
|
+
"security",
|
|
114
|
+
"performance",
|
|
115
|
+
"ux"
|
|
116
|
+
];
|
|
117
|
+
let type = "functional";
|
|
118
|
+
for (const t of types) {
|
|
119
|
+
if (lower.includes(`[${t}]`)) type = t;
|
|
120
|
+
}
|
|
121
|
+
let priority = "medium";
|
|
122
|
+
if (trimmed.includes("[critical]") || trimmed.includes("[prio:high]")) {
|
|
123
|
+
priority = "critical";
|
|
124
|
+
} else if (trimmed.includes("[high]")) {
|
|
125
|
+
priority = "high";
|
|
126
|
+
} else if (trimmed.includes("[low]")) {
|
|
127
|
+
priority = "low";
|
|
128
|
+
}
|
|
129
|
+
return {
|
|
130
|
+
id,
|
|
131
|
+
type,
|
|
132
|
+
priority,
|
|
133
|
+
description: trimmed.replace(/\[[^\]]+\]/g, "").trim(),
|
|
134
|
+
acceptanceCriteria: []
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
mapSectionType(title) {
|
|
138
|
+
const t = title.toLowerCase();
|
|
139
|
+
if (t.includes("overview")) return "overview";
|
|
140
|
+
if (t.includes("requirement")) return "requirements";
|
|
141
|
+
if (t.includes("architect")) return "architecture";
|
|
142
|
+
if (t.includes("api")) return "api";
|
|
143
|
+
if (t.includes("data")) return "data";
|
|
144
|
+
if (t.includes("security")) return "security";
|
|
145
|
+
if (t.includes("acceptance")) return "acceptance";
|
|
146
|
+
return "overview";
|
|
147
|
+
}
|
|
148
|
+
analyze(spec) {
|
|
149
|
+
const gaps = [];
|
|
150
|
+
const suggestions = [];
|
|
151
|
+
const risks = [];
|
|
152
|
+
const hasOverview = spec.sections.some((s) => s.type === "overview");
|
|
153
|
+
const hasRequirements = spec.sections.some((s) => s.type === "requirements");
|
|
154
|
+
const hasAcceptance = spec.sections.some((s) => s.type === "acceptance");
|
|
155
|
+
if (!hasOverview) gaps.push("Missing Overview section");
|
|
156
|
+
if (!hasRequirements) gaps.push("Missing Requirements section");
|
|
157
|
+
if (!hasAcceptance) gaps.push("Missing Acceptance Criteria section");
|
|
158
|
+
if (spec.requirements.length === 0) {
|
|
159
|
+
gaps.push("No requirements defined");
|
|
160
|
+
suggestions.push("Add specific functional and non-functional requirements");
|
|
161
|
+
}
|
|
162
|
+
const unverifiedReqs = spec.requirements.filter((r) => r.acceptanceCriteria.length === 0);
|
|
163
|
+
if (unverifiedReqs.length > 0) {
|
|
164
|
+
gaps.push(`${unverifiedReqs.length} requirements without acceptance criteria`);
|
|
165
|
+
suggestions.push("Define clear acceptance criteria for each requirement");
|
|
166
|
+
}
|
|
167
|
+
const criticalUnresolved = spec.requirements.filter(
|
|
168
|
+
(r) => r.priority === "critical" && r.blockedBy && r.blockedBy.length > 0
|
|
169
|
+
);
|
|
170
|
+
for (const req of criticalUnresolved) {
|
|
171
|
+
risks.push({
|
|
172
|
+
requirement: req.id,
|
|
173
|
+
risk: `Critical requirement blocked by ${req.blockedBy?.length} other requirements`,
|
|
174
|
+
severity: "high"
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
const completeness = Math.round(
|
|
178
|
+
((hasOverview ? 1 : 0) + (hasRequirements ? 1 : 0) + (hasAcceptance ? 1 : 0) + (spec.requirements.length > 0 ? 1 : 0) + (spec.sections.length > 3 ? 1 : 0)) / 5 * 100
|
|
179
|
+
);
|
|
180
|
+
return {
|
|
181
|
+
specId: spec.id,
|
|
182
|
+
completeness,
|
|
183
|
+
coverage: {
|
|
184
|
+
requirements: spec.requirements.length,
|
|
185
|
+
apiEndpoints: spec.apiEndpoints?.length ?? 0,
|
|
186
|
+
edgeCases: 0,
|
|
187
|
+
errorHandling: 0
|
|
188
|
+
},
|
|
189
|
+
gaps,
|
|
190
|
+
risks,
|
|
191
|
+
suggestions
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
validate(spec) {
|
|
195
|
+
const errors = [];
|
|
196
|
+
const warnings = [];
|
|
197
|
+
if (!spec.title.trim()) {
|
|
198
|
+
errors.push({ path: "title", message: "Title is required" });
|
|
199
|
+
}
|
|
200
|
+
if (!spec.version.trim()) {
|
|
201
|
+
errors.push({ path: "version", message: "Version is required" });
|
|
202
|
+
}
|
|
203
|
+
for (const req of spec.requirements) {
|
|
204
|
+
if (!req.description.trim()) {
|
|
205
|
+
errors.push({ path: `requirement.${req.id}`, message: "Requirement description is empty" });
|
|
206
|
+
}
|
|
207
|
+
if (req.acceptanceCriteria.length === 0) {
|
|
208
|
+
warnings.push({ path: `requirement.${req.id}`, message: "No acceptance criteria defined" });
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
const reqIds = new Set(spec.requirements.map((r) => r.id));
|
|
212
|
+
const blockedByIds = new Set(spec.requirements.flatMap((r) => r.blockedBy ?? []));
|
|
213
|
+
for (const id of blockedByIds) {
|
|
214
|
+
if (!reqIds.has(id)) {
|
|
215
|
+
errors.push({
|
|
216
|
+
path: "requirements",
|
|
217
|
+
message: `BlockedBy references non-existent requirement: ${id}`
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return {
|
|
222
|
+
valid: errors.length === 0,
|
|
223
|
+
errors,
|
|
224
|
+
warnings
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
// src/sdd/task-generator.ts
|
|
230
|
+
var TaskGenerator = class {
|
|
231
|
+
constructor(opts) {
|
|
232
|
+
this.opts = opts;
|
|
233
|
+
}
|
|
234
|
+
opts;
|
|
235
|
+
async generateFromSpec(spec) {
|
|
236
|
+
const graph = await this.opts.taskTracker.createGraph(spec.id, spec.title);
|
|
237
|
+
const overview = spec.sections.find((s) => s.type === "overview");
|
|
238
|
+
if (overview) {
|
|
239
|
+
this.opts.taskTracker.addNode({
|
|
240
|
+
title: `Implement ${spec.title}`,
|
|
241
|
+
description: overview.content,
|
|
242
|
+
type: "feature",
|
|
243
|
+
priority: "high",
|
|
244
|
+
status: "pending"
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
const byPriority = {
|
|
248
|
+
critical: [],
|
|
249
|
+
high: [],
|
|
250
|
+
medium: [],
|
|
251
|
+
low: []
|
|
252
|
+
};
|
|
253
|
+
for (const req of spec.requirements) {
|
|
254
|
+
const bucket = byPriority[req.priority] ?? byPriority.medium;
|
|
255
|
+
bucket.push(req);
|
|
256
|
+
}
|
|
257
|
+
const order = ["critical", "high", "medium", "low"];
|
|
258
|
+
for (const p of order) {
|
|
259
|
+
for (const req of byPriority[p]) {
|
|
260
|
+
this.opts.taskTracker.addNode(this.createTaskFromRequirement(req));
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
if (spec.apiEndpoints && spec.apiEndpoints.length > 0) {
|
|
264
|
+
const apiParent = this.opts.taskTracker.addNode({
|
|
265
|
+
title: "API Implementation",
|
|
266
|
+
description: `Implement ${spec.apiEndpoints.length} API endpoints`,
|
|
267
|
+
type: "feature",
|
|
268
|
+
priority: "high",
|
|
269
|
+
status: "pending"
|
|
270
|
+
});
|
|
271
|
+
for (const endpoint of spec.apiEndpoints) {
|
|
272
|
+
const task = this.createTaskFromEndpoint(endpoint);
|
|
273
|
+
this.opts.taskTracker.addNode({
|
|
274
|
+
...task,
|
|
275
|
+
parentId: apiParent.id
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
this.opts.taskTracker.addNode({
|
|
280
|
+
title: "Write Tests",
|
|
281
|
+
description: "Comprehensive test coverage for all features",
|
|
282
|
+
type: "test",
|
|
283
|
+
priority: "high",
|
|
284
|
+
status: "pending"
|
|
285
|
+
});
|
|
286
|
+
this.opts.taskTracker.addNode({
|
|
287
|
+
title: "Update Documentation",
|
|
288
|
+
description: "Update docs for new features",
|
|
289
|
+
type: "docs",
|
|
290
|
+
priority: "medium",
|
|
291
|
+
status: "pending"
|
|
292
|
+
});
|
|
293
|
+
return graph;
|
|
294
|
+
}
|
|
295
|
+
createTaskFromRequirement(req) {
|
|
296
|
+
return {
|
|
297
|
+
title: req.description,
|
|
298
|
+
description: this.buildDescription(req),
|
|
299
|
+
type: this.mapRequirementType(req.type),
|
|
300
|
+
priority: req.priority,
|
|
301
|
+
status: "pending",
|
|
302
|
+
specRequirementId: req.id,
|
|
303
|
+
tags: [req.type, req.priority],
|
|
304
|
+
estimateHours: this.estimateHours(req)
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
createTaskFromEndpoint(endpoint) {
|
|
308
|
+
return {
|
|
309
|
+
title: `${endpoint.method} ${endpoint.path}`,
|
|
310
|
+
description: endpoint.description,
|
|
311
|
+
type: "feature",
|
|
312
|
+
priority: "high",
|
|
313
|
+
status: "pending",
|
|
314
|
+
tags: [endpoint.method],
|
|
315
|
+
estimateHours: this.estimateForEndpoint(endpoint)
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
buildDescription(req) {
|
|
319
|
+
const lines = [req.description, "", "**Type:** " + req.type, "**Priority:** " + req.priority];
|
|
320
|
+
if (req.acceptanceCriteria.length > 0) {
|
|
321
|
+
lines.push("", "**Acceptance Criteria:**");
|
|
322
|
+
for (const criterion of req.acceptanceCriteria) {
|
|
323
|
+
lines.push(`- ${criterion}`);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
if (req.blockedBy && req.blockedBy.length > 0) {
|
|
327
|
+
lines.push("", `**Blocked by:** ${req.blockedBy.join(", ")}`);
|
|
328
|
+
}
|
|
329
|
+
return lines.join("\n");
|
|
330
|
+
}
|
|
331
|
+
mapRequirementType(type) {
|
|
332
|
+
switch (type) {
|
|
333
|
+
case "functional":
|
|
334
|
+
return "feature";
|
|
335
|
+
case "non-functional":
|
|
336
|
+
return "feature";
|
|
337
|
+
case "security":
|
|
338
|
+
return "feature";
|
|
339
|
+
case "performance":
|
|
340
|
+
return "feature";
|
|
341
|
+
case "ux":
|
|
342
|
+
return "feature";
|
|
343
|
+
default:
|
|
344
|
+
return "feature";
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
estimateHours(req) {
|
|
348
|
+
switch (req.priority) {
|
|
349
|
+
case "critical":
|
|
350
|
+
return 8;
|
|
351
|
+
case "high":
|
|
352
|
+
return 4;
|
|
353
|
+
case "medium":
|
|
354
|
+
return 2;
|
|
355
|
+
case "low":
|
|
356
|
+
return 1;
|
|
357
|
+
default:
|
|
358
|
+
return 2;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
estimateForEndpoint(endpoint) {
|
|
362
|
+
let hours = 2;
|
|
363
|
+
if (endpoint.auth) hours += 1;
|
|
364
|
+
if (endpoint.request) hours += 1;
|
|
365
|
+
return hours;
|
|
366
|
+
}
|
|
367
|
+
async generateSubtasks(parentTaskId, spec) {
|
|
368
|
+
const reqId = this.opts.taskTracker.getNode(parentTaskId)?.specRequirementId;
|
|
369
|
+
if (!reqId) return;
|
|
370
|
+
const req = spec.requirements.find((r) => r.id === reqId);
|
|
371
|
+
if (!req) return;
|
|
372
|
+
if (req.acceptanceCriteria.length > 0) {
|
|
373
|
+
for (const criterion of req.acceptanceCriteria) {
|
|
374
|
+
this.opts.taskTracker.addNode({
|
|
375
|
+
title: criterion,
|
|
376
|
+
description: `Verify: ${criterion}`,
|
|
377
|
+
type: "test",
|
|
378
|
+
priority: "medium",
|
|
379
|
+
status: "pending",
|
|
380
|
+
parentId: parentTaskId
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
};
|
|
386
|
+
var DefaultTaskStore = class {
|
|
387
|
+
graphs = /* @__PURE__ */ new Map();
|
|
388
|
+
async saveGraph(graph) {
|
|
389
|
+
this.graphs.set(graph.id, this.cloneGraph(graph));
|
|
390
|
+
}
|
|
391
|
+
async loadGraph(id) {
|
|
392
|
+
const g = this.graphs.get(id);
|
|
393
|
+
return g ? this.cloneGraph(g) : null;
|
|
394
|
+
}
|
|
395
|
+
async listGraphs() {
|
|
396
|
+
return Array.from(this.graphs.values()).map((g) => ({
|
|
397
|
+
id: g.id,
|
|
398
|
+
title: g.title,
|
|
399
|
+
updatedAt: g.updatedAt
|
|
400
|
+
}));
|
|
401
|
+
}
|
|
402
|
+
async deleteGraph(id) {
|
|
403
|
+
this.graphs.delete(id);
|
|
404
|
+
}
|
|
405
|
+
cloneGraph(g) {
|
|
406
|
+
return {
|
|
407
|
+
...g,
|
|
408
|
+
nodes: new Map(g.nodes),
|
|
409
|
+
edges: [...g.edges],
|
|
410
|
+
rootNodes: [...g.rootNodes]
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
};
|
|
414
|
+
|
|
415
|
+
// src/types/task-graph.ts
|
|
416
|
+
function computeTaskProgress(graph) {
|
|
417
|
+
let completed = 0;
|
|
418
|
+
let pending = 0;
|
|
419
|
+
let inProgress = 0;
|
|
420
|
+
let blocked = 0;
|
|
421
|
+
let failed = 0;
|
|
422
|
+
let review = 0;
|
|
423
|
+
let estimatedHours = 0;
|
|
424
|
+
let actualHours = 0;
|
|
425
|
+
for (const n of graph.nodes.values()) {
|
|
426
|
+
switch (n.status) {
|
|
427
|
+
case "completed":
|
|
428
|
+
completed++;
|
|
429
|
+
break;
|
|
430
|
+
case "pending":
|
|
431
|
+
pending++;
|
|
432
|
+
break;
|
|
433
|
+
case "in_progress":
|
|
434
|
+
inProgress++;
|
|
435
|
+
break;
|
|
436
|
+
case "blocked":
|
|
437
|
+
blocked++;
|
|
438
|
+
break;
|
|
439
|
+
case "failed":
|
|
440
|
+
failed++;
|
|
441
|
+
break;
|
|
442
|
+
case "review":
|
|
443
|
+
review++;
|
|
444
|
+
break;
|
|
445
|
+
}
|
|
446
|
+
estimatedHours += n.estimateHours ?? 0;
|
|
447
|
+
actualHours += n.actualHours ?? 0;
|
|
448
|
+
}
|
|
449
|
+
const total = graph.nodes.size;
|
|
450
|
+
return {
|
|
451
|
+
total,
|
|
452
|
+
pending,
|
|
453
|
+
inProgress,
|
|
454
|
+
blocked,
|
|
455
|
+
failed,
|
|
456
|
+
review,
|
|
457
|
+
completed,
|
|
458
|
+
percentComplete: total > 0 ? Math.round(completed / total * 100) : 0,
|
|
459
|
+
estimatedHours,
|
|
460
|
+
actualHours
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// src/sdd/task-tracker.ts
|
|
465
|
+
var TaskTracker = class {
|
|
466
|
+
constructor(opts) {
|
|
467
|
+
this.opts = opts;
|
|
468
|
+
}
|
|
469
|
+
opts;
|
|
470
|
+
graph = null;
|
|
471
|
+
transitions = [];
|
|
472
|
+
async createGraph(specId, title) {
|
|
473
|
+
this.graph = {
|
|
474
|
+
id: crypto.randomUUID(),
|
|
475
|
+
specId,
|
|
476
|
+
title,
|
|
477
|
+
nodes: /* @__PURE__ */ new Map(),
|
|
478
|
+
edges: [],
|
|
479
|
+
rootNodes: [],
|
|
480
|
+
createdAt: Date.now(),
|
|
481
|
+
updatedAt: Date.now()
|
|
482
|
+
};
|
|
483
|
+
await this.opts.store.saveGraph(this.graph);
|
|
484
|
+
return this.graph;
|
|
485
|
+
}
|
|
486
|
+
async loadGraph(id) {
|
|
487
|
+
this.graph = await this.opts.store.loadGraph(id);
|
|
488
|
+
return this.graph;
|
|
489
|
+
}
|
|
490
|
+
addNode(node) {
|
|
491
|
+
if (!this.graph) throw new Error("No graph loaded");
|
|
492
|
+
const now = Date.now();
|
|
493
|
+
const newNode = {
|
|
494
|
+
...node,
|
|
495
|
+
id: crypto.randomUUID(),
|
|
496
|
+
status: node.status ?? "pending",
|
|
497
|
+
createdAt: now,
|
|
498
|
+
updatedAt: now
|
|
499
|
+
};
|
|
500
|
+
this.graph.nodes.set(newNode.id, newNode);
|
|
501
|
+
if (!node.parentId) {
|
|
502
|
+
this.graph.rootNodes.push(newNode.id);
|
|
503
|
+
}
|
|
504
|
+
this.graph.updatedAt = now;
|
|
505
|
+
this.persist();
|
|
506
|
+
return newNode;
|
|
507
|
+
}
|
|
508
|
+
addEdge(from, to, type = "depends_on") {
|
|
509
|
+
if (!this.graph) throw new Error("No graph loaded");
|
|
510
|
+
this.graph.edges.push({
|
|
511
|
+
id: crypto.randomUUID(),
|
|
512
|
+
from,
|
|
513
|
+
to,
|
|
514
|
+
type
|
|
515
|
+
});
|
|
516
|
+
this.graph.updatedAt = Date.now();
|
|
517
|
+
this.persist();
|
|
518
|
+
}
|
|
519
|
+
updateNodeStatus(id, status, reason) {
|
|
520
|
+
if (!this.graph) throw new Error("No graph loaded");
|
|
521
|
+
const node = this.graph.nodes.get(id);
|
|
522
|
+
if (!node) throw new Error(`Node ${id} not found`);
|
|
523
|
+
const from = node.status;
|
|
524
|
+
const now = Date.now();
|
|
525
|
+
node.status = status;
|
|
526
|
+
node.updatedAt = now;
|
|
527
|
+
if (status === "completed") {
|
|
528
|
+
node.completedAt = now;
|
|
529
|
+
}
|
|
530
|
+
this.transitions.push({ from, to: status, timestamp: now, reason });
|
|
531
|
+
if (status === "completed") {
|
|
532
|
+
this.unblockDependents(id);
|
|
533
|
+
}
|
|
534
|
+
if (status === "in_progress") {
|
|
535
|
+
this.checkAndBlockIfNeeded(id);
|
|
536
|
+
}
|
|
537
|
+
this.graph.updatedAt = now;
|
|
538
|
+
this.persist();
|
|
539
|
+
}
|
|
540
|
+
getNode(id) {
|
|
541
|
+
return this.graph?.nodes.get(id);
|
|
542
|
+
}
|
|
543
|
+
getAllNodes(filter, sort) {
|
|
544
|
+
if (!this.graph) return [];
|
|
545
|
+
let nodes = Array.from(this.graph.nodes.values());
|
|
546
|
+
if (filter) {
|
|
547
|
+
nodes = nodes.filter((n) => {
|
|
548
|
+
if (filter.status?.length && !filter.status.includes(n.status)) return false;
|
|
549
|
+
if (filter.priority?.length && !filter.priority.includes(n.priority)) return false;
|
|
550
|
+
if (filter.type?.length && !filter.type.includes(n.type)) return false;
|
|
551
|
+
if (filter.assignee?.length && n.assignee && !filter.assignee.includes(n.assignee))
|
|
552
|
+
return false;
|
|
553
|
+
if (filter.tags?.length && n.tags && !n.tags.some((t) => filter.tags.includes(t)))
|
|
554
|
+
return false;
|
|
555
|
+
if (filter.specRequirementId && n.specRequirementId !== filter.specRequirementId)
|
|
556
|
+
return false;
|
|
557
|
+
return true;
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
if (sort) {
|
|
561
|
+
nodes.sort((a, b) => {
|
|
562
|
+
const cmp = compareByField(a, b, sort.field);
|
|
563
|
+
return sort.direction === "asc" ? cmp : -cmp;
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
return nodes;
|
|
567
|
+
}
|
|
568
|
+
getChildren(parentId) {
|
|
569
|
+
if (!this.graph) return [];
|
|
570
|
+
return Array.from(this.graph.nodes.values()).filter((n) => n.parentId === parentId);
|
|
571
|
+
}
|
|
572
|
+
getDependents(taskId) {
|
|
573
|
+
if (!this.graph) return [];
|
|
574
|
+
return this.graph.edges.filter((e) => e.from === taskId && e.type === "depends_on").map((e) => e.to);
|
|
575
|
+
}
|
|
576
|
+
getBlockers(taskId) {
|
|
577
|
+
if (!this.graph) return [];
|
|
578
|
+
return this.graph.edges.filter((e) => e.to === taskId && e.type === "depends_on").map((e) => e.from);
|
|
579
|
+
}
|
|
580
|
+
canStart(taskId) {
|
|
581
|
+
const blockers = this.getBlockers(taskId);
|
|
582
|
+
return blockers.every((id) => {
|
|
583
|
+
const node = this.graph?.nodes.get(id);
|
|
584
|
+
return node?.status === "completed";
|
|
585
|
+
});
|
|
586
|
+
}
|
|
587
|
+
getProgress() {
|
|
588
|
+
if (!this.graph) {
|
|
589
|
+
return {
|
|
590
|
+
total: 0,
|
|
591
|
+
pending: 0,
|
|
592
|
+
inProgress: 0,
|
|
593
|
+
blocked: 0,
|
|
594
|
+
failed: 0,
|
|
595
|
+
review: 0,
|
|
596
|
+
completed: 0,
|
|
597
|
+
percentComplete: 0,
|
|
598
|
+
estimatedHours: 0,
|
|
599
|
+
actualHours: 0
|
|
600
|
+
};
|
|
601
|
+
}
|
|
602
|
+
return computeTaskProgress(this.graph);
|
|
603
|
+
}
|
|
604
|
+
getTransitions(taskId) {
|
|
605
|
+
if (!taskId) return [...this.transitions];
|
|
606
|
+
return [...this.transitions];
|
|
607
|
+
}
|
|
608
|
+
unblockDependents(completedId) {
|
|
609
|
+
if (!this.graph) return;
|
|
610
|
+
const dependents = this.getDependents(completedId);
|
|
611
|
+
for (const depId of dependents) {
|
|
612
|
+
const dep = this.graph.nodes.get(depId);
|
|
613
|
+
if (dep?.status === "blocked") {
|
|
614
|
+
const remainingBlockers = this.getBlockers(depId);
|
|
615
|
+
const allUnblocked = remainingBlockers.every((id) => {
|
|
616
|
+
const blocker = this.graph?.nodes.get(id);
|
|
617
|
+
return blocker?.status === "completed";
|
|
618
|
+
});
|
|
619
|
+
if (allUnblocked) {
|
|
620
|
+
dep.status = "pending";
|
|
621
|
+
dep.updatedAt = Date.now();
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
checkAndBlockIfNeeded(taskId) {
|
|
627
|
+
if (!this.graph) return;
|
|
628
|
+
const blockers = this.getBlockers(taskId);
|
|
629
|
+
const someBlocked = blockers.some((id) => {
|
|
630
|
+
const blocker = this.graph?.nodes.get(id);
|
|
631
|
+
return blocker?.status !== "completed";
|
|
632
|
+
});
|
|
633
|
+
if (someBlocked) {
|
|
634
|
+
const node = this.graph.nodes.get(taskId);
|
|
635
|
+
if (node) {
|
|
636
|
+
node.status = "blocked";
|
|
637
|
+
node.updatedAt = Date.now();
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
/**
|
|
642
|
+
* Fire-and-forget persistence with attached error handler.
|
|
643
|
+
* Synchronous mutators (addNode/addEdge/updateNodeStatus) use this to
|
|
644
|
+
* avoid forcing an async cascade through every caller; if the store
|
|
645
|
+
* rejects, the configured `onPersistError` is invoked so failures are
|
|
646
|
+
* surfaced instead of swallowed by an unhandled promise rejection.
|
|
647
|
+
*/
|
|
648
|
+
persist() {
|
|
649
|
+
if (!this.graph) return;
|
|
650
|
+
this.opts.store.saveGraph(this.graph).catch((err) => {
|
|
651
|
+
if (this.opts.onPersistError) this.opts.onPersistError(err);
|
|
652
|
+
else
|
|
653
|
+
console.warn(
|
|
654
|
+
"[task-tracker] saveGraph failed:",
|
|
655
|
+
err instanceof Error ? err.message : String(err)
|
|
656
|
+
);
|
|
657
|
+
});
|
|
658
|
+
}
|
|
659
|
+
};
|
|
660
|
+
var PRIORITY_RANK = {
|
|
661
|
+
critical: 0,
|
|
662
|
+
high: 1,
|
|
663
|
+
medium: 2,
|
|
664
|
+
low: 3
|
|
665
|
+
};
|
|
666
|
+
var STATUS_RANK = {
|
|
667
|
+
in_progress: 0,
|
|
668
|
+
pending: 1,
|
|
669
|
+
review: 2,
|
|
670
|
+
blocked: 3,
|
|
671
|
+
failed: 4,
|
|
672
|
+
completed: 5
|
|
673
|
+
};
|
|
674
|
+
function compareByField(a, b, field) {
|
|
675
|
+
switch (field) {
|
|
676
|
+
case "priority":
|
|
677
|
+
return PRIORITY_RANK[a.priority] - PRIORITY_RANK[b.priority];
|
|
678
|
+
case "status":
|
|
679
|
+
return STATUS_RANK[a.status] - STATUS_RANK[b.status];
|
|
680
|
+
case "createdAt":
|
|
681
|
+
return a.createdAt - b.createdAt;
|
|
682
|
+
case "updatedAt":
|
|
683
|
+
return a.updatedAt - b.updatedAt;
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
// src/sdd/task-flow.ts
|
|
688
|
+
var TaskFlow = class {
|
|
689
|
+
constructor(opts) {
|
|
690
|
+
this.opts = opts;
|
|
691
|
+
this.setPhase("idle");
|
|
692
|
+
}
|
|
693
|
+
opts;
|
|
694
|
+
phase = "idle";
|
|
695
|
+
spec = null;
|
|
696
|
+
graph = null;
|
|
697
|
+
stopped = false;
|
|
698
|
+
emit(event, payload) {
|
|
699
|
+
this.opts.events.emit(event, payload);
|
|
700
|
+
}
|
|
701
|
+
async fromSpec(specContent) {
|
|
702
|
+
this.setPhase("parsing");
|
|
703
|
+
const parser = new SpecParser();
|
|
704
|
+
this.spec = parser.parse(specContent);
|
|
705
|
+
this.setPhase("analyzing");
|
|
706
|
+
const analysis = parser.analyze(this.spec);
|
|
707
|
+
this.emit("spec.analyzed", { analysis });
|
|
708
|
+
if (analysis.completeness < 50) {
|
|
709
|
+
this.emit("error", {
|
|
710
|
+
phase: "analyzing",
|
|
711
|
+
error: new Error(`Spec completeness too low: ${analysis.completeness}%`)
|
|
712
|
+
});
|
|
713
|
+
this.setPhase("failed");
|
|
714
|
+
throw new Error("Spec too incomplete");
|
|
715
|
+
}
|
|
716
|
+
this.setPhase("generating");
|
|
717
|
+
const generator = new TaskGenerator({ taskTracker: this.opts.tracker });
|
|
718
|
+
this.graph = await generator.generateFromSpec(this.spec);
|
|
719
|
+
return this.graph;
|
|
720
|
+
}
|
|
721
|
+
async execute(ctx) {
|
|
722
|
+
if (!this.graph) throw new Error("No graph loaded. Call fromSpec first.");
|
|
723
|
+
this.setPhase("executing");
|
|
724
|
+
this.stopped = false;
|
|
725
|
+
const pendingTasks = this.getExecutableTasks();
|
|
726
|
+
const maxConcurrent = this.opts.maxConcurrent ?? 2;
|
|
727
|
+
while (pendingTasks.length > 0 && !this.stopped) {
|
|
728
|
+
const batch = pendingTasks.splice(0, maxConcurrent);
|
|
729
|
+
const results = await Promise.allSettled(
|
|
730
|
+
batch.map((task) => this.executeSingleTask(task, ctx))
|
|
731
|
+
);
|
|
732
|
+
for (let i = 0; i < results.length; i++) {
|
|
733
|
+
const result = results[i];
|
|
734
|
+
const task = batch[i];
|
|
735
|
+
if (!result || !task) continue;
|
|
736
|
+
if (result.status === "rejected") {
|
|
737
|
+
const reason = result.reason;
|
|
738
|
+
this.opts.tracker.updateNodeStatus(task.id, "failed", reason?.message);
|
|
739
|
+
this.emit("task.failed", { taskId: task.id, error: reason?.message ?? "unknown" });
|
|
740
|
+
ctx.onTaskFail?.(task, reason);
|
|
741
|
+
} else {
|
|
742
|
+
this.opts.tracker.updateNodeStatus(task.id, "completed");
|
|
743
|
+
this.emit("task.completed", { taskId: task.id, result: result.value });
|
|
744
|
+
ctx.onTaskComplete?.(task, result.value);
|
|
745
|
+
}
|
|
746
|
+
this.emitProgress();
|
|
747
|
+
}
|
|
748
|
+
const stillPending = this.getExecutableTasks();
|
|
749
|
+
pendingTasks.length = 0;
|
|
750
|
+
pendingTasks.push(...stillPending);
|
|
751
|
+
if (this.checkDoneCondition()) {
|
|
752
|
+
break;
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
this.setPhase("completing");
|
|
756
|
+
this.emit("done", { graph: this.graph });
|
|
757
|
+
this.setPhase("done");
|
|
758
|
+
return this.graph;
|
|
759
|
+
}
|
|
760
|
+
async reviewTask(taskId, approved, comment) {
|
|
761
|
+
const task = this.opts.tracker.getNode(taskId);
|
|
762
|
+
if (!task) throw new Error(`Task ${taskId} not found`);
|
|
763
|
+
if (approved) {
|
|
764
|
+
this.opts.tracker.updateNodeStatus(taskId, "completed", comment);
|
|
765
|
+
this.emit("task.completed", { taskId });
|
|
766
|
+
} else {
|
|
767
|
+
this.opts.tracker.updateNodeStatus(taskId, "in_progress", comment ?? "Needs revision");
|
|
768
|
+
this.emit("task.review", { taskId });
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
stop() {
|
|
772
|
+
this.stopped = true;
|
|
773
|
+
}
|
|
774
|
+
getPhase() {
|
|
775
|
+
return this.phase;
|
|
776
|
+
}
|
|
777
|
+
getGraph() {
|
|
778
|
+
return this.graph;
|
|
779
|
+
}
|
|
780
|
+
getSpec() {
|
|
781
|
+
return this.spec;
|
|
782
|
+
}
|
|
783
|
+
setPhase(phase) {
|
|
784
|
+
const from = this.phase;
|
|
785
|
+
this.phase = phase;
|
|
786
|
+
this.emit("phase.change", { from, to: phase });
|
|
787
|
+
}
|
|
788
|
+
getExecutableTasks() {
|
|
789
|
+
return this.opts.tracker.getAllNodes({ status: ["pending", "blocked"] }).filter((n) => n.status === "pending" && this.opts.tracker.canStart(n.id)).sort((a, b) => {
|
|
790
|
+
const priorityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
791
|
+
return (priorityOrder[a.priority] ?? 4) - (priorityOrder[b.priority] ?? 4);
|
|
792
|
+
});
|
|
793
|
+
}
|
|
794
|
+
async executeSingleTask(task, ctx) {
|
|
795
|
+
this.opts.tracker.updateNodeStatus(task.id, "in_progress");
|
|
796
|
+
this.emit("task.started", { taskId: task.id });
|
|
797
|
+
return ctx.executeTask(task);
|
|
798
|
+
}
|
|
799
|
+
checkDoneCondition() {
|
|
800
|
+
const condition = this.opts.doneCondition;
|
|
801
|
+
if (!condition) {
|
|
802
|
+
const progress = this.opts.tracker.getProgress();
|
|
803
|
+
return progress.percentComplete === 100;
|
|
804
|
+
}
|
|
805
|
+
switch (condition.type) {
|
|
806
|
+
case "all_tasks_done": {
|
|
807
|
+
const progress = this.opts.tracker.getProgress();
|
|
808
|
+
return progress.pending === 0 && progress.inProgress === 0;
|
|
809
|
+
}
|
|
810
|
+
case "iterations":
|
|
811
|
+
return false;
|
|
812
|
+
// Not tracked here
|
|
813
|
+
case "tool_calls":
|
|
814
|
+
return false;
|
|
815
|
+
default:
|
|
816
|
+
return false;
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
emitProgress() {
|
|
820
|
+
const progress = this.opts.tracker.getProgress();
|
|
821
|
+
this.emit("progress", {
|
|
822
|
+
percent: progress.percentComplete,
|
|
823
|
+
message: `${progress.completed}/${progress.total} tasks completed`
|
|
824
|
+
});
|
|
825
|
+
}
|
|
826
|
+
};
|
|
827
|
+
var SpecDrivenDev = class {
|
|
828
|
+
store;
|
|
829
|
+
tracker;
|
|
830
|
+
events;
|
|
831
|
+
flows = /* @__PURE__ */ new Map();
|
|
832
|
+
constructor(opts) {
|
|
833
|
+
this.store = new DefaultTaskStore();
|
|
834
|
+
this.tracker = new TaskTracker({ store: this.store });
|
|
835
|
+
this.events = opts.events;
|
|
836
|
+
}
|
|
837
|
+
async createFlow(specContent, options) {
|
|
838
|
+
const flow = new TaskFlow({
|
|
839
|
+
tracker: this.tracker,
|
|
840
|
+
events: this.events,
|
|
841
|
+
...options
|
|
842
|
+
});
|
|
843
|
+
const graph = await flow.fromSpec(specContent);
|
|
844
|
+
this.flows.set(graph.id, flow);
|
|
845
|
+
return flow;
|
|
846
|
+
}
|
|
847
|
+
getTracker() {
|
|
848
|
+
return this.tracker;
|
|
849
|
+
}
|
|
850
|
+
getFlow(graphId) {
|
|
851
|
+
return this.flows.get(graphId);
|
|
852
|
+
}
|
|
853
|
+
listFlows() {
|
|
854
|
+
return Array.from(this.flows.entries()).map(([id, flow]) => ({
|
|
855
|
+
id,
|
|
856
|
+
title: flow.getGraph()?.title ?? "Untitled",
|
|
857
|
+
phase: flow.getPhase()
|
|
858
|
+
}));
|
|
859
|
+
}
|
|
860
|
+
};
|
|
861
|
+
|
|
862
|
+
export { DefaultTaskStore, SpecDrivenDev, SpecParser, TaskFlow, TaskGenerator, TaskTracker };
|
|
863
|
+
//# sourceMappingURL=index.js.map
|
|
864
|
+
//# sourceMappingURL=index.js.map
|