@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.
Files changed (69) hide show
  1. package/dist/agent-bridge-DmBiCipY.d.ts +33 -0
  2. package/dist/compactor-DSl2FK7a.d.ts +17 -0
  3. package/dist/config-DXrqb41m.d.ts +193 -0
  4. package/dist/{provider-txgB0Oq9.d.ts → context-u0bryklF.d.ts} +540 -472
  5. package/dist/coordination/index.d.ts +892 -0
  6. package/dist/coordination/index.js +2869 -0
  7. package/dist/coordination/index.js.map +1 -0
  8. package/dist/defaults/index.d.ts +34 -2309
  9. package/dist/defaults/index.js +5610 -4608
  10. package/dist/defaults/index.js.map +1 -1
  11. package/dist/events-B6Q03pTu.d.ts +290 -0
  12. package/dist/execution/index.d.ts +260 -0
  13. package/dist/execution/index.js +1625 -0
  14. package/dist/execution/index.js.map +1 -0
  15. package/dist/index.d.ts +81 -11
  16. package/dist/index.js +7727 -6174
  17. package/dist/index.js.map +1 -1
  18. package/dist/infrastructure/index.d.ts +10 -0
  19. package/dist/infrastructure/index.js +575 -0
  20. package/dist/infrastructure/index.js.map +1 -0
  21. package/dist/input-reader-E-ffP2ee.d.ts +12 -0
  22. package/dist/kernel/index.d.ts +15 -4
  23. package/dist/kernel/index.js.map +1 -1
  24. package/dist/logger-BH6AE0W9.d.ts +24 -0
  25. package/dist/logger-BMQgxvdy.d.ts +12 -0
  26. package/dist/mcp-servers-BA1Ofmfj.d.ts +100 -0
  27. package/dist/memory-CEXuo7sz.d.ts +16 -0
  28. package/dist/mode-CV077NjV.d.ts +27 -0
  29. package/dist/models/index.d.ts +60 -0
  30. package/dist/models/index.js +621 -0
  31. package/dist/models/index.js.map +1 -0
  32. package/dist/models-registry-DqzwpBQy.d.ts +46 -0
  33. package/dist/models-registry-Y2xbog0E.d.ts +95 -0
  34. package/dist/multi-agent-BDfkxL5C.d.ts +351 -0
  35. package/dist/observability/index.d.ts +353 -0
  36. package/dist/observability/index.js +691 -0
  37. package/dist/observability/index.js.map +1 -0
  38. package/dist/observability-BhnVLBLS.d.ts +67 -0
  39. package/dist/path-resolver-CPRj4bFY.d.ts +10 -0
  40. package/dist/path-resolver-Crkt8wTQ.d.ts +54 -0
  41. package/dist/plugin-CoYYZKdn.d.ts +447 -0
  42. package/dist/renderer-0A2ZEtca.d.ts +158 -0
  43. package/dist/sdd/index.d.ts +206 -0
  44. package/dist/sdd/index.js +864 -0
  45. package/dist/sdd/index.js.map +1 -0
  46. package/dist/secret-scrubber-3TLUkiCV.d.ts +31 -0
  47. package/dist/secret-scrubber-CwYliRWd.d.ts +54 -0
  48. package/dist/secret-vault-DoISxaKO.d.ts +19 -0
  49. package/dist/security/index.d.ts +46 -0
  50. package/dist/security/index.js +536 -0
  51. package/dist/security/index.js.map +1 -0
  52. package/dist/selector-BRqzvugb.d.ts +51 -0
  53. package/dist/session-reader-C3x96CDR.d.ts +150 -0
  54. package/dist/skill-Bx8jxznf.d.ts +72 -0
  55. package/dist/storage/index.d.ts +540 -0
  56. package/dist/storage/index.js +1802 -0
  57. package/dist/storage/index.js.map +1 -0
  58. package/dist/{system-prompt-vAB0F54-.d.ts → system-prompt-CG9jU5-5.d.ts} +9 -1
  59. package/dist/task-graph-BITvWt4t.d.ts +160 -0
  60. package/dist/tool-executor-CYdZdtno.d.ts +97 -0
  61. package/dist/types/index.d.ts +26 -4
  62. package/dist/types/index.js +1787 -4
  63. package/dist/types/index.js.map +1 -1
  64. package/dist/utils/index.d.ts +49 -2
  65. package/dist/utils/index.js +100 -2
  66. package/dist/utils/index.js.map +1 -1
  67. package/package.json +34 -2
  68. package/dist/mode-Pjt5vMS6.d.ts +0 -815
  69. 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